fix(qdreader): align weekly exchange payload
This commit is contained in:
@@ -71,7 +71,7 @@ GET /api/sms/stats
|
||||
- Cookie 维护:`启点账号` / `启点ck <cookie>` / `启点查询`
|
||||
- 任务配置入口:`启点账号` → `账号任务配置`,按账号保存到 `qdreader_task_pref_json`;`all on/off` 只影响选中的账号
|
||||
- 默认执行:主签到 `/argus/api/v3/checkin/checkin` + 固定低风险领取奖励 `/argus/api/v3/checkin/receivegifts`
|
||||
- 可选任务(默认关闭):激励任务 `mainPage`/`finishWatch`、每日任务 `packagelist`/`finishWatch`、每日抽奖 `detail`/`lottery`、每周兑换 `checkinexchangepage`/`exchangegoods`(仅周日)、章节卡片 `chaptercardwithbook`、大咖荐书 `pullmessage`
|
||||
- 可选任务(默认关闭):激励任务 `mainPage`/`finishWatch`、每日任务 `packagelist`/`finishWatch`、每日抽奖 `detail`/`lottery`、每周兑换 `checkinexchangepage`/`exchangegoods`(仅周日;按上游 `Goods`/`GoodsScore` 选择并提交 `goodId`)、章节卡片 `chaptercardwithbook`、大咖荐书 `pullmessage`
|
||||
- 签名环境:相对路径走 `https://h5.if.qidian.com`;大咖荐书使用绝对地址 `https://magev6.if.qidian.com/argus/api/v2/message/pullmessage`;请求签名 method 使用小写;Autman 运行时需支持 `BigInt`(插件避免使用 BigInt 字面量以兼容较旧 JS 解析器)
|
||||
- v2.10.3:复核全部任务矩阵、补齐执行窗口/接口文档,并移除 BigInt 字面量,保留 Autman 单插件、独立 bucket、多用户隔离、手动只跑当前用户、cron 跑全部账号、运行后立即归档清日志。
|
||||
|
||||
|
||||
@@ -650,6 +650,7 @@ QDReader 签到结果判定补充:
|
||||
- 激励任务 `finishWatch` 可能返回 `result=9` 或 `Result=9`,上游将其视为可继续/已受理,再回查 `mainPage` 判断任务是否完成;插件不能把大小写不同的 `result=9` 直接计为失败。
|
||||
- 每日任务不是简单取任务列表第一个。上游会先处理 `packagelist` 里的惊喜/前置任务,再从任务列表按标题定位“每日任务”,执行多次 `finishWatch` 后回查完成状态。
|
||||
- 大咖荐书不是一次 `pullmessage` 即结束。消息 token 需要由 `UserId` 经过 `(uid * 4 & 0x7ffffffffffffffc) ^ 0x113a4f` 后转 base62;流程是:拉会话列表(`appappend` + `categoryIds=0`)→ 对红点会话带 `fromId` 拉消息 → 从最近消息的 `ActionUrl/RefUrl` 提取 `orderId` → `/argus/api/v1/interaction/rewarddetail`(`refresh=0`)→ 根据 `ChanceCount/BookStatus` 走 `/argus/api/v1/interaction/rewardcallback` 或 `/argus/api/v1/interaction/rewardaddshelf`。如果直接用十进制 token、旧的 `token/type/senderId` 参数,或旧的 `/argus/api/v2/recommend/...` 路径,实测会偏离当前上游并容易返回“参数错误”。
|
||||
- 每周兑换按上游字段执行:读取兑换页 `Goods` / `GoodsScore` / `ChapterCardCount`,选择当前余额可覆盖的最高 `GoodsScore` 章节卡商品,并用小写 `goodId` 调用 `exchangegoods`;`result=-220000` 明确视为“未到兑换时间”。
|
||||
- 大咖荐书不应再做固定北京时间窗口限制;当前反混淆上游里没有 `20:00-22:00` 之类限制,只保留“消息必须是最近一段时间”的过滤。若接口在某些时段返回空列表/无未读消息,按正常无任务处理,不要在插件侧提前跳过。
|
||||
- “无可领取礼包/今日已签到”属于幂等成功类提示;这类提示不应让整个账号汇总失败。
|
||||
- 每次修复这类链路必须补 VM 回归测试:请求路径、签名 payload 里的参数、特殊返回码分类、最终账号成功/失败汇总。
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//[author: Hermes]
|
||||
//[service: BOSS]
|
||||
//[class: 工具类]
|
||||
//[version: 2.11.0]
|
||||
//[version: 2.11.1]
|
||||
//[platform: web,qq,wx,tg,tb,fs,we]
|
||||
//[public: false]
|
||||
//[price: 0]
|
||||
@@ -716,12 +716,24 @@ function doWeeklyExchange(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)
|
||||
if (balance <= 0) return { name: "每周兑换", ok: false, msg: "余额不足(" + balance + ")", path: "/argus/api/v3/checkin/checkinexchangepage" }
|
||||
var goods = firstArray(data.Goods, data.GoodsList, data.GiftList, data.List, data.Items)
|
||||
if (!goods.length) return { name: "每周兑换", ok: false, msg: "章节卡列表为空", path: "/argus/api/v3/checkin/checkinexchangepage" }
|
||||
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" }
|
||||
for (var i = 0; i < goods.length; i++) {
|
||||
var price = Number(goods[i].GoodsScore || goods[i].NeedScore || goods[i].Price || goods[i].Score || 0)
|
||||
if (price > 0 && price <= balance && (!chosen || price > Number(chosen.GoodsScore || chosen.NeedScore || chosen.Price || chosen.Score || 0))) chosen = goods[i]
|
||||
}
|
||||
if (!chosen) return { name: "每周兑换", ok: false, msg: "余额不足(" + balance + ")", path: "/argus/api/v3/checkin/checkinexchangepage" }
|
||||
var gid = chosen.GoodsId || chosen.Id || chosen.GoodsID
|
||||
var priceText = Number(chosen.GoodsScore || chosen.NeedScore || chosen.Price || chosen.Score || 0)
|
||||
var cardCount = chosen.ChapterCardCount || chosen.CardCount || chosen.Count || ""
|
||||
var r = qdPost("/argus/api/v3/checkin/exchangegoods", { goodId: gid }, cookie)
|
||||
var code = Number(r && (r.result !== undefined ? r.result : (r.Result !== undefined ? r.Result : (r.code !== undefined ? r.code : r.Code))))
|
||||
if (code === -220000) return { name: "每周兑换", ok: false, msg: "未到兑换时间", path: "/argus/api/v3/checkin/exchangegoods" }
|
||||
var ok = taskResultOk(r)
|
||||
var successMsg = cardCount ? ("消耗 " + priceText + " 点兑换 " + cardCount + " 点章节卡") : "兑换成功"
|
||||
return { name: "每周兑换", ok: ok, msg: resultMsg(r) || (ok ? successMsg : "兑换失败"), path: "/argus/api/v3/checkin/exchangegoods" }
|
||||
}
|
||||
function doChapterCard(cookie) {
|
||||
var res = qdGet("/argus/api/v1/readtime/scoremall/chaptercardwithbook", {}, cookie)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//[author: Hermes]
|
||||
//[service: BOSS]
|
||||
//[class: 工具类]
|
||||
//[version: 2.11.0]
|
||||
//[version: 2.11.1]
|
||||
//[platform: web,qq,wx,tg,tb,fs,we]
|
||||
//[public: false]
|
||||
//[price: 0]
|
||||
@@ -716,12 +716,24 @@ function doWeeklyExchange(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)
|
||||
if (balance <= 0) return { name: "每周兑换", ok: false, msg: "余额不足(" + balance + ")", path: "/argus/api/v3/checkin/checkinexchangepage" }
|
||||
var goods = firstArray(data.Goods, data.GoodsList, data.GiftList, data.List, data.Items)
|
||||
if (!goods.length) return { name: "每周兑换", ok: false, msg: "章节卡列表为空", path: "/argus/api/v3/checkin/checkinexchangepage" }
|
||||
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" }
|
||||
for (var i = 0; i < goods.length; i++) {
|
||||
var price = Number(goods[i].GoodsScore || goods[i].NeedScore || goods[i].Price || goods[i].Score || 0)
|
||||
if (price > 0 && price <= balance && (!chosen || price > Number(chosen.GoodsScore || chosen.NeedScore || chosen.Price || chosen.Score || 0))) chosen = goods[i]
|
||||
}
|
||||
if (!chosen) return { name: "每周兑换", ok: false, msg: "余额不足(" + balance + ")", path: "/argus/api/v3/checkin/checkinexchangepage" }
|
||||
var gid = chosen.GoodsId || chosen.Id || chosen.GoodsID
|
||||
var priceText = Number(chosen.GoodsScore || chosen.NeedScore || chosen.Price || chosen.Score || 0)
|
||||
var cardCount = chosen.ChapterCardCount || chosen.CardCount || chosen.Count || ""
|
||||
var r = qdPost("/argus/api/v3/checkin/exchangegoods", { goodId: gid }, cookie)
|
||||
var code = Number(r && (r.result !== undefined ? r.result : (r.Result !== undefined ? r.Result : (r.code !== undefined ? r.code : r.Code))))
|
||||
if (code === -220000) return { name: "每周兑换", ok: false, msg: "未到兑换时间", path: "/argus/api/v3/checkin/exchangegoods" }
|
||||
var ok = taskResultOk(r)
|
||||
var successMsg = cardCount ? ("消耗 " + priceText + " 点兑换 " + cardCount + " 点章节卡") : "兑换成功"
|
||||
return { name: "每周兑换", ok: ok, msg: resultMsg(r) || (ok ? successMsg : "兑换失败"), path: "/argus/api/v3/checkin/exchangegoods" }
|
||||
}
|
||||
function doChapterCard(cookie) {
|
||||
var res = qdGet("/argus/api/v1/readtime/scoremall/chaptercardwithbook", {}, cookie)
|
||||
|
||||
@@ -432,7 +432,7 @@ 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: { Balance: 100, GoodsList: [{ GoodsId: 'g1', NeedScore: 10 }] } } },
|
||||
{ statusCode: 200, body: { code: 0, Data: { Balance: 100, Goods: [{ GoodsId: 'g1', GoodsScore: 10, ChapterCardCount: 5 }] } } },
|
||||
(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' } },
|
||||
@@ -458,6 +458,9 @@ test('optional QDReader full tasks use upstream executable endpoints and sign ab
|
||||
const finishCalls = r.calls.filter(c => /argus\/api\/v1\/video\/adv\/finishWatch$/.test(c.url));
|
||||
assert.equal(finishCalls.length, 6);
|
||||
assert.deepEqual(finishCalls.map(c => new URLSearchParams(c.body).get('taskId')), ['adv-1', 'surprise-1', 'one-ad', 'daily-3', 'daily-3', 'daily-3']);
|
||||
const exchangeCall = r.calls.find(c => /argus\/api\/v3\/checkin\/exchangegoods$/.test(c.url));
|
||||
assert.equal(new URLSearchParams(exchangeCall.body).get('goodId'), 'g1');
|
||||
assert.equal(new URLSearchParams(exchangeCall.body).get('GoodsId'), null);
|
||||
assert.match(r.replies[0], /激励任务✅/);
|
||||
assert.match(r.replies[0], /每日任务✅/);
|
||||
assert.match(r.replies[0], /每日抽奖✅/);
|
||||
@@ -466,6 +469,66 @@ test('optional QDReader full tasks use upstream executable endpoints and sign ab
|
||||
assert.match(r.replies[0], /大咖荐书✅/);
|
||||
});
|
||||
|
||||
test('weekly exchange follows upstream Goods/GoodsScore and posts lowercase goodId', () => {
|
||||
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': { weeklyExchange: true } }),
|
||||
};
|
||||
const sunday = fixedDateClass('2026-05-24T07:01:00.000+08:00');
|
||||
const r = runPlugin({
|
||||
content: '启点签到',
|
||||
store,
|
||||
userId: 'user-a',
|
||||
globals: { Date: sunday },
|
||||
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: { Balance: 25, Goods: [
|
||||
{ GoodsId: 'cheap', GoodsScore: 10, ChapterCardCount: 1 },
|
||||
{ GoodsId: 'best', GoodsScore: 20, ChapterCardCount: 3 },
|
||||
{ GoodsId: 'too-expensive', GoodsScore: 50, ChapterCardCount: 10 },
|
||||
] } } },
|
||||
{ body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } },
|
||||
{ statusCode: 200, body: { code: 0, Data: {}, msg: '' } },
|
||||
],
|
||||
});
|
||||
const exchangeCall = r.calls.find(c => /argus\/api\/v3\/checkin\/exchangegoods$/.test(c.url));
|
||||
assert.ok(exchangeCall);
|
||||
assert.equal(new URLSearchParams(exchangeCall.body).get('goodId'), 'best');
|
||||
assert.equal(new URLSearchParams(exchangeCall.body).get('GoodsId'), null);
|
||||
assert.match(r.replies[0], /每周兑换✅\(消耗 20 点兑换 3 点章节卡\)/);
|
||||
});
|
||||
|
||||
test('weekly exchange reports upstream not-time result explicitly', () => {
|
||||
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': { weeklyExchange: true } }),
|
||||
};
|
||||
const sunday = fixedDateClass('2026-05-24T07:01:00.000+08:00');
|
||||
const r = runPlugin({
|
||||
content: '启点签到',
|
||||
store,
|
||||
userId: 'user-a',
|
||||
globals: { Date: sunday },
|
||||
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: { Balance: 25, Goods: [{ GoodsId: 'g1', GoodsScore: 10, ChapterCardCount: 1 }] } } },
|
||||
{ body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } },
|
||||
{ statusCode: 200, body: { result: -220000, message: 'raw not time' } },
|
||||
],
|
||||
});
|
||||
assert.match(r.replies[0], /每周兑换❌\(未到兑换时间\)/);
|
||||
});
|
||||
|
||||
test('bucketGet/bucketSet fallback rejects when user identity is absent', () => {
|
||||
const replies = [];
|
||||
const bucketStore = {};
|
||||
|
||||
Reference in New Issue
Block a user