Skip to content

Commit

Permalink
3.0更新 修复403BUG 优化API代码结构
Browse files Browse the repository at this point in the history
  • Loading branch information
Samueli924 committed Nov 14, 2023
1 parent 1e499cf commit 1f45243
Show file tree
Hide file tree
Showing 23 changed files with 530 additions and 1,472 deletions.
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,15 @@ logs/
saves/
build/
dist/
*.spec
*.spec

# Custom files
.cookies.txt
cookies.txt
.config.ini
config.ini
chaoxing.log
.chaoxing.log
./config.ini
./chaoxing.log
./cookies.txt
13 changes: 0 additions & 13 deletions Dockerfile

This file was deleted.

674 changes: 0 additions & 674 deletions LICENSE

This file was deleted.

29 changes: 13 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,21 @@
:star: 觉得有帮助的朋友可以给个Star

## :point_up: 更新通知
20221123更新通知: 已由[B1gM8c](https://github.com/B1gM8c)修复重复获取章节BUG
20231114更新通知: 3.0大版本更新,修复403报错,优化代码结构

## :books: 使用方法

### 源码运行(推荐)
1. 提前准备: Python版本>=3.9 因为使用到了:=表达式。urllib3=1.25.11 因为后面的版本对代理的支持有变化。
2. `git clone --depth=1 https://github.com/Samueli924/chaoxing` 项目至本地
3. `cd chaoxing`
4. `pip install -r requirements.txt`
5. `python main.py` 运行程序
6. 可选参数 -debug 开启DEBUG模式 --no-log 不输出日志 --no-logo 隐藏开头项目LOGO --no-sec 关闭隐私保护
### 源码运行
1. `git clone --depth=1 https://github.com/Samueli924/chaoxing` 项目至本地
2. `cd chaoxing`
3. `pip install -r requirements.txt`
4. (可选配置文件运行)复制config_template.ini文件为config.ini文件,修改文件内的账号密码内容, 执行 `python main.py -c config.ini`
5. (可选命令行运行)`python main.py -u 手机号 -p 密码 -l 课程ID1,课程ID2,课程ID3...(可选)`

### 使用Docker运行
1. `git clone --depth=1 https://github.com/Samueli924/chaoxing` 获取项目源码
2. `docker-compose run --rm app`, 在交互式终端中运行容器

你可以在终端中使用`ctrl+p+q`来让容器退出交互式终端并在后台运行
### 打包文件运行
1. 从最新[Releases](https://github.com/Samueli924/chaoxing/releases)中下载exe文件
2. (可选配置文件运行)下载config_template.ini文件保存为config.ini文件,修改文件内的账号密码内容, 执行 `./chaoxing.exe -c config.ini`
3. (可选命令行运行)`./chaoxing.exe -u "手机号" -p "密码" -l 课程ID1,课程ID2,课程ID3...(可选)`

## :heart: CONTRIBUTORS
### :one:感谢[huajijam](https://github.com/huajijam)对chaoxing项目的贡献! [PR #73](https://github.com/Samueli924/chaoxing/pull/73)
Expand All @@ -46,10 +44,9 @@
### :six:感谢[a81608882](https://github.com/a81608882)修复403报错BUG[Pull #142](https://github.com/Samueli924/chaoxing/pull/142)
### :seven:感谢[yhm97](https://github.com/yhm97)修复音频格式导致的403BUG[Pull #187](https://github.com/Samueli924/chaoxing/pull/187)
### :eight:感谢[evibhm](https://github.com/evibhm)修复Docker运行的DNS问题、优化Docker映像大小[Pull #232](https://github.com/Samueli924/chaoxing/pull/232)
### 对于代码有任何问题或建议欢迎Pull&Request

### 对于代码有任何问题或建议欢迎Pull&Request

## :warning: 免责声明
- 本代码遵循 [GPL-3.0 License](https://github.com/Samueli924/chaoxing/blob/main/LICENSE)协议,允许**开源/免费使用和引用/修改/衍生代码的开源/免费使用**,不允许**修改和衍生的代码作为闭源的商业软件发布和销售**,禁止**使用本代码盈利**,以此代码为基础的程序**必须**同样遵守[GPL-3.0 License](https://github.com/Samueli924/chaoxing/blob/main/LICENSE)协议
- 本代码仅用于**学习讨论**,禁止**用于盈利**
- 他人或组织使用本代码进行的任何**违法行为**与本人无关
- 他人或组织使用本代码进行的任何**违法行为**与本人无关
3 changes: 0 additions & 3 deletions api/.gitignore

This file was deleted.

3 changes: 3 additions & 0 deletions api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
def formatted_output(_status, _text, _data):
return {"status": _status, "msg": _text, "data": _data}
172 changes: 172 additions & 0 deletions api/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
import re
import requests
import time
import random
from hashlib import md5

from api import formatted_output
from api.cipher import AESCipher
from api.logger import logger
from api.cookies import save_cookies, use_cookies
from api.process import show_progress
from api.config import GlobalConst as gc
from api.decode import (decode_course_list,
decode_course_point,
decode_course_card)


def get_timestamp():
return str(int(time.time() * 1000))


def get_random_seconds():
return random.randint(30, 90)


def init_session(isVideo: bool = False):
_session = requests.session()
if isVideo:
_session.headers = gc.VIDEO_HEADERS
else:
_session.headers = gc.HEADERS
_session.cookies.update(use_cookies())
return _session


class Account:
username = None
password = None
last_login = None
isSuccess = None
def __init__(self, _username, _password):
self.username = _username
self.password = _password


class Chaoxing:
def __init__(self, account: Account = None):
self.account = account
self.cipher = AESCipher()

def login(self):
_session = requests.session()
_url = "https://passport2.chaoxing.com/fanyalogin"
_data = {"fid": "-1",
"uname": self.cipher.encrypt(self.account.username),
"password": self.cipher.encrypt(self.account.password),
"refer": "https%3A%2F%2Fi.chaoxing.com",
"t": True,
"forbidotherlogin": 0,
"validate": "",
"doubleFactorLogin": 0,
"independentId": 0
}
logger.trace("正在尝试登录...")
resp = _session.post(_url, headers=gc.HEADERS, data=_data)
if resp and resp.json()["status"] == True:
save_cookies(_session)
logger.info("登录成功...")
return {"status": True, "msg": "登录成功"}
else:
return {"status": False, "msg": str(resp.json()["msg2"])}

def get_fid(self):
_session = init_session()
return _session.cookies.get("fid")

def get_uid(self):
_session = init_session()
return _session.cookies.get("_uid")

def get_course_list(self):
_session = init_session()
_url = "https://mooc2-ans.chaoxing.com/mooc2-ans/visit/courselistdata"
_data = {
"courseType": 1,
"courseFolderId": 0,
"query": "",
"superstarClass": 0
}
logger.trace("正在读取所有的课程列表...")
_resp = _session.post(_url, data=_data)
logger.info("课程列表读取完毕...")
return decode_course_list(_resp.text)

def get_course_point(self, _courseid, _clazzid, _cpi):
_session = init_session()
_url = f"https://mooc2-ans.chaoxing.com/mooc2-ans/mycourse/studentcourse?courseid={_courseid}&clazzid={_clazzid}&cpi={_cpi}&ut=s"
logger.trace("开始读取课程所有章节...")
_resp = _session.get(_url)
logger.info("课程章节读取成功...")
return decode_course_point(_resp.text)

def get_job_list(self, _clazzid, _courseid, _cpi, _knowledgeid):
_session = init_session()
_url = f"https://mooc1.chaoxing.com/mooc-ans/knowledge/cards?clazzid={_clazzid}&courseid={_courseid}&knowledgeid={_knowledgeid}&num=0&ut=s&cpi={_cpi}&v=20160407-3&mooc2=1"
logger.trace("开始读取章节所有任务点...")
_resp = _session.get(_url)
_job_list, _job_info = decode_course_card(_resp.text)
logger.info("章节任务点读取成功...")
return _job_list, _job_info

def get_enc(self, clazzId, jobid, objectId, playingTime, duration, userid):
# https://github.com/ZhyMC/chaoxing-xuexitong-autoflush/blob/445c8d8a8cc63472dd90cdf2a6ab28542c56d93b/logger.js
return md5(
f"[{clazzId}][{userid}][{jobid}][{objectId}][{playingTime * 1000}][d_yHJ!$pdA~5][{duration * 1000}][0_{duration}]"
.encode()).hexdigest()

def video_progress_log(self, _session, _course, _job, _job_info, _dtoken, _duration, _playingTime):
_url = (f"https://mooc1.chaoxing.com/mooc-ans/multimedia/log/a/"
f"{_course['cpi']}/"
f"{_dtoken}?"
f"clazzId={_course['clazzId']}&"
f"playingTime=0&"
f"duration={_duration}&"
f"clipTime=0_{_duration}&"
f"objectId={_job['objectid']}&"
f"otherInfo={_job['otherinfo']}&"
f"courseId={_course['courseId']}&"
f"jobid={_job['jobid']}&"
f"userid={self.get_uid()}&"
f"isdrag=3&"
f"view=pc&"
f"enc={self.get_enc(_course['clazzId'], _job['jobid'], _job['objectid'], _playingTime, _duration, self.get_uid())}&"
f"rt=0.9&"
f"dtype=Video&"
f"_t={get_timestamp()}")
resp = _session.get(_url)
return resp.json()["isPassed"]

def study_video(self, _course, _job, _job_info, _speed: float = 1):
_session = init_session(isVideo=True)
_session.headers.update()
_info_url = f"https://mooc1.chaoxing.com/ananas/status/{_job['objectid']}?k={self.get_fid()}&flag=normal"
_video_info = _session.get(_info_url).json()
_dtoken = _video_info["dtoken"] # 7d8349c683a65af92b68ed42b9eebfc1
_duration = _video_info["duration"]
_crc = _video_info["crc"] # 993fd31ba058895723016fa4fe9a6486
_key = _video_info["key"] # 71980072a32dd57d151f7c3f6b3b122c
_isPassed = False
_isFinished = False
_playingTime = 0
logger.info(f"开始任务:{_job['name']}, 总时长: {_duration}秒")
while not _isPassed:
if _isFinished:
_playingTime = _duration
_isPassed = self.video_progress_log(_session, _course, _job, _job_info, _dtoken, _duration, 0)
if _isPassed:
break
_wait_time = get_random_seconds()
if _playingTime + _wait_time >= int(_duration):
_wait_time = int(_duration) - _playingTime
_isFinished = True
# 播放进度条
show_progress(_job['name'], _playingTime, _wait_time, _duration, _speed)
_playingTime += _wait_time
logger.info(f"\n任务完成:{_job['name']}")

def study_document(self, _course, _job):
_session = init_session()
_url = f"https://mooc1.chaoxing.com/ananas/job/document?jobid={_job['jobid']}&knowledgeid={re.findall('nodeId_(.*?)-', _job['otherinfo'])[0]}&courseid={_course['courseId']}&clazzid={_course['clazzId']}&jtoken={_job['jtoken']}&_dc={get_timestamp()}"
_resp = _session.get(_url)
Loading

0 comments on commit 1f45243

Please sign in to comment.