aa48145557
体检修复:启动日志不再打印 AUTH_TOKEN、密钥可清空、推送失败不占整段冷却、目录名与文案统一成 plugins/。
16 KiB
16 KiB
更新日志
Unreleased
v0.0.8
体检修复
- 启动日志不再打印
AUTH_TOKEN明文:日志同时进控制台 / 内存 / SQLite,前端「系统日志」里能直接看到,之前等于把令牌贴在墙上;现在只提示「需要访问令牌:见 .env 的 AUTH_TOKEN」 - 系统设置:温度留空(0)不再把已保存的温度清成 0;API Key / app_secret 设置后原本无法清空,字段右侧加「清空」按钮(
clear_api_key/notify.clear_app_secret) - 故障推送:推送失败不再占用整段
cooldown_min冷却,留 1 分钟重试,避免一次网络抖动把通知压掉十分钟 - 用户可见报错里的旧目录名
platforms/改成plugins/ pyproject.toml的版本与metadata.yaml对齐,bumpversion改成同时更新两处,发版不再漂移- 文档里的旧目录名
pugins/全部改成plugins/,docs/pugin.md更名为docs/plugin.md;.gitignore补上.idea/、.ruff_cache/
故障推送
- 新增通知中心
study_tool/notify.py:fault(kind, key, title, content)上报故障、retry(key, what)累计重试、send()直接推送;同一(类型, 账号)按cooldown_min冷却,重试只在刚达到阈值时推一次 - 新增推送渠道插件
plugins/daidai_push/(呆呆面板):/api/open-api/token换 token(内存缓存、按expires_in过期、401 自动换新重发)+/api/notifications/send;plugin.json用"kind": "notify"声明类型 - 插件加载器支持非平台插件:
kind=notify的插件注册到notify注册表,停用 / 重载同样摘除;/api/plugins返回kind,前端能力显示「推送」 - 设置项新增
notify(base_url/app_key/app_secret/channel_name/ 各类型开关 /retry_threshold/cooldown_min),/api/settings读写(密钥只回显前后几位),新增POST /api/notify/test测试推送 - 触发点:课程学完仍未完成 / 进度没推进 / 没有可识别活动(
notify.check_progress)、课程处理异常、整体运行异常、登录失败、会话失效且无法续期;公需课掉线重登与自动重学、莞易学单课时重报计入重试次数 - 前端「系统设置」改成左侧分类(大模型 / 学习模式 / 故障推送)+ 右侧内容的两栏布局:
Modal新增settings宽度档与bodyClassName覆盖;「测试连接」「测试推送」分别放进对应面板右上角,弹窗底部只留取消 / 保存
刷新课程 / 会话
- 修复「平台「」还没有实现刷新课程」:
session_expired自动重登后的重试请求补上platform,不再把登录成功的结果丢掉 - 修复反复重登:运行状态里的旧 cookie 不再在运行结束后回写账号(只在
discovering/studying时同步),重新登录时也会清掉运行状态里的cookies - 公需课学习中掉线重登后,把新会话写回账号与运行状态(新增
database.account_save_cookies),库里不再留失效会话
公需课交互式测验
- 平台契约新增
quiz_submit(self, s, b)槽位,能力quiz改为「quiz+quiz_submit都实现」才算;/api/quiz/submit改成按registry.get(platform).quiz_submit分发,莞易学的交卷逻辑移到quiz.submit_quiz - 公需课实现
quiz/quiz_submit:考核是独立活动页(/<aid>/study/course/<cid>),从课程目录找标题含「考核 / 考试」的活动再解析#testForm(题型映射 01 单选 / 02 多选 / 03 判断),弹窗里看题、AI 答题、交卷;交卷按用户选的字母换算成平台选项值提交,已交卷的课也能回看题目 - 修复公需课自动考核:
exam.run之前读课程主页(没有testForm,永远拿不到题),改为读考核活动页 - 前端测验弹窗交卷请求带上
platform
公需课自动重学
- 公需课
study改为「刷一轮 → 重取进度页(/study/course/progess)」循环:即使进入时所有任务点都显示已完成,也会重新读取一次平台状态;仍有未完成任务点时等 60 秒后自动重新开始学习,不再需要手动点「开始学习」 - 连续 3 轮平台完成数都没增加(如考核等非视频任务点)或达到 5 轮(
MAX_ROUNDS)时停止,并在日志里列出剩余任务点
公需课选课兜底
- 公需课刷新课程时,若
/uc/store/courseRegister里没有目标年度的课,自动去/uc/store/course/选课:已有「进入学习」的课就直接用,否则按配置选第几门;目标年度和序号放在插件目录plugins/gdgxk/config.json(year空 = 当前年份,pick默认 1),不进数据库、不动前端
插件解耦
- 插件可直接删除:核心不再 import 或写死任何具体平台。加载器记录插件 import 期间注册的路由,停用 / 重载 / 加载失败时一并摘除(修复重载后
/api/quiz*重复注册、停用后仍可访问) - 平台契约新增能力
quiz、capture;前端「打开测验」「抓包」改为按GET /api/platforms的capabilities显示;/api/quiz改走平台的quiz能力 - 去掉写死的
guanyixue默认值(accounts.platform、请求模型、抓包兜底、前端初值与回退文案),页脚与添加账号提示不再写死平台名 - 插件专属脚本迁到插件目录:
des.js/desenc.js/study_video.py→plugins/guanyixue/script/,capture-jsglpt.py/test-gdgxk.py→plugins/gdgxk/script/;test-gdgxk.py改用新的插件 API
工程
- 新增
.pre-commit-config.yaml:提交前跑 ruff 检查 / 格式化、yaml 与私钥 / 大文件检查,web/有改动时跑pnpm typecheck,提交信息由 commitizen 校验(约定式提交);pyproject.toml增加[tool.ruff]与[tool.bumpversion],发版用bump-my-version bump patch同步metadata.yaml、提交并打v开头的 tag
重构
- 清理
study_tool顶层的插件专属代码:莞易学的站点地址 / 登录授权入口 / 课程板块 /NODE/DESENC/HEX32/CODE_RE/DEBUG_DIR等常量从config/__init__.py迁到plugins/guanyixue/config.py,config/只剩ROOT/DATA/STATIC_DIR通用路径;测验请求模型(QuizIn/QuizSubmitIn/QuizAnswerIn)、答题提示词DEFAULT_ANSWER_PROMPT、题型名TYPE_LABEL从models.py/config/settings.py迁到plugins/guanyixue/quiz.py;引擎里"新版课程 ID"日志挪进莞易学study()分发 - 删除无人引用的代码:
config里的LOGDIR(及启动时建data/logs目录)、SessionExpired再导出、models.py的AccountsIn app.py/engine.py/routes/accounts.py/routes/misc.py以及莞易学utils/*的from study_tool.config import *、from study_tool.models import *改成显式 import- 前端从单文件
static/index.html(原生 JS)改为web/工程:React 19 + TypeScript + Vite + Tailwind CSS v4;Vite 直接输出到static/,FastAPI 托管/与/assets,运行 Python 不再需要 Node - 新旧学习平台合并为「莞易学」:
jspx.dgjy.net(登录入口)与study.dgjy.net(学习入口)统一注册为Platform(id="guanyixue", label="莞易学"),课程页代际(legacy/spa)由platforms/detect.py判断,平台内部按代际分发到jspx.py/study.py Platform新增entries(登录 / 学习入口 URL 片段)与registry.detect();课程发现按入口识别平台,新增平台不再需要改引擎。新增GET /api/platforms供前端展示平台名- 添加账号新增「学习平台」下拉(数据来自
GET /api/platforms);accounts表新增platform字段(老库启动时自动补列),账号选定平台后,登录 / 刷新课程 / 发现课程分别走该平台的login/list_courses/discover,学习与测验按course["platform"]+generation分发 Platform增加账号级可选入口:session/dump_cookies/login/list_courses/discover/renew_session/profile;引擎和路由按registry.get(account["platform"])取实现,接入第二个平台只需写一个模块- 测验交卷从
platform(jspx / study)改为generation(legacy / spa),接口返回platform="guanyixue"+generation - 代码改为标准
src/包布局:业务代码全部在src/study_tool/,根目录app.py只做启动壳(python app.py、start.bat、Dockerfile 都不用改);新增pyproject.toml,pip install -e .后可用study-tool命令 - 平台拆成插件包
src/study_tool/platforms/:base.py定义Platform契约、registry.py注册表;引擎按course["platform"]从注册表分发 - HTTP 接口收进
routes/(accounts / misc / logs),docs/architecture.md说明分层、平台识别与新增平台的步骤 - 散落的 JS 脚本统一收进
script/:运行必需的des.js/desenc.js放script/(config.py的DESENC指向script/desenc.js),不参与运行的dgjy-toolkit.user.js、study-axios.mjs移到script/reference/作参考 - 平台契约第一步:新增
platforms/contract.py定义生命周期 ↔ 槽位 ↔ 能力名映射,Platform.capabilities()按已实现槽位推导,GET /api/platforms返回capabilities - 平台契约第二步:
SessionExpired移到platforms/errors.py;旧版 / 新版学习流程分别从engine.py搬到platforms/jspx.py/platforms/study.py,engine.py只剩账号级编排(115 行);routes/*、quiz.py去掉平台 star import,session/dump_cookies缺失时明确报错
新增
- 公开查询页
/query:不需要AUTH_TOKEN,顶部输入框填账号用户名后点「查询」,显示该账号的课程列表与当前学习进度;对应公开接口GET /api/query?username=,只返回课程名 / 章节 / 状态 / 进度与运行进度,不含密码、cookie、账号 id - 新增平台「广东公需课」(
platforms/gdgxk.py):jsglpt.gds.edu.cn账号密码(base64)登录后换 ITLMS 票进jsxx.gds.edu.cn,课程列表读/uc/store/courseRegister;学习只跑updateViewTime1进度心跳(服务端按lastViewTime直接记账,不需要真播)。票按ITLMS-xxx||ticketId两段交给jsxx.gds.edu.cn/nts/loginToStoreByAccount;登录要过 4 位图形验证码,用 ddddocr 离线识别并自动重试。配套script/capture-jsglpt.py(抓包取证)与script/test-gdgxk.py(接口自测);课程列表解析出培训年度 / 学时 / 完成状态,卡片显示成「2026年公需课 · 未完成 · 30 学时」 - 统一日志源
runtime.log():控制台、内存(/api/log)、SQLite(/api/log/history)同一份内容;新增右上角「系统日志」弹窗查看key=system的启动/设置类日志 - 设置保存、服务启动都写入统一日志;卡片「课程日志」和「系统日志」的「清空」会同时清内存和 SQLite
- 前端:卡片状态徽标、学习进度、课程勾选、卡片内实时日志、测验 / 设置 / 系统日志弹窗全部保留;新增平台名徽标与空态引导
修复
- 账号接口的错误分支引用未导入的
JSONResponse、删除账号引用未导入的RUNS/RUNS_LOCK,登录失败 / 删除账号会 500:补齐导入 discover()引用未定义的detect_platform/has_study_marker导致课程发现NameError:代际识别集中到platforms/detect.py,两代页面共用同一套判断- 系统设置里的大模型配置保存后重新打开会回到默认值:
save_settings写的是嵌套结构{llm, study},load_settings却按顶层字段读,现在改成从llm子对象读取 - AI 答题整段同步等待导致浏览器 / 反代超时:
/api/quiz/answer改为流式(text/event-stream)分批返回,每批失败自动重试一次 - AI 答题解析不出 JSON 时直接判空:补
max_tokens、空内容 / 非 JSON 抛错重试,并支持从自由文本、截断 JSON 里抠答案,多选连写(AC)也能识别 - 控制台是 GBK 时日志里含
⚠之类字符会让工作线程抛UnicodeEncodeError:runtime.log现在编码失败会自动降级 - 服务重启把上次没跑完的运行标成「已中断」后,那句话会被后续
set_state一起写回库,新一轮运行一起步就顶着「服务重启,上次运行已中断」,库里还留着status=studying的幽灵记录:runtime.set_state()在状态切入discovering/studying时清掉上一轮的error/progress/finished_at,hydrate_runs()标记后立即落库 - 服务重启后卡片「课程日志」显示「暂无日志」,SQLite 里的历史读不出来:前端兜底条件原为「内存为空且 status 为空」,而重启后 status 恰好是
interrupted(非空),历史永远不读;现在只看内存是否为空 - 删除账号只清了按账号 id 记的
run_state/run_log,登录日志(key 与login_log.username记的是用户名 / 手机号)残留:account_delete()改为按账号 id + 用户名一起清,DELETE /api/accounts/{id}同时清掉内存里两个 key 的日志 - 学习链路上三处引用未定义的全局名(
platforms/jspx.py的extract_study_token/StudyClient/save_snapshot等、platforms/study.py的parse_qs、quiz.py的BeautifulSoup/urlsplit)会让课程发现 / 答题直接NameError:parse_qs、BeautifulSoup、urlsplit改为模块顶部导入,jspx↔study之间改用函数内延迟导入(模块级会成环) - 服务重启留下的「已中断」状态在重新登录后仍然压在卡片上,徽标显示「已中断」并挂着旧进度和红条:登录成功后清掉该账号上一轮已结束的运行状态(内存 + SQLite),正在跑(
discovering/studying)的不动,内存日志保留 - 本机启动(
python app.py/uv run app.py)时根目录.env完全无效:只有 docker compose 会读它,AUTH_TOKEN/PORT/RUN_LOG_KEEP/SNAPSHOT_KEEP都得靠命令行环境变量;现在study_tool/__init__.py用python-dotenv加载.env(已存在的环境变量优先,Docker 由 compose 注入的不受影响),新增依赖python-dotenv>=1.0 .env里写BIND_ADDR在本机启动时不起作用(代码只读HOST):监听地址改为HOST→BIND_ADDR→127.0.0.1依次回退
其他
- 新增
uv.lock依赖锁定与 uv 安装方式;pyproject.toml增加阿里云 PyPI 镜像([[tool.uv.index]]),Dockerfile增加PIP_INDEX_URL构建参数(默认阿里云,可回官方源),README 补充 uv / pip / 换源说明 .gitignore忽略*.egg-info/(uv run可编辑安装的产物)- 文档整理:
OPTIMIZATION.md、todo.md移入docs/;移除不再使用的start.bat
v0.0.1
首个版本。
新增
- 学习方式配置:快速学习(只调
period/study/log接口)/ 常规学习(开浏览器真播视频,1–5 路并发) - 常规学习:每路独立无头浏览器,可见性补丁防暂停;平台按课节顺序放行时自动降为 1 路,并把被指定的课时提到队首
- 抓包模式:
POST /api/capture记录所有 XHR/fetch(不限域名),window.open新开的标签页同样挂监听,结果落data/capture-*.json - 界面:系统设置新增「学习方式」(模式 + 并发数),卡片「更多」菜单新增「抓包 / 停止抓包」
修复
- 视频完成上报补齐学时台账
study/log/detail/save(stime/etime),完成包不再带lastProgressTime - 培训里的「课程」容器节点(periodType 10/13)不会自动完成:补一发
isComplete,并用PUT /course/front/calFinalScore重算课程完成计数 - 并发上报导致课程完成计数丢失(实测 14 个课时只记 11 个),现在每门课跑完自动重算