给 DeepSeek Harness(DSH)用的自动解题插件,现在覆盖三个平台:
- 头歌(Educoder):丢一个题目网址给它,它自己抓题干、让 AI 写代码、写回平台、提交评测,评测不过还会看着报错重写。
- iWrite(外研社英语写作 iwrite.unipus.cn):丢一个写作页网址(或者干脆不给,自动挑一份没写的),它按体裁 / 词数区间 / 题干要点 / 关键词写一篇、逐条自检、保存并提交。
- 学习通(超星 mooc1-api.chaoxing.com):丢一个作业地址(或者干脆不给),它把整份作业的题抓下来、AI 逐题作答、逐题暂存、回读页面对一遍答案,确认无误再交卷。
你:帮我做这个 https://www.educoder.net/tasks/xxxxx?homework_common_id=123
它:→ 登录态检查 → 拉题目 → AI 出码 → 写回 → 评测 → (不过就重写)→ 报结果
你:帮我写这个 https://iwrite.unipus.cn/student/daily/write?taskId=...&uid=...&classId=...
它:→ 登录态检查 → 拉任务(体裁/词数/要点)→ AI 写稿 → 自检(词数/要点/中文)→ 保存 → 提交 → 回读
你:帮我把学习通这个作业做了 https://mooc1-api.chaoxing.com/mooc-ans/work/phone/doHomeWork?...
它:→ 登录态检查 → 翻页抓题 → AI 逐题作答 → 逐题暂存 → 回读页面核对 → 交卷
三个平台各用各的登录态、各用各的浏览器 profile,互不影响。
从 GitHub 直接装(推荐):
dsh plugin --profile desktop add git+https://github.com/Lumidouces/dsh-educoder-auto.git也可以钉版本:
dsh plugin --profile desktop add git+https://github.com/Lumidouces/dsh-educoder-auto.git#v0.3.0本地开发(clone 后 link,改代码即时生效):
git clone https://github.com/Lumidouces/dsh-educoder-auto.git
dsh plugin --profile desktop add link:/path/to/dsh-educoder-auto装完重启 DSH(服务端插件不是热更新的)。
| 工具 | 干什么 |
|---|---|
educoder_status |
头歌体检:登录态、AI 路由、浏览器能不能用、拟人化开没开 |
educoder_login |
开浏览器让你登录头歌一次,把 session cookie 存到本地 |
educoder_solve |
给网址,解一道头歌的题 |
iwrite_status |
iWrite 体检:登录态、有几份待写作业、AI 路由 |
iwrite_login |
登录 iWrite:默认用保存的账号密码自动登,失败才弹窗手动登 |
iwrite_solve |
给网址(或不给),写一篇并提交 |
chaoxing_status |
学习通体检:登录态、待做作业列表、AI 路由 |
chaoxing_login |
开浏览器让你登录学习通一次,把整套 cookie 存到本地 |
chaoxing_solve |
给作业地址(或不给),抓整份作业的题、逐题作答、暂存、回读核对、交卷 |
educoder_solve 的参数:
url(必填)头歌题目网址mode:auto(默认,API 优先、失败降级浏览器)/api/browsersubmit:是否真提交。false是 dry-run,只生成代码不碰平台extra:额外提示,比如「上次报错是 NameError」keepOpen:跑完是否留着浏览器窗口。默认false(关掉)。想盯着看过程就设truehumanize:是否用拟人节奏,默认true。想立刻拿结果就设falsespeed:节奏倍率。1= 真人速度(一道编程题 2~6 分钟),0.05= 快进 20 倍,0= 完全不等待
默认会自己关浏览器:一是别在桌面上留一堆 Edge,二是
user-data-dir被占住时下次启动会直接报Target page, context or browser has been closed。
默认 browser.headless: true:抓题、截图、点评测全在无头浏览器里跑,不弹窗口、不抢焦点,你可以正常用电脑。
- 登录是例外:
educoder_login强制有头,否则你看不见窗口就没法输密码。 - 想看浏览器到底在干什么:配置里
browser.headless: false,或者解题时传keepOpen: true留着窗口。 - 无头下头歌的 Monaco 编辑器加载比有头慢 1~2 秒,插件会自动等(
pageSnapshot的editorTimeoutMs),实测单题整体 7 秒左右,有头约 5 秒,差得不多。 - 代价:无头浏览器的 UA 里带
HeadlessChrome字样(navigator.webdriver已处理成false)。如果介意这点,就设headless: false。
educoder_login会弹一个 Edge 窗口,在里面把账号密码输完。登录成功后 cookie 自动落到 ~/.dsh/educoder-auto/session.json(权限 600),之后就不用再登了,直到它过期。
头歌登录页只有「账号密码」+ 微信/钉钉两个第三方入口,没有扫码。所以得手动输一次密码。
插件自动识别三类题:编程题、选择题、填空题。前两类(客观题)不走浏览器,直接交答案。
走平台前端自己在用的 data.educoder.net/api,签名算法是前端公开常量:
X-EDU-Signature = md5(base64("method=<M>&ak=<AK>&sk=<SK>&time=<ms>"))
这不是破解,是把浏览器里已经在跑的那套请求原样搬到 Node 里。好处是题干、文件清单、评测用例结果全是结构化 JSON,比截图 + 视觉模型可靠得多,也快。
流程:/tasks/<id>.json 拿题干和文件 → 缺内容就 /rep_content.json 单独读 → AI 出码 → /myshixuns/<id>/update_file.json 保存 → /tasks/<id>/game_build.json 触发评测 → 轮询 test_sets[].result 拿结果。
API 拿不到结构时(题型特殊、页面纯前端渲染、或者你只是想看着它做),就用 playwright-core 驱动系统 Edge:打开页面 → 截图 + 抓 DOM 和编辑器内容 → 喂给 AI → 写回编辑器 → 点「评测」→ 用 game_status 接口读结论(读不到再退回页面文字)。
编辑器探测顺序:Monaco → CodeMirror 5 → CodeMirror 6 → textarea,支持 iframe。头歌是 React SPA,所以 goto 之后会等页面 settle,不然截图是白图。
两个必须知道的实现细节:
- 头歌的 Monaco 挂在
window.Monaco(大写 M),不是标准的window.monaco。只认小写会退到 textarea 兜底,读到的是输入代理框残片。 - 不能用 Playwright 的
fill()写 Monaco:它逐字符 insertText,每个换行都触发一次 auto-indent,缩进会一路累积。走原生 value setter + 派发input/change,写完回读比对(filled.verified)。
题干是准确文本,截图反而有 OCR 误差,所以截图是兜底不是主路。触发条件:
- API 返回的题干少于 40 字(这个题型/版本的题干不在接口里)
- 浏览器路页面正文少于 200 字(纯图、canvas、还没渲染完)
触发后把截图作为图像输入交给模型。宿主模型若声明只接受文本(inputModalities 不含 image),会自动跳过,不会白烧 token;educoder_status 会提前告诉你截图识题能不能用。
关掉它:配置里 visionFallback: false。
detectKind() 按 chooses[].question_type 分流:0 是选择,3 是填空。
流程:chooses[] 里已经带着题干、选项(选择题是 challenge_question[].option_name)、空数(multi_count)
→ 组 prompt 让模型只输出 {"answers":[...]} → 提交到 POST /tasks/<id>/choose_build.json。
两种题型的 answer 形状不同:
判分:平台不回逐题对错(test_sets[].result 和 choose_correct_num 恒为 null,页面也只显示「已提交」)。
只能看 game.status === 2(通过)或 game.final_score / challenge.score 的得分率,>= 1 才算通过。
所以 verdict 里会带 finalScore / maxScore / radio,让用户自己核对。
填空题的答案对空白敏感——换行符本身就是答案。所以
extractObjectiveAnswers对数组元素刻意不 trim。
平台 iwrite.unipus.cn 是外研社的英语写作教学与评阅系统,老师布置作文题,学生写完提交进批改流程。
第一次把账号密码交给插件,之后就一直自动登:
educoder-auto iwrite-login 13628198902 你的密码 # 保存账号密码 + 立即自动登录或者用工具:
iwrite_login(username="手机号", password="密码") # 保存并自动登录
iwrite_login() # 之后不带参数也能自动登录
自动登录走的是外研社 SSO 的普通表单(input[name=username] / input[name=password] + 那个无 name 的「我已阅读并同意」复选框)。极验是按风险触发的——同机同 profile、正常速度填表时实测不弹;万一弹了(loginWithCredentials 会返回 needsManual: true 并说明原因),插件自动回落到弹窗,让你手动拖一次滑块。
账号密码存在 ~/.dsh/educoder-auto/iwrite-credentials.json(chmod 600,Windows 上等于没有),登录态存 iwrite-session.json。这两个文件都是明文,别把 .dsh 放网盘或共享目录。
iWrite 的登录态和头歌完全独立:不同 profile 目录、不同 session 文件。过期了重新
iwrite_login即可,不用再输账号密码。
iwrite_solve(url="https://iwrite.unipus.cn/student/daily/write?taskId=123&uid=456&classId=789")
iwrite_solve() # 不给 url:自动挑一份没提交的
iwrite_solve(url=..., submit=false) # 只存草稿,先看写得好不好
iwrite_solve(url=..., extra="多用简单句,别用生僻词")
iwrite_solve 的参数:
url:写作页网址。里面最好带taskId/uid/classId;缺classId时插件会去作业列表里反查taskId/classId:url 没带时手动给submit:是否真提交。false= 只保存草稿(dry-run)extra:额外写作要求,会当成硬约束拼进题干maxAttempts:不合格时最多重写几轮,默认 3reuseDraft:已有草稿合格就沿用不重写,默认true(省一次模型调用,也更像"自己改过")level:写作水平档位,highschool(默认)/cet4/cet6,见下节polish:定稿前再润色一遍(只改措辞,内容结构不动),默认truehumanize/speed:拟人节奏,见下
平台给的任务信息里,真正构成硬约束的是这几项,插件全部显式塞进提示词,并且拿到稿子后逐条复核,不合格就打回重写(带着「哪里不合格」回喂):
| 约束 | 来源字段 | 怎么校验 |
|---|---|---|
| 体裁 | genre(0 通用 / 1 应用文 / 2 议论文 / 3 说明文 / 4 记叙文 / 5 学术 / 6 雅思) |
写进提示词的 GENRE_GUIDE,要求文体、人称、时态、结构都对应 |
| 词数区间 | words / words2 |
平台按空白分词计数,插件用同一口径(countWords),留 ±5% 余量 |
| 题干要点 | stem 原文 |
题干逐字进提示词,要求每条 bullet 都覆盖 |
| 关键词 | keyWord |
逐个检查是否出现在正文里,缺一个就报问题 |
| 标题 | title |
平台标题是独立字段。题干没给标题时让模型起一个(3~10 词 Title Case);正文里不许再写标题行 |
| 中文混入 | — | 平台会拦「识别到中文内容」,插件提前查 |
| 写作水平 | 本地档位 | 见下节「写作水平档位」 |
默认 highschool,也就是高三生水平、词汇落在四级范围内。三档:
| 档位 | 词汇边界 | 句子/结构 |
|---|---|---|
highschool(默认) |
高考核心词 + 四级常见词,逐词要求「高中认识且在四级词表内」 | 12~20 词为主,连接词只用 first / also / however / in short 这类 |
cet4 |
四级词汇 | 15~25 词,标准学术连接词 |
cet6 |
六级词汇 | 句式可以更复杂 |
高三档还有两条硬检查:
- 超纲词拦截:内置一份「一眼像 AI / 明显超出四级」的词表(
delve、tapestry、multifaceted、ubiquitous、leverage、facilitate、paramount、holistic、intricate、notwithstanding等 60 多个),出现任何一个就报问题打回重写。 - 缩写拦截:
don't/can't/it's这类在考试作文里要写全称,出现就报问题。
定稿后还有一步润色(polish: true):只改措辞、重复用词和句子衔接,不许加论点、不许改段落数、不许把词拔高。润色稿要重新过一遍同样的校验,过不了就保留原稿。
另外三条硬规则写死在系统提示词里:正文不写标题、输出纯正文不带 markdown 围栏、段落之间空一行(少于 2 段判不合格)。
定位任务(URL 或作业列表)→ 用列表里的 task(字段最全)
→ loadTaskStudent 取已有草稿(有就接着改)
→ AI 写稿 → 校验(不合格重写,最多 3 轮)
→ saveTask(source:0, savePattern:0)
→ submitTask
→ 回读:loadTaskStudent 再看正文是否一致 + 列表里 submitted 是否变 true
保存 / 提交的 payload 形状是从写作页源码抠出来的:
{
"taskStudentId": "12", "classId": "789", "taskId": "1001", "studentId": "456",
"title": "My Campus Life",
"article": "……正文……",
"simpleArticle": "……正文压成单行的摘要……",
"words": 178,
"source": 0, // 0 = 正式保存(自动存盘是 1)
"savePattern": 0
}为什么优先走接口:一次 POST 就写完了,还能拿到结构化字段(体裁、词数区间、关键词、题干原文)——这些正是「严格按照其要求写作」的依据。走页面要先过「写前构思」门、再往编辑区塞几百词,多几步能坏的地方。接口保存/提交报错时会自动切浏览器路兜底(
lib/iwrite-browser.mjs)。
接口保存或提交报错时自动切过去。默认走整页的 /student/toSubmitTask,不是抽屉那条 daily/write:
注入登录 cookie → 打开 /student/toSubmitTask?id=<taskId>&clsId=<classId>&addNew=1
→ 填标题(input.biaoti[placeholder="请输入作文标题"])
→ 填正文(UEditor:iframe#ueditor_0 里的 body[contenteditable])
→ 点 <a>保存</a> → 点 <a>提交</a> → layui 弹窗点「确认并提交」
→ 等跳 /student/showTask
为什么选这条:daily/write 在 preWriteRequired=1 的作业上会先被拦到 /student/daily/prewrite,要跟「写作构思智能体」聊一轮才解锁,慢且不可靠;toSubmitTask 没这道门,保存/提交按钮都带文字,好定位。
同一份作业,两个入口的编辑区实现完全不同,两种都支持:
| 入口 | 正文编辑器 | 保存/提交 | 确认弹窗 |
|---|---|---|---|
/student/toSubmitTask |
UEditor(iframe#ueditor_0 → body[contenteditable]) |
<a>保存</a> / <a>提交</a> |
layui,a.layui-layer-btn0「确认并提交」 |
/student/daily/write |
普通 textarea.editor___* |
<button>保存</button> / <button aria-label="提交"><img></button> |
antd,.ant-btn-primary「确定」 |
实测踩到的坑,代码里都绕过了:
daily/write的正文不是 TinyMCE。umi.js 里有 tinymce 依赖,但线上这版没启用:window.tinymce不存在、没有 iframe、没有 contenteditable,正文就是普通textarea。早先按 TinyMCE 写的逻辑全部失效。toSubmitTask的正文在 UEditor 的 iframe 里,主文档读不到,必须遍历page.frames()找body[contenteditable="true"]。写的时候直接改innerHTML再派发input——不能用 Playwright 的fill()/type(),逐字符输入会触发编辑器的自动缩进和换行规则。- 提交按钮两种页面长得不一样。
daily/write是<button aria-label="提交">里塞一张图(没有文字),按文字永远找不到;toSubmitTask是带文字的<a>提交</a>。两套选择器都得有。 - 确认弹窗是两套 UI 库。
daily/write用 antd(.ant-modal-confirm+.ant-btn-primary),toSubmitTask用 layui(a.layui-layer-btn0,文案「确认并提交」,旁边还有个「返回修改」别点错)。 preWriteRequired: 1的作业多一道门。daily/write上「我要写作」按钮在没聊过天时带writeBtnLocked类且禁用,必须往聊天框发一条消息才解锁。- 提交成功后跳的页不一样。
daily/write跳/report,toSubmitTask跳/student/showTask。两个都算成功,回读正文时都要跳过(跳走了就读不到编辑区)。
写作的时间开销比做题大得多,单独一套:
| 阶段 | 时长 |
|---|---|
| 读题 | 25 秒左右(±) |
| 构思 + 敲字 | 按 8 |
| 交卷前检查 | 12 秒左右 |
档位同上(brisk ×0.6 / normal / relaxed ×1.5)。沿用已有草稿时不重复计构思时间(本来就没重写)。
调试时用 speed: 0.02 快进 50 倍,speed: 0 完全不等待。
学习通(超星)的作业是整份一份的:一份作业十道题、二十分,全在同一个页面上翻页。所以这里的做法跟头歌不一样——不是「一题一提交」,而是「一次抓全、逐题作答、整份交卷」。
chaoxing_login({ timeoutSec: 300 })开一个有头 Edge,你在里面登录一次(手机号 + 验证码、或者扫码),登录成功后插件把整套 cookie(_uid / _d / fid / vc3 等,覆盖 chaoxing.com 几个域)存到 ~/.dsh/educoder-auto/chaoxing-session.json(chmod 600),浏览器 profile 单独放在 ~/.dsh/educoder-auto/chaoxing-profile。
已经有有效登录态时默认直接复用、不弹窗;要重登传 force: true。
chaoxing_solve({ url: 'https://mooc1-api.chaoxing.com/mooc-ans/work/phone/doHomeWork?courseId=...' })
chaoxing_solve({}) // 挑作业列表第一份
chaoxing_solve({ pick: '第三章' }) // 按标题关键词挑
chaoxing_solve({ submit: false }) // 只暂存,不交卷url:doHomeWork作业地址最准;android/mtaskmsgspecial?type=work&taskrefId=...的入口链接也认;给别的就当成pick关键词。submit:默认跟随配置chaoxing.autoSubmit(默认true)。false就只暂存,老师那边看不到。verify:暂存后回读页面对答案,默认true。建议别关——作业交上去就撤不回来了。extra:额外作答要求,会当硬约束拼进题干(「全用中文」「简答不少于 80 字」)。gapMs:每题之间的间隔毫秒,默认 0。
- 定位 ——
GET https://mooc1-api.chaoxing.com/work/stu-work列作业,挑出mtaskmsgspecial?type=work&taskrefId=的入口;入口会 302 到mooc-ans/work/phone/task-work?...,真正的doHomeWork?...地址在这一页的 HTML 里(所以request()会跟同源 GET 跳转,最多 5 跳)。 - 抓题 —— 给地址加
index=N一页一页翻(一页一题),直到题号重复或翻不动。翻页前必须删掉checkIntegrity=true:带着它服务端会忽略index,永远返回第一题(enc反过来不能删,删了直接给信息提示页)。每页解析:题号(id="questionId")、题型、分值、题干(.ans-cc)、选项(手机端不是 radio,是div.Answer span[orichoice])、填空控件个数,以及这一页自己的隐藏域。 - 作答 —— 把整份题(题型 + 题干 + 选项 value)一次交给模型,让它只回
{"answers":[{"id","answer"}]}。再把答案翻译成页面控件的 value。 - 暂存 —— 逐题
POST /mooc-ans/work/phone/doNormalHomeWorkSubmit?tempSave=true,每题用它自己那一页的隐藏域 +answer{题号}。 - 回读 —— 重新翻一遍页面,把每道题实际存进去的内容跟预期比对。选择题看
span[orichoice]的checked类;简答题的答案在隐藏域answer{qid}里(单引号包裹、可跨行、里面还可能有双引号),读不到再退回span#answerAria的aria-label。对不上时verified.mismatch会给「页面 N 字 / 提交 M 字、从第几字开始岔」。— 两条都不认,就说明真没存进去。 - 交卷 —— 回读没问题才
tempSave=false走最后一遍。
| 题型 | 模型给什么 | 提交成什么 |
|---|---|---|
| 单选题 | B(字母) |
answer{qid}= 选项 value(就是 B) |
| 多选题 | AC(连着写) |
两个同名字段 answer{qid}=A&answer{qid}=C |
| 判断题 | 对 / 错 |
answer{qid}=true / false(控件 value 就是这个) |
| 填空题(多空) | 每行一个空 | 每空一个同名 answer{qid} 字段 |
| 简答 / 名词解释 / 论述 / 计算 | 正文 | 原样塞进那唯一一个 textarea |
答案格式不靠猜:选项的合法值直接读 span[orichoice] 的 orichoice(页面上显示的字母常被打乱,orichoice 才是提交值),判断题只可能是 true/false,所以模型给的是「人话」,插件负责翻成控件值。另外题型编号是固定表:0 单选 / 1 多选 / 2 填空 / 3 判断 / 4 简答 / 5 名词解释 / 6 论述 / 7 计算——4~7 在页面上都是「一个多行编辑器」,所以必须靠编号区分成 text,不能只看控件。
PC 端作业正文在 iframe 里,填空/简答用 UEditor(UE.getEditor(name).setContent()),页面结构随版本变。移动端的 mooc1-api.chaoxing.com 接口是同一份数据、结构稳定、还带全部隐藏域,所以整条链路都走接口,浏览器只用来登录一次。
- 报
NEED_LOGIN/ 一直 302 跳passport2:登录态过期了,重跑chaoxing_login。 - 抓不到题(
没抓到题目):多半是这份作业已经交过、或者这个入口不是作业(是考试type=exam)。 - 回读有个别题对不上:看
verified.mismatch里给的题号和原因——「页面 0 字」基本就是没存进去(再跑一次);「页面 N 字 / 提交 M 字,从第几字开始岔」说明后台改写了内容(超长截断、编辑器过滤)。 - 暂存成功但交卷报错:先看
trace里最后一步,别反复重点交卷——学习通也会限流。
默认开启。目的只有一个:让提交记录别一眼就看出是脚本刷的。
每个阶段插入随机停顿,区间按"普通学生做一道 C 语言课后题"估:
| 阶段 | 编程题 | 选择题/填空题 |
|---|---|---|
| 读题 | 10~40 秒 | 8~25 秒 |
| 敲代码 / 想答案 | 1~4 分钟(代码越长越久) | 20~90 秒 |
| 自查后点评测 | 5~20 秒 | 5~15 秒 |
| 评测失败改 bug | 1~4 分钟,第 2 轮起再乘轮次 | — |
三档:humanizeProfile: brisk(熟练工,×0.55)、normal(默认)、relaxed(边玩边写,×1.6)。
一道普通编程题整体耗时落在 2~6 分钟,和你自己敲一遍差不多。
三步:
- 归纳——从「本题模板骨架(Begin/End 之外的部分)」+「同一作业里已通过关卡的历史提交」里数出:
缩进是 tab 还是空格、几个空格、
#include <x>还是#include<x>、{同行还是换行、if (x)还是if(x)。 - 写进提示词——让模型照着这个风格写(比事后改更自然)。
- 事后套用——把缩进宽度、include 空格、花括号位置、关键字空格按画像改一遍。
安全阀(重要):改完会把所有空白抹掉,必须和原文完全相同,否则整体回退。跨行字符串、注释行、宏定义里的 { 都不动。
humanize: true # 总开关
humanizeSpeed: 1 # 倍率,1 = 拟人;0.2 = 快进 5 倍调试;0 = 不等待
humanizeProfile: normal # brisk / normal / relaxed
maxWaitMs: 600000 # 单次停顿上限
styleMatch: true # 代码风格对齐总开关
styleSampleTasks: 4 # 归纳风格时最多翻几道历史关卡调试点快:humanizeSpeed: 0.05(快进 20 倍,一道题十几秒跑完,日志里仍会打印每段本来要等多久)。
最多 3 轮(maxAttempts)。每轮把失败用例的报错回喂给模型:
上一轮提交的代码:...
评测没有通过,报错如下:
用例 2 用例2:AssertionError: expected [3,2,1], got None
请针对报错修正,重新给出完整代码。
写在 ~/.dsh/profiles/desktop/package.json 的 dsh.profile.bundles 里那一行:
- id: educoder-auto
name: dsh-educoder-auto
config:
ai:
provider: '' # 留空 = 自动挑宿主第一个可用路由
model: ''
baseURL: '' # 没有宿主 llm 时走这个 OpenAI 兼容接口
apiKey: ''
maxTokens: 8192
temperature: 0.2
browser:
headless: true # 默认无头,后台跑不弹窗;登录不受影响(登录强制有头)
executablePath: '' # 留空 = 自动找 Edge
profileDir: '' # 留空 = ~/.dsh/educoder-auto/browser-profile
viewportWidth: 1440
viewportHeight: 900
autoSubmit: true # 默认真提交
maxAttempts: 3
buildTimeoutMs: 240000 # 头歌评测要拉容器,实测 5~10 秒,慢的时候 3 分钟
taskTimeoutMs: 1800000 # 拟人节奏下单题可能跑 2~6 分钟,给 30 分钟
visionFallback: true # 题干缺失时用截图识题兜底
humanize: true # 拟人化(节奏 + 风格)
humanizeSpeed: 1
humanizeProfile: normal
maxWaitMs: 600000
styleMatch: true
styleSampleTasks: 4
iwrite: # iWrite 写作部分,和头歌完全独立
autoSubmit: true # false = 只存草稿不提交
maxAttempts: 3 # 作文不合格最多重写几轮
reuseDraft: true # 已有草稿合格就沿用
level: highschool # 写作水平:highschool / cet4 / cet6
polish: true # 定稿前润色一遍
humanize: true
humanizeSpeed: 1 # 300 词作文约 30~60 分钟;0.02 = 快进 50 倍
humanizeProfile: normal
taskTimeoutMs: 7200000 # 单篇最多 2 小时(拟人节奏下确实要这么久)
chaoxing: # 学习通作业部分,同样独立
autoSubmit: true # false = 只暂存不交卷
verify: true # 暂存后回读页面对答案,建议别关
gapMs: 0 # 每题之间的间隔毫秒
extra: '' # 额外作答要求(工具参数优先)
taskTimeoutMs: 1800000AI 通道默认用 DSH 自己的模型(不用额外配 key)。只有宿主 llm 服务不可用时才会走 HTTP 兜底。
# 头歌
node bin/educoder-auto.mjs status
node bin/educoder-auto.mjs login
node bin/educoder-auto.mjs solve <url> --submit --mode api
node bin/educoder-auto.mjs courses
node bin/educoder-auto.mjs homework <courseId>
# iWrite
node bin/educoder-auto.mjs iwrite-status
node bin/educoder-auto.mjs iwrite-login
node bin/educoder-auto.mjs iwrite-tasks
node bin/educoder-auto.mjs iwrite-solve [url] --submit --speed 0.02 --extra "多用简单句"
# 学习通
node bin/educoder-auto.mjs chaoxing-status
node bin/educoder-auto.mjs chaoxing-login
node bin/educoder-auto.mjs chaoxing-works
node bin/educoder-auto.mjs chaoxing-solve [url|关键词] --submit --extra "简答题不少于 80 字"环境变量:EDUC_AI_BASEURL / EDUC_AI_KEY / EDUC_AI_MODEL。
-
占位符:平台初始代码里塞的是连续 U+2014(
————),不替换掉必然编译失败。PLACEHOLDER_RE专门盯它。 -
challenge.task_pass是题干不是状态,字段名容易误解。 -
challenge.path多文件用全角「;」分隔,用半角分会拆错。 -
评测是异步的,而且会先「拉容器」:点评测后
game_status.json有一段时间不返回test_sets,只返回{running_code_status, running_code_message}("服务启动中..."→"服务启动完成")。这期间必须判pending,判成unknown就会一直空转到超时。等test_sets出现,test_sets[].result是布尔值(不是对象),另有compile_success(0 = 编译失败)。 -
保存接口的参数少一个就静默不写盘:URL 里要
myshixun.identifier(字符串如ajlk5ovpzr,不是数字 id),query 要zzud=<user.login>,body 要evaluate:1(数字)、tab_type:1、代码在content、game_id取game.id(challenge.game_id不存在)、extras.challenge_id/extras.currentUserId。返回的content_modified: 0不可信,内容其实写进去了。 -
next_game在上一关没通过时是 null,想枚举全部关卡只能靠 prev/next 走链。 -
客观题平台不回逐题对错:
choose_build的test_sets[].result和choose_correct_num恒为null,页面「测试结果」也只写「已提交」。只能靠game.status === 2或final_score / challenge.score >= 1判通过。答对一部分也是 fail(满分制)。 -
填空题答案不能 trim:答案可能就是换行符本身,去掉空白就答错了。
-
进程被 kill 后残留 Edge 会占住 profile,下次启动报
Target page, context or browser has been closed。清理:Get-CimInstance Win32_Process -Filter "Name='msedge.exe'" | Where-Object { $_.CommandLine -like '*educoder-auto*' } | Stop-Process -Force
-
iWrite 的接口路径不全是通的:
/api/task/show/<id>/<class>和/api/studenttask/getStudentTaskList/...实测 404,任务详情和草稿都走POST /api/studenttask/loadTaskStudent。 -
iWrite 的响应不都带信封:
/api/student_task_index/loadByStudentId直接返回{totalPages, stid, totalElements},没有{code,msg,data}。 -
iWrite 的题干是转义过的 HTML(
<p>),要先htmlToText再喂模型,否则模型看到一堆实体符号。 -
iWrite 写作页的提交按钮没有文字,只有
aria-label="提交"和一张图片,按文字点不到。 -
学习通交作业每题必须用它自己那一页的隐藏域:混进别的页的字段(尤其
questionId)后台只认第一条,后面的题会存成空白。所以fetchWork会把每页的隐藏域一起留着,提交时按题号取自己那份。 -
学习通多选/多空要发同名重复字段:
answer1234=A&answer1234=C;拼成answer1234=A,C后台只认第一个,等于只选了一个。 -
学习通的判断题控件 value 是
true/false,不是「对/错」也不是 0/1;选择题是选项字母。 -
学习通 PC 端作业正文在 iframe 里 + UEditor,DOM 路不稳定,整条链路走
mooc1-api.chaoxing.com的移动端接口。 -
学习通交卷不可逆:默认先全量
tempSave=true→ 回读页面比对 → 才tempSave=false。改了这块务必保留回读。 -
学习通作业入口是 302 的:
mtaskmsgspecial跳task-work,doHomeWork地址在跳转后的 HTML 里。请求层不跟跳转就只会拿到空 body,然后误报「找不到作业入口」。 -
学习通翻页必须摘掉
checkIntegrity=true:带着它服务端无视index,每页都返回第一题(题号一模一样,看起来像「抓到了但只有一题」)。而enc不能删,删了返回的是信息提示页。 -
学习通简答答案的隐藏域是单引号 + 可跨行:
<input type="hidden" name="answer{题号}" value='一大段正文'>。用/<input[^>]*>/抓会在第一个>处断掉,读回空串 → 回读永远「对不上」。正确写法是按引号扫标签(scanTags)。 -
学习通选项要取
orichoice而不是显示的文字:span里显示的字母是打乱后的,orichoice才是提交值。
educoder-auto/
├── index.js 插件入口:注册 9 个工具 + 1 个 skill
├── cordis.patch.yml bundle patch
├── lib/
│ ├── browser.mjs 浏览器层(playwright-core + 系统 Edge)
│ ├── educoder-api.mjs 头歌平台 API + 签名 + 评测结果解析
│ ├── session.mjs 头歌登录态存取
│ ├── ai.mjs 解题 prompt / 双通道调用 / 代码提取 / 视觉识题
│ ├── images.mjs 截图 → 图像输入(宿主 attachment + HTTP base64 双形态)
│ ├── humanize.mjs 拟人化:节奏规划 + 代码风格归纳/套用
│ ├── solver.mjs 头歌编排层:URL → 答案 → 提交 → 重试
│ ├── iwrite-api.mjs iWrite 接口层(Umi 前端逆向出来的)
│ ├── iwrite-session.mjs iWrite 登录态(独立 profile、自动登录 + 手动兜底)
│ ├── iwrite-ai.mjs 作文 prompt / 抽取 / 逐条校验 / 重写
│ ├── iwrite-solver.mjs 写作编排层:任务 → 写稿 → 校验 → 保存 → 提交 → 回读
│ ├── iwrite-browser.mjs 写作页兜底路(过写前构思门 + 填 textarea/TinyMCE + 点提交)
│ ├── chaoxing-api.mjs 学习通接口层(列作业 / 翻页抓题 / 逐题提交 / 回读)
│ ├── chaoxing-session.mjs 学习通登录态(独立 profile、整套 cookie 落盘)
│ ├── chaoxing-ai.mjs 逐题作答 prompt / JSON 抽取 / 答案翻译成控件 value
│ └── chaoxing-solver.mjs 学习通编排层:定位 → 抓题 → 作答 → 暂存 → 回读 → 交卷
├── bin/educoder-auto.mjs CLI
├── tests/ 测试(见下)
└── skills/educoder-auto/SKILL.md
node tests/run.mjs # 快测:纯函数 + 插件装配,约 5s,不起浏览器
node tests/run.mjs --all # 全测:再加浏览器 e2e,会真开一个 Edge 窗口,约 25s| 用例 | 覆盖 |
|---|---|
api.test.mjs |
签名算法、URL 解析(含三段式 /tasks/<课>/<hw>/<task>)、字段归一、占位符识别、题型判定 |
ai-code.test.mjs |
代码块提取(单/多文件)、裸代码 vs 人话、prompt 截断 |
verdict.test.mjs |
页面文字判定、game_status 解析(含「拉容器」阶段必须算 pending) |
load.test.mjs |
用假 ctx 跑 apply(),校验 9 个工具 / skill 形状能过宿主校验(含无损 JSON) |
api-e2e.test.mjs |
fetch 打桩跑完整 API 编排,验证 fail → 回喂报错 → pass |
browser-e2e.test.mjs |
真开 Edge 跑本地 fixture,正例过、反例挂 |
vision.test.mjs |
截图兜底:文本模型下正确跳过,不多烧模型调用 |
objective.test.mjs |
客观题:选项解析、答案 JSON 抽取(含脏回复)、判分、dry-run 不提交 |
credentials.test.mjs |
iWrite 账号密码存取:落盘路径、回读、清空、脏文件兜底(用临时 DSH_HOME,不碰真实凭据) |
humanize.test.mjs |
拟人化:节奏抖动区间、sleep 可中断、风格归纳、风格套用(含字符串/注释/宏不误改的不变式) |
iwrite.test.mjs |
iWrite:接口信封/登录失效识别、保存 payload 形状、作文抽取与逐条校验(词数/关键词/中文/单段)、拟人时长 |
chaoxing.test.mjs |
学习通:翻页抓题、五种题型识别、答案翻译成控件 value、逐题用自己那页的隐藏域、多选/多空同名重复字段、先暂存后回读再交卷、submit: false 只暂存、空作业与 302 跳登录 |
这是给自己的账号做作业/练习用的效率工具。它不会绕过平台的任何校验——代码还是走平台自己的编译器和测试用例,过了就是真过了;作文也是走平台自己的提交与批改流程。别拿去刷别人的号或者跑违规用途。
iWrite 的登录页在触发风控时才会出极验滑块,插件不会去破解滑块——遇到滑块就停下来让用户手动过,正常情况用保存的账号密码自动登录。
它能让提交时间看起来像人做的,但有几件事它做不到,也别指望:
- 答案全对反而可疑。真人会错、会改、会有一次编译不过。如果你在意,可以手动留一两道自己做。
- 风格只能对齐格式(缩进、括号、include 空格),改不了「这人平时爱用
for还是while」「变量名是i/j/k还是idx」这些习惯。这部分靠提示词让模型模仿,但没有保证。 - 提交间隔能拉开,但一天内做完整个学期的作业这种宏观模式,插件管不了。要分散到不同天。
- 平台侧日志(IP、UA、请求顺序、是否有 DOM 交互)插件改不了。API 路是纯 HTTP,和真人点按钮的痕迹本来就不一样。真要更隐蔽只能走浏览器路(
mode: "browser"),代价是慢且脆。
这些是使用者的判断,不是技术能完全解决的事。