feat: 故障推送 / 公需课自动重学 / 插件解耦,发版 v0.0.8

体检修复:启动日志不再打印 AUTH_TOKEN、密钥可清空、推送失败不占整段冷却、目录名与文案统一成 plugins/。
This commit is contained in:
study-tool
2026-11-01 17:47:22 +08:00
parent 491fd08c1e
commit aa48145557
114 changed files with 7779 additions and 3302 deletions
+57
View File
@@ -2,8 +2,64 @@
## 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` 供前端展示平台名
@@ -19,6 +75,7 @@
### 新增
- 公开查询页 `/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