From 1b546fa3e16099e64bec2d2cbc95bdef37f23bae Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 21 May 2026 19:56:50 +0800 Subject: [PATCH] fix(qdreader): restore upstream ad task flow --- docs/autman-plugin-rules.md | 3 + js/qdreader_sign_autman.js | 83 +++++++++--- plugins/qdreader/qdreader_sign_autman.js | 83 +++++++++--- tests/qdreader_plugin.test.mjs | 153 ++++++++++++++++++++++- 4 files changed, 285 insertions(+), 37 deletions(-) diff --git a/docs/autman-plugin-rules.md b/docs/autman-plugin-rules.md index 678cbf8..525d6c6 100644 --- a/docs/autman-plugin-rules.md +++ b/docs/autman-plugin-rules.md @@ -695,6 +695,9 @@ QDReader 签到结果判定补充: - `今日已签到`、`重复签到`、`已领取`、`已完成`、`无可领取礼包` 等幂等提示应视为成功类,不应让账号失败。 - 激励任务 `finishWatch` 的 `Result/result = 9` 是上游可接受状态,要继续回查完成状态,而不是直接失败。 +- 激励任务不要只对每个任务 `finishWatch` 一次;如果上游任务体带 `NeedCount` / `Count` / `Times` 等次数字段(例如“激励碎片”),应按次数重复提交同一个 `TaskId`,再回查 `mainPage`。 +- 每日任务的原版链路是 `packagelist` 后先处理惊喜/前置任务,再处理“完成1个广告任务”,最后对标题为“每日任务”的目标按次数(通常 3 次)执行 `finishWatch`;不能只提交每日任务本身一次或跳过前置广告任务。 +- 回查完成状态时要优先校验本轮提交的具体 `TaskId`,不能因为其它旧任务已完成就把当前任务判定为成功。 - 可选任务如果被签名网关返回 `url 不在允许的接口列表内`,应标记为跳过 `⏭`,不要污染基础签到成功率。 - 大咖荐书必须用 base62 token,并带 `senderId` 拉取具体会话;单次 `pullmessage` 或十进制 token 容易导致“参数错误”。 diff --git a/js/qdreader_sign_autman.js b/js/qdreader_sign_autman.js index 0b1ec3d..12ce394 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.3] +//[version: 2.10.5] //[platform: web,qq,wx,tg,tb,fs,we] //[public: false] //[price: 0] @@ -276,7 +276,7 @@ function contentText() { } function usage() { return [ - "启点读书签到插件 v2.10.2", + "启点读书签到插件 v2.10.5", "【一级菜单】", "账号管理:启点账号", "执行查询:启点查询", @@ -602,6 +602,34 @@ function field(obj, names, def) { function taskTitle(task) { return String(field(task, ["Title", "TaskName", "Name", "TaskTitle", "Desc", "Description"], "")) } function taskIdOf(task) { return field(task, ["TaskId", "TaskID", "taskId", "TaskIDStr", "Id", "id"], "") } function taskFinished(task) { return Number(field(task, ["IsFinished", "Finished", "Status", "status"], 0)) === 1 } +function taskIdKey(id) { return String(id === undefined || id === null ? "" : id) } +function taskRunCount(task, def) { + var n = Number(field(task, ["NeedCount", "needCount", "Count", "count", "TotalCount", "totalCount", "Times", "times", "Limit", "limit"], def || 1)) + if (!n || isNaN(n) || n < 1) n = def || 1 + if (n > 10) n = 10 + return n +} +function taskAccepted(res) { return taskResultOk(res) || Number(resultCode(res)) === 9 } +function finishVideoTask(taskOrId, cookie, repeat) { + var tid = typeof taskOrId === "object" ? taskIdOf(taskOrId || {}) : taskOrId + var n = repeat || (typeof taskOrId === "object" ? taskRunCount(taskOrId, 1) : 1) + var ok = 0 + var last = null + if (!tid) return { ok: 0, total: 0, last: null } + for (var i = 0; i < n; i++) { + last = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie) + if (taskAccepted(last)) ok++ + else break + } + return { ok: ok, total: n, last: last, taskId: tid } +} +function advTaskActionable(task) { + var title = taskTitle(task) + var id = taskIdKey(taskIdOf(task || {})) + if (/签到|check.?in|每日任务|抽奖|lottery|兑换|exchange|章节|chapter|荐书|消息|message/i.test(title)) return false + if (/1165585369246138370$/.test(id)) return false + return true +} function makeBase62Token(num) { var alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var n = BigInt(num) @@ -624,41 +652,64 @@ function doAdvTask(cookie) { if (!tasks.length) return { name: "激励任务", ok: true, msg: "无待完成任务", path: "/argus/api/v2/video/adv/mainPage" } if (taskFinished(tasks[tasks.length - 1])) return { name: "激励任务", ok: true, msg: "已完成", path: "/argus/api/v2/video/adv/mainPage" } var done = 0 + var attempted = false + var blocked = 0 for (var i = 0; i < tasks.length; i++) { var t = tasks[i] || {} if (taskFinished(t)) continue var tid = taskIdOf(t) if (!tid) continue - var r = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie) - if (taskResultOk(r) || Number(resultCode(r)) === 9) done++ - else return { name: "激励任务", ok: false, msg: resultMsg(r) || ("任务 " + tid + " 失败"), path: "/argus/api/v1/video/adv/finishWatch" } + if (!advTaskActionable(t)) { blocked++; continue } + attempted = true + var fr = finishVideoTask(t, cookie) + done += fr.ok + if (fr.ok < fr.total) return { name: "激励任务", ok: false, msg: resultMsg(fr.last) || ("任务 " + tid + " 失败"), path: "/argus/api/v1/video/adv/finishWatch" } } var verify = qdGet("/argus/api/v2/video/adv/mainPage", {}, cookie) var after = firstArray(nested(verify, ["Data", "VideoRewardTab", "TaskList"]), nested(verify, ["data", "VideoRewardTab", "TaskList"]), nested(verify, ["Data", "TaskList"]), nested(verify, ["data", "TaskList"])) - var finished = false - for (var v = 0; v < after.length; v++) if (taskFinished(after[v])) { finished = true; break } - return { name: "激励任务", ok: finished || done > 0, msg: finished ? "已完成" : (done ? ("完成 " + done + " 个视频任务") : "无待完成任务"), path: "/argus/api/v2/video/adv/mainPage" } + var actionableLeft = 0 + for (var v = 0; v < after.length; v++) if (!taskFinished(after[v]) && taskIdOf(after[v]) && advTaskActionable(after[v])) actionableLeft++ + var finished = actionableLeft === 0 + var suffix = blocked ? (",跳过 " + blocked + " 个需其它入口任务") : "" + return { name: "激励任务", ok: finished, msg: finished ? (attempted ? ("已完成" + suffix) : ("无待完成任务" + suffix)) : (done ? ("已提交 " + done + " 个视频任务,仍有 " + actionableLeft + " 个未完成" + suffix) : ("仍有 " + actionableLeft + " 个未完成" + suffix)), path: "/argus/api/v2/video/adv/mainPage" } } function doDailyTask(cookie) { var list = qdGet("/argus/api/v3/checkin/packagelist", {}, cookie) if (!taskResultOk(list)) return { name: "每日任务", ok: false, msg: resultMsg(list) || "获取任务列表失败", path: "/argus/api/v3/checkin/packagelist" } var data = list.Data || list.data || {} var pre = field(data, ["Task", "SurpriseTask", "CurrentTask"], null) - if (pre && !taskFinished(pre) && taskIdOf(pre)) qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(taskIdOf(pre)), cookie) + var done = 0 + if (pre && !taskFinished(pre) && taskIdOf(pre)) { + var pr = finishVideoTask(pre, cookie, 1) + done += pr.ok + if (pr.ok < pr.total) return { name: "每日任务", ok: false, msg: resultMsg(pr.last) || "惊喜福利任务失败", path: "/argus/api/v1/video/adv/finishWatch" } + } var tasks = firstArray(nested(list, ["Data", "PackageList", "TaskList"]), nested(list, ["data", "PackageList", "TaskList"]), nested(list, ["Data", "TaskList"]), nested(list, ["data", "TaskList"]), nested(list, ["Data", "PackageList"]), nested(list, ["data", "PackageList"])) + var oneAd = null var target = null - for (var i = 0; i < tasks.length; i++) { var n = taskTitle(tasks[i]); if (n === "每日任务") { target = tasks[i]; break } } + for (var i = 0; i < tasks.length; i++) { + var n = taskTitle(tasks[i]) + if (!oneAd && /完成\s*1\s*个广告任务|1个广告|one.?ad/i.test(n)) oneAd = tasks[i] + if (!target && n === "每日任务") target = tasks[i] + } if (!target) for (var k = 0; k < tasks.length; k++) { var n2 = taskTitle(tasks[k]); if (/每日|daily/i.test(n2)) { target = tasks[k]; break } } - if (!target) return { name: "每日任务", ok: true, msg: "无每日任务", path: "/argus/api/v3/checkin/packagelist" } + if (oneAd && oneAd !== target && !taskFinished(oneAd) && taskIdOf(oneAd)) { + var ar = finishVideoTask(oneAd, cookie, 1) + done += ar.ok + if (ar.ok < ar.total) return { name: "每日任务", ok: false, msg: resultMsg(ar.last) || "完成1个广告任务失败", path: "/argus/api/v1/video/adv/finishWatch" } + } + if (!target) return { name: "每日任务", ok: true, msg: done ? ("完成 " + done + " 次") : "无每日任务", path: "/argus/api/v3/checkin/packagelist" } if (taskFinished(target)) return { name: "每日任务", ok: true, msg: "已完成", path: "/argus/api/v3/checkin/packagelist" } var tid = taskIdOf(target) - if (!tid) return { name: "每日任务", ok: true, msg: "无每日任务", path: "/argus/api/v3/checkin/packagelist" } - var done = 0 - for (var j = 0; j < 3; j++) { var r = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie); if (taskResultOk(r) || Number(resultCode(r)) === 9) done++ } + if (!tid) return { name: "每日任务", ok: true, msg: done ? ("完成 " + done + " 次") : "无每日任务", path: "/argus/api/v3/checkin/packagelist" } + var tr = finishVideoTask(target, cookie, taskRunCount(target, 3)) + done += tr.ok + if (tr.ok < tr.total) return { name: "每日任务", ok: false, msg: resultMsg(tr.last) || "每日任务广告失败", path: "/argus/api/v1/video/adv/finishWatch" } var verify = qdGet("/argus/api/v2/video/adv/mainPage", {}, cookie) - var after = firstArray(nested(verify, ["Data", "VideoRewardTab", "TaskList"]), nested(verify, ["data", "VideoRewardTab", "TaskList"])) + var after = firstArray(nested(verify, ["Data", "VideoRewardTab", "TaskList"]), nested(verify, ["data", "VideoRewardTab", "TaskList"]), nested(verify, ["Data", "TaskList"]), nested(verify, ["data", "TaskList"])) var finished = false - for (var v = 0; v < after.length; v++) if (taskTitle(after[v]) === "每日任务" && taskFinished(after[v])) { finished = true; break } + for (var v = 0; v < after.length; v++) if (taskIdKey(taskIdOf(after[v])) === taskIdKey(tid) && taskFinished(after[v])) { finished = true; break } + if (!finished) for (var x = 0; x < after.length; x++) if (taskTitle(after[x]) === "每日任务" && taskFinished(after[x])) { finished = true; break } return { name: "每日任务", ok: finished || done > 0, msg: finished ? "已完成" : (done > 0 ? ("完成 " + done + " 次") : "任务未完成"), path: "/argus/api/v3/checkin/packagelist" } } function doLotteryTask(cookie) { diff --git a/plugins/qdreader/qdreader_sign_autman.js b/plugins/qdreader/qdreader_sign_autman.js index 0b1ec3d..12ce394 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.3] +//[version: 2.10.5] //[platform: web,qq,wx,tg,tb,fs,we] //[public: false] //[price: 0] @@ -276,7 +276,7 @@ function contentText() { } function usage() { return [ - "启点读书签到插件 v2.10.2", + "启点读书签到插件 v2.10.5", "【一级菜单】", "账号管理:启点账号", "执行查询:启点查询", @@ -602,6 +602,34 @@ function field(obj, names, def) { function taskTitle(task) { return String(field(task, ["Title", "TaskName", "Name", "TaskTitle", "Desc", "Description"], "")) } function taskIdOf(task) { return field(task, ["TaskId", "TaskID", "taskId", "TaskIDStr", "Id", "id"], "") } function taskFinished(task) { return Number(field(task, ["IsFinished", "Finished", "Status", "status"], 0)) === 1 } +function taskIdKey(id) { return String(id === undefined || id === null ? "" : id) } +function taskRunCount(task, def) { + var n = Number(field(task, ["NeedCount", "needCount", "Count", "count", "TotalCount", "totalCount", "Times", "times", "Limit", "limit"], def || 1)) + if (!n || isNaN(n) || n < 1) n = def || 1 + if (n > 10) n = 10 + return n +} +function taskAccepted(res) { return taskResultOk(res) || Number(resultCode(res)) === 9 } +function finishVideoTask(taskOrId, cookie, repeat) { + var tid = typeof taskOrId === "object" ? taskIdOf(taskOrId || {}) : taskOrId + var n = repeat || (typeof taskOrId === "object" ? taskRunCount(taskOrId, 1) : 1) + var ok = 0 + var last = null + if (!tid) return { ok: 0, total: 0, last: null } + for (var i = 0; i < n; i++) { + last = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie) + if (taskAccepted(last)) ok++ + else break + } + return { ok: ok, total: n, last: last, taskId: tid } +} +function advTaskActionable(task) { + var title = taskTitle(task) + var id = taskIdKey(taskIdOf(task || {})) + if (/签到|check.?in|每日任务|抽奖|lottery|兑换|exchange|章节|chapter|荐书|消息|message/i.test(title)) return false + if (/1165585369246138370$/.test(id)) return false + return true +} function makeBase62Token(num) { var alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var n = BigInt(num) @@ -624,41 +652,64 @@ function doAdvTask(cookie) { if (!tasks.length) return { name: "激励任务", ok: true, msg: "无待完成任务", path: "/argus/api/v2/video/adv/mainPage" } if (taskFinished(tasks[tasks.length - 1])) return { name: "激励任务", ok: true, msg: "已完成", path: "/argus/api/v2/video/adv/mainPage" } var done = 0 + var attempted = false + var blocked = 0 for (var i = 0; i < tasks.length; i++) { var t = tasks[i] || {} if (taskFinished(t)) continue var tid = taskIdOf(t) if (!tid) continue - var r = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie) - if (taskResultOk(r) || Number(resultCode(r)) === 9) done++ - else return { name: "激励任务", ok: false, msg: resultMsg(r) || ("任务 " + tid + " 失败"), path: "/argus/api/v1/video/adv/finishWatch" } + if (!advTaskActionable(t)) { blocked++; continue } + attempted = true + var fr = finishVideoTask(t, cookie) + done += fr.ok + if (fr.ok < fr.total) return { name: "激励任务", ok: false, msg: resultMsg(fr.last) || ("任务 " + tid + " 失败"), path: "/argus/api/v1/video/adv/finishWatch" } } var verify = qdGet("/argus/api/v2/video/adv/mainPage", {}, cookie) var after = firstArray(nested(verify, ["Data", "VideoRewardTab", "TaskList"]), nested(verify, ["data", "VideoRewardTab", "TaskList"]), nested(verify, ["Data", "TaskList"]), nested(verify, ["data", "TaskList"])) - var finished = false - for (var v = 0; v < after.length; v++) if (taskFinished(after[v])) { finished = true; break } - return { name: "激励任务", ok: finished || done > 0, msg: finished ? "已完成" : (done ? ("完成 " + done + " 个视频任务") : "无待完成任务"), path: "/argus/api/v2/video/adv/mainPage" } + var actionableLeft = 0 + for (var v = 0; v < after.length; v++) if (!taskFinished(after[v]) && taskIdOf(after[v]) && advTaskActionable(after[v])) actionableLeft++ + var finished = actionableLeft === 0 + var suffix = blocked ? (",跳过 " + blocked + " 个需其它入口任务") : "" + return { name: "激励任务", ok: finished, msg: finished ? (attempted ? ("已完成" + suffix) : ("无待完成任务" + suffix)) : (done ? ("已提交 " + done + " 个视频任务,仍有 " + actionableLeft + " 个未完成" + suffix) : ("仍有 " + actionableLeft + " 个未完成" + suffix)), path: "/argus/api/v2/video/adv/mainPage" } } function doDailyTask(cookie) { var list = qdGet("/argus/api/v3/checkin/packagelist", {}, cookie) if (!taskResultOk(list)) return { name: "每日任务", ok: false, msg: resultMsg(list) || "获取任务列表失败", path: "/argus/api/v3/checkin/packagelist" } var data = list.Data || list.data || {} var pre = field(data, ["Task", "SurpriseTask", "CurrentTask"], null) - if (pre && !taskFinished(pre) && taskIdOf(pre)) qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(taskIdOf(pre)), cookie) + var done = 0 + if (pre && !taskFinished(pre) && taskIdOf(pre)) { + var pr = finishVideoTask(pre, cookie, 1) + done += pr.ok + if (pr.ok < pr.total) return { name: "每日任务", ok: false, msg: resultMsg(pr.last) || "惊喜福利任务失败", path: "/argus/api/v1/video/adv/finishWatch" } + } var tasks = firstArray(nested(list, ["Data", "PackageList", "TaskList"]), nested(list, ["data", "PackageList", "TaskList"]), nested(list, ["Data", "TaskList"]), nested(list, ["data", "TaskList"]), nested(list, ["Data", "PackageList"]), nested(list, ["data", "PackageList"])) + var oneAd = null var target = null - for (var i = 0; i < tasks.length; i++) { var n = taskTitle(tasks[i]); if (n === "每日任务") { target = tasks[i]; break } } + for (var i = 0; i < tasks.length; i++) { + var n = taskTitle(tasks[i]) + if (!oneAd && /完成\s*1\s*个广告任务|1个广告|one.?ad/i.test(n)) oneAd = tasks[i] + if (!target && n === "每日任务") target = tasks[i] + } if (!target) for (var k = 0; k < tasks.length; k++) { var n2 = taskTitle(tasks[k]); if (/每日|daily/i.test(n2)) { target = tasks[k]; break } } - if (!target) return { name: "每日任务", ok: true, msg: "无每日任务", path: "/argus/api/v3/checkin/packagelist" } + if (oneAd && oneAd !== target && !taskFinished(oneAd) && taskIdOf(oneAd)) { + var ar = finishVideoTask(oneAd, cookie, 1) + done += ar.ok + if (ar.ok < ar.total) return { name: "每日任务", ok: false, msg: resultMsg(ar.last) || "完成1个广告任务失败", path: "/argus/api/v1/video/adv/finishWatch" } + } + if (!target) return { name: "每日任务", ok: true, msg: done ? ("完成 " + done + " 次") : "无每日任务", path: "/argus/api/v3/checkin/packagelist" } if (taskFinished(target)) return { name: "每日任务", ok: true, msg: "已完成", path: "/argus/api/v3/checkin/packagelist" } var tid = taskIdOf(target) - if (!tid) return { name: "每日任务", ok: true, msg: "无每日任务", path: "/argus/api/v3/checkin/packagelist" } - var done = 0 - for (var j = 0; j < 3; j++) { var r = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie); if (taskResultOk(r) || Number(resultCode(r)) === 9) done++ } + if (!tid) return { name: "每日任务", ok: true, msg: done ? ("完成 " + done + " 次") : "无每日任务", path: "/argus/api/v3/checkin/packagelist" } + var tr = finishVideoTask(target, cookie, taskRunCount(target, 3)) + done += tr.ok + if (tr.ok < tr.total) return { name: "每日任务", ok: false, msg: resultMsg(tr.last) || "每日任务广告失败", path: "/argus/api/v1/video/adv/finishWatch" } var verify = qdGet("/argus/api/v2/video/adv/mainPage", {}, cookie) - var after = firstArray(nested(verify, ["Data", "VideoRewardTab", "TaskList"]), nested(verify, ["data", "VideoRewardTab", "TaskList"])) + var after = firstArray(nested(verify, ["Data", "VideoRewardTab", "TaskList"]), nested(verify, ["data", "VideoRewardTab", "TaskList"]), nested(verify, ["Data", "TaskList"]), nested(verify, ["data", "TaskList"])) var finished = false - for (var v = 0; v < after.length; v++) if (taskTitle(after[v]) === "每日任务" && taskFinished(after[v])) { finished = true; break } + for (var v = 0; v < after.length; v++) if (taskIdKey(taskIdOf(after[v])) === taskIdKey(tid) && taskFinished(after[v])) { finished = true; break } + if (!finished) for (var x = 0; x < after.length; x++) if (taskTitle(after[x]) === "每日任务" && taskFinished(after[x])) { finished = true; break } return { name: "每日任务", ok: finished || done > 0, msg: finished ? "已完成" : (done > 0 ? ("完成 " + done + " 次") : "任务未完成"), path: "/argus/api/v3/checkin/packagelist" } } function doLotteryTask(cookie) { diff --git a/tests/qdreader_plugin.test.mjs b/tests/qdreader_plugin.test.mjs index 327c21c..4705702 100644 --- a/tests/qdreader_plugin.test.mjs +++ b/tests/qdreader_plugin.test.mjs @@ -363,7 +363,7 @@ test('manual sign and cron sign both call sign gateway then qidian checkin', () qdreader_cookie_json: JSON.stringify({ '12345': cookie }), qdreader_user_bind_json: JSON.stringify({ '12345': 'user-a' }), }; - const manual = runPlugin({ content: '启点签到', store: { ...store } }); + const manual = runPlugin({ content: '启点签到', store: { ...store }, globals: { Date: fixedDateClass('2026-05-20T07:01:00.000+08:00') } }); assert.equal(manual.calls.length, 4); assert.match(manual.calls[0].url, /api\.120399\.xyz\/qdreader\/sign$/); assert.match(manual.calls[1].url, /h5\.if\.qidian\.com\/argus\/api\/v3\/checkin\/checkin$/); @@ -410,9 +410,9 @@ test('optional QDReader full tasks use upstream executable endpoints and sign ab (req) => String(req.url).includes('/sign') ? { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } } : { statusCode: 200, body: { code: 0, msg: 'ok' } }, { statusCode: 200, body: { code: 0, msg: '视频完成' } }, (req) => String(req.url).includes('/sign') ? { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } } : { statusCode: 200, body: { code: 0, msg: 'ok' } }, - { statusCode: 200, body: { code: 0, Data: { TaskList: [{ TaskId: 'daily-1', TaskName: '每日任务', IsFinished: 0, NeedWatchCount: 1 }] } } }, + { statusCode: 200, body: { code: 0, Data: { VideoRewardTab: { TaskList: [{ TaskId: 'adv-1', IsFinished: 1 }] } } } }, (req) => String(req.url).includes('/sign') ? { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } } : { statusCode: 200, body: { code: 0, msg: 'ok' } }, - { statusCode: 200, body: { code: 0, msg: '每日视频完成' } }, + { statusCode: 200, body: { code: 0, Data: { TaskList: [{ TaskId: 'daily-1', TaskName: '每日任务', IsFinished: 1, NeedWatchCount: 1 }] } } }, (req) => String(req.url).includes('/sign') ? { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } } : { statusCode: 200, body: { code: 0, msg: 'ok' } }, { statusCode: 200, body: { code: 0, Data: { HasVideoUrge: 0, LotteryCount: 1 } } }, (req) => String(req.url).includes('/sign') ? { body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } } : { statusCode: 200, body: { code: 0, msg: 'ok' } }, @@ -802,11 +802,11 @@ test('adv task treats upstream result 9 as accepted and verifies finished state' { 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: '1165585369246138372', Title: '看视频领福利', IsFinished: 0 }] } } } }, + { statusCode: 200, body: { code: 0, Data: { VideoRewardTab: { TaskList: [{ TaskId: 'old-done', Title: '已完成旧任务', IsFinished: 1 }, { TaskId: '1165585369246138372', 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: '1165585369246138372', Title: '看视频领福利', IsFinished: 1 }] } } } }, + { statusCode: 200, body: { code: 0, Data: { VideoRewardTab: { TaskList: [{ TaskId: 'old-done', Title: '已完成旧任务', IsFinished: 1 }, { TaskId: '1165585369246138372', Title: '看视频领福利', IsFinished: 1 }] } } } }, ], }); assert.match(r.replies[0], /激励任务✅\(已完成\)/); @@ -815,6 +815,67 @@ test('adv task treats upstream result 9 as accepted and verifies finished state' assert.match(r.calls.find(c => /video\/adv\/finishWatch$/.test(c.url)).body, /1165585369246138372/); }); +test('adv task does not report success when only unrelated task is finished after callback', () => { + 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: 'old-done', Title: '已完成旧任务', IsFinished: 1 }, { TaskId: '1165585369246138372', 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: 'old-done', Title: '已完成旧任务', IsFinished: 1 }, { TaskId: '1165585369246138372', Title: '看视频领福利', IsFinished: 0 }] } } } }, + ], + }); + assert.match(r.replies[0], /激励任务❌\(已提交 1 个视频任务,仍有 1 个未完成\)/); + assert.match(r.replies[0], /汇总:成功 0 \/ 失败 1 \/ 总计 1/); +}); + +test('adv task skips non-ad entries that must be handled by their own modules', () => { + 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: '1165585369246138372', Title: '看视频领福利', IsFinished: 0 }, + { TaskId: '1165585369246138370', 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: '1165585369246138372', Title: '看视频领福利', IsFinished: 1 }, + { TaskId: '1165585369246138370', Title: '每日签到', IsFinished: 0 } + ] } } } }, + ], + }); + assert.match(r.replies[0], /激励任务✅\(已完成,跳过 1 个需其它入口任务\)/); + assert.equal(r.calls.filter(c => /video\/adv\/finishWatch$/.test(c.url)).length, 1); + assert.doesNotMatch(r.calls.find(c => /video\/adv\/finishWatch$/.test(c.url)).body, /1165585369246138370/); +}); + test('adv task accepts nested data result 9 with empty message from sign gateway wrapper', () => { const store = { qdreader_cookie_json: JSON.stringify({ '12345': cookie }), @@ -843,6 +904,88 @@ test('adv task accepts nested data result 9 with empty message from sign gateway }); +test('adv task repeats same upstream fragment task until requested count before verification', () => { + 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, NeedCount: 3 }] } } } }, + { 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: { result: 9, message: '' } }, + { 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, NeedCount: 3 }] } } } }, + ], + }); + assert.match(r.replies[0], /激励任务✅\(已完成\)/); + const finishCalls = r.calls.filter(c => /video\/adv\/finishWatch$/.test(c.url)); + assert.equal(finishCalls.length, 3); + assert.ok(finishCalls.every(c => /1187767793786748928/.test(c.body))); +}); + +test('daily task follows upstream sequence: surprise task, one-ad task, then three daily ad callbacks', () => { + 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': { extraAdv: 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: { + CurrentTask: { TaskId: '1209756598013788162', Title: '惊喜福利任务', IsFinished: 0 }, + PackageList: { TaskList: [ + { TaskId: '1165585369246138373', Title: '完成1个广告任务', IsFinished: 0 }, + { TaskId: '1165585369246138372', Title: '每日任务', IsFinished: 0, NeedCount: 3 }, + ] } + } } }, + { 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: { result: 9, message: '' } }, + { 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: { result: 9, message: '' } }, + { 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: '1165585369246138373', Title: '完成1个广告任务', IsFinished: 1 }, + { TaskId: '1165585369246138372', Title: '每日任务', IsFinished: 1, NeedCount: 3 }, + ] } } } }, + ], + }); + assert.match(r.replies[0], /每日任务✅\(已完成\)/); + const bodies = r.calls.filter(c => /video\/adv\/finishWatch$/.test(c.url)).map(c => c.body); + assert.equal(bodies.length, 5); + assert.match(bodies[0], /1209756598013788162/); + assert.match(bodies[1], /1165585369246138373/); + assert.equal(bodies.filter(b => /1165585369246138372/.test(b)).length, 3); +}); + + test('message box skips outside Beijing 20-22 window without calling message API', () => { const store = { qdreader_cookie_json: JSON.stringify({ '12345': cookie }),