fix(qdreader): use upstream executable task endpoints

This commit is contained in:
2026-05-20 18:29:59 +08:00
parent 85405751b8
commit 9091137852
4 changed files with 296 additions and 30 deletions
+111 -9
View File
@@ -38,13 +38,13 @@ var DEFAULT_SIGN_API = "https://api.120399.xyz/qdreader"
var QIDIAN_BASE = "https://h5.if.qidian.com" var QIDIAN_BASE = "https://h5.if.qidian.com"
var CHECKIN_PATH = "/argus/api/v3/checkin/checkin" var CHECKIN_PATH = "/argus/api/v3/checkin/checkin"
var QD_TASKS = [ var QD_TASKS = [
{ key: "reward", label: "领取奖励", path: "/argus/api/v3/checkin/getcurrentweekcheckininfo", method: "GET", defaultEnabled: true, builtin: true }, { key: "reward", label: "领取奖励", handler: "receiveGifts", defaultEnabled: true, builtin: true },
{ key: "adv", label: "激励任务", path: "/argus/api/v2/video/adv/mainPage", method: "GET", settingKey: ADV_JOB_ENABLE_KEY }, { key: "adv", label: "激励任务", handler: "adv", settingKey: ADV_JOB_ENABLE_KEY },
{ key: "extraAdv", label: "每日任务", path: "/argus/api/v2/video/adv/packageList", method: "GET", settingKey: EXTRA_ADV_JOB_ENABLE_KEY }, { key: "extraAdv", label: "每日任务", handler: "daily", 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: "lottery", label: "每日抽奖", handler: "lottery", settingKey: LOTTERY_ENABLE_KEY },
{ key: "weeklyExchange", label: "每周兑换", path: "/argus/api/v2/video/exchange/page", method: "GET", settingKey: WEEKLY_EXCHANGE_ENABLE_KEY, sundayOnly: true }, { key: "weeklyExchange", label: "每周兑换", handler: "exchange", 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: "chapterCard", label: "章节卡片", handler: "chapterCard", settingKey: CHAPTER_CARD_ENABLE_KEY },
{ key: "messageBox", label: "大咖荐书", path: "/ajax/message/getMessageBox", method: "GET", settingKey: MESSAGE_BOX_ENABLE_KEY, params: { type: "qdreader" } } { key: "messageBox", label: "大咖荐书", handler: "messageBox", settingKey: MESSAGE_BOX_ENABLE_KEY }
] ]
function defined(name) { try { return typeof this[name] !== "undefined" } catch (e) { return false } } 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) var info = normalizeCookie(cookie)
if (!info.uid) throw new Error("Cookie 缺少 uid/QDHeader") if (!info.uid) throw new Error("Cookie 缺少 uid/QDHeader")
var signApi = String(cfgGet(SIGN_API_KEY, DEFAULT_SIGN_API)).replace(/\/+$/, "") 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)) var headers = normalizeSignHeaders(postJson(signApi + "/sign", payload))
headers.Cookie = info.cookie 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" 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") { if (method === "GET") {
var query = qs(data || {}) var query = qs(data || {})
if (query) req.url += (req.url.indexOf("?") >= 0 ? "&" : "?") + query if (query) req.url += (req.url.indexOf("?") >= 0 ? "&" : "?") + query
@@ -570,7 +571,108 @@ function taskMenuForUid(uid) {
setTaskPrefs(all) setTaskPrefs(all)
return reply(task.label + "已" + (enable ? "开启" : "关闭") + "(账号 " + maskUid(uid) + "\n" + taskStatusLines(uid)) 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) { 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 data = task.body || task.params || {}
var res = qidianRequest(task.path, task.method || "GET", data, cookie) var res = qidianRequest(task.path, task.method || "GET", data, cookie)
var ok = taskResultOk(res) var ok = taskResultOk(res)
+7 -7
View File
@@ -158,13 +158,13 @@ set(key, value)
3. `POST https://h5.if.qidian.com/argus/api/v3/checkin/checkin` 完成签到。 3. `POST https://h5.if.qidian.com/argus/api/v3/checkin/checkin` 完成签到。
4. “今日已签到/已经签到/重复签到”等签到响应按成功处理,避免重复执行时误计失败。 4. “今日已签到/已经签到/重复签到”等签到响应按成功处理,避免重复执行时误计失败。
5. 按账号读取 `qdreader_task_pref_json` 后追加完全体任务链路: 5. 按账号读取 `qdreader_task_pref_json` 后追加完全体任务链路:
- 固定执行领取奖励状态查询:`GET /argus/api/v3/checkin/getcurrentweekcheckininfo` - 固定执行领取奖励`POST /argus/api/v3/checkin/receivegifts`
- 可选激励任务:`GET /argus/api/v2/video/adv/mainPage` - 可选激励任务:`GET /argus/api/v2/video/adv/mainPage`,再对未完成任务调用 `POST /argus/api/v1/video/adv/finishWatch`
- 可选每日任务:`GET /argus/api/v2/video/adv/packageList` - 可选每日任务:`GET /argus/api/v3/checkin/packagelist`,按返回任务调用 `POST /argus/api/v1/video/adv/finishWatch`
- 可选每日抽奖:`POST /argus/api/v2/video/lottery` - 可选每日抽奖:`GET /argus/api/v2/checkin/detail`,有次数时 `POST /argus/api/v2/checkin/lottery`
- 可选每周兑换:`GET /argus/api/v2/video/exchange/page`,仅北京时间周日执行; - 可选每周兑换:`GET /argus/api/v3/checkin/checkinexchangepage`,可兑换时 `POST /argus/api/v3/checkin/exchangegoods`,仅北京时间周日执行;
- 可选章节卡片:`GET /argus/api/v2/video/chapterCard/info` - 可选章节卡片:`GET /argus/api/v1/readtime/scoremall/chaptercardwithbook`
- 可选大咖荐书:`GET /ajax/message/getMessageBox` - 可选大咖荐书:`GET https://magev6.if.qidian.com/argus/api/v2/message/pullmessage`
6. 对签名网关返回 `url 不在允许的接口列表内` 的可选任务,记为“已跳过”而不是账号失败,用于兼容网关白名单尚未覆盖的上游接口。 6. 对签名网关返回 `url 不在允许的接口列表内` 的可选任务,记为“已跳过”而不是账号失败,用于兼容网关白名单尚未覆盖的上游接口。
7. 写入单账号当日结果 `qdreader_last_result_json`,包括 `ok/msg/time/source/runId/tasks` 7. 写入单账号当日结果 `qdreader_last_result_json`,包括 `ok/msg/time/source/runId/tasks`
8. 写入当日批次日志 `qdreader_last_run_json`,包括触发来源、开始/结束时间、成功/失败/总数和每账号结果;即使账号执行异常也会记录失败日志。 8. 写入当日批次日志 `qdreader_last_run_json`,包括触发来源、开始/结束时间、成功/失败/总数和每账号结果;即使账号执行异常也会记录失败日志。
+111 -9
View File
@@ -38,13 +38,13 @@ var DEFAULT_SIGN_API = "https://api.120399.xyz/qdreader"
var QIDIAN_BASE = "https://h5.if.qidian.com" var QIDIAN_BASE = "https://h5.if.qidian.com"
var CHECKIN_PATH = "/argus/api/v3/checkin/checkin" var CHECKIN_PATH = "/argus/api/v3/checkin/checkin"
var QD_TASKS = [ var QD_TASKS = [
{ key: "reward", label: "领取奖励", path: "/argus/api/v3/checkin/getcurrentweekcheckininfo", method: "GET", defaultEnabled: true, builtin: true }, { key: "reward", label: "领取奖励", handler: "receiveGifts", defaultEnabled: true, builtin: true },
{ key: "adv", label: "激励任务", path: "/argus/api/v2/video/adv/mainPage", method: "GET", settingKey: ADV_JOB_ENABLE_KEY }, { key: "adv", label: "激励任务", handler: "adv", settingKey: ADV_JOB_ENABLE_KEY },
{ key: "extraAdv", label: "每日任务", path: "/argus/api/v2/video/adv/packageList", method: "GET", settingKey: EXTRA_ADV_JOB_ENABLE_KEY }, { key: "extraAdv", label: "每日任务", handler: "daily", 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: "lottery", label: "每日抽奖", handler: "lottery", settingKey: LOTTERY_ENABLE_KEY },
{ key: "weeklyExchange", label: "每周兑换", path: "/argus/api/v2/video/exchange/page", method: "GET", settingKey: WEEKLY_EXCHANGE_ENABLE_KEY, sundayOnly: true }, { key: "weeklyExchange", label: "每周兑换", handler: "exchange", 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: "chapterCard", label: "章节卡片", handler: "chapterCard", settingKey: CHAPTER_CARD_ENABLE_KEY },
{ key: "messageBox", label: "大咖荐书", path: "/ajax/message/getMessageBox", method: "GET", settingKey: MESSAGE_BOX_ENABLE_KEY, params: { type: "qdreader" } } { key: "messageBox", label: "大咖荐书", handler: "messageBox", settingKey: MESSAGE_BOX_ENABLE_KEY }
] ]
function defined(name) { try { return typeof this[name] !== "undefined" } catch (e) { return false } } 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) var info = normalizeCookie(cookie)
if (!info.uid) throw new Error("Cookie 缺少 uid/QDHeader") if (!info.uid) throw new Error("Cookie 缺少 uid/QDHeader")
var signApi = String(cfgGet(SIGN_API_KEY, DEFAULT_SIGN_API)).replace(/\/+$/, "") 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)) var headers = normalizeSignHeaders(postJson(signApi + "/sign", payload))
headers.Cookie = info.cookie 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" 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") { if (method === "GET") {
var query = qs(data || {}) var query = qs(data || {})
if (query) req.url += (req.url.indexOf("?") >= 0 ? "&" : "?") + query if (query) req.url += (req.url.indexOf("?") >= 0 ? "&" : "?") + query
@@ -570,7 +571,108 @@ function taskMenuForUid(uid) {
setTaskPrefs(all) setTaskPrefs(all)
return reply(task.label + "已" + (enable ? "开启" : "关闭") + "(账号 " + maskUid(uid) + "\n" + taskStatusLines(uid)) 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) { 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 data = task.body || task.params || {}
var res = qidianRequest(task.path, task.method || "GET", data, cookie) var res = qidianRequest(task.path, task.method || "GET", data, cookie)
var ok = taskResultOk(res) var ok = taskResultOk(res)
+67 -5
View File
@@ -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') } }); 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.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], /启点读书签到结果/);
assert.match(r.replies[0], /日志已归档:2026-05-19/); assert.match(r.replies[0], /日志已归档:2026-05-19/);
const archive = JSON.parse(r.store.qdreader_archive_json); 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') } }); 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.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.match(r.replies[0], /日志已归档:2026-05-19/);
assert.equal(JSON.parse(r.store.qdreader_archive_json)['2026-05-19'].source, 'manual'); assert.equal(JSON.parse(r.store.qdreader_archive_json)['2026-05-19'].source, 'manual');
assert.deepEqual(JSON.parse(r.store.qdreader_last_run_json), {}); 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.equal(manual.calls.length, 4);
assert.match(manual.calls[0].url, /api\.120399\.xyz\/qdreader\/sign$/); 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[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], /✅ 12\*\*\*45/);
assert.match(manual.replies[0], /记录时间:/); assert.match(manual.replies[0], /记录时间:/);
assert.equal(JSON.parse(manual.store.qdreader_archive_json)['2026-05-20'].source, 'manual'); 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') } }); const cron = runPlugin({ content: '', store: { ...store }, globals: { Date: fixedDateClass('2026-05-19T07:01:00.000+08:00') } });
assert.equal(cron.calls.length, 4); 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.match(cron.replies[0], /启点读书签到结果/);
assert.equal(JSON.parse(cron.store.qdreader_archive_json)['2026-05-19'].source, 'cron'); assert.equal(JSON.parse(cron.store.qdreader_archive_json)['2026-05-19'].source, 'cron');
assert.deepEqual(JSON.parse(cron.store.qdreader_last_run_json), {}); 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', () => { test('bucketGet/bucketSet fallback rejects when user identity is absent', () => {
const replies = []; const replies = [];
const bucketStore = {}; 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' }); 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.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.calls[5].url, /video\/adv\/mainPage$/);
assert.match(sign.replies[0], /激励任务/); assert.match(sign.replies[0], /激励任务/);
}); });