Skip to content

Commit f5db7f5

Browse files
AllenXiao95Samueli924
authored andcommitted
feature: Replace the form encryption method with AES Samueli924#216
1 parent bed93b6 commit f5db7f5

2 files changed

Lines changed: 112 additions & 54 deletions

File tree

api/chaoxing.py

Lines changed: 111 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
from base64 import b64encode
77
from hashlib import md5
88

9-
from pyDes import des, PAD_PKCS5
9+
from Crypto.Cipher import AES
1010
import binascii
1111

12-
1312
import requests
1413
from requests.utils import dict_from_cookiejar
1514

@@ -18,6 +17,7 @@
1817

1918

2019
class 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

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ natsort
33
requests
44
urllib3==1.25.11
55
pyDes
6+
pycryptodome

0 commit comments

Comments
 (0)