├── README.md ├── Useragreement.txt ├── config.ini └── sign.py /README.md: -------------------------------------------------------------------------------- 1 | # xxtSign 2 | > 用于学习通自动签到的程序 公众号:给我一碗炒饭 3 | 4 | > 版本号V2.2.0 更新日期2022.3.12 5 | ## 支持的签到类型 6 | 支持以下类型的自动签到 7 | - 普通签到 8 | - 拍照签到 9 | - 位置签到 10 | - 手势签到 11 | - 签到码签到 12 | 13 | ###### 二维码签到需要到微信小程序「炒饭云签」中进行手动签到 14 | ###### (无视二维码10秒刷新) 15 | 16 | ## 优点 17 | 除了二维码签到外均支持自动签到 18 | 19 | 允许用户自行设定各种参数,如签到名称|签到地点显示|签到经纬度|签到图片等 20 | 21 | #### ==提供了多彩的控制台界面,让用户及时了解当前程序运行情况== 22 | ![多彩的控制台](http://qiniu.dancedj.cn/iShot2022-03-12%2021.33.35.png) 23 | 24 | ### ==支持多个账号同时运行监控任务== 25 | ![多个账号](http://qiniu.dancedj.cn/1647091800208.jpg) 26 | 27 | 分离配置文件与源代码,代码小白无需关心代码实现,只需要按要求修改配置文件即可运行 28 | 29 | 多种通知方式,让用户及时知道任务状态 30 | 31 | ![多种通知方式](http://qiniu.dancedj.cn/iShot2022-03-12%2021.37.10.png) 32 | 33 | ### ==**无经验小白可直接使用打包版本,无需安装python环境即可运行**== 34 | 35 | ## 使用方式 36 | #### 源码运行 37 | 安装python环境 38 | 39 | 安装包 40 | ```python 41 | pip install requests 42 | pip install rich 43 | ``` 44 | 修改配置文件 45 | 46 | ``` 47 | [全局配置] 48 | #循环休眠时间,也就是多久运行一次 49 | sleep:10 50 | #每门课程只检测前N个活动 避免因课程活动太多而卡住 51 | count:3 52 | #同时运行的用户数量,增加用户数量需在下方复制一个[用户N] 53 | usercount:1 54 | [通知] 55 | #Server酱通知Key,不填则为不使用 56 | serverKey: 57 | #Bark通知Key,不填则为不使用 58 | barkKey: 59 | [用户1] 60 | #用户名/手机号 61 | account: 62 | #用户密码 63 | password: 64 | #签到姓名,必填 65 | name: 炒饭 66 | #图片签到图片ID,可自行抓包获取,或者在小程序[炒饭云签]中的个人设置进行获取 67 | oid: 68 | #位置签到显示地址 69 | address: 70 | #位置签到纬度,可在小程序[炒饭云签]中的个人设置进行地图选点获取(精确不会漂移) 71 | lat: 72 | #位置签到经度,可在小程序[炒饭云签]中的个人设置进行地图选点获取(精确不会漂移) 73 | long: 74 | ``` 75 | 76 | 运行即可 77 | ![运行即可](http://qiniu.dancedj.cn/iShot2022-03-12%2021.41.53.png) 78 | 79 | #### 小白运行 80 | 修改配置文件 81 | 82 | 双击exe文件即可运行 83 | 84 | ## 配置文件说明 85 | 各个参数意义均在上文说明,这里要特别指出多用户情况 86 | 增加一个用户,就需要在配置文件中增加将这部分,其中的N即为当前用户编号(从1开始,1,2,3,4) 87 | 88 | ``` 89 | [用户N] 90 | account: 91 | password: 92 | name: 93 | oid: 94 | address: 95 | lat: 96 | long: 97 | ``` 98 | 并且需要修改全局配置中的usercount参数为当前用户数量 99 | 100 | ``` 101 | [全局配置] 102 | usercount:2 103 | ``` 104 | ## 炒饭云签 105 | 对于没有条件在服务器或者本地电脑上运行程序的小伙伴,我们还提供了云签到小程序,微信搜索小程序「炒饭云签」即可体验,在线提交任务后就什么也不用管啦 106 | 107 | 或者搜索微信公众号「给我一碗炒饭」从菜单访问 108 | 109 | 110 | ## 联系方式 111 | ![公众号](http://qiniu.dancedj.cn/gzh.jpg) 112 | 113 | QQ交流群等信息也都在公众号哦~,可以进群咨询问题 114 | -------------------------------------------------------------------------------- /Useragreement.txt: -------------------------------------------------------------------------------- 1 | 本程序禁止以逃课、旷课等非正常行为作为使用目的,仅适用于用户手机异常无法正常定位,教室网络过差无法进行签到等场景下使用! 2 | 由用户非法行为造成的后果均由用户自行负责 3 | 仅作为学习交流使用 4 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w964522982/xxtSign/b93db7e97adc9cc4056d2a560eeb9affa46e1420/config.ini -------------------------------------------------------------------------------- /sign.py: -------------------------------------------------------------------------------- 1 | import requests,json,hashlib 2 | import urllib.parse 3 | from random import choices 4 | import datetime,os,time 5 | from configparser import ConfigParser 6 | from rich.console import Console 7 | from rich.table import Column, Table 8 | from rich.progress import track 9 | console = Console() 10 | session = requests.session() 11 | requests.packages.urllib3.disable_warnings() 12 | users=[] 13 | noticeId=[] 14 | passed=[] 15 | config={} 16 | version="v.2.2.0" 17 | def printUserInfo(): 18 | table = Table(show_header=True, header_style="bold magenta") 19 | table.add_column("用户名", style="dim", width=12) 20 | table.add_column("登录状态") 21 | table.add_column("课程列表", justify="right") 22 | table.add_column("签到状态", justify="right") 23 | for user in users: 24 | if(user['stats']==0): 25 | table.add_row(user['account'],"[red]未登录[/red]","[red]无法加载[/red]","[red]无法开启[/red]") 26 | else: 27 | table.add_row(user['account'],"[green]登录成功[/green]","已经加载%s门"%(len(user['courseList'])),"[red]未开启[/red]") 28 | console.print(table) 29 | 30 | def printConfig(): 31 | table = Table(show_header=True, header_style="bold magenta") 32 | table.add_column("循环时间") 33 | table.add_column("检测前N条任务") 34 | table.add_column("server酱通知", justify="right") 35 | table.add_column("Bark通知", justify="right") 36 | n1="未开启" 37 | n2="未开启" 38 | if(config['serverKey']!=""): 39 | n1="开启" 40 | if(config['barkKey']!=""): 41 | n2="开启" 42 | table.add_row(str(config['sleep'])+"秒",str(config['count']),n1,n2) 43 | console.print(table) 44 | 45 | def md5(data): 46 | res=hashlib.md5(data.encode(encoding='UTF-8')).hexdigest() 47 | return res 48 | #{"account":"","password":""} 49 | def updateUserInfo(account,key,value): 50 | global users 51 | for i in users: 52 | if(i['account']==account): 53 | i[key]=value 54 | return 1 55 | return 0 56 | 57 | def getUserInfo(account,key): 58 | for i in users: 59 | if(i['account']==account): 60 | return i[key],i['stats'] 61 | return -1,0 62 | 63 | def login(uname,code): 64 | url="https://passport2-api.chaoxing.com/v11/loginregister?code="+code+"&cx_xxt_passport=json&uname="+uname+"&loginType=1&roleSelect=true" 65 | res=session.get(url) 66 | data = requests.utils.dict_from_cookiejar(session.cookies) 67 | mycookie="" 68 | for key in data: 69 | mycookie+=key+"="+data[key]+";" 70 | d=json.loads(res.text) 71 | if(d['mes']=="验证通过"): 72 | url="https://sso.chaoxing.com/apis/login/userLogin4Uname.do" 73 | res=session.get(url) 74 | a=json.loads(res.text) 75 | if(a['result']==1): 76 | myuid=str(a['msg']['puid']) 77 | #updateUserInfo(uname,"cookie",mycookie) 78 | #updateUserInfo(uname,"uid",myuid) 79 | #updateUserInfo(uname,"stats",1) 80 | return {"cookie":mycookie,"uid":myuid} 81 | else: 82 | console.log(uname+"获取UID失败",style="bold red") 83 | return 0 84 | 85 | def checkCookie(account): 86 | url='https://sso.chaoxing.com/apis/login/userLogin4Uname.do' 87 | headers=getheaders(account) 88 | if(headers==0): 89 | return 0 90 | res=requests.get(url,headers=headers) 91 | data=json.loads(res.text) 92 | if(data['result']==0): 93 | return 1 94 | else: 95 | return 0 96 | 97 | #获取同意请求头 包含Cookie 98 | def getheaders(account): 99 | mycookie,stats=getUserInfo(account,"cookie") 100 | if(mycookie==-1 or stats==0): 101 | return 0 102 | headers={"Accept-Encoding": "gzip", 103 | "Accept-Language": "zh-Hans-CN;q=1, zh-Hant-CN;q=0.9", 104 | "Cookie": mycookie, 105 | "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 com.ssreader.ChaoXingStudy/ChaoXingStudy_3_4.8_ios_phone_202012052220_56 (@Kalimdor)_12787186548451577248",} 106 | return headers 107 | 108 | def checkCookieTmp(cookie): 109 | url='https://sso.chaoxing.com/apis/login/userLogin4Uname.do' 110 | headers={"Accept-Encoding": "gzip", 111 | "Accept-Language": "zh-Hans-CN;q=1, zh-Hant-CN;q=0.9", 112 | "Cookie": cookie, 113 | "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 com.ssreader.ChaoXingStudy/ChaoXingStudy_3_4.8_ios_phone_202012052220_56 (@Kalimdor)_12787186548451577248",} 114 | res=requests.get(url,headers=headers) 115 | data=json.loads(res.text) 116 | if(data['result']==1): 117 | return 1 118 | else: 119 | return 0 120 | 121 | #获取课程列表 122 | def getcourse(account): 123 | url="http://mooc1-api.chaoxing.com/mycourse/backclazzdata?view=json&rss=1" 124 | headers=getheaders(account) 125 | if(headers==0): 126 | return 0 127 | res=requests.get(url,headers=headers) 128 | if('请重新登录' in res.text): 129 | return 0 130 | else: 131 | d=json.loads(res.text) 132 | courselist=d['channelList'] 133 | return courselist 134 | 135 | def initCourse(): 136 | for user in users: 137 | if(user['stats']==1): 138 | data=getcourse(user['account']) 139 | if(data==0): 140 | console.log('用户'+user['account']+"读取课程列表失败,请检查账号登录状态,可以尝试重新运行程序",style="bold red") 141 | updateUserInfo(user['account'],"courseList",[]) 142 | else: 143 | updateUserInfo(user['account'],"courseList",data) 144 | else: 145 | updateUserInfo(user['account'],"courseList",[]) 146 | 147 | def initConfig(): 148 | global users,config 149 | CONFIGFILE = "config.ini" 150 | isExists=os.path.exists("data") 151 | if not isExists: 152 | os.makedirs("data") 153 | try: 154 | pconfig = ConfigParser() 155 | pconfig.read(CONFIGFILE,encoding='gbk') 156 | usercount=pconfig['全局配置'].getint('usercount') 157 | config['sleep']=pconfig['全局配置'].getint('sleep') 158 | if(config['sleep']<40): 159 | console.log("循环间隔设置过小,会造成严重后果,如账号被冻结访问,无法进行操作等,建议修改,请按任意键确认后继续",style="bold red") 160 | input() 161 | config['count']=pconfig['全局配置'].getint('count') 162 | config['serverKey']=pconfig['通知'].get('serverKey') 163 | config['barkKey']=pconfig['通知'].get('barkKey') 164 | for i in range(1,usercount+1): 165 | if '用户'+str(i) in pconfig.sections(): 166 | tmpinfo={"account":"","password":"","stats":0} 167 | account=pconfig['用户'+str(i)].get('account') 168 | password=pconfig['用户'+str(i)].get('password') 169 | tmpinfo['account']=account 170 | tmpinfo['password']=password 171 | tmpinfo['name']=pconfig['用户'+str(i)].get('name') 172 | tmpinfo['oid']=pconfig['用户'+str(i)].get('oid') 173 | tmpinfo['address']=pconfig['用户'+str(i)].get('address') 174 | tmpinfo['lat']=pconfig['用户'+str(i)].get('lat') 175 | tmpinfo['long']=pconfig['用户'+str(i)].get('long') 176 | if(os.path.isfile('data/'+account+'.json')): 177 | with open('data/'+account+'.json','r',encoding = "utf-8") as f: 178 | tmp=f.read() 179 | tmpdata=json.loads(tmp) 180 | tmpinfo['cookie']=tmpdata['cookie'] 181 | tmpinfo['uid']=tmpdata['uid'] 182 | res=checkCookieTmp(tmpdata['cookie']) 183 | if(res==1): 184 | tmpinfo['stats']=1 185 | console.log('用户'+str(i)+"身份信息依然有效,登录成功",style="bold green") 186 | else: 187 | tmpinfo['stats']=0 188 | console.log('用户'+str(i)+"身份信息过期,需要重新登录",style="bold yellow") 189 | if(tmpinfo['stats']==0): 190 | loginRes=login(account,password) 191 | if(loginRes==0): 192 | tmpinfo['stats']=0 193 | console.log('用户'+str(i)+"登录失败,请检查账号密码是否正确",style="bold red") 194 | else: 195 | with open('data/'+account+'.json',"w",encoding = "utf-8") as f: 196 | f.write(json.dumps(loginRes)) 197 | tmpinfo['cookie']=loginRes['cookie'] 198 | tmpinfo['uid']=loginRes['uid'] 199 | tmpinfo['stats']=1 200 | console.log('用户'+str(i)+"登录成功 UID:"+loginRes['uid'],style="bold green") 201 | users.append(tmpinfo) 202 | #console.log('用户'+str(i)+"读取成功",style="bold green") 203 | else: 204 | console.log('用户'+str(i)+"不存在,请保持「全局配置」中的usercount与实际用户配置数量一致",style="bold yellow") 205 | #console.log("读取配置文件成功",style="bold green") 206 | except Exception as e: 207 | console.log("配置文件config.ini读取出错,请尝试还原该文件后重新修改",style="bold red") 208 | console.log("具体报错如下:\n"+str(e),style="bold red") 209 | 210 | def showInfo(): 211 | console.print("Hello, 欢迎使用炒饭学习通自动签到[bold magenta]"+version+"[/bold magenta]!") 212 | console.print("[*]本程序由炒饭进行开发,首发于公众号[bold green]给我一碗炒饭[/bold green]") 213 | console.print("[*]本程序开源地址为:[bold gray]https://github.com/w964522982/xxtSign[/bold gray]") 214 | console.print("[*]使用前请仔细阅读用户协议:[bold gray]https://github.com/w964522982/xxtSign/blob/main/Useragreement.txt[/bold gray] 使用即默认同意该协议") 215 | console.print("[*][bold red]本程序仅作为交流学习使用,请问用于非法用途,由用户造成的一切后果均与作者无关[/bold red]") 216 | console.print("[*]关注公众号[bold green]给我一碗炒饭[/bold green]获取更多新鲜好玩的知识,如遇到程序无法使用请在公众号或者github更新") 217 | console.print("[*]支持 普通|拍照|手势|位置|签到码 的自动签到,二维码需到小程序[bold green]「炒饭云签」[/bold green]进行手动签到") 218 | console.print("[*]拍照签到的图片以及位置签到的定位可在小程序[bold green]「炒饭云签」[/bold green]的个人设置中获取,如有能力可自行设置") 219 | console.print("---------------------------------------") 220 | 221 | def ifopenAddress(aid): 222 | url="https://mobilelearn.chaoxing.com/newsign/signDetail?activePrimaryId="+aid+"&type=1" 223 | res=requests.get(url) 224 | d=json.loads(res.text) 225 | t=json.loads(d['content']) 226 | if(t['ifopenAddress']==1): 227 | return t 228 | else: 229 | return 0 230 | 231 | def getTaskType(aid): 232 | url="https://mobilelearn.chaoxing.com/newsign/signDetail?activePrimaryId="+aid+"&type=1" 233 | res=requests.get(url) 234 | d=json.loads(res.text) 235 | if(d['otherId']==0): 236 | if(d['ifPhoto']==1): 237 | return 1 238 | else: 239 | return 2 240 | elif(d['otherId']==2): 241 | return 3 242 | elif(d['otherId']==3): 243 | return 6 244 | elif(d['otherId']==4): 245 | return 5 246 | elif(d['otherId']==5): 247 | return 7 248 | else: 249 | return 0 250 | 251 | def notice(aid,account,msg): 252 | global noticeId 253 | if(md5(aid+account) in noticeId): 254 | return 0 255 | if(config['serverKey']!=""): 256 | url="https://sctapi.ftqq.com/"+config['serverKey']+".send?title=炒饭自动签到&desp="+msg 257 | requests.get(url) 258 | if(config['barkKey']!=""): 259 | url="https://api.day.app/"+config['barkKey']+"/炒饭自动签到/"+msg 260 | requests.get(url) 261 | noticeId.append(md5(aid+account)) 262 | 263 | def sign(aid,user,atype,coursename): 264 | global passed 265 | name=urllib.parse.quote(user['name']) 266 | uid=user['uid'] 267 | oid=user['oid'] 268 | address=user['address'] 269 | lat=user['lat'] 270 | long=user['long'] 271 | if(md5(aid+user['account']) in passed): 272 | return 0 273 | if(atype==1): 274 | url="https://mobilelearn.chaoxing.com/pptSign/stuSignajax?activeId="+aid+"&uid="+uid+"&clientip=&useragent=&latitude=-1&longitude=-1&appType=15&fid=0&objectId="+oid+"&name="+name 275 | elif(atype==2): 276 | url="https://mobilelearn.chaoxing.com/pptSign/stuSignajax?activeId="+aid+"&uid="+uid+"&clientip=&latitude=-1&longitude=-1&appType=15&fid=0&name="+name 277 | elif(atype==6): 278 | url="https://mobilelearn.chaoxing.com/pptSign/stuSignajax?activeId="+aid+"&uid="+uid+"&clientip=&useragent=&latitude=-1&longitude=-1&appType=15&fid=0&objectId=&name="+name 279 | elif(atype==5): 280 | info=ifopenAddress(aid) 281 | if(info==0): 282 | url="https://mobilelearn.chaoxing.com/pptSign/stuSignajax?name="+name+"&address="+address+"&activeId="+aid+"&uid="+uid+"&clientip=&latitude="+lat+"&longitude="+long+"&fid=0&appType=15&ifTiJiao=1" 283 | else: 284 | url="https://mobilelearn.chaoxing.com/pptSign/stuSignajax?name="+name+"&address="+info['locationText']+"&activeId="+aid+"&uid="+uid+"&clientip=&latitude="+info['locationLatitude']+"&longitude="+info['locationLongitude']+"&fid=0&appType=15&ifTiJiao=1" 285 | elif(atype==7): 286 | url="https://mobilelearn.chaoxing.com/pptSign/stuSignajax?activeId="+aid+"&uid="+uid+"&clientip=&latitude=-1&longitude=-1&appType=15&fid=0&name="+name 287 | headers=getheaders(user['account']) 288 | if(headers==0): 289 | return 0 290 | res=requests.get(url,headers=headers) 291 | if(res.text=="success"): 292 | passed.append(md5(aid+user['account'])) 293 | console.log("[+]"+aid+"签到成功",style="bold green") 294 | notice(aid,user['account'],"课程[%s]签到成功"%(coursename)+"--来自炒饭自动签到") 295 | return 1 296 | else: 297 | passed.append(md5(aid+user['account'])) 298 | console.log("[x]签到失败,可能是已经签到过,或者账号登录失效,请在app中自行查看",style="bold yellow") 299 | return 0 300 | 301 | def gettask(courseId,classId,uid,cpi,account,user,coursename): 302 | try: 303 | url="https://mobilelearn.chaoxing.com/ppt/activeAPI/taskactivelist?courseId="+courseId+"&classId="+classId+"&uid="+uid+"&cpi="+cpi 304 | headers=getheaders(account) 305 | if(headers==0): 306 | return 0 307 | res=requests.get(url,headers=headers) 308 | d=json.loads(res.text) 309 | if(d['result']==1): 310 | activeList=d['activeList'] 311 | count=0 312 | n=0 313 | if(len(activeList)count): 319 | return 1 320 | status=active['status'] 321 | aid=str(active['id']) 322 | if(status!=1): 323 | n+=1 324 | continue 325 | atype=getTaskType(aid) 326 | if(atype==0): 327 | console.log("未知的签到类型,请及时查看更新",style="bold red") 328 | elif(atype==3): 329 | notice(aid,account,account+"检测到二维码签到,无法自动签到,请使用小程序「炒饭云签」进行手动签到,更多内容请关注公众号「给我一碗炒饭」") 330 | console.log("[-]二维码任务无法自动签到,请使用公众号[bold green]给我一碗炒饭[/bold green]的小程序[bold green]炒饭云签[/bold green]进行手动签到",style="bold yellow") 331 | elif(atype==1): 332 | sign(aid,user,atype,coursename) 333 | console.log("[-]"+account+"用户监测到拍照签到,此处使用的图片可在小程序[bold green]炒饭云签[/bold green]中进行获取objectId",style="bold yellow") 334 | elif(atype==2): 335 | sign(aid,user,atype,coursename) 336 | console.log("[-]"+account+"用户监测到普通签到",style="bold yellow") 337 | elif(atype==5): 338 | sign(aid,user,atype,coursename) 339 | console.log("[-]"+account+"用户监测到定位签到",style="bold yellow") 340 | elif(atype==6): 341 | sign(aid,user,atype,coursename) 342 | console.log("[-]"+account+"用户监测到手势签到",style="bold yellow") 343 | elif(atype==7): 344 | sign(aid,user,atype,coursename) 345 | console.log("[-]"+account+"用户监测到签到码签到",style="bold yellow") 346 | n+=1 347 | except Exception as e: 348 | print(e) 349 | 350 | def check(): 351 | for user in track(users,description="任务执行中..."): 352 | if(user['stats']==1): 353 | for course in user['courseList']: 354 | if('roletype' in course['content']): 355 | roletype=course['content']['roletype'] 356 | else: 357 | continue 358 | if(roletype!=3): 359 | continue 360 | classId=str(course['content']['id']) 361 | courseId=str(course['content']['course']['data'][0]['id']) 362 | cpi=str(course['content']['cpi']) 363 | coursename=course['content']['course']['data'][0]['name'] 364 | gettask(courseId,classId,user['uid'],cpi,user['account'],user,coursename) 365 | console.log("此次任务执行完毕,程序正常运行中",style="bold green") 366 | 367 | if __name__=="__main__": 368 | showInfo() 369 | initConfig() 370 | initCourse() 371 | printUserInfo() 372 | console.print("---------------------------------------") 373 | printConfig() 374 | input("请确认配置正确无误后,按任意键即可开始") 375 | while True: 376 | check() 377 | time.sleep(config['sleep']) --------------------------------------------------------------------------------