66from base64 import b64encode
77from hashlib import md5
88
9- from pyDes import des , PAD_PKCS5
9+ from Crypto . Cipher import AES
1010import binascii
1111
12-
1312import requests
1413from requests .utils import dict_from_cookiejar
1514
1817
1918
2019class Chaoxing :
20+
2121 def __init__ (self , usernm , passwd , debug , show ):
2222 self .usernm = usernm
2323 self .passwd = passwd
@@ -33,33 +33,66 @@ def __init__(self, usernm, passwd, debug, show):
3333 def init_explorer (self ):
3434 self .session = requests .session ()
3535 self .session .headers = {
36- 'User-Agent' : f'Dalvik/2.1.0 (Linux; U; Android { random .randint (9 , 12 )} ; MI{ random .randint (10 , 12 )} Build/SKQ1.210216.001) (device:MI{ random .randint (10 , 12 )} ) Language/zh_CN com.chaoxing.mobile/ChaoXingStudy_3_5.1.4_android_phone_614_74 (@Kalimdor)_{ secrets .token_hex (16 )} ' ,
36+ 'User-Agent' :
37+ f'Dalvik/2.1.0 (Linux; U; Android { random .randint (9 , 12 )} ; MI{ random .randint (10 , 12 )} Build/SKQ1.210216.001) (device:MI{ random .randint (10 , 12 )} ) Language/zh_CN com.chaoxing.mobile/ChaoXingStudy_3_5.1.4_android_phone_614_74 (@Kalimdor)_{ secrets .token_hex (16 )} ' ,
3738 'X-Requested-With' : 'com.chaoxing.mobile'
3839 }
3940
4041 def re_init_login (self ):
4142 del self .session
4243 self .session = requests .session ()
4344 self .session .headers = {
44- 'User-Agent' : f'Dalvik/2.1.0 (Linux; U; Android { random .randint (9 , 12 )} ; MI{ random .randint (10 , 12 )} Build/SKQ1.210216.001) (device:MI{ random .randint (10 , 12 )} ) Language/zh_CN com.chaoxing.mobile/ChaoXingStudy_3_5.1.4_android_phone_614_74 (@Kalimdor)_{ secrets .token_hex (16 )} ' ,
45+ 'User-Agent' :
46+ f'Dalvik/2.1.0 (Linux; U; Android { random .randint (9 , 12 )} ; MI{ random .randint (10 , 12 )} Build/SKQ1.210216.001) (device:MI{ random .randint (10 , 12 )} ) Language/zh_CN com.chaoxing.mobile/ChaoXingStudy_3_5.1.4_android_phone_614_74 (@Kalimdor)_{ secrets .token_hex (16 )} ' ,
4547 'X-Requested-With' : 'com.chaoxing.mobile'
4648 }
4749 self .login ()
4850
51+ def pkcs7padding (self , text ):
52+ """
53+ 明文使用PKCS7填充
54+ """
55+ bs = 16
56+ length = len (text )
57+ bytes_length = len (text .encode ('utf-8' ))
58+ padding_size = length if (bytes_length == length ) else bytes_length
59+ padding = bs - padding_size % bs
60+ padding_text = chr (padding ) * padding
61+ self .coding = chr (padding )
62+ return text + padding_text
63+
64+ def encryptByAES (self , message ):
65+ keyword = "u2oh6Vu^HWe4_AES"
66+ key = keyword .encode ('utf-8' )
67+ iv = keyword .encode ('utf-8' )
68+
69+ cipher = AES .new (key , AES .MODE_CBC , iv )
70+ # 处理明文
71+ content_padding = self .pkcs7padding (message )
72+ # 加密
73+ encrypt_bytes = cipher .encrypt (content_padding .encode ('utf-8' ))
74+ # 重新编码
75+ result = str (b64encode (encrypt_bytes ), encoding = 'utf-8' )
76+ return result
77+
4978 def login (self ):
5079 """
5180 登录
5281 :return:
5382 """
5483 url = "https://passport2.chaoxing.com/fanyalogin"
55- des_obj = des ("u2oh6Vu^" , "u2oh6Vu^" , pad = None , padmode = PAD_PKCS5 )
56- secret_bytes = des_obj .encrypt (self .passwd , padmode = PAD_PKCS5 )
57- data = {"fid" : "-1" ,
58- "uname" : self .usernm ,
59- "password" : binascii .b2a_hex (secret_bytes ).decode ("utf-8" ),
60- "t" : "true" ,
61- "forbidotherlogin" : "0" ,
62- "validate" : "" }
84+ data = {
85+ "fid" : "-1" ,
86+ "uname" : self .encryptByAES (self .usernm ),
87+ "password" : self .encryptByAES (self .passwd ),
88+ "t" : "true" ,
89+ "refer" : "http%3A%2F%2Fi.chaoxing.com" ,
90+ "forbidotherlogin" : "0" ,
91+ "validate" : "" ,
92+ "doubleFactorLogin" : "0" ,
93+ "independentId" : "0"
94+ }
95+
6396 self .logger .debug ("发送登录数据" )
6497 resp = self .session .post (url , data = data )
6598 self .logger .debug ("收到返回数据" )
@@ -77,7 +110,9 @@ def status(self):
77110 检测Cookies是否有效
78111 :return:
79112 """
80- if not re .findall ("<title>用户登录</title>" , self .session .get ("https://i.chaoxing.com/base" ).text ):
113+ if not re .findall (
114+ "<title>用户登录</title>" ,
115+ self .session .get ("https://i.chaoxing.com/base" ).text ):
81116 self .logger .debug ("用户Cookies有效" )
82117 return True
83118 else :
@@ -91,7 +126,7 @@ def get_all_courses(self):
91126 courses = self .session .get (url ).json ()
92127 if courses ["result" ] == 1 : # 假如返回值为1
93128 __temp = courses ["channelList" ]
94- for course in __temp : # 删除所有的自建课程
129+ for course in __temp : # 删除所有的自建课程
95130 if "course" not in course ['content' ]:
96131 __temp .remove (course )
97132 self .courses = __temp
@@ -113,10 +148,13 @@ def get_selected_course_data(self):
113148 url = 'https://mooc1-api.chaoxing.com/gas/clazz'
114149 params = {
115150 'id' : self .selected_course ["key" ],
116- 'fields' : 'id,bbsid,classscore,isstart,allowdownload,chatid,name,state,isthirdaq,isfiled,information,discuss,visiblescore,begindate,coursesetting.fields(id,courseid,hiddencoursecover,hiddenwrongset,coursefacecheck),course.fields(id,name,infocontent,objectid,app,bulletformat,mappingcourseid,imageurl,teacherfactor,knowledge.fields(id,name,indexOrder,parentnodeid,status,layer,label,begintime,endtime,attachment.fields(id,type,objectid,extension).type(video)))' ,
151+ 'fields' :
152+ 'id,bbsid,classscore,isstart,allowdownload,chatid,name,state,isthirdaq,isfiled,information,discuss,visiblescore,begindate,coursesetting.fields(id,courseid,hiddencoursecover,hiddenwrongset,coursefacecheck),course.fields(id,name,infocontent,objectid,app,bulletformat,mappingcourseid,imageurl,teacherfactor,knowledge.fields(id,name,indexOrder,parentnodeid,status,layer,label,begintime,endtime,attachment.fields(id,type,objectid,extension).type(video)))' ,
117153 'view' : 'json'
118154 }
119- self .missions = sort_missions (self .session .get (url , params = params ).json ()["data" ][0 ]["course" ]["data" ][0 ]["knowledge" ]["data" ])
155+ self .missions = sort_missions (
156+ self .session .get (url , params = params ).json ()["data" ][0 ]["course" ]
157+ ["data" ][0 ]["knowledge" ]["data" ])
120158 return True
121159
122160 def get_mission (self , mission_id , course_id ):
@@ -125,7 +163,8 @@ def get_mission(self, mission_id, course_id):
125163 params = {
126164 'id' : mission_id ,
127165 'courseid' : course_id ,
128- 'fields' : 'id,parentnodeid,indexorder,label,layer,name,begintime,createtime,lastmodifytime,status,jobUnfinishedCount,clickcount,openlock,card.fields(id,knowledgeid,title,knowledgeTitile,description,cardorder).contentcard(all)' ,
166+ 'fields' :
167+ 'id,parentnodeid,indexorder,label,layer,name,begintime,createtime,lastmodifytime,status,jobUnfinishedCount,clickcount,openlock,card.fields(id,knowledgeid,title,knowledgeTitile,description,cardorder).contentcard(all)' ,
129168 'view' : 'json' ,
130169 'token' : "4faa8662c59590c6f43ae9fe5b002b42" ,
131170 '_time' : enc [0 ],
@@ -146,15 +185,18 @@ def get_knowledge(self, clazzid, courseid, knowledgeid, num):
146185 return self .session .get (url , params = params ).text
147186
148187 def get_attachments (self , text ):
149- if res := re .search (r'window\.AttachmentSetting =({\"hiddenConfig\":false,\"attachments\":.*})' , text ):
188+ if res := re .search (
189+ r'window\.AttachmentSetting =({\"hiddenConfig\":false,\"attachments\":.*})' ,
190+ text ):
150191 attachments = json .loads (res [1 ])
151192 self .logger .debug ("---attachments info begin---" )
152193 self .logger .debug (attachments )
153194 self .logger .debug ("---attachments info end---" )
154195 return attachments
155196
156197 def get_d_token (self , objectid , fid ):
157- url = 'https://mooc1-api.chaoxing.com/ananas/status/{}' .format (objectid )
198+ url = 'https://mooc1-api.chaoxing.com/ananas/status/{}' .format (
199+ objectid )
158200 params = {
159201 'k' : fid ,
160202 'flag' : 'normal' ,
@@ -168,15 +210,16 @@ def get_d_token(self, objectid, fid):
168210 self .logger .debug ("---d_token info end---" )
169211 try :
170212 d_token = d_token_raw .json ()
171- except json . JSONDecoder or json . decoder . JSONDecodeError :
213+ except :
172214 self .logger .debug ("出现JSONDecoder异常,正在跳过当前任务" )
173215 d_token = None
174216 return d_token
175217
176218 def get_enc (self , clazzId , jobid , objectId , playingTime , duration , userid ):
177219 # https://github.com/ZhyMC/chaoxing-xuexitong-autoflush/blob/445c8d8a8cc63472dd90cdf2a6ab28542c56d93b/logger.js
178220 return md5 (
179- f"[{ clazzId } ][{ userid } ][{ jobid } ][{ objectId } ][{ playingTime * 1000 } ][d_yHJ!$pdA~5][{ duration * 1000 } ][0_{ duration } ]" .encode ()).hexdigest ()
221+ f"[{ clazzId } ][{ userid } ][{ jobid } ][{ objectId } ][{ playingTime * 1000 } ][d_yHJ!$pdA~5][{ duration * 1000 } ][0_{ duration } ]"
222+ .encode ()).hexdigest ()
180223
181224 def add_log (self , personid , courseid , classid , encode ):
182225 log_url = f"https://fystat-ans.chaoxing.com/log/setlog?personid={ personid } &courseId={ courseid } &classId={ classid } &encode={ encode } "
@@ -192,26 +235,44 @@ def add_log(self, personid, courseid, classid, encode):
192235 self .logger .debug (resp .text )
193236 self .logger .debug ("---resp.text info end---" )
194237
195- def main_pass_video (self , personid , dtoken , otherInfo , playingTime , clazzId , duration , jobid , objectId , userid , dtype , _tsp ):
196- url = 'https://mooc1-api.chaoxing.com/multimedia/log/a/{}/{}' .format (personid , dtoken )
238+ def main_pass_video (self , personid , dtoken , otherInfo , playingTime ,
239+ clazzId , duration , jobid , objectId , userid , dtype ,
240+ _tsp ):
241+ url = 'https://mooc1-api.chaoxing.com/multimedia/log/a/{}/{}' .format (
242+ personid , dtoken )
197243 # print(url)
198244 params = {
199- 'otherInfo' : otherInfo ,
200- 'playingTime' : str (playingTime ),
201- 'duration' : str (duration ),
245+ 'otherInfo' :
246+ otherInfo ,
247+ 'playingTime' :
248+ str (playingTime ),
249+ 'duration' :
250+ str (duration ),
202251 # 'akid': None,
203- 'jobid' : jobid ,
204- 'clipTime' : '0_{}' .format (duration ),
205- 'clazzId' : str (clazzId ),
206- 'objectId' : objectId ,
207- 'userid' : userid ,
208- 'isdrag' : '0' ,
209- 'enc' : self .get_enc (clazzId , jobid , objectId , playingTime , duration , userid ),
210- 'rt' : '0.9' , # 'rt': '1.0', ??
252+ 'jobid' :
253+ jobid ,
254+ 'clipTime' :
255+ '0_{}' .format (duration ),
256+ 'clazzId' :
257+ str (clazzId ),
258+ 'objectId' :
259+ objectId ,
260+ 'userid' :
261+ userid ,
262+ 'isdrag' :
263+ '0' ,
264+ 'enc' :
265+ self .get_enc (clazzId , jobid , objectId , playingTime , duration ,
266+ userid ),
267+ 'rt' :
268+ '0.9' , # 'rt': '1.0', ??
211269 # 'dtype': 'Video', 音频文件为Audio
212- 'dtype' : dtype ,
213- 'view' : 'pc' ,
214- '_t' : str (int (round ( time .time ()* 1000 )))
270+ 'dtype' :
271+ dtype ,
272+ 'view' :
273+ 'pc' ,
274+ '_t' :
275+ str (int (round (time .time () * 1000 )))
215276 }
216277 mylist = []
217278 for key in params .items ():
@@ -224,31 +285,27 @@ def main_pass_video(self, personid, dtoken, otherInfo, playingTime, clazzId, dur
224285 result = tmp_response .json ()
225286 except Exception :
226287 self .logger .debug ("任务失败" )
227- result = {'error' : {'status_code' : tmp_response .status_code , 'text' : tmp_response .text }}
288+ result = {
289+ 'error' : {
290+ 'status_code' : tmp_response .status_code ,
291+ 'text' : tmp_response .text
292+ }
293+ }
228294 return result
229295 # return self.session.get(url, params=params).json()
230296
231- def pass_video (self , video_duration , cpi , dtoken , otherInfo , clazzid , jobid , objectid , userid , name , speed , dtype , _tsp ):
297+ def pass_video (self , video_duration , cpi , dtoken , otherInfo , clazzid ,
298+ jobid , objectid , userid , name , speed , dtype , _tsp ):
232299 sec = 58
233300 playingTime = 0
234- print ("当前播放速率:" + str (speed )+ "倍速" )
301+ print ("当前播放速率:" + str (speed ) + "倍速" )
235302 while True :
236303 if sec >= 58 :
237304 sec = 0
238- res = self .main_pass_video (
239- cpi ,
240- dtoken ,
241- otherInfo ,
242- playingTime ,
243- clazzid ,
244- video_duration ,
245- jobid ,
246- objectid ,
247- userid ,
248- dtype ,
249- _tsp
250- )
251- # print(res)
305+ res = self .main_pass_video (cpi , dtoken , otherInfo , playingTime ,
306+ clazzid , video_duration , jobid ,
307+ objectid , userid , dtype , _tsp )
308+ print (res )
252309 if res .get ('isPassed' ):
253310 show_progress (name , video_duration , video_duration )
254311 break
0 commit comments