You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

207 lines
8.5 KiB

import json
import traceback
from hashlib import md5
import httpx
from loguru import logger
from .captcha import recognize
class Tree: # 任务树
def __init__(self, task):
self.task = task
self.child = []
def sort(self):
try:
self.child.sort(
key=lambda node: node.task['displayOrder']
)
for ch in self.child:
ch.sort()
except Exception:
logger.warning(f'[构建任务树] | 排序出错:\n{traceback.format_exc()}')
class Spider(httpx.AsyncClient):
def __init__(self):
super().__init__(follow_redirects=True)
self.is_login = False
async def login(self, username, password):
try:
if self.is_login:
return {}
self.cookies.clear() # 重置 cookies
logger.info('[登录] | 正在获取验证码...')
result = await self.get('http://sso.ismartlearning.cn/captcha.html')
code = recognize(result.content)
password = md5(md5(password.encode()).hexdigest().encode() + b'fa&s*l%$k!fq$k!ld@fjlk').hexdigest()
logger.info('[登录] | 正在登录...')
info = (await self.post(
'http://sso.ismartlearning.cn/v2/tickets-v2',
data={
'username': username,
'password': password,
'captcha': code
},
headers={
'X-Requested-With': 'XMLHttpRequest',
'Origin': 'http://me.ismartlearning.cn',
'Referer': 'http://me.ismartlearning.cn/'
}
)).json()['result']
logger.debug(f"[登录] | {info}")
assert info['code'] == -26 # 断言登录结果
self.is_login = True
logger.success('[登录] | 登录成功')
return info
except Exception:
logger.warning(f'[登录] | 登录出错:\n{traceback.format_exc()}')
async def get_courses(self): # 获取课程列表
try:
logger.info('[获取课程列表] | 正在获取课程列表...')
courses = (await self.post(
'https://school.ismartlearning.cn/client/course/list-of-student?status=1',
data={
'pager.currentPage': 1,
'pager.pageSize': 100
}
)).json()['data']['list']
logger.debug(f"[获取课程列表] | {courses}")
logger.success('[获取课程列表] | 获取课程列表成功')
return courses
except Exception:
logger.warning(f'[获取课程列表] | 获取课程列表出错:\n{traceback.format_exc()}')
async def get_books(self, course_id): # 获取某课程的书籍列表
try:
await self.post( # 必须有这个请求,否则后面会报错
'http://school.ismartlearning.cn/client/course/list-of-student?status=1',
data={
'pager.currentPage': 1,
'pager.pageSize': 100
}
)
books = (await self.post(
'http://school.ismartlearning.cn/client/course/textbook/list-of-student',
data={
'courseId': course_id
}
)).json()['data']
return books
except Exception:
logger.warning(f'[获取书籍列表] | 获取书籍列表出错:\n{traceback.format_exc()}')
async def get_tasks(self, book_id, book_type, course_id): # 获取某书籍的任务树
try:
logger.info('[获取任务列表] | 正在获取任务列表...')
await self.post('http://school.ismartlearning.cn/client/course/textbook/chapters')
tasks = (await self.post(
'http://school.ismartlearning.cn/client/course/textbook/chapters',
data={
'bookId': book_id,
'bookType': book_type,
'courseId': course_id
}
)).json()['data']
id_record = {task['id']: Tree(task) for task in tasks}
book_name = (await self.book_info(book_id))['bookName']
root = Tree({
'book_id': tasks[0]['book_id'],
'unitStudyPercent': 0,
'name': book_name
})
logger.info('[构建任务树] | 正在构建任务树...')
for task_id in id_record:
node = id_record[task_id]
node_name = f'{node.task.get("name","")}[id:{node.task["id"]}]'
if 'parent_id' in node.task:
if node.task['parent_id'] in id_record:
id_record[node.task['parent_id']].child.append(node)
else:
logger.warning(f'[构建任务树] | {node_name} 父节点不存在')
else:
root.child.append(node)
root.sort()
logger.success('[构建任务树] | 构建任务树完成')
logger.success('[获取任务列表] | 获取任务列表完成')
return root
except Exception:
logger.warning(f'[获取任务列表] | 获取任务列表出错:\n{traceback.format_exc()}')
async def get_paper(self, paper_id): # 获取任务点信息(包括题目和答案)
try:
logger.info('[获取任务点] | 正在获取任务点信息...')
ticket = (await self.post(
'http://sso.ismartlearning.cn/v1/serviceTicket',
data={
'service': 'http://xot-api.ismartlearning.cn/client/textbook/paperinfo'
}
)).json()['data']['serverTicket']
logger.debug(f'[获取任务点] | {ticket}')
paper_info = (await self.post(
'http://xot-api.ismartlearning.cn/client/textbook/paperinfo',
params={
'ticket': ticket
},
data={
'paperId': paper_id
},
headers={
'Origin': 'http://me.ismartlearning.cn',
'Referer': 'http://me.ismartlearning.cn/',
'X-Requested-With': 'XMLHttpRequest',
'Accept-Encoding': 'gzip, deflate'
}
)).json()['data']
# logger.debug(f'[获取任务点] | {json.dumps(paper_info, indent=4)}')
logger.success('[获取任务点] | 获取任务点信息完成')
return paper_info
except Exception:
logger.warning(f'[获取任务点] | 获取任务点出错:\n{traceback.format_exc()}')
async def user_info(self):
try:
logger.info('[获取用户信息] | 正在获取用户信息...')
info = (await self.post('https://school.ismartlearning.cn/client/user/student-info')).json()
logger.success('[获取用户信息] | 获取用户信息完成')
return info
except Exception:
logger.warning(f'[获取用户信息] | 获取用户信息出错:\n{traceback.format_exc()}')
async def book_info(self, book_id):
try:
logger.info('[获取书籍信息] | 正在获取书籍信息...')
ticket = (await self.post(
'http://sso.ismartlearning.cn/v1/serviceTicket',
data={
'service': 'http://book-api.ismartlearning.cn/client/v2/book/info'
}
)).json()['data']['serverTicket']
logger.debug(f'[获取书籍信息] | {ticket}')
book_info = (await self.post(
'http://book-api.ismartlearning.cn/client/v2/book/info',
params={
'ticket': ticket
},
data={
'bookId': book_id,
'bookType': 0
},
headers={
'Origin': 'http://me.ismartlearning.cn',
'Referer': 'http://me.ismartlearning.cn/',
'X-Requested-With': 'XMLHttpRequest',
'Accept-Encoding': 'gzip, deflate'
}
)).json()['data']
logger.debug(f'[获取书籍信息] | {json.dumps(book_info, indent=4)}')
logger.success('[获取书籍信息] | 获取书籍信息完成')
return book_info
except Exception:
logger.warning(f'[获取书籍信息] | 获取书籍信息出错:\n{traceback.format_exc()}')