Install
openclaw skills install @laneovcc/qq-group-homework-summarizerQQ群作业整理 —— 从 QQ 群「群作业」抓取指定日期的作业内容(含图片附件),生成排版规范的 A4 Word 文档(默认单页、可多页,支持多群合并、科目筛选、仅文字版),并可发送到指定邮箱或微信;内置 doctor 环境自检(CLI/daemon/连通性/登录态/产物),适合挂无人值守定时任务。This skill should be used when the user asks to organize, export, or send QQ group homework (群作业) as a document — for example "把X月X日的群作业整理成Word"、"把今天的作业导出成文档"、"把作业发到我邮箱/微信"。触发词:群作业、作业整理、作业文档、QQ作业、发作业给XX。
openclaw skills install @laneovcc/qq-group-homework-summarizer将 QQ 群「群作业」中某一天的内容(文字 + 图片)抓取出来,自动排成一页 A4 的 Word 文档, 并按需发送到邮箱或微信。
| 事实 | 说明 |
|---|---|
| 登录态在浏览器 | 需要 QQ 浏览器内已登录的 QQ 账号;QQ 客户端的登录不共享给浏览器 |
| 列表接口剥图 | get_hw_list.fcg 只返回文字,图片作业仅显示「【图片】」 |
| 图片须走详情接口 | get_hw_detail.fcg 才返回 type:"img" 与 url |
| 会话层按版本区分 | 1.5.4 时代扩展无会话生命周期,跳过 browser_start_session;1.6.x 起为会话模型,get_session_id 只 mint 令牌,必须再 browser_start_session 建会话,否则 browser_* 报 Session ... does not exist |
| 不抢桌面焦点 | 自动化拉起的浏览器 / daemon 不得改变用户当前前台窗口(全屏游戏、办公场景尤甚);统一走 scripts/focusless.py 的 FocusGuard,禁止 --start-maximized |
| CLI 会被强制下线 | 旧版(≤1.5.4)所有子命令返回 blocked until you upgrade;先升级 + 重启 daemon,别误判成未登录 |
| 必须在沙箱外运行 | 浏览器命令需 dangerouslyDisableSandbox: true,否则安全删除机制 fail-closed |
| daemon 必须常驻 | 未常驻时每条命令都会 lazy-start 并新建 tab,是「eval 落错 tab」的主要诱因;见下方后台模式 |
接口细节、参数、返回结构见 references/api.md。
2026-09-14 增补。浏览器操作是同步阻塞的(单次 35–45s),无人值守定时任务里必须后台化。
serve --daemon(2026-11-04 根因修复)这是「QQ 登录态反复失效」的元凶:自动化每轮开头不要 status 检查后手动 serve --daemon。
qqbrowser-skill(x5use 1.6.3)有两处会强杀 QQ 浏览器:
| 位置 | 行为 |
|---|---|
server.py 的 serve 前置检查 | 无条件调用 installer.close_browser_process() → taskkill /F /IM QQBrowser.exe |
vnc_util.py::_check_qqbrowser_alive_win() | 每次业务命令都检查浏览器命令行是否含 --x5use-automatic-in-tab;用户手动开的浏览器不带该 flag → taskkill /F 再启动 about:blank 新实例 |
强杀 + 全新实例 → cookie / ptlogin 会话丢失 → day --refresh 报 ptlogin-ex verify fail;
用户重新登录后下一轮又被杀,形成循环。日志证据:2026-11-04 18:01:49、18:08:43 均有
「启动QQ浏览器(Windows)」记录,11-02 一天内重启 7 次。
修复(已落地):qq_hw.py 内置幂等自愈补丁 ensure_no_kill_patch(),每次运行自动校验并重建,
去掉上述两处强杀,只保留「浏览器没运行时自动启动」。因为 venv 的 x5use 代码会被平台重置,
所以做成每次自愈,而不是一次性改包。原件备份在工作区 _x5use_backup/。
正确做法:
# 直接跑业务命令即可;daemon 未运行时 CLI 会自己 lazy-start,不经过 serve 的前置检查
python qq_hw.py bkn --gid <群号>
python qq_hw.py day <TODAY> --refresh
# status 只用来「读」,即使显示 not running 也不要手动 serve
qqbrowser-skill status
serve --daemon(补丁已禁用其强杀)。https://qun.qq.com/#/login 登录;补丁后不会再被强杀,登录态可跨轮保持。wmic.exe(PROGRAM BLOCKED BY SECURITY POLICY),
不影响 daemon 与业务命令,忽略即可。SP="<venv>/Lib/site-packages/x5use"
grep -n "qq-hw no-kill patch" "$SP/vnc_util.py" "$SP/server.py" # 应在两个文件里都能看到
# 回滚:把工作区 _x5use_backup/*.orig 复制回 x5use 目录(去掉 .orig 后缀)
用户明确要求(2026-09-14):不要每次检查完就关掉 daemon 和 QQ 浏览器, 否则下次调度要重新拉起浏览器,窗口会弹到前台打断用户。
qqbrowser-skill stop,也不要关闭 QQ 浏览器进程,让它们保持运行。Win32_Process.Create 逃离进程树已被本机安全策略拦截
(WMI/CIM process creation is equivalent to Start-Process),不要再尝试。
→ 缓解手段:让 QQ 浏览器由用户手动启动或设为开机自启(不经过 agent 启动),
它就不在自动化的进程树里,不会被清理;之后每轮 daemon 直接连接已有浏览器即可,不再弹窗。确认为真实未登录(day --refresh 报 ptlogin-ex verify fail、重探 bkn 数值不变)且
本机 QQ 客户端已登录时,可用 scripts/autologin.py 自动点快捷登录头像恢复,无需人工重登:
# 浏览器命令需沙箱外运行;全链路必须在单次调用内完成
QQB_CLI="<venv>/Scripts/qqbrowser-skill.exe" python scripts/autologin.py --probe
QQB_CLI="<venv>/Scripts/qqbrowser-skill.exe" python scripts/autologin.py \
--gid <群号> --verify-day --date <日期>
四个关键坑(根因详见 troubleshooting.md §17):
单次调用内跑完(扩展不重连新 daemon)→ 独立 --user-data-dir(否则单实例转发、flag 失效)
→ 顶层打开 ptlogin xlogin(#qlogin_list 在跨域 iframe 里)
→ 选择器必须是 #qlogin_list a(#qlogin a 是「QQ手机版」下载链接)。
无焦点是硬性约束:所有新拉起的进程都经 scripts/focusless.py 的 FocusGuard
(SW_SHOWNOACTIVATE + 禁 --start-maximized + 新窗口 WS_EX_NOACTIVATE 置底 +
焦点恢复兜底),日志里的 [FocusGuard] 报告应全部为「焦点保持不变」。
不要直接 subprocess.Popen 拉浏览器,也不要对前台窗口无条件 SW_RESTORE
(会把全屏游戏 / 最大化窗口还原成普通窗口)。
bkn --gid / day <日期> --refresh 用 run_in_background=true 启动,再用 TaskOutput 取结果,
避免前台长时间干等。整个过程约 35–45s。
eval_js() 自带重试:检测到 eval 落在扩展首页(报错形如
无法解析 Result 内容 / No page navigation triggered / Current page: [...] 首页 chrome-extension://…)
时,自动 goto(WAKE_URL) 唤醒浏览器后重试,最多 2 次,日志形如:
[retry 1/2] eval 落错 tab,导航唤醒后重试…
因此直接跑 qq_hw.py day <日期> --refresh 即可,不需要在外面先 go_to_url。
⚠️ 反而有害的两种做法(均已实测无效):
browser_go_to_url,再在另一个 Bash 调用里跑 day —— eval 会命中另一个新建 tab。browser_tab_switch --tabId <id> —— 同样漂移到新 tab,切不过去。上述 tab 漂移报错与登录状态无关(页面完全可能是已登录的 #/member-manage/base-manage)。
只有「重试耗尽仍失败」或「浏览器页面文本确实是「QQ登录」」时,才判定为未登录。
登录态是本 skill 最高频的故障源(2026-09-01~09-03 连续三天、每天至少一次)。 且存在双向误判:既可能「bkn 有值但其实没登录」,也可能「浏览器已登录但接口仍报错」。
bkn 由 cookie 里的 skey 算出。只要 cookie 没过期就一定能算出来,
而 cookie 过期时间远长于 ptlogin 会话有效期。所以:
bkn 仍能正常输出,且数值往往与失效前完全相同(实测前后不变)get_hw_detail.fcg)会暴露问题;列表接口校验较松,照常返回数据| # | bkn | day --refresh | 登录页 innerText | 判定 | 处理 |
|---|---|---|---|---|---|
| 1 | ✅ 有值 | ✅ 成功 | (已跳转群管理页) | 正常 | 直接生成文档 |
| 2 | ✅ 有值 | ❌ verify fail | QQ群 / QQ登录 | 真的没登录 | 引导用户登录 → 重探 bkn → 再拉数据 |
| 3 | ✅ 有值 | ❌ verify fail | 已跳转(有昵称 +「退出」) | 已登录,但 bkn 缓存过期 | 只需重探 bkn,不用再登录 |
第 3 态是最隐蔽的:用户明明点了登录,命令却照样报 verify fail, 极易误判成「登录没生效」而让用户反复登录。
# ① 接口侧:详情接口是否报错(先重试一次再下结论,避免偶发抖动)
python scripts/qq_hw.py day <日期> --refresh
# ② 浏览器侧:直接看登录页(判定第 2/3 态的唯一可靠依据)
qqbrowser-skill browser_go_to_url --url "https://qun.qq.com/#/login"
qqbrowser-skill browser_eval_content_js --script "document.body.innerText.slice(0,300)"
innerText 判读:
QQ群 / QQ登录 → 未登录(第 2 态)https://qun.qq.com/#/member-manage/base-manage,含昵称与「退出」按钮 → 已登录(第 3 态)用户点完登录后,qq_hw.json 里缓存的仍是旧会话的 bkn,而 skey 已经变了。
不重探就去拉数据,必然再次 verify fail。
# 用户点登录 → 先刷新 bkn(数值会变化)→ 再拉数据
python scripts/qq_hw.py bkn --gid <群号>
python scripts/qq_hw.py day <日期> --refresh
先跑自检,别凭感觉排查——大部分「失败」其实卡在环境,而不是作业数据:
python scripts/qq_hw.py doctor --gid <群号>
一次输出:Python 依赖 / CLI 路径 / daemon / 扩展连通性实测 / 登录态 / bkn 缓存 / 现存产物。
全绿才继续;有 [FAIL] 就按它的提示修。定时任务每轮开头都跑一次,
可避免整轮耗在错误方向(CLI 被封锁却去让用户登录、产物已生成却重做一遍)。
⚠️ 解释器别选错(2026-09-04 新增):依赖通常装在隔离 venv 里,而 base 解释器 (如
.../python/versions/3.13.x/python.exe)没有任何依赖,用它跑docx会直接报ModuleNotFoundError。始终用与qqbrowser-skill同目录的 python(即QQB_CLI所在Scripts/下的python.exe)。doctor 检测到依赖缺失时会自动提示正确路径。🛟 脚本会自己兜底:
docx子命令发现当前解释器缺python-docx/pillow时, 会自动切到QQB_CLI同目录的 venv 解释器重跑(打印一行提示,输出与退出码原样透传)。 所以定时任务 prompt 里即使写死了 base 解释器路径也不会翻车, 不用为此批量修改已配好的 automation。
自检不通过时的常见修法:
# ① CLI 被服务端强制下线(<=1.5.4)→ 所有浏览器子命令都返回
# "❌ Browser skill commands are blocked until you upgrade"
# 这不是扩展问题、也不是未登录 —— 看到 blocked 就先升级,别去查扩展:
pip install --index-url https://pypi.org/simple --upgrade qqbrowser-skill # 必须官方源,镜像常滞后
# 升级后直接跑一次业务命令即可:qq_hw.py 会自动拉起 daemon 并重建 no-kill 补丁。
# ⛔ 定时任务里绝不手动 serve --daemon(会强杀浏览器 → 登录态失效,见第 1 节)。
# 仅人工排障、且确认补丁在位时才用:qqbrowser-skill stop && qqbrowser-skill serve --daemon
# 🆕 1.5.6 的 `status` 里 `Connected clients` 可能恒为 0,但业务命令照常可用。
# → 不要死等 clients≥1,直接跑一次业务命令(或 doctor)验证连通性。
# ② CLI 不在 PATH(隔离 venv 安装的常见情况)→ FileNotFoundError: [WinError 2]
QQB_CLI="<venv>/Scripts/qqbrowser-skill.exe" python scripts/qq_hw.py doctor
# ③ 依赖缺失
pip install python-docx pillow pypdf
首次安装时才需要比对 pip 源版本(镜像滞后会装到旧版,旧版协议不兼容):
pip index versions qqbrowser-skill --index-url https://pypi.org/simple # 官方源最新版
pip index versions qqbrowser-skill # 本地镜像源版本
# 两源一致 → 用本地镜像源;不一致 → 用官方源;安装失败 → 阿里云源
# https://mirrors.aliyun.com/pypi/simple/
python scripts/qq_hw.py bkn --gid <群号> # 单群
python scripts/qq_hw.py bkn --gid <群号1>,<群号2> # 多群(逗号分隔)
bkn = xxxxx,自动写入 qq_hw.json(--gid 支持逗号分隔多群)⚠️ 本节最容易被误判,详见下方「登录态:三态判定与恢复」。 核心结论:
bkn探测成功 ≠ 已登录;反过来,浏览器已登录也仍可能 verify fail(bkn 缓存没刷新)。
python scripts/qq_hw.py list --size 100 # → hw_list.json
python scripts/qq_hw.py day 2026-05-11 # → hw_day_2026-05-11.json
列表过期时加 --refresh。
⚠️ 若接口返回
ptlogin-ex verify fail(retcode 2001),先按上方 「登录态:三态判定与恢复」排查,不要直接认定未登录。
📌 老师用「消息」而非「群作业」布置科目时,接口拉不到(2026-09-01 数学作业即如此)。 处理方式:让用户把内容发来,手动补一条进
hw_day_<日期>.json,再直接跑docx:json { "id": "<自拟唯一 id>", "title": "X月X日XX作业", "ts": <Unix 秒>, "course": "<科目>", "pub": "<发布人>", "fbname": "<昵称>", "group_id": "<群号>", "manual": true, "c": [{"t": "str", "s": "1. xxx\n2. yyy"}] }⚠️
ts要插在已有条目之间以保持时间顺序。 ⚠️ 补完后千万不要再跑day --refresh,否则整份 JSON 被覆盖、手动补的内容丢失。
supp 子命令追加补充内容(不怕 refresh 覆盖)老师口头补充 / 家长追加 / 对已有某科再补几条时,用这个,不要手改 JSON:
python scripts/qq_hw.py supp 2026-09-10 --course 语文 --lines "园地一默写" "二号本订正"
hw_supplement_<日期>.json;每次 day --refresh 会自动重新合并,
所以定时任务下一轮刷新数据也不会丢(手改 hw_day_*.json 会丢,别再手改)。hw_day_<日期>.json,可马上跑 docx 预览。--course 按 course/title 子串匹配已有条目;匹配不到则新建一条该科目的作业。--lines 多条用空格分隔,也支持一条里用中英文逗号/分号分隔(会自动拆条)。--title "<标题>" 指定标题。python scripts/qq_hw.py docx 2026-05-11 # 默认:单页、全部科目、含图片
python scripts/qq_hw.py docx 2026-05-11 --courses 语文,数学 # 仅指定科目
python scripts/qq_hw.py docx 2026-05-11 --text-only # 仅文字版(不插图)→ 作业_2026-05-11_文字版.docx
python scripts/qq_hw.py docx 2026-05-11 --allow-multi # 允许分页(内容过多时)
| 参数 | 说明 |
|---|---|
--courses 语文,数学 | 科目筛选(逗号分隔);默认当天全部科目 |
--text-only | 仅文字版,跳过图片下载与插入 |
--allow-multi | 允许多页:内容过多放不下一页时使用,必须先征得用户同意 |
--scale N | 单页模式下压缩图片高度的系数(>1 更保守) |
多页逻辑:默认仍按单页排版。若
pages校验发现 >1 页,先询问用户是否允许多页; 用户同意后再加--allow-multi重新生成(图片按自然高度排,允许分页)。
PDF 是 Epson 等打印邮箱最稳的格式。默认一律把作业以 PDF 附件发送,docx 仅作本地留存 (打印前记得提醒用户在打印服务里设为「只打印附件」,见下方「打印省纸提醒」)。
两级生成策略(按优先级):
pdf、pdfkit-py 等)做 docx → PDF;
它是纯 Python 方案、不依赖本机 Word、跨机器更稳。python scripts/qq_hw.py pdf 2026-05-11 # → 作业_2026-05-11.pdf(Word COM 回退)
Word COM 方案的中文路径坑(直接 SaveAs 中文名被静默吞、8.3 短路径等)见 troubleshooting.md §10。docx 单页则 PDF 也单页,页数以步骤 5 校验 docx 为准。
python scripts/qq_hw.py pages 作业_2026-05-11.docx
PAGES=1 → 完成PAGES=2 → 加大 --scale(默认 1.15,试 1.3~1.5)后重新生成:python scripts/qq_hw.py docx 2026-05-11 --scale 1.4
| 元素 | 规范 |
|---|---|
| 文首 | 日期标题,居中,微软雅黑 14pt |
| 每条作业 | Heading 2,微软雅黑 13pt;仅保留科目名——去掉 ^\d{1,2}月\d{1,2}日 前缀,不加编号、不显示发布人昵称(多群时保留「(群xxx)」标注) |
| 具体内容 | 编号列表 + 正文样式 + 宋体 10.5pt;剥掉原文自带的 1. 2. 3. 前缀,每条作业独立编号(从 1 开始)——手工编号 + 悬挂缩进(避免 Word「List Number」跨科目连续计数) |
| 图片 | 竖图(宽<高)两两横排,横图单独成行 |
单页约束的关键:必须在样式层把 Normal / Heading 2 / List Number 的
space_before/after 归零、line_spacing 设为 1.0(样式自带 1.15 倍行距会撑到第二页);
页边距 0.8cm;表格单元格边距清零。
🚨 第一步必须用精确名加载工具(2026-09-02 踩坑修正):MCP 工具是延迟注册的,
用 ToolSearch(queries=[...]) 模糊搜索搜不到 mcp__agent-mail__*(只能搜到内置的
agent_mail_upload_attachment / agent_mail_download_attachment),会误判成「没有发信工具」。
正确做法:
ToolSearch(tool_names=["mcp__agent-mail__SendMessage", "mcp__agent-mail__GetMe"])
拿到 schema 后再 DeferExecuteTool("mcp__agent-mail__SendMessage", {...})。
不要再去 app.asar 里翻 HTTP 端点——本机没有对应的本地 HTTP 服务,纯属浪费时间。
🆕 发信工具可能整轮缺席(2026-09-04 17:00 / 17:30 连续两轮实测):
agent_mail_upload_attachment是内置工具,几乎总在;但发信的 MCP 工具mcp__agent-mail__SendMessage不保证注册。缺席时精确名 + 模糊 ToolSearch 都只回两个附件工具,DeferExecuteTool 报not found in the deferred tools index, 且~/.workbuddy/mcp.json里查不到该服务器。 → 上传附件成功 ≠ 能发信;每轮先确认发信工具可用,别等附件传完才发现发不出去。🔑 但缺席是「按轮次」的,不是服务没启用(2026-09-04 17:41 证实): 同一台机器、同一任务,前两轮完全搜不到,用户新开一轮对话后立刻可用。 所以正确应对是:产物落盘 + 发告警 + 等下一轮调度或用户介入,并且——
- ❌ 不要据此判定「Agent Mail 没启用」
- ❌ 不要去改
~/.workbuddy/mcp.json- ❌ 不要用 curl / Python 自建 HTTP 绕过(本机没有对应本地服务,纯属浪费时间)
- ❌ 不要在一轮里反复重试耗尽轮次:一轮只探一次,探不到就收尾
调用序列:
1. agent_mail_upload_attachment(file_path) → file_id
2. mcp__agent-mail__SendMessage(
to=[{"email": "<打印邮箱>"}], # 注意是对象数组,不是字符串数组
subject="作业_<日期>",
body=" ", # 空字符串会校验失败,传一个空格
file_refs=[{"file_id": "<上一步的 file_id>"}],
skip_confirmation=true # 仅当用户已预先授权
) → {"queued": true}
3. 可选校验:mcp__agent-mail__ListMessages(dir="sent", limit=1)
⚠️ 未经用户明确授权时不要传 skip_confirmation:SendMessage 会返回
CONFIRMATION_REQUIRED,此时必须先向用户展示收件人 / 主题 / 附件并征得明确同意,
再带 confirmation_token 重试。定时任务等无人值守场景由用户预先授权,可直接 skip。
账号上下文(mcp__agent-mail__GetMe)可查别名、scopes(需含 mail:send)、
日发送配额(50/天)、附件上限(单文件 20MB)。
备用通道:企业微信邮件 wecom-cli mail send(先加载 wecomcli-email skill)
⚠️ 别把它当兜底:该通道 2026-09-02 起持续返回 850003(机器人「邮件」权限过期), 与「消息」权限是两套,消息能发不代表邮件能发。发信失败时不要为它反复重试多轮, 最多试一次确认状态,然后转「告警 + 等下一轮」。
wecom-cli mail send --json '{
"to": {"emails": ["<打印邮箱>"]},
"subject": "作业_<日期>",
"file_path": "<正文 .md 绝对路径>",
"content_type": "markdown",
"attachments": [{"file_path": "<PDF 绝对路径>"}]
}'
.md 写成一个空格(空字符串会被接口判校验失败)。📌 中文路径分场景,别一刀切(都实测过):
| 场景 | 中文路径 |
|---|---|
agent_mail_upload_attachment 上传附件 | ✅ 直接用中文绝对路径,没问题 |
SendMessage / 企微邮件传路径 | ✅ 同上 |
PowerShell 传参(Word COM 转 PDF、count_pages.ps1) | ❌ 会被按系统编码解码成乱码 → 必须先 cp 成 ASCII 名(见 troubleshooting §10) |
两条通道都不通时:不要写已发送标记,只发企微告警 + 保留现场文件(PDF/docx), 等下一轮调度重试。
🖨️ 打印省纸提醒(重要):Epson 等打印邮箱默认会连邮件正文一起打印, 常常多出一张只写了两行说明的纸。发送前应提醒用户在打印服务(如 Epson Connect)的 打印设置里勾「只打印附件 / 不打印正文」,或把邮件正文留空、尽量简短。 这样每份作业只出 1 页,避免浪费纸张。
加载 wecomcli-message skill 发送消息;需先上传文件时用 wecomcli-media 换取 media_id。
⚠️
sessions list经常返回sessions_count: 0(本机连续多日为 0)。 此时按 skill 规则兜底:用wecom-cli identity whoami返回的授权真人用户 ID 作为chat_id发给用户本人,不要因为「找不到「本地助理」会话」就放弃推送。
本节是 2026-09-01~09-04 连续多天定时跑通「群作业 → PDF → 打印邮箱」沉淀出的经验。
这是配定时任务时最容易翻车的一条(2026-09-04 实测确认):
BYHOUR/BYMINUTE只接受单个整数,传逗号列表直接报BYHOUR must be an integer between 0 and 23/BYMINUTE must be an integer between 0 and 59。 只有BYDAY支持逗号列表(BYDAY=MO,TU,WE,TH,FR)。
❌ 下面这条看着合理但平台不接受,照抄必报错:
FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=15,16,17,18;BYMINUTE=0,30
❌ 另一个反例:FREQ=HOURLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR;BYMINUTE=0,30
—— 它是工作日全天 24 小时每半小时触发(48 次/天)。若实际只在 15:00-18:30 干活,
约 40 次是空转(实测累计 48+ 条无意义记录)。
✅ 正解:一个时间点 = 一条 automation,拆成多条。例如「工作日 15:00-18:30 每半小时」拆 8 条:
FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=15;BYMINUTE=0
FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=15;BYMINUTE=30
FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=16;BYMINUTE=0
... 以此类推,直到 BYHOUR=18;BYMINUTE=30
拆多条时保留最关键的那一轮沿用原 id(历史经验绑在 id 上),其余新建。 例:把 18:00 兜底那条沿用原 id,这样它继承下来的运维上下文不丢。
即使 rrule 已精确限定,仍建议在任务 prompt 里保留一道时间窗闸门(第 0 步), 双保险避免边界情况空跑。
每条 automation 的 prompt 都要自包含(平台不会把其它条目的上下文带过来), 环境要点(Python/CLI 绝对路径、打印邮箱、登录态与 bkn 的处理)要写全。
发送成功后立即在工作区写 hw_auto_sent_<日期>.flag,下一轮开头检查到就跳过。
这让截止时间之后的重复调度天然安全,也避免同一天重复打印。
检查 hw_auto_sent_<TODAY>.flag → 存在即直接结束,不拉作业、不发邮件、不推送
两条邮件通道(Agent Mail / 企微邮件)都不通时,绝不写已发送标记;
只发企微告警 + 保留现场文件(作业_<日期>.pdf / .docx),等下一轮重试。
写了标记会导致当天再也发不出去。
上一轮若已生成 作业_<日期>.docx / .pdf,下一轮重试时直接复用,
不要重新 day --refresh + docx + pdf(2026-09-04 17:41 即靠复用 17:30 的 PDF 秒发成功):
| 情况 | 做法 |
|---|---|
有 .pdf 且 JSON 未变 | 直接发 |
只有 .docx | 转 PDF 后发 |
| 都没有 | 才走完整流程 |
重跑 day --refresh 还有个副作用:会覆盖 hw_day_<日期>.json,
把手动补录的条目冲掉(见步骤 3)。
「产物齐备但发信通道不通」是最常见的僵局(2026-09-04 连挂两轮)。收尾姿势:
三科(语文/数学/英语)未集齐时的推荐策略:
| 条件 | 行为 |
|---|---|
| 缺科 且 未到截止时间 | 不发送,推送「已收到 XX,仍缺 XX,XX 点前每半小时复查」 |
| 缺科 且 已到截止时间 | 按已收到科目强制生成发送(需用户预先授权),主题与推送里标注缺科 |
| 当天一条作业都没有 | 不发送,推送说明 |
强制发送时 --courses 只传已收到的科目,主题形如 作业_2026-09-03(缺英语)。
登录态会在窗口中途掉线,不能凭上一轮成功就跳过本轮校验(见上方三态判定)。
Epson 打印邮箱会连正文一起打印 → 多打一张纸。 邮件正文留空(传一个空格),并提醒用户在打印服务里勾「只打印附件」。
scripts/qq_hw.py 单入口,子命令:
| 子命令 | 作用 |
|---|---|
doctor | 环境自检:依赖 / CLI / daemon / 扩展连通性 / 登录态 / bkn 缓存 / 现存产物,一次说清。定时任务每轮开头先跑它 |
bkn | 探测 bkn 并写入配置 |
list | 拉取作业列表 |
day <日期> | 拉取当天详情(含图片 URL) |
docx <日期> | 生成 Word |
pdf <日期> | docx → PDF(Word COM 回退方案) |
pages <file.docx> | 统计页数(需本机装 Word) |
bkn/list/day 的 --gid 支持逗号分隔多群;docx 支持 --courses(科目筛选)、
--text-only(仅文字)、--allow-multi(多页)。
多群数据自动按(群号, hw_id)去重,避免同群号重复拉取产生重复条目。
辅助脚本:
| 文件 | 作用 |
|---|---|
scripts/autologin.py | 登录态自动恢复:原生点击快捷登录头像;--probe / --gid / --verify-day / --date,见 §1c |
scripts/focusless.py | FocusGuard:Windows 无焦点拉起进程,只加固新窗口并兜底恢复焦点;autologin.py 已内置,qq_hw.py 的 ensure_focusless_patch() 给 x5use 冷启动分支打同类自愈补丁 |
scripts/count_pages.ps1 | 供 pages 子命令调用(需本机装有 Word) |
见 references/troubleshooting.md,覆盖: 会话协议不兼容、pip 镜像滞后、CLI 输出双重转义、Word COM 页数统计、单页约束失效、 登录态三态判定、MCP 工具延迟注册与按轮次缺席、定时任务运维、 CLI 被服务端强制下线、rrule 平台限制、解释器选错、 登录态自动恢复 / 扩展不重连 / 无焦点启动等。
💡 拿不准是哪一类问题时,先跑
python scripts/qq_hw.py doctor --gid <群号>, 它会直接告诉你卡在哪一步。