From 9091137852fb0412ebdf2cb6317deb845b50523e Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Wed, 20 May 2026 18:29:59 +0800 Subject: [PATCH] fix(qdreader): use upstream executable task endpoints --- js/qdreader_sign_autman.js | 120 +++++++++++++++++++++-- plugins/qdreader/README.md | 14 +-- plugins/qdreader/qdreader_sign_autman.js | 120 +++++++++++++++++++++-- tests/qdreader_plugin.test.mjs | 72 +++++++++++++- 4 files changed, 296 insertions(+), 30 deletions(-) diff --git a/js/qdreader_sign_autman.js b/js/qdreader_sign_autman.js index f17438b..17246ca 100644 --- a/js/qdreader_sign_autman.js +++ b/js/qdreader_sign_autman.js @@ -38,13 +38,13 @@ var DEFAULT_SIGN_API = "https://api.120399.xyz/qdreader" var QIDIAN_BASE = "https://h5.if.qidian.com" var CHECKIN_PATH = "/argus/api/v3/checkin/checkin" var QD_TASKS = [ - { key: "reward", label: "领取奖励", path: "/argus/api/v3/checkin/getcurrentweekcheckininfo", method: "GET", defaultEnabled: true, builtin: true }, - { key: "adv", label: "激励任务", path: "/argus/api/v2/video/adv/mainPage", method: "GET", settingKey: ADV_JOB_ENABLE_KEY }, - { key: "extraAdv", label: "每日任务", path: "/argus/api/v2/video/adv/packageList", method: "GET", settingKey: EXTRA_ADV_JOB_ENABLE_KEY }, - { key: "lottery", label: "每日抽奖", path: "/argus/api/v2/video/lottery", method: "POST", settingKey: LOTTERY_ENABLE_KEY, body: { source: "autman" } }, - { key: "weeklyExchange", label: "每周兑换", path: "/argus/api/v2/video/exchange/page", method: "GET", settingKey: WEEKLY_EXCHANGE_ENABLE_KEY, sundayOnly: true }, - { key: "chapterCard", label: "章节卡片", path: "/argus/api/v2/video/chapterCard/info", method: "GET", settingKey: CHAPTER_CARD_ENABLE_KEY }, - { key: "messageBox", label: "大咖荐书", path: "/ajax/message/getMessageBox", method: "GET", settingKey: MESSAGE_BOX_ENABLE_KEY, params: { type: "qdreader" } } + { key: "reward", label: "领取奖励", handler: "receiveGifts", defaultEnabled: true, builtin: true }, + { key: "adv", label: "激励任务", handler: "adv", settingKey: ADV_JOB_ENABLE_KEY }, + { key: "extraAdv", label: "每日任务", handler: "daily", settingKey: EXTRA_ADV_JOB_ENABLE_KEY }, + { key: "lottery", label: "每日抽奖", handler: "lottery", settingKey: LOTTERY_ENABLE_KEY }, + { key: "weeklyExchange", label: "每周兑换", handler: "exchange", settingKey: WEEKLY_EXCHANGE_ENABLE_KEY, sundayOnly: true }, + { key: "chapterCard", label: "章节卡片", handler: "chapterCard", settingKey: CHAPTER_CARD_ENABLE_KEY }, + { key: "messageBox", label: "大咖荐书", handler: "messageBox", settingKey: MESSAGE_BOX_ENABLE_KEY } ] function defined(name) { try { return typeof this[name] !== "undefined" } catch (e) { return false } } @@ -386,11 +386,12 @@ function qidianRequest(path, method, data, cookie) { var info = normalizeCookie(cookie) if (!info.uid) throw new Error("Cookie 缺少 uid/QDHeader") var signApi = String(cfgGet(SIGN_API_KEY, DEFAULT_SIGN_API)).replace(/\/+$/, "") - var payload = { qdh: info.qdh, qdheader: info.qdheader, url: QIDIAN_BASE + path, method: method, data: data || {}, uid: info.uid, guid: info.guid, qimei: info.qimei, qimei36: info.qimei36 } + var targetUrl = /^https?:\/\//i.test(path) ? path : QIDIAN_BASE + path + var payload = { qdh: info.qdh, qdheader: info.qdheader, url: targetUrl, method: lower(method), data: data || {}, uid: info.uid, guid: info.guid, qimei: info.qimei, qimei36: info.qimei36 } var headers = normalizeSignHeaders(postJson(signApi + "/sign", payload)) headers.Cookie = info.cookie if (!headers["User-Agent"]) headers["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/QDReaderiOS/5.9.456/728/QDReaderAppstore" - var req = { url: QIDIAN_BASE + path, method: lower(method), dataType: "json", timeOut: 10000, headers: headers } + var req = { url: targetUrl, method: lower(method), dataType: "json", timeOut: 10000, headers: headers } if (method === "GET") { var query = qs(data || {}) if (query) req.url += (req.url.indexOf("?") >= 0 ? "&" : "?") + query @@ -570,7 +571,108 @@ function taskMenuForUid(uid) { setTaskPrefs(all) return reply(task.label + "已" + (enable ? "开启" : "关闭") + "(账号 " + maskUid(uid) + ")\n" + taskStatusLines(uid)) } +function qdGet(path, data, cookie) { return qidianRequest(path, "GET", data || {}, cookie) } +function qdPost(path, data, cookie) { return qidianRequest(path, "POST", data || {}, cookie) } +function firstArray() { + for (var i = 0; i < arguments.length; i++) if (Array.isArray(arguments[i])) return arguments[i] + return [] +} +function nested(obj, path) { + var cur = obj + for (var i = 0; i < path.length; i++) { + if (!cur || cur[path[i]] === undefined || cur[path[i]] === null) return undefined + cur = cur[path[i]] + } + return cur +} +function makeVideoPayload(taskId) { + return { TaskId: String(taskId || ""), BanId: "0", BanMessage: "", Challenge: "", CaptchaType: "0", Fee: "", Gif: "", Gt: "", NewCaptcha: "0", Offline: "0", PhoneTicket: "", SessionKey: "" } +} +function doReceiveGifts(cookie) { + var res = qdPost("/argus/api/v3/checkin/receivegifts", { GameId: "0" }, cookie) + var ok = taskResultOk(res) + return { name: "领取奖励", ok: !!ok, msg: resultMsg(res) || (ok ? "领取成功" : "领取失败"), path: "/argus/api/v3/checkin/receivegifts" } +} +function doAdvTask(cookie) { + var main = qdGet("/argus/api/v2/video/adv/mainPage", {}, cookie) + if (!taskResultOk(main)) return { name: "激励任务", ok: false, msg: resultMsg(main) || "获取任务失败", path: "/argus/api/v2/video/adv/mainPage" } + var tasks = firstArray(nested(main, ["Data", "VideoRewardTab", "TaskList"]), nested(main, ["data", "VideoRewardTab", "TaskList"]), nested(main, ["Data", "TaskList"]), nested(main, ["data", "TaskList"])) + var done = 0 + for (var i = 0; i < tasks.length; i++) { + var t = tasks[i] || {} + if (Number(t.IsFinished) === 1) continue + var tid = t.TaskId || t.TaskID || t.taskId || "" + if (!tid) continue + var r = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie) + if (taskResultOk(r)) done++ + else return { name: "激励任务", ok: false, msg: resultMsg(r) || ("任务 " + tid + " 失败"), path: "/argus/api/v1/video/adv/finishWatch" } + } + return { name: "激励任务", ok: true, msg: done ? ("完成 " + done + " 个视频任务") : "无待完成任务", 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 tasks = firstArray(nested(list, ["Data", "TaskList"]), nested(list, ["data", "TaskList"]), nested(list, ["Data", "PackageList"]), nested(list, ["data", "PackageList"])) + var target = null + for (var i = 0; i < tasks.length; i++) { var n = String(tasks[i].TaskName || tasks[i].Name || tasks[i].Title || ""); if (/每日|daily/i.test(n)) { target = tasks[i]; break } } + if (!target && tasks.length) target = tasks[0] + if (!target) return { name: "每日任务", ok: true, msg: "无每日任务", path: "/argus/api/v3/checkin/packagelist" } + if (Number(target.IsFinished) === 1) return { name: "每日任务", ok: true, msg: "已完成", path: "/argus/api/v3/checkin/packagelist" } + var count = Math.max(1, Math.min(3, Number(target.NeedWatchCount || target.Times || 1) || 1)) + var tid = target.TaskId || target.TaskID || target.taskId || "" + var done = 0 + for (var j = 0; j < count; j++) { var r = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie); if (taskResultOk(r)) done++ } + return { name: "每日任务", ok: done > 0, msg: done > 0 ? ("完成 " + done + " 次") : "任务未完成", path: "/argus/api/v3/checkin/packagelist" } +} +function doLotteryTask(cookie) { + var detail = qdGet("/argus/api/v2/checkin/detail", {}, cookie) + if (!taskResultOk(detail)) return { name: "每日抽奖", ok: false, msg: resultMsg(detail) || "获取抽奖信息失败", path: "/argus/api/v2/checkin/detail" } + var data = detail.Data || detail.data || {} + var times = Number(data.HasVideoUrge || 0) + Number(data.LotteryCount || data.LotteryChance || data.ChanceCount || 0) + if (!times) return { name: "每日抽奖", ok: true, msg: "无可用抽奖次数", path: "/argus/api/v2/checkin/detail" } + times = Math.min(times, 10) + var okCount = 0 + for (var i = 0; i < times; i++) { var r = qdPost("/argus/api/v2/checkin/lottery", { GameId: "0", IsVideo: i < Number(data.HasVideoUrge || 0) ? "1" : "0" }, cookie); if (taskResultOk(r)) okCount++ } + return { name: "每日抽奖", ok: okCount > 0, msg: "成功 " + okCount + "/" + times + " 次抽奖", path: "/argus/api/v2/checkin/lottery" } +} +function doWeeklyExchange(cookie) { + var page = qdGet("/argus/api/v3/checkin/checkinexchangepage", {}, cookie) + if (!taskResultOk(page)) return { name: "每周兑换", ok: false, msg: resultMsg(page) || "获取兑换页失败", path: "/argus/api/v3/checkin/checkinexchangepage" } + var data = page.Data || page.data || {} + var balance = Number(data.Balance || data.Score || 0) + var goods = firstArray(data.GoodsList, data.GiftList, data.List, data.Items) + var chosen = null + for (var i = 0; i < goods.length; i++) { var price = Number(goods[i].NeedScore || goods[i].Price || goods[i].Score || 0); if (price > 0 && price <= balance && (!chosen || price > Number(chosen.NeedScore || chosen.Price || chosen.Score || 0))) chosen = goods[i] } + if (!chosen) return { name: "每周兑换", ok: true, msg: "无可兑换商品或余额不足(" + balance + ")", path: "/argus/api/v3/checkin/checkinexchangepage" } + var r = qdPost("/argus/api/v3/checkin/exchangegoods", { GoodsId: chosen.GoodsId || chosen.Id || chosen.GoodsID }, cookie) + return { name: "每周兑换", ok: taskResultOk(r), msg: resultMsg(r) || (taskResultOk(r) ? "兑换成功" : "兑换失败"), path: "/argus/api/v3/checkin/exchangegoods" } +} +function doChapterCard(cookie) { + var res = qdGet("/argus/api/v1/readtime/scoremall/chaptercardwithbook", {}, cookie) + var ok = taskResultOk(res) + var cards = firstArray(nested(res, ["Data", "Cards"]), nested(res, ["data", "Cards"]), nested(res, ["Data", "ChapterCards"]), nested(res, ["data", "ChapterCards"])) + return { name: "章节卡片", ok: !!ok, msg: ok ? (cards.length ? ("章节卡 " + cards.length + " 张") : "暂无章节卡") : (resultMsg(res) || "查询失败"), path: "/argus/api/v1/readtime/scoremall/chaptercardwithbook" } +} +function messageTokenFromCookie(cookie) { + var jar = parseCookieString(cookie) + var uid = extractUid(cookie) + var raw = uid ? String((BigInt(uid) * 4n & 0x7ffffffffffffffcn) ^ 0x113a4fen) : "" + return raw || uid || jar.ywguid || "" +} +function doMessageBox(cookie) { + var token = messageTokenFromCookie(cookie) + if (!token) return { name: "大咖荐书", ok: true, msg: "缺少消息 token,已跳过", path: "https://magev6.if.qidian.com/argus/api/v2/message/pullmessage" } + var res = qdGet("https://magev6.if.qidian.com/argus/api/v2/message/pullmessage", { token: token, type: "0", pg: "1", pz: "10" }, cookie) + return { name: "大咖荐书", ok: taskResultOk(res), msg: taskResultOk(res) ? "消息检查完成" : (resultMsg(res) || "消息检查失败"), path: "https://magev6.if.qidian.com/argus/api/v2/message/pullmessage" } +} function doExtraTask(task, cookie) { + if (task.handler === "receiveGifts") return doReceiveGifts(cookie) + if (task.handler === "adv") return doAdvTask(cookie) + if (task.handler === "daily") return doDailyTask(cookie) + if (task.handler === "lottery") return doLotteryTask(cookie) + if (task.handler === "exchange") return doWeeklyExchange(cookie) + if (task.handler === "chapterCard") return doChapterCard(cookie) + if (task.handler === "messageBox") return doMessageBox(cookie) var data = task.body || task.params || {} var res = qidianRequest(task.path, task.method || "GET", data, cookie) var ok = taskResultOk(res) diff --git a/plugins/qdreader/README.md b/plugins/qdreader/README.md index 7402b50..e96c234 100644 --- a/plugins/qdreader/README.md +++ b/plugins/qdreader/README.md @@ -158,13 +158,13 @@ set(key, value) 3. `POST https://h5.if.qidian.com/argus/api/v3/checkin/checkin` 完成签到。 4. “今日已签到/已经签到/重复签到”等签到响应按成功处理,避免重复执行时误计失败。 5. 按账号读取 `qdreader_task_pref_json` 后追加完全体任务链路: - - 固定执行领取奖励状态查询:`GET /argus/api/v3/checkin/getcurrentweekcheckininfo`; - - 可选激励任务:`GET /argus/api/v2/video/adv/mainPage`; - - 可选每日任务:`GET /argus/api/v2/video/adv/packageList`; - - 可选每日抽奖:`POST /argus/api/v2/video/lottery`; - - 可选每周兑换:`GET /argus/api/v2/video/exchange/page`,仅北京时间周日执行; - - 可选章节卡片:`GET /argus/api/v2/video/chapterCard/info`; - - 可选大咖荐书:`GET /ajax/message/getMessageBox`。 + - 固定执行领取奖励:`POST /argus/api/v3/checkin/receivegifts`; + - 可选激励任务:`GET /argus/api/v2/video/adv/mainPage`,再对未完成任务调用 `POST /argus/api/v1/video/adv/finishWatch`; + - 可选每日任务:`GET /argus/api/v3/checkin/packagelist`,按返回任务调用 `POST /argus/api/v1/video/adv/finishWatch`; + - 可选每日抽奖:先 `GET /argus/api/v2/checkin/detail`,有次数时 `POST /argus/api/v2/checkin/lottery`; + - 可选每周兑换:`GET /argus/api/v3/checkin/checkinexchangepage`,可兑换时 `POST /argus/api/v3/checkin/exchangegoods`,仅北京时间周日执行; + - 可选章节卡片:`GET /argus/api/v1/readtime/scoremall/chaptercardwithbook`; + - 可选大咖荐书:`GET https://magev6.if.qidian.com/argus/api/v2/message/pullmessage`。 6. 对签名网关返回 `url 不在允许的接口列表内` 的可选任务,记为“已跳过”而不是账号失败,用于兼容网关白名单尚未覆盖的上游接口。 7. 写入单账号当日结果 `qdreader_last_result_json`,包括 `ok/msg/time/source/runId/tasks`。 8. 写入当日批次日志 `qdreader_last_run_json`,包括触发来源、开始/结束时间、成功/失败/总数和每账号结果;即使账号执行异常也会记录失败日志。 diff --git a/plugins/qdreader/qdreader_sign_autman.js b/plugins/qdreader/qdreader_sign_autman.js index f17438b..17246ca 100644 --- a/plugins/qdreader/qdreader_sign_autman.js +++ b/plugins/qdreader/qdreader_sign_autman.js @@ -38,13 +38,13 @@ var DEFAULT_SIGN_API = "https://api.120399.xyz/qdreader" var QIDIAN_BASE = "https://h5.if.qidian.com" var CHECKIN_PATH = "/argus/api/v3/checkin/checkin" var QD_TASKS = [ - { key: "reward", label: "领取奖励", path: "/argus/api/v3/checkin/getcurrentweekcheckininfo", method: "GET", defaultEnabled: true, builtin: true }, - { key: "adv", label: "激励任务", path: "/argus/api/v2/video/adv/mainPage", method: "GET", settingKey: ADV_JOB_ENABLE_KEY }, - { key: "extraAdv", label: "每日任务", path: "/argus/api/v2/video/adv/packageList", method: "GET", settingKey: EXTRA_ADV_JOB_ENABLE_KEY }, - { key: "lottery", label: "每日抽奖", path: "/argus/api/v2/video/lottery", method: "POST", settingKey: LOTTERY_ENABLE_KEY, body: { source: "autman" } }, - { key: "weeklyExchange", label: "每周兑换", path: "/argus/api/v2/video/exchange/page", method: "GET", settingKey: WEEKLY_EXCHANGE_ENABLE_KEY, sundayOnly: true }, - { key: "chapterCard", label: "章节卡片", path: "/argus/api/v2/video/chapterCard/info", method: "GET", settingKey: CHAPTER_CARD_ENABLE_KEY }, - { key: "messageBox", label: "大咖荐书", path: "/ajax/message/getMessageBox", method: "GET", settingKey: MESSAGE_BOX_ENABLE_KEY, params: { type: "qdreader" } } + { key: "reward", label: "领取奖励", handler: "receiveGifts", defaultEnabled: true, builtin: true }, + { key: "adv", label: "激励任务", handler: "adv", settingKey: ADV_JOB_ENABLE_KEY }, + { key: "extraAdv", label: "每日任务", handler: "daily", settingKey: EXTRA_ADV_JOB_ENABLE_KEY }, + { key: "lottery", label: "每日抽奖", handler: "lottery", settingKey: LOTTERY_ENABLE_KEY }, + { key: "weeklyExchange", label: "每周兑换", handler: "exchange", settingKey: WEEKLY_EXCHANGE_ENABLE_KEY, sundayOnly: true }, + { key: "chapterCard", label: "章节卡片", handler: "chapterCard", settingKey: CHAPTER_CARD_ENABLE_KEY }, + { key: "messageBox", label: "大咖荐书", handler: "messageBox", settingKey: MESSAGE_BOX_ENABLE_KEY } ] function defined(name) { try { return typeof this[name] !== "undefined" } catch (e) { return false } } @@ -386,11 +386,12 @@ function qidianRequest(path, method, data, cookie) { var info = normalizeCookie(cookie) if (!info.uid) throw new Error("Cookie 缺少 uid/QDHeader") var signApi = String(cfgGet(SIGN_API_KEY, DEFAULT_SIGN_API)).replace(/\/+$/, "") - var payload = { qdh: info.qdh, qdheader: info.qdheader, url: QIDIAN_BASE + path, method: method, data: data || {}, uid: info.uid, guid: info.guid, qimei: info.qimei, qimei36: info.qimei36 } + var targetUrl = /^https?:\/\//i.test(path) ? path : QIDIAN_BASE + path + var payload = { qdh: info.qdh, qdheader: info.qdheader, url: targetUrl, method: lower(method), data: data || {}, uid: info.uid, guid: info.guid, qimei: info.qimei, qimei36: info.qimei36 } var headers = normalizeSignHeaders(postJson(signApi + "/sign", payload)) headers.Cookie = info.cookie if (!headers["User-Agent"]) headers["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/QDReaderiOS/5.9.456/728/QDReaderAppstore" - var req = { url: QIDIAN_BASE + path, method: lower(method), dataType: "json", timeOut: 10000, headers: headers } + var req = { url: targetUrl, method: lower(method), dataType: "json", timeOut: 10000, headers: headers } if (method === "GET") { var query = qs(data || {}) if (query) req.url += (req.url.indexOf("?") >= 0 ? "&" : "?") + query @@ -570,7 +571,108 @@ function taskMenuForUid(uid) { setTaskPrefs(all) return reply(task.label + "已" + (enable ? "开启" : "关闭") + "(账号 " + maskUid(uid) + ")\n" + taskStatusLines(uid)) } +function qdGet(path, data, cookie) { return qidianRequest(path, "GET", data || {}, cookie) } +function qdPost(path, data, cookie) { return qidianRequest(path, "POST", data || {}, cookie) } +function firstArray() { + for (var i = 0; i < arguments.length; i++) if (Array.isArray(arguments[i])) return arguments[i] + return [] +} +function nested(obj, path) { + var cur = obj + for (var i = 0; i < path.length; i++) { + if (!cur || cur[path[i]] === undefined || cur[path[i]] === null) return undefined + cur = cur[path[i]] + } + return cur +} +function makeVideoPayload(taskId) { + return { TaskId: String(taskId || ""), BanId: "0", BanMessage: "", Challenge: "", CaptchaType: "0", Fee: "", Gif: "", Gt: "", NewCaptcha: "0", Offline: "0", PhoneTicket: "", SessionKey: "" } +} +function doReceiveGifts(cookie) { + var res = qdPost("/argus/api/v3/checkin/receivegifts", { GameId: "0" }, cookie) + var ok = taskResultOk(res) + return { name: "领取奖励", ok: !!ok, msg: resultMsg(res) || (ok ? "领取成功" : "领取失败"), path: "/argus/api/v3/checkin/receivegifts" } +} +function doAdvTask(cookie) { + var main = qdGet("/argus/api/v2/video/adv/mainPage", {}, cookie) + if (!taskResultOk(main)) return { name: "激励任务", ok: false, msg: resultMsg(main) || "获取任务失败", path: "/argus/api/v2/video/adv/mainPage" } + var tasks = firstArray(nested(main, ["Data", "VideoRewardTab", "TaskList"]), nested(main, ["data", "VideoRewardTab", "TaskList"]), nested(main, ["Data", "TaskList"]), nested(main, ["data", "TaskList"])) + var done = 0 + for (var i = 0; i < tasks.length; i++) { + var t = tasks[i] || {} + if (Number(t.IsFinished) === 1) continue + var tid = t.TaskId || t.TaskID || t.taskId || "" + if (!tid) continue + var r = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie) + if (taskResultOk(r)) done++ + else return { name: "激励任务", ok: false, msg: resultMsg(r) || ("任务 " + tid + " 失败"), path: "/argus/api/v1/video/adv/finishWatch" } + } + return { name: "激励任务", ok: true, msg: done ? ("完成 " + done + " 个视频任务") : "无待完成任务", 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 tasks = firstArray(nested(list, ["Data", "TaskList"]), nested(list, ["data", "TaskList"]), nested(list, ["Data", "PackageList"]), nested(list, ["data", "PackageList"])) + var target = null + for (var i = 0; i < tasks.length; i++) { var n = String(tasks[i].TaskName || tasks[i].Name || tasks[i].Title || ""); if (/每日|daily/i.test(n)) { target = tasks[i]; break } } + if (!target && tasks.length) target = tasks[0] + if (!target) return { name: "每日任务", ok: true, msg: "无每日任务", path: "/argus/api/v3/checkin/packagelist" } + if (Number(target.IsFinished) === 1) return { name: "每日任务", ok: true, msg: "已完成", path: "/argus/api/v3/checkin/packagelist" } + var count = Math.max(1, Math.min(3, Number(target.NeedWatchCount || target.Times || 1) || 1)) + var tid = target.TaskId || target.TaskID || target.taskId || "" + var done = 0 + for (var j = 0; j < count; j++) { var r = qdPost("/argus/api/v1/video/adv/finishWatch", makeVideoPayload(tid), cookie); if (taskResultOk(r)) done++ } + return { name: "每日任务", ok: done > 0, msg: done > 0 ? ("完成 " + done + " 次") : "任务未完成", path: "/argus/api/v3/checkin/packagelist" } +} +function doLotteryTask(cookie) { + var detail = qdGet("/argus/api/v2/checkin/detail", {}, cookie) + if (!taskResultOk(detail)) return { name: "每日抽奖", ok: false, msg: resultMsg(detail) || "获取抽奖信息失败", path: "/argus/api/v2/checkin/detail" } + var data = detail.Data || detail.data || {} + var times = Number(data.HasVideoUrge || 0) + Number(data.LotteryCount || data.LotteryChance || data.ChanceCount || 0) + if (!times) return { name: "每日抽奖", ok: true, msg: "无可用抽奖次数", path: "/argus/api/v2/checkin/detail" } + times = Math.min(times, 10) + var okCount = 0 + for (var i = 0; i < times; i++) { var r = qdPost("/argus/api/v2/checkin/lottery", { GameId: "0", IsVideo: i < Number(data.HasVideoUrge || 0) ? "1" : "0" }, cookie); if (taskResultOk(r)) okCount++ } + return { name: "每日抽奖", ok: okCount > 0, msg: "成功 " + okCount + "/" + times + " 次抽奖", path: "/argus/api/v2/checkin/lottery" } +} +function doWeeklyExchange(cookie) { + var page = qdGet("/argus/api/v3/checkin/checkinexchangepage", {}, cookie) + if (!taskResultOk(page)) return { name: "每周兑换", ok: false, msg: resultMsg(page) || "获取兑换页失败", path: "/argus/api/v3/checkin/checkinexchangepage" } + var data = page.Data || page.data || {} + var balance = Number(data.Balance || data.Score || 0) + var goods = firstArray(data.GoodsList, data.GiftList, data.List, data.Items) + var chosen = null + for (var i = 0; i < goods.length; i++) { var price = Number(goods[i].NeedScore || goods[i].Price || goods[i].Score || 0); if (price > 0 && price <= balance && (!chosen || price > Number(chosen.NeedScore || chosen.Price || chosen.Score || 0))) chosen = goods[i] } + if (!chosen) return { name: "每周兑换", ok: true, msg: "无可兑换商品或余额不足(" + balance + ")", path: "/argus/api/v3/checkin/checkinexchangepage" } + var r = qdPost("/argus/api/v3/checkin/exchangegoods", { GoodsId: chosen.GoodsId || chosen.Id || chosen.GoodsID }, cookie) + return { name: "每周兑换", ok: taskResultOk(r), msg: resultMsg(r) || (taskResultOk(r) ? "兑换成功" : "兑换失败"), path: "/argus/api/v3/checkin/exchangegoods" } +} +function doChapterCard(cookie) { + var res = qdGet("/argus/api/v1/readtime/scoremall/chaptercardwithbook", {}, cookie) + var ok = taskResultOk(res) + var cards = firstArray(nested(res, ["Data", "Cards"]), nested(res, ["data", "Cards"]), nested(res, ["Data", "ChapterCards"]), nested(res, ["data", "ChapterCards"])) + return { name: "章节卡片", ok: !!ok, msg: ok ? (cards.length ? ("章节卡 " + cards.length + " 张") : "暂无章节卡") : (resultMsg(res) || "查询失败"), path: "/argus/api/v1/readtime/scoremall/chaptercardwithbook" } +} +function messageTokenFromCookie(cookie) { + var jar = parseCookieString(cookie) + var uid = extractUid(cookie) + var raw = uid ? String((BigInt(uid) * 4n & 0x7ffffffffffffffcn) ^ 0x113a4fen) : "" + return raw || uid || jar.ywguid || "" +} +function doMessageBox(cookie) { + var token = messageTokenFromCookie(cookie) + if (!token) return { name: "大咖荐书", ok: true, msg: "缺少消息 token,已跳过", path: "https://magev6.if.qidian.com/argus/api/v2/message/pullmessage" } + var res = qdGet("https://magev6.if.qidian.com/argus/api/v2/message/pullmessage", { token: token, type: "0", pg: "1", pz: "10" }, cookie) + return { name: "大咖荐书", ok: taskResultOk(res), msg: taskResultOk(res) ? "消息检查完成" : (resultMsg(res) || "消息检查失败"), path: "https://magev6.if.qidian.com/argus/api/v2/message/pullmessage" } +} function doExtraTask(task, cookie) { + if (task.handler === "receiveGifts") return doReceiveGifts(cookie) + if (task.handler === "adv") return doAdvTask(cookie) + if (task.handler === "daily") return doDailyTask(cookie) + if (task.handler === "lottery") return doLotteryTask(cookie) + if (task.handler === "exchange") return doWeeklyExchange(cookie) + if (task.handler === "chapterCard") return doChapterCard(cookie) + if (task.handler === "messageBox") return doMessageBox(cookie) var data = task.body || task.params || {} var res = qidianRequest(task.path, task.method || "GET", data, cookie) var ok = taskResultOk(res) diff --git a/tests/qdreader_plugin.test.mjs b/tests/qdreader_plugin.test.mjs index 26c0430..cd0ef6c 100644 --- a/tests/qdreader_plugin.test.mjs +++ b/tests/qdreader_plugin.test.mjs @@ -245,7 +245,7 @@ test('cron signs then immediately archives and clears recent logs', () => { }; const r = runPlugin({ content: '', store, userId: 'cron', globals: { Date: fixedDateClass('2026-05-19T07:01:00.000+08:00') } }); assert.equal(r.calls.length, 4); - assert.match(r.calls[3].url, /getcurrentweekcheckininfo$/); + assert.match(r.calls[3].url, /receivegifts$/); assert.match(r.replies[0], /启点读书签到结果/); assert.match(r.replies[0], /日志已归档:2026-05-19/); const archive = JSON.parse(r.store.qdreader_archive_json); @@ -263,7 +263,7 @@ test('manual sign also archives immediately after run', () => { }; const r = runPlugin({ content: '启点签到', store, userId: 'user-a', globals: { Date: fixedDateClass('2026-05-19T08:01:00.000+08:00') } }); assert.equal(r.calls.length, 4); - assert.match(r.calls[3].url, /getcurrentweekcheckininfo$/); + assert.match(r.calls[3].url, /receivegifts$/); assert.match(r.replies[0], /日志已归档:2026-05-19/); assert.equal(JSON.parse(r.store.qdreader_archive_json)['2026-05-19'].source, 'manual'); assert.deepEqual(JSON.parse(r.store.qdreader_last_run_json), {}); @@ -342,7 +342,7 @@ test('manual sign and cron sign both call sign gateway then qidian checkin', () 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$/); - assert.match(manual.calls[3].url, /getcurrentweekcheckininfo$/); + assert.match(manual.calls[3].url, /receivegifts$/); assert.match(manual.replies[0], /✅ 12\*\*\*45/); assert.match(manual.replies[0], /记录时间:/); assert.equal(JSON.parse(manual.store.qdreader_archive_json)['2026-05-20'].source, 'manual'); @@ -355,12 +355,74 @@ test('manual sign and cron sign both call sign gateway then qidian checkin', () const cron = runPlugin({ content: '', store: { ...store }, globals: { Date: fixedDateClass('2026-05-19T07:01:00.000+08:00') } }); assert.equal(cron.calls.length, 4); - assert.match(cron.calls[3].url, /getcurrentweekcheckininfo$/); + assert.match(cron.calls[3].url, /receivegifts$/); assert.match(cron.replies[0], /启点读书签到结果/); assert.equal(JSON.parse(cron.store.qdreader_archive_json)['2026-05-19'].source, 'cron'); assert.deepEqual(JSON.parse(cron.store.qdreader_last_run_json), {}); }); + +test('optional QDReader full tasks use upstream executable endpoints and sign absolute message API', () => { + 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, extraAdv: true, lottery: true, weeklyExchange: true, chapterCard: true, messageBox: true } }), + }; + const originalDate = Date; + const sunday = fixedDateClass('2026-05-24T07:01:00.000+08:00'); + const r = runPlugin({ + content: '启点签到', + store, + userId: 'user-a', + globals: { Date: sunday }, + requests: [ + (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, 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: { VideoRewardTab: { TaskList: [{ TaskId: 'adv-1', IsFinished: 0 }] } } } }, + (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 }] } } }, + (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: { 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' } }, + { 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: { Balance: 100, GoodsList: [{ GoodsId: 'g1', NeedScore: 10 }] } } }, + (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: { Cards: [{ id: 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: { List: [] } } }, + ], + }); + const urls = r.calls.map(c => c.url); + assert.ok(urls.some(u => /argus\/api\/v3\/checkin\/receivegifts$/.test(u))); + assert.ok(urls.some(u => /argus\/api\/v2\/video\/adv\/mainPage$/.test(u))); + assert.ok(urls.some(u => /argus\/api\/v1\/video\/adv\/finishWatch$/.test(u))); + assert.ok(urls.some(u => /argus\/api\/v3\/checkin\/packagelist$/.test(u))); + assert.ok(urls.some(u => /argus\/api\/v2\/checkin\/detail$/.test(u))); + assert.ok(urls.some(u => /argus\/api\/v2\/checkin\/lottery$/.test(u))); + assert.ok(urls.some(u => /argus\/api\/v3\/checkin\/checkinexchangepage$/.test(u))); + assert.ok(urls.some(u => /argus\/api\/v3\/checkin\/exchangegoods$/.test(u))); + assert.ok(urls.some(u => /argus\/api\/v1\/readtime\/scoremall\/chaptercardwithbook$/.test(u))); + assert.ok(urls.some(u => /^https:\/\/magev6\.if\.qidian\.com\/argus\/api\/v2\/message\/pullmessage/.test(u))); + const signed = r.calls.filter(c => /api\.120399\.xyz\/qdreader\/sign$/.test(c.url)).map(c => JSON.parse(c.body).url); + assert.ok(signed.includes('https://magev6.if.qidian.com/argus/api/v2/message/pullmessage')); + assert.match(r.replies[0], /激励任务✅/); + assert.match(r.replies[0], /每日任务✅/); + assert.match(r.replies[0], /每日抽奖✅/); + assert.match(r.replies[0], /每周兑换✅/); + assert.match(r.replies[0], /章节卡片✅/); + assert.match(r.replies[0], /大咖荐书✅/); +}); + test('bucketGet/bucketSet fallback rejects when user identity is absent', () => { const replies = []; const bucketStore = {}; @@ -585,7 +647,7 @@ test('account task menu stores full task switches per account and sign uses sele const sign = runPlugin({ content: '启点签到', store: { ...store, qdreader_task_pref_json: JSON.stringify({ '12345': { adv: true } }) }, userId: 'user-a' }); assert.equal(sign.calls.length, 6); - assert.match(sign.calls[3].url, /getcurrentweekcheckininfo$/); + assert.match(sign.calls[3].url, /receivegifts$/); assert.match(sign.calls[5].url, /video\/adv\/mainPage$/); assert.match(sign.replies[0], /激励任务/); });