diff --git a/docs/autman-plugin-rules.md b/docs/autman-plugin-rules.md index 5f03894..6940e31 100644 --- a/docs/autman-plugin-rules.md +++ b/docs/autman-plugin-rules.md @@ -733,3 +733,25 @@ cmp -s plugins/qdreader/qdreader_sign_autman.js js/qdreader_sign_autman.js - 明确默认只跑安全签到 + 领取奖励;可选任务需要在 `启点账号 → 账号任务配置` 按账号开启。 - 明确查询结果从归档判断,不要把清空近期日志误解释成没运行。 - 给出测试、commit、push、raw 验证结果,避免只说“应该可以”。 + +### 10.6 QDReader 原项目接口转写:字段大小写必须逐项核对 + +2026-05 复核启点激励任务时发现:原脚本能完成、插件不能完成的根因不是任务流程,而是 `finishWatchAdv` 的 POST payload 被转写错了字段名。上游提交字段为: + +```text +taskId=&BanId=0&BanMessage=&CaptchaAId=&CaptchaURL=&CaptchaType=0&Challenge=&Gt=&NewCaptcha=0&Offline=0&PhoneNumber=&SessionKey= +``` + +插件曾误写成: + +```text +TaskId=&BanId=0&BanMessage=&Challenge=&CaptchaType=0&Fee=&Gif=&Gt=&NewCaptcha=0&Offline=0&PhoneTicket=&SessionKey= +``` + +注意点: + +- `taskId` 是小写 `t`,不是 `TaskId`。 +- 必须保留上游的空字段 `CaptchaAId`、`CaptchaURL`、`PhoneNumber`。 +- 不要凭字段含义替换成 `Fee`、`Gif`、`PhoneTicket`。 +- 复刻上游混淆脚本时,除流程外还要用测试断言实际请求 `body` 的完整字符串,避免只断言 URL/次数导致参数漂移漏检。 +- QDReader 激励/每日任务都共用 `finishWatchAdv`,修改 payload 时必须覆盖两类任务测试。 diff --git a/js/qdreader_sign_autman.js b/js/qdreader_sign_autman.js index 0735f72..cb2724f 100644 --- a/js/qdreader_sign_autman.js +++ b/js/qdreader_sign_autman.js @@ -4,7 +4,7 @@ //[author: Hermes] //[service: BOSS] //[class: 工具类] -//[version: 2.10.8] +//[version: 2.10.9] //[platform: web,qq,wx,tg,tb,fs,we] //[public: false] //[price: 0] @@ -276,7 +276,7 @@ function contentText() { } function usage() { return [ - "启点读书签到插件 v2.10.8", + "启点读书签到插件 v2.10.9", "【一级菜单】", "账号管理:启点账号", "执行查询:启点查询", @@ -593,7 +593,7 @@ function nested(obj, path) { return cur } function makeVideoPayload(taskId) { - return { TaskId: String(taskId || ""), BanId: "0", BanMessage: "", Challenge: "", CaptchaType: "0", Fee: "", Gif: "", Gt: "", NewCaptcha: "0", Offline: "0", PhoneTicket: "", SessionKey: "" } + return { taskId: String(taskId || ""), BanId: "0", BanMessage: "", CaptchaAId: "", CaptchaURL: "", CaptchaType: "0", Challenge: "", Gt: "", NewCaptcha: "0", Offline: "0", PhoneNumber: "", SessionKey: "" } } function field(obj, names, def) { for (var i = 0; i < names.length; i++) if (obj && obj[names[i]] !== undefined && obj[names[i]] !== null) return obj[names[i]] diff --git a/plugins/qdreader/qdreader_sign_autman.js b/plugins/qdreader/qdreader_sign_autman.js index 0735f72..cb2724f 100644 --- a/plugins/qdreader/qdreader_sign_autman.js +++ b/plugins/qdreader/qdreader_sign_autman.js @@ -4,7 +4,7 @@ //[author: Hermes] //[service: BOSS] //[class: 工具类] -//[version: 2.10.8] +//[version: 2.10.9] //[platform: web,qq,wx,tg,tb,fs,we] //[public: false] //[price: 0] @@ -276,7 +276,7 @@ function contentText() { } function usage() { return [ - "启点读书签到插件 v2.10.8", + "启点读书签到插件 v2.10.9", "【一级菜单】", "账号管理:启点账号", "执行查询:启点查询", @@ -593,7 +593,7 @@ function nested(obj, path) { return cur } function makeVideoPayload(taskId) { - return { TaskId: String(taskId || ""), BanId: "0", BanMessage: "", Challenge: "", CaptchaType: "0", Fee: "", Gif: "", Gt: "", NewCaptcha: "0", Offline: "0", PhoneTicket: "", SessionKey: "" } + return { taskId: String(taskId || ""), BanId: "0", BanMessage: "", CaptchaAId: "", CaptchaURL: "", CaptchaType: "0", Challenge: "", Gt: "", NewCaptcha: "0", Offline: "0", PhoneNumber: "", SessionKey: "" } } function field(obj, names, def) { for (var i = 0; i < names.length; i++) if (obj && obj[names[i]] !== undefined && obj[names[i]] !== null) return obj[names[i]] diff --git a/tests/qdreader_plugin.test.mjs b/tests/qdreader_plugin.test.mjs index d6c06be..21fe1bc 100644 --- a/tests/qdreader_plugin.test.mjs +++ b/tests/qdreader_plugin.test.mjs @@ -966,6 +966,37 @@ test('adv task accepts nested data result 9 with empty message from sign gateway }); + + +test('finishWatch payload matches upstream lowercase taskId and full captcha field names', () => { + const store = { + qdreader_cookie_json: JSON.stringify({ '12345': cookie }), + qdreader_user_bind_json: JSON.stringify({ '12345': 'user-a' }), + qdreader_task_pref_json: JSON.stringify({ '12345': { adv: true } }), + }; + const r = runPlugin({ + content: '启点签到', + store, + userId: 'user-a', + requests: [ + { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } }, + { statusCode: 200, body: { code: 1, msg: '今日已签到' } }, + { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } }, + { statusCode: 200, body: { code: 1, msg: '无可领取礼包' } }, + { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } }, + { statusCode: 200, body: { code: 0, Data: { VideoRewardTab: { TaskList: [{ TaskId: '1187767793786748928', Title: '激励碎片', IsFinished: 0 }] } } } }, + { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } }, + { statusCode: 200, body: { result: 9, message: '' } }, + { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } }, + { statusCode: 200, body: { code: 0, Data: { VideoRewardTab: { TaskList: [{ TaskId: '1187767793786748928', Title: '激励碎片', IsFinished: 1 }] } } } }, + ], + }); + const finishCall = r.calls.find(c => /video\/adv\/finishWatch$/.test(c.url)); + assert.ok(finishCall, 'finishWatch request should be issued'); + assert.equal(finishCall.body, 'taskId=1187767793786748928&BanId=0&BanMessage=&CaptchaAId=&CaptchaURL=&CaptchaType=0&Challenge=&Gt=&NewCaptcha=0&Offline=0&PhoneNumber=&SessionKey='); + assert.doesNotMatch(finishCall.body, /TaskId=/); + assert.doesNotMatch(finishCall.body, /Fee=|Gif=|PhoneTicket=/); +}); test('adv task submits each unfinished upstream list item once before verification', () => { const store = { qdreader_cookie_json: JSON.stringify({ '12345': cookie }),