fix(qdreader): discover shifted task lists

This commit is contained in:
2026-06-20 14:01:09 +08:00
parent e03ad733f4
commit becdf739dc
3 changed files with 207 additions and 12 deletions
+61 -6
View File
@@ -618,6 +618,61 @@ function nested(obj, path) {
}
return cur
}
function payloadData(obj) { return (obj && (obj.Data || obj.data)) || {} }
function looksLikeVideoTask(task) { return isObject(task) && !!taskIdOf(task) && (task["IsFinished"] !== undefined || task["IsReceived"] !== undefined || task["Title"] !== undefined || task["TaskName"] !== undefined) }
function taskArrayScore(list, kind) {
if (!Array.isArray(list) || !list.length) return 0
var score = 0
for (var i = 0; i < list.length; i++) {
var t = list[i] || {}
if (!looksLikeVideoTask(t)) continue
var title = taskTitle(t)
score += 2
if (kind === "daily" && (title === "完成1个广告任务得奖励" || title === "完成3个广告任务得奖励" || t["IsReceived"] !== undefined)) score += 5
if (kind === "adv" && (t["IsFinished"] !== undefined || /激励|碎片|福利|广告/.test(title))) score += 3
}
return score
}
function findTaskListByShape(root, kind) {
var best = []
var bestScore = 0
var seen = []
function walk(node, depth) {
if (!node || depth > 8) return
if (Array.isArray(node)) {
var score = taskArrayScore(node, kind)
if (score > bestScore) { bestScore = score; best = node }
for (var i = 0; i < node.length; i++) walk(node[i], depth + 1)
return
}
if (!isObject(node) || seen.indexOf(node) >= 0) return
seen.push(node)
for (var k in node) walk(node[k], depth + 1)
}
walk(root, 0)
return best
}
function advTaskList(main) {
return firstArray(
nested(main, ["Data", "DailyBenefitModule", "TaskList"]),
nested(main, ["data", "DailyBenefitModule", "TaskList"]),
nested(main, ["Data", "AdvTaskList"]),
nested(main, ["data", "AdvTaskList"]),
findTaskListByShape(payloadData(main), "adv")
)
}
function dailyTaskList(main) {
return firstArray(
nested(main, ["Data", "VideoRewardTab", "TaskList"]),
nested(main, ["data", "VideoRewardTab", "TaskList"]),
nested(main, ["Data", "TaskList"]),
nested(main, ["data", "TaskList"]),
findTaskListByShape(payloadData(main), "daily")
)
}
function surpriseBenefit(data) {
return data.SurpriseBenefit || data.SurpriseTask || data.SurpriseBenefitTask || null
}
function makeVideoPayload(taskId) {
return { taskId: String(taskId || ""), BanId: "0", BanMessage: "", CaptchaAId: "", CaptchaURL: "", CaptchaType: "0", Challenge: "", Gt: "", NewCaptcha: "0", Offline: "0", PhoneNumber: "", SessionKey: "" }
}
@@ -667,7 +722,7 @@ function doReceiveGifts(cookie) {
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", "DailyBenefitModule", "TaskList"]), nested(main, ["data", "DailyBenefitModule", "TaskList"]))
var tasks = advTaskList(main)
if (!tasks.length) return { name: "激励任务", ok: false, 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 submittedCount = 0
@@ -681,7 +736,7 @@ function doAdvTask(cookie) {
if (fr.ok < fr.total) return { name: "激励任务", ok: false, msg: resultMsg(fr.last) || ("第 " + (i + 1) + " 个任务失败"), path: "/argus/api/v1/video/adv/finishWatch" }
}
var verify = qdGet("/argus/api/v2/video/adv/mainPage", {}, cookie)
var after = firstArray(nested(verify, ["Data", "DailyBenefitModule", "TaskList"]), nested(verify, ["data", "DailyBenefitModule", "TaskList"]))
var after = advTaskList(verify)
var finished = !!(after && after.length && after.every(function (t) { return taskFinished(t) }))
return { name: "激励任务", ok: finished, msg: finished ? "任务已完成" : (submittedCount ? "部分任务未完成" : "未找到可提交任务"), path: "/argus/api/v2/video/adv/mainPage" }
}
@@ -691,15 +746,15 @@ function taskReceived(task) {
function doDailyTask(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 data = main.Data || main.data || {}
var data = payloadData(main)
var done = 0
var pre = data.SurpriseBenefit
var pre = surpriseBenefit(data)
if (pre && !taskFinished(pre) && taskIdOf(pre)) {
var pr = finishVideoTask(taskIdOf(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(main, ["Data", "VideoRewardTab", "TaskList"]), nested(main, ["data", "VideoRewardTab", "TaskList"]))
var tasks = dailyTaskList(main)
if (!tasks.length) return { name: "每日任务", ok: false, msg: "任务列表为空", path: "/argus/api/v2/video/adv/mainPage" }
var oneAd = null
var target = null
@@ -721,7 +776,7 @@ function doDailyTask(cookie) {
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 = dailyTaskList(verify)
var finished = false
for (var v = 0; v < after.length; v++) if (taskTitle(after[v]) === "完成3个广告任务得奖励" && taskReceived(after[v])) { finished = true; break }
return { name: "每日任务", ok: finished, msg: finished ? "任务已完成" : "任务未完成", path: "/argus/api/v2/video/adv/mainPage" }
+61 -6
View File
@@ -618,6 +618,61 @@ function nested(obj, path) {
}
return cur
}
function payloadData(obj) { return (obj && (obj.Data || obj.data)) || {} }
function looksLikeVideoTask(task) { return isObject(task) && !!taskIdOf(task) && (task["IsFinished"] !== undefined || task["IsReceived"] !== undefined || task["Title"] !== undefined || task["TaskName"] !== undefined) }
function taskArrayScore(list, kind) {
if (!Array.isArray(list) || !list.length) return 0
var score = 0
for (var i = 0; i < list.length; i++) {
var t = list[i] || {}
if (!looksLikeVideoTask(t)) continue
var title = taskTitle(t)
score += 2
if (kind === "daily" && (title === "完成1个广告任务得奖励" || title === "完成3个广告任务得奖励" || t["IsReceived"] !== undefined)) score += 5
if (kind === "adv" && (t["IsFinished"] !== undefined || /激励|碎片|福利|广告/.test(title))) score += 3
}
return score
}
function findTaskListByShape(root, kind) {
var best = []
var bestScore = 0
var seen = []
function walk(node, depth) {
if (!node || depth > 8) return
if (Array.isArray(node)) {
var score = taskArrayScore(node, kind)
if (score > bestScore) { bestScore = score; best = node }
for (var i = 0; i < node.length; i++) walk(node[i], depth + 1)
return
}
if (!isObject(node) || seen.indexOf(node) >= 0) return
seen.push(node)
for (var k in node) walk(node[k], depth + 1)
}
walk(root, 0)
return best
}
function advTaskList(main) {
return firstArray(
nested(main, ["Data", "DailyBenefitModule", "TaskList"]),
nested(main, ["data", "DailyBenefitModule", "TaskList"]),
nested(main, ["Data", "AdvTaskList"]),
nested(main, ["data", "AdvTaskList"]),
findTaskListByShape(payloadData(main), "adv")
)
}
function dailyTaskList(main) {
return firstArray(
nested(main, ["Data", "VideoRewardTab", "TaskList"]),
nested(main, ["data", "VideoRewardTab", "TaskList"]),
nested(main, ["Data", "TaskList"]),
nested(main, ["data", "TaskList"]),
findTaskListByShape(payloadData(main), "daily")
)
}
function surpriseBenefit(data) {
return data.SurpriseBenefit || data.SurpriseTask || data.SurpriseBenefitTask || null
}
function makeVideoPayload(taskId) {
return { taskId: String(taskId || ""), BanId: "0", BanMessage: "", CaptchaAId: "", CaptchaURL: "", CaptchaType: "0", Challenge: "", Gt: "", NewCaptcha: "0", Offline: "0", PhoneNumber: "", SessionKey: "" }
}
@@ -667,7 +722,7 @@ function doReceiveGifts(cookie) {
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", "DailyBenefitModule", "TaskList"]), nested(main, ["data", "DailyBenefitModule", "TaskList"]))
var tasks = advTaskList(main)
if (!tasks.length) return { name: "激励任务", ok: false, 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 submittedCount = 0
@@ -681,7 +736,7 @@ function doAdvTask(cookie) {
if (fr.ok < fr.total) return { name: "激励任务", ok: false, msg: resultMsg(fr.last) || ("第 " + (i + 1) + " 个任务失败"), path: "/argus/api/v1/video/adv/finishWatch" }
}
var verify = qdGet("/argus/api/v2/video/adv/mainPage", {}, cookie)
var after = firstArray(nested(verify, ["Data", "DailyBenefitModule", "TaskList"]), nested(verify, ["data", "DailyBenefitModule", "TaskList"]))
var after = advTaskList(verify)
var finished = !!(after && after.length && after.every(function (t) { return taskFinished(t) }))
return { name: "激励任务", ok: finished, msg: finished ? "任务已完成" : (submittedCount ? "部分任务未完成" : "未找到可提交任务"), path: "/argus/api/v2/video/adv/mainPage" }
}
@@ -691,15 +746,15 @@ function taskReceived(task) {
function doDailyTask(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 data = main.Data || main.data || {}
var data = payloadData(main)
var done = 0
var pre = data.SurpriseBenefit
var pre = surpriseBenefit(data)
if (pre && !taskFinished(pre) && taskIdOf(pre)) {
var pr = finishVideoTask(taskIdOf(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(main, ["Data", "VideoRewardTab", "TaskList"]), nested(main, ["data", "VideoRewardTab", "TaskList"]))
var tasks = dailyTaskList(main)
if (!tasks.length) return { name: "每日任务", ok: false, msg: "任务列表为空", path: "/argus/api/v2/video/adv/mainPage" }
var oneAd = null
var target = null
@@ -721,7 +776,7 @@ function doDailyTask(cookie) {
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 = dailyTaskList(verify)
var finished = false
for (var v = 0; v < after.length; v++) if (taskTitle(after[v]) === "完成3个广告任务得奖励" && taskReceived(after[v])) { finished = true; break }
return { name: "每日任务", ok: finished, msg: finished ? "任务已完成" : "任务未完成", path: "/argus/api/v2/video/adv/mainPage" }
+85
View File
@@ -1245,6 +1245,43 @@ test('adv task ignores count metadata and submits each unfinished upstream task
assert.ok(finishCalls.every(c => /partial-task/.test(c.body)));
});
test('adv task accepts discovered task list when upstream module name changes', () => {
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: { WelfareCenter: { AdvTasks: [
{ TaskId: 'new-adv-a', Title: '激励碎片', IsFinished: 0 },
{ TaskId: 'new-adv-b', 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: { result: 9, message: '' } },
{ body: { code: 0, data: { headers: { 'QD-Sign': 'sig' } } } },
{ statusCode: 200, body: { code: 0, Data: { WelfareCenter: { AdvTasks: [
{ TaskId: 'new-adv-a', Title: '激励碎片', IsFinished: 1 },
{ TaskId: 'new-adv-b', Title: '广告福利', IsFinished: 1 },
] } } } },
],
});
assert.match(r.replies[0], /激励任务✅\(任务已完成\)/);
assert.doesNotMatch(r.replies[0], /任务列表为空/);
assert.equal(r.calls.filter(c => /video\/adv\/finishWatch$/.test(c.url)).length, 2);
});
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 }),
@@ -1294,6 +1331,54 @@ test('daily task follows upstream sequence: surprise task, one-ad task, then thr
});
test('daily task accepts discovered reward list when upstream module name changes', () => {
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: {
RewardModules: { DailyAd: [
{ TaskId: 'new-one-ad', Title: '完成1个广告任务得奖励', IsReceived: 0 },
{ TaskId: 'new-three-ad', Title: '完成3个广告任务得奖励', IsReceived: 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: { 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: {
RewardModules: { DailyAd: [
{ TaskId: 'new-one-ad', Title: '完成1个广告任务得奖励', IsReceived: 1 },
{ TaskId: 'new-three-ad', Title: '完成3个广告任务得奖励', IsReceived: 1 },
] }
} } },
],
});
assert.match(r.replies[0], /每日任务✅\(任务已完成\)/);
assert.doesNotMatch(r.replies[0], /任务列表为空/);
const bodies = r.calls.filter(c => /video\/adv\/finishWatch$/.test(c.url)).map(c => c.body);
assert.equal(bodies.length, 4);
assert.match(bodies[0], /new-one-ad/);
assert.equal(bodies.filter(b => /new-three-ad/.test(b)).length, 3);
});
test('message box runs outside former Beijing 20-22 window and calls message API', () => {
const store = {
qdreader_cookie_json: JSON.stringify({ '12345': cookie }),