feat(qqmusic): 日志与通知按体系分组,每条都标清是什么签到
原来 63 处 step 和 60+ 处 log 全平铺,看日志分不清哪条属于哪套体系: [签到] / [金币] / [福利站] 混在一起,通知里也是「✓ 签到 +15」这种没有上下文的行。 日志前缀统一成「体系·子项」两段: [任务中心·签到] [任务中心·挂件] [任务中心·装扮] [任务中心·听书] [任务中心·节目] [任务中心·领奖] (原 [任务]) [福利站·签到] [福利站·礼券] [福利站·激励] [福利站·月里程碑] [福利站·月礼] [福利站·抽奖] [福利站·周里程碑] [金币·签到] [金币·抽奖签到] run() 里加四段分隔标题,日志一眼能看出走到哪套: ───── ① 任务中心(成长值)───── ───── ② 会员福利站(成长值 + 权益)───── ───── ③ 金币中心(金币,不计入成长值)───── ───── ④ VIP 体验券(分享卡)───── 末尾补一行说明:金币中心为独立体系,不产成长值(明细见上方 ③ 段)。 之前只有「本次新增成长值合计: 45」,看不出金币那部分跑没跑。 step 名同样统一成「体系·子项」(63 处),renderAccount 按体系分组渲染: 🟢 1006764347:成长值 +45 📈 任务中心(成长值) ✓ 签到 +15 ✓ 挂件 已佩戴 · 无需处理 1 项:装扮 🎁 会员福利站(成长值 + 权益) ✓ 签到 15成长值(本月 8 天) ✓ 礼券 +10 心愿值 💰 金币中心(金币,不计成长值) ✓ 签到 已签 + 30 金币 · 无需处理 1 项:抽奖签到 分组规则: - 组内每条都是「子项名 + detail」,不再出现只有 detail 的行 - 整组全 skip 时收成一行「💰 金币中心(金币,不计成长值):签到、抽奖签到 今日无需处理」 - 未知前缀落到「📌 其他」组,名字原样保留 - 分享卡链接改成「🔗 领卡链接 <url>」单独一行 coinCenter / 金币抽奖签到开头各加一行开始日志,带 ActID 值 —— 换季排查时 直接能看到脚本用的是哪个 ActID。 新增 test_render.js:24 条断言冻结分组契约 ① 三套体系各自成组 + 组标题 emoji + 金币标注不计成长值 ② 组内每条都带子项名(断言没有「只有 detail」的行) ③ 整组 skip 收成一行 ④ ActID 过期 → warn 单独列且 detail 含 ActID ⑤ 分享卡链接单独一行 ⑥ 空 steps 不崩 ⑦ 未知前缀 → 其他组 test_coin.js 里 3 处 step 名断言同步改成新名(金币·签到 / 金币·抽奖签到)。
This commit is contained in:
@@ -329,12 +329,12 @@ class QMClient {
|
||||
if (data.Ret === 0) {
|
||||
const gotN = Number(data.Total)
|
||||
const got = Number.isFinite(gotN) && gotN > 0 ? gotN : null
|
||||
log(`[签到] 签到成功${got ? ` 成长值+${got}` : '(成长值实值见 App 明细)'}`)
|
||||
this.step('任务中心签到', 'done', got ? `+${got}` : '已签')
|
||||
log(`[任务中心·签到] 签到成功${got ? ` 成长值+${got}` : '(成长值实值见 App 明细)'}`)
|
||||
this.step('任务中心·签到', 'done', got ? `+${got}` : '已签')
|
||||
return got || 0
|
||||
}
|
||||
log(`[签到] Ret:${data.Ret} ${data.Msg || ''}`)
|
||||
this.step('任务中心签到', 'fail', `Ret ${data.Ret} ${data.Msg || ''}`.trim())
|
||||
log(`[任务中心·签到] Ret:${data.Ret} ${data.Msg || ''}`)
|
||||
this.step('任务中心·签到', 'fail', `Ret ${data.Ret} ${data.Msg || ''}`.trim())
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ class QMClient {
|
||||
async pendant() {
|
||||
const d = await this.api('music.vip.PendantUserSvr', 'QueryUserPendant', {})
|
||||
const pid = (d.data || {}).pendant && d.data.pendant.id
|
||||
if (!pid) { log('[挂件] 无挂件库存,任务8跳过'); this.step('挂件', 'skip', '无库存'); return }
|
||||
if (!pid) { log('[任务中心·挂件] 无挂件库存,任务8跳过'); this.step('任务中心·挂件', 'skip', '无库存'); return }
|
||||
let r = await this.api('music.vip.PendantUserSvr', 'UsePendant', { id: Number(pid) })
|
||||
let rc = Number((r.data || {}).retCode)
|
||||
if (rc === 42502) { // 已佩戴 → Cancel→Use 成对制造当日动作
|
||||
@@ -350,8 +350,8 @@ class QMClient {
|
||||
r = await this.api('music.vip.PendantUserSvr', 'UsePendant', { id: Number(pid) })
|
||||
rc = Number((r.data || {}).retCode)
|
||||
}
|
||||
log(`[挂件] UsePendant retCode:${rc}`)
|
||||
this.step('挂件', rc === 0 ? 'done' : 'warn', `retCode ${rc}`)
|
||||
log(`[任务中心·挂件] UsePendant retCode:${rc}`)
|
||||
this.step('任务中心·挂件', rc === 0 ? 'done' : 'warn', `retCode ${rc}`)
|
||||
}
|
||||
|
||||
/** 任务9 装扮:UseCosmetic 当前使用中的装扮(cosmeticcgi 专用 comm) */
|
||||
@@ -363,11 +363,11 @@ class QMClient {
|
||||
const d = await this.api('music.cosmeticcgi.UserCosmeticCgi', 'GetUserCosmetic', { category: 'subject' }, comm)
|
||||
const items = ((d.data || {}).cosmetics) || []
|
||||
const cur = items.find(c => Number(c.state) === 1) || items[0]
|
||||
if (!cur) { log('[装扮] subject 类目无装扮,任务9跳过'); this.step('装扮', 'skip', '无 subject 装扮'); return }
|
||||
if (!cur) { log('[任务中心·装扮] subject 类目无装扮,任务9跳过'); this.step('任务中心·装扮', 'skip', '无 subject 装扮'); return }
|
||||
const r = await this.api('music.cosmeticcgi.UserCosmeticCgi', 'UseCosmetic',
|
||||
{ category: 'subject', itemID: cur.id, manufacture: 'vivo' }, comm)
|
||||
log(`[装扮] UseCosmetic code:${r.code} (id=${cur.id})`)
|
||||
this.step('装扮', r.code === 0 ? 'done' : 'warn', `code ${r.code}`)
|
||||
log(`[任务中心·装扮] UseCosmetic code:${r.code} (id=${cur.id})`)
|
||||
this.step('任务中心·装扮', r.code === 0 ? 'done' : 'warn', `code ${r.code}`)
|
||||
}
|
||||
|
||||
/** 领取循环:20020 冷却3s重领 / 30005 任务20只读探测一次 / 10006 写锁即停 / 30009 已领 */
|
||||
@@ -377,19 +377,19 @@ class QMClient {
|
||||
for (const [tid, name] of CLAIMS) {
|
||||
const k = String(tid)
|
||||
const v = stNow[k]
|
||||
if (v === 'Y') { log(`[任务] ${name}: 今日已完成`); this.step(`领奖·${name}`, 'skip', '今日已完成'); continue }
|
||||
if (v === 'Y') { log(`[任务中心·领奖] ${name}: 今日已完成`); this.step(`领奖·${name}`, 'skip', '今日已完成'); continue }
|
||||
if (tid === 20 && v === 'W') {
|
||||
await this.api('music.lvz.LevelConfigSvr', 'StartTask', { ID: '20' }) // 领取钥匙
|
||||
}
|
||||
let d = await this.api('music.lvz.LevelConfigSvr', 'IssueAward', { ID: k })
|
||||
let code = d.code
|
||||
if (code === 20020) {
|
||||
log(`[任务] ${name}: 20020 冷却,3s 后原地重领`)
|
||||
log(`[任务中心·领奖] ${name}: 20020 冷却,3s 后原地重领`)
|
||||
await sleep(3000)
|
||||
d = await this.api('music.lvz.LevelConfigSvr', 'IssueAward', { ID: k })
|
||||
code = d.code
|
||||
} else if (code === 30005 && tid === 20) {
|
||||
log(`[任务] ${name}: 30005 未计次(本版无播放上报,仅 ${POLL_PROBE_MS / 1000}s 只读探测一次)`)
|
||||
log(`[任务中心·领奖] ${name}: 30005 未计次(本版无播放上报,仅 ${POLL_PROBE_MS / 1000}s 只读探测一次)`)
|
||||
await sleep(POLL_PROBE_MS)
|
||||
const v2 = (await this.state())[k]
|
||||
if (v2 === 'W') {
|
||||
@@ -399,26 +399,26 @@ class QMClient {
|
||||
} else if (v2 === 'Y') {
|
||||
code = 30009
|
||||
} else {
|
||||
log(`[任务] ${name}: state 仍 ${v2 || 'N'},留待下次运行`)
|
||||
log(`[任务中心·领奖] ${name}: state 仍 ${v2 || 'N'},留待下次运行`)
|
||||
this.step(`领奖·${name}`, 'warn', '未计次,待 App 内收听')
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (code === 0) {
|
||||
const got = (d.data && d.data.DrawScores) || 0
|
||||
log(`[任务] ${name}: 领取成功 +${got}`)
|
||||
log(`[任务中心·领奖] ${name}: 领取成功 +${got}`)
|
||||
total += got
|
||||
this.step(`领奖·${name}`, 'done', `+${got}`)
|
||||
await sleep(3000) // 成功发放后 ~2s 冷却,拉开下一个
|
||||
} else if (code === 30009) {
|
||||
log(`[任务] ${name}: 今日已完成`)
|
||||
log(`[任务中心·领奖] ${name}: 今日已完成`)
|
||||
this.step(`领奖·${name}`, 'skip', '今日已完成')
|
||||
} else if (code === 10006) {
|
||||
log(`[任务] ${name}: 10006 写操作锁,停止本账号领取(次日自动恢复)`)
|
||||
log(`[任务中心·领奖] ${name}: 10006 写操作锁,停止本账号领取(次日自动恢复)`)
|
||||
this.step(`领奖·${name}`, 'warn', '10006 写锁,次日恢复')
|
||||
break
|
||||
} else {
|
||||
log(`[任务] ${name}: code:${code}(未识别,留待下次运行)`)
|
||||
log(`[任务中心·领奖] ${name}: code:${code}(未识别,留待下次运行)`)
|
||||
this.step(`领奖·${name}`, 'warn', `code ${code}`)
|
||||
}
|
||||
}
|
||||
@@ -442,29 +442,29 @@ class QMClient {
|
||||
monthDays = (d.data || {}).signInDays || signedCnt
|
||||
const today = days.find(x => x.day === day)
|
||||
if (today && today.signInStatus === 2) {
|
||||
log(`[福利站] 今日已签到(本月 ${monthDays} 天)`)
|
||||
this.step('福利站签到', 'skip', `今日已签(本月 ${monthDays} 天)`)
|
||||
log(`[福利站·签到] 今日已签到(本月 ${monthDays} 天)`)
|
||||
this.step('福利站·签到', 'skip', `今日已签(本月 ${monthDays} 天)`)
|
||||
} else {
|
||||
const r = await this.api('music.vipcgi.RenewalPlaySignInCgi', 'RenewalPlaySignIn', {})
|
||||
if (r.code === 0) {
|
||||
const data = r.data || {}
|
||||
const name = (data.todayIncentive || {}).incentiveName || '奖励'
|
||||
log(`[福利站] 签到成功:${name}(本月 ${monthDays + 1} 天)`)
|
||||
log(`[福利站·签到] 签到成功:${name}(本月 ${monthDays + 1} 天)`)
|
||||
const m = /^(\d+)成长值/.exec(name)
|
||||
if (m) gained += Number(m[1])
|
||||
this.step('福利站签到', 'done', `${name}(本月 ${monthDays + 1} 天)`)
|
||||
this.step('福利站·签到', 'done', `${name}(本月 ${monthDays + 1} 天)`)
|
||||
for (const mk of ['weekMilestoneIncentive', 'monthMilestoneIncentive']) {
|
||||
const inc = data[mk]
|
||||
if (inc && inc.incentiveName) {
|
||||
log(`[福利站] ${mk.startsWith('week') ? '周' : '月'}里程碑达成:${inc.incentiveName}`)
|
||||
log(`[福利站·${mk.startsWith('week') ? '周' : '月'}里程碑] 达成:${inc.incentiveName}`)
|
||||
}
|
||||
}
|
||||
} else if (r.code === 14822150) {
|
||||
log(`[福利站] 今日已签到(幂等码,本月 ${monthDays} 天)`)
|
||||
this.step('福利站签到', 'skip', '今日已签')
|
||||
log(`[福利站·签到] 今日已签到(幂等码,本月 ${monthDays} 天)`)
|
||||
this.step('福利站·签到', 'skip', '今日已签')
|
||||
} else {
|
||||
log(`[福利站] 签到异常 code:${r.code}`)
|
||||
this.step('福利站签到', 'fail', `code ${r.code}`)
|
||||
log(`[福利站·签到] 异常 code:${r.code}`)
|
||||
this.step('福利站·签到', 'fail', `code ${r.code}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,22 +484,22 @@ class QMClient {
|
||||
const pick = ok[day % ok.length]
|
||||
const r = await this.api('music.vipcgi.VipRenewalCouponCgi', 'ClaimVipRenewCoupon', { couponID: pick.couponID })
|
||||
if (r.code === 0) {
|
||||
log(`[礼券] ${pick.couponName} 领取成功 +${t502.taskReward || 10}心愿值`)
|
||||
this.step('福利站礼券', 'done', pick.couponName)
|
||||
log(`[福利站·礼券] ${pick.couponName} 领取成功 +${t502.taskReward || 10}心愿值`)
|
||||
this.step('福利站·礼券', 'done', pick.couponName)
|
||||
} else if (r.code === 14816772) {
|
||||
log('[礼券] 今日已领取')
|
||||
this.step('福利站礼券', 'skip', '今日已领')
|
||||
log('[福利站·礼券] 今日已领取')
|
||||
this.step('福利站·礼券', 'skip', '今日已领')
|
||||
} else {
|
||||
log(`[礼券] code:${r.code}`)
|
||||
this.step('福利站礼券', 'warn', `code ${r.code}`)
|
||||
log(`[福利站·礼券] code:${r.code}`)
|
||||
this.step('福利站·礼券', 'warn', `code ${r.code}`)
|
||||
}
|
||||
} else {
|
||||
log(`[礼券] 券池今日无可领券(${infos.length} 张全非可领态)`)
|
||||
this.step('福利站礼券', 'skip', '券池无可领')
|
||||
log(`[福利站·礼券] 券池今日无可领券(${infos.length} 张全非可领态)`)
|
||||
this.step('福利站·礼券', 'skip', '券池无可领')
|
||||
}
|
||||
} else if (t502) {
|
||||
log('[礼券] 今日已完成')
|
||||
this.step('福利站礼券', 'skip', '今日已完成')
|
||||
log('[福利站·礼券] 今日已完成')
|
||||
this.step('福利站·礼券', 'skip', '今日已完成')
|
||||
}
|
||||
|
||||
// ---- 旅程激励:心愿值达标档位实时领取(ClaimIncentives 幂等)----
|
||||
@@ -508,19 +508,19 @@ class QMClient {
|
||||
if (unlock.length) {
|
||||
const r = await this.api('music.vipcgi.StarWishLotteryCgi', 'ClaimIncentives', { seasonID: season })
|
||||
if (r.code === 0) {
|
||||
log(`[激励] 领取成功:${unlock.map(x => `${x.name}(${x.score}分)`).join('、')}`)
|
||||
this.step('旅程激励', 'done', unlock.map(x => x.name).join('、'))
|
||||
log(`[福利站·激励] 领取成功:${unlock.map(x => `${x.name}(${x.score}分)`).join('、')}`)
|
||||
this.step('福利站·旅程激励', 'done', unlock.map(x => x.name).join('、'))
|
||||
} else {
|
||||
log(`[激励] code:${r.code}`)
|
||||
this.step('旅程激励', 'warn', `code ${r.code}`)
|
||||
log(`[福利站·激励] code:${r.code}`)
|
||||
this.step('福利站·旅程激励', 'warn', `code ${r.code}`)
|
||||
}
|
||||
} else {
|
||||
const nxt = (idata.incentives || []).find(x => x.status === 5)
|
||||
if (nxt) {
|
||||
log(`[激励] 心愿值${idata.currentScore || 0}/${nxt.score}(下档 ${nxt.name})`)
|
||||
this.step('旅程激励', 'skip', `心愿值 ${idata.currentScore || 0}/${nxt.score}`)
|
||||
log(`[福利站·激励] 心愿值${idata.currentScore || 0}/${nxt.score}(下档 ${nxt.name})`)
|
||||
this.step('福利站·旅程激励', 'skip', `心愿值 ${idata.currentScore || 0}/${nxt.score}`)
|
||||
} else {
|
||||
this.step('旅程激励', 'skip', '无待领档位')
|
||||
this.step('福利站·旅程激励', 'skip', '无待领档位')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,15 +534,15 @@ class QMClient {
|
||||
if (signedCnt >= need || status === 2 || status === 3) {
|
||||
const r = await this.api('music.vipcgi.RenewalPlaySignInCgi', 'ClaimMilestoneIncentive', { month })
|
||||
if (r.code === 0) {
|
||||
log(`[月里程碑] 领取:${target.incentiveName}`)
|
||||
this.step('月里程碑', 'done', target.incentiveName)
|
||||
log(`[福利站·月里程碑] 领取:${target.incentiveName}`)
|
||||
this.step('福利站·月里程碑', 'done', target.incentiveName)
|
||||
} else {
|
||||
log(`[月里程碑] code:${r.code}(${signedCnt} 天达标待领)`)
|
||||
this.step('月里程碑', 'warn', `code ${r.code}(${signedCnt} 天达标)`)
|
||||
log(`[福利站·月里程碑] code:${r.code}(${signedCnt} 天达标待领)`)
|
||||
this.step('福利站·月里程碑', 'warn', `code ${r.code}(${signedCnt} 天达标)`)
|
||||
}
|
||||
} else {
|
||||
log(`[月里程碑] ${signedCnt}/${need} 天(${target.incentiveName})`)
|
||||
this.step('月里程碑', 'skip', `${signedCnt}/${need} 天`)
|
||||
log(`[福利站·月里程碑] ${signedCnt}/${need} 天(${target.incentiveName})`)
|
||||
this.step('福利站·月里程碑', 'skip', `${signedCnt}/${need} 天`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,18 +554,18 @@ class QMClient {
|
||||
if (p.poolStatus === 3 && (g.availableStock || 0) > 0) {
|
||||
const r = await this.api('music.vipcgi.VipRenewalMonthGiftCgi', 'ReceiveVipRenewalMonthlyGift',
|
||||
{ giftPoolType: p.poolType, tab: 'cjhy' })
|
||||
if (r.code === 0) { log(`[月礼] 领取:${g.giftName}`); gotGifts.push(g.giftName) }
|
||||
else { log(`[月礼] code:${r.code}`); noStock.push(`${g.giftName}(code ${r.code})`) }
|
||||
if (r.code === 0) { log(`[福利站·月礼] 领取:${g.giftName}`); gotGifts.push(g.giftName) }
|
||||
else { log(`[福利站·月礼] code:${r.code}`); noStock.push(`${g.giftName}(code ${r.code})`) }
|
||||
} else if (p.poolStatus === 3) {
|
||||
const ts = g.nextStockRefreshTime || 0
|
||||
const nxt = ts ? new Date(ts * 1000).toTimeString().slice(0, 5) : '未知'
|
||||
log(`[月礼] ${g.giftName} 今日库存0(${nxt} 补货)`)
|
||||
log(`[福利站·月礼] ${g.giftName} 今日库存0(${nxt} 补货)`)
|
||||
noStock.push(`${g.giftName}(库存0)`)
|
||||
}
|
||||
}
|
||||
if (gotGifts.length) this.step('月度好礼', 'done', gotGifts.join('、'))
|
||||
else if (noStock.length) this.step('月度好礼', 'skip', noStock.join('、'))
|
||||
else this.step('月度好礼', 'skip', '无可用礼品池')
|
||||
if (gotGifts.length) this.step('福利站·月礼', 'done', gotGifts.join('、'))
|
||||
else if (noStock.length) this.step('福利站·月礼', 'skip', noStock.join('、'))
|
||||
else this.step('福利站·月礼', 'skip', '无可用礼品池')
|
||||
|
||||
// ---- 臻享抽奖:有抽奖券才抽,抽到清零或失败即停 ----
|
||||
const dd = (await this.api('music.vipcgi.VipRenewalLotteryCgi', 'QueryVipRenewDrawInfo', {})).data || {}
|
||||
@@ -575,18 +575,18 @@ class QMClient {
|
||||
const r = await this.api('music.vipcgi.VipRenewalLotteryCgi', 'DrawVipRenewActivity', { lotteryPageType: 1 })
|
||||
if (r.code === 0) {
|
||||
const prize = ((r.data || {}).prize || {}).prizeName
|
||||
log(`[抽奖] ${prize || '成功'}`)
|
||||
log(`[福利站·抽奖] ${prize || '成功'}`)
|
||||
prizes.push(prize || '成功')
|
||||
draws -= 1
|
||||
} else {
|
||||
log(`[抽奖] code:${r.code},停止`)
|
||||
log(`[福利站·抽奖] code:${r.code},停止`)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (prizes.length) this.step('臻享抽奖', 'done', `${prizes.length} 次:${prizes.join('、')}`)
|
||||
else this.step('臻享抽奖', 'skip', '无抽奖券')
|
||||
if (prizes.length) this.step('福利站·臻享抽奖', 'done', `${prizes.length} 次:${prizes.join('、')}`)
|
||||
else this.step('福利站·臻享抽奖', 'skip', '无抽奖券')
|
||||
|
||||
log(`[福利站] ${ymd} 巡检完成`)
|
||||
log(`[福利站] ${ymd} 巡检完成(签到/礼券/激励/里程碑/月礼/抽奖)`)
|
||||
return gained
|
||||
}
|
||||
|
||||
@@ -601,15 +601,16 @@ class QMClient {
|
||||
* 不做的:金币抽奖(drawCoinLottery,消耗金币抽奖券)、红包雨、每日任务列表、
|
||||
* 定时金币 —— 那些要么花金币要么要伪造播放/浏览行为,风险与收益不成比例。 */
|
||||
async coinCenter() {
|
||||
if (!COIN_SIGN) { this.step('金币签到', 'skip', 'QM_COIN_SIGN=off'); return 0 }
|
||||
if (!COIN_SIGN) { this.step('金币·签到', 'skip', 'QM_COIN_SIGN=off'); return 0 }
|
||||
|
||||
log(`[金币·签到] 开始:ActID=${COIN_ACT_ID} ScenesID=${COIN_SCENE_ID}`)
|
||||
let st = await this.coinSignState(COIN_ACT_ID, COIN_SCENE_ID)
|
||||
if (!st.ok) {
|
||||
// ActID 会随活动换季失效。这里不打 fail(不是登录态问题,也不是脚本 bug),
|
||||
// 用 warn 并把「改哪个 env」写进 detail,看到通知就知道怎么修。
|
||||
log(`[金币] 状态查询失败 code=${st.sCode}/${st.tCode} ret=${st.sRet}/${st.tRet}`)
|
||||
log(`[金币] 若持续失败,多半是活动换季导致 ActID 失效 —— 抓包看 ActID,改 QM_COIN_ACT_ID`)
|
||||
this.step('金币签到', 'warn', `查询失败 code ${st.sCode}/${st.tCode}(可能 ActID 过期)`)
|
||||
log(`[金币·签到] 状态查询失败 code=${st.sCode}/${st.tCode} ret=${st.sRet}/${st.tRet}`)
|
||||
log(`[金币·签到] 若持续失败,多半是活动换季导致 ActID 失效 —— 抓包看 ActID,改 QM_COIN_ACT_ID`)
|
||||
this.step('金币·签到', 'warn', `查询失败 code ${st.sCode}/${st.tCode}(可能 ActID 过期)`)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -620,14 +621,14 @@ class QMClient {
|
||||
const okSign = d.code === 0 && req.code === 0 && Number(data.retCode ?? 0) === 0
|
||||
&& data.Info && data.Info.IsSignIn
|
||||
if (!okSign) {
|
||||
log(`[金币] 签到失败 code=${req.code} ret=${data.retCode}`)
|
||||
this.step('金币签到', 'warn', `签到失败 code ${req.code} ret ${data.retCode}`)
|
||||
log(`[金币·签到] 签到失败 code=${req.code} ret=${data.retCode}`)
|
||||
this.step('金币·签到', 'warn', `签到失败 code ${req.code} ret ${data.retCode}`)
|
||||
return 0
|
||||
}
|
||||
signedNow = true
|
||||
log('[金币] 签到成功')
|
||||
log('[金币·签到] 签到成功')
|
||||
st = await this.coinSignState(COIN_ACT_ID, COIN_SCENE_ID)
|
||||
if (!st.ok) { this.step('金币签到', 'done', '已签(状态复查失败)'); return 0 }
|
||||
if (!st.ok) { this.step('金币·签到', 'done', '已签(状态复查失败)'); return 0 }
|
||||
}
|
||||
|
||||
// 连续签到第几天 → 用于在 TaskList 里定位「今天该领的那个」
|
||||
@@ -649,27 +650,28 @@ class QMClient {
|
||||
// 100004 = 已领过(幂等),算成功
|
||||
const okAward = d.code === 0 && req.code === 0 && (ret === 0 || ret === 100004)
|
||||
if (okAward) {
|
||||
log(`[金币] ${signedNow ? '签到并领取' : '补领'}成功${rewardName ? `:${rewardName}` : ''}`)
|
||||
this.step('金币签到', 'done', `${signedNow ? '已签 + ' : ''}${rewardName || '已领'}`)
|
||||
log(`[金币·签到] ${signedNow ? '签到并领取' : '补领'}成功${rewardName ? `:${rewardName}` : ''}`)
|
||||
this.step('金币·签到', 'done', `${signedNow ? '已签 + ' : ''}${rewardName || '已领'}`)
|
||||
} else {
|
||||
log(`[金币] 领奖失败 code=${req.code} ret=${ret}`)
|
||||
this.step('金币签到', 'warn', `已签,领奖失败 ret ${ret}`)
|
||||
log(`[金币·签到] 领奖失败 code=${req.code} ret=${ret}`)
|
||||
this.step('金币·签到', 'warn', `已签,领奖失败 ret ${ret}`)
|
||||
}
|
||||
} else if (st.info.IsSignIn) {
|
||||
const nth = day ? `连续第 ${day} 天` : ''
|
||||
log(`[金币] 今日已签到${nth ? `(${nth})` : ''}`)
|
||||
this.step('金币签到', 'skip', `今日已签${nth ? `(${nth})` : ''}`)
|
||||
log(`[金币·签到] 今日已签到${nth ? `(${nth})` : ''}`)
|
||||
this.step('金币·签到', 'skip', `今日已签${nth ? `(${nth})` : ''}`)
|
||||
} else {
|
||||
log('[金币] 签到状态未确认')
|
||||
this.step('金币签到', 'warn', '签到状态未确认')
|
||||
log('[金币·签到] 签到状态未确认')
|
||||
this.step('金币·签到', 'warn', '签到状态未确认')
|
||||
}
|
||||
|
||||
// ---- 金币抽奖签到(另一个 ActID,独立活动)----
|
||||
// 只做「签到 + 领奖」,不碰 drawCoinLottery(那个要消耗抽奖券去抽,属于花金币)
|
||||
try {
|
||||
log(`[金币·抽奖签到] 开始:ActID=${LOTTERY_ACT_ID}`)
|
||||
let ls = await this.coinSignState(LOTTERY_ACT_ID, '')
|
||||
if (!ls.ok) {
|
||||
log(`[金币抽奖] 状态查询失败 code=${ls.sCode}/${ls.tCode},跳过`)
|
||||
log(`[金币·抽奖签到] 状态查询失败 code=${ls.sCode}/${ls.tCode},跳过`)
|
||||
} else {
|
||||
let lotterySigned = false
|
||||
if (!ls.info.IsSignIn) {
|
||||
@@ -688,23 +690,23 @@ class QMClient {
|
||||
const req = d.req_0 || {}, data = req.data || {}
|
||||
const ret = Number(data.retCode ?? 0)
|
||||
if (d.code === 0 && req.code === 0 && (ret === 0 || ret === 100004)) {
|
||||
this.step('金币抽奖签到', 'done', lotterySigned ? '已签 + 已领' : '已领')
|
||||
this.step('金币·抽奖签到', 'done', lotterySigned ? '已签 + 已领' : '已领')
|
||||
} else {
|
||||
this.step('金币抽奖签到', 'warn', `领奖失败 ret ${ret}`)
|
||||
this.step('金币·抽奖签到', 'warn', `领奖失败 ret ${ret}`)
|
||||
}
|
||||
} else if (lotterySigned) {
|
||||
// 签了但当场没查到可领项(接口有延迟)→ 也记一笔,别让「做过的事」在通知里消失
|
||||
this.step('金币抽奖签到', 'done', '已签(奖励待下次领)')
|
||||
this.step('金币·抽奖签到', 'done', '已签(奖励待下次领)')
|
||||
} else if (ls.ok && ls.info.IsSignIn) {
|
||||
this.step('金币抽奖签到', 'skip', '今日已签')
|
||||
this.step('金币·抽奖签到', 'skip', '今日已签')
|
||||
} else {
|
||||
this.step('金币抽奖签到', 'skip', '无需处理')
|
||||
this.step('金币·抽奖签到', 'skip', '无需处理')
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof QmDead) throw e
|
||||
log(`[金币抽奖] 异常: ${e.message}`)
|
||||
this.step('金币抽奖签到', 'warn', e.message)
|
||||
log(`[金币·抽奖签到] 异常: ${e.message}`)
|
||||
this.step('金币·抽奖签到', 'warn', e.message)
|
||||
}
|
||||
|
||||
return 0 // 金币不计入成长值合计
|
||||
@@ -801,7 +803,7 @@ class QMClient {
|
||||
return 0 // 不产成长值,不计入 total
|
||||
}
|
||||
|
||||
/** 单账号全流程:续期 → 签到 → 任务动作 → 全量领取 → 福利站 → 分享卡 */
|
||||
/** 单账号全流程:续期 → 任务中心 → 福利站 → 金币中心 → 分享卡 */
|
||||
async run() {
|
||||
const out = { total: 0, dead: false, cardUrl: '' }
|
||||
try {
|
||||
@@ -810,53 +812,60 @@ class QMClient {
|
||||
const signed = await this.signQry()
|
||||
log(`[状态] 签到:${signed ? '已签' : '未签'} 挂件:${st['8']} 装扮:${st['9']} 听书:${st['30']} 节目:${st['20']}`)
|
||||
|
||||
if (signed) { log('[签到] 今日已签到'); this.step('任务中心签到', 'skip', '今日已签') }
|
||||
// 三套签到体系各自独立,用段落标题分开 —— 日志里一眼能看出哪套做了什么
|
||||
log('\n───── ① 任务中心(成长值)─────')
|
||||
if (signed) { log('[任务中心·签到] 今日已签到'); this.step('任务中心·签到', 'skip', '今日已签') }
|
||||
else out.total += await this.signDaily()
|
||||
|
||||
// 动作语义:N=执行动作 / W=只领(能否领取决于行为已计次)/ Y=零请求(防风控)
|
||||
if (st['8'] === 'N') await this.pendant()
|
||||
else this.step('挂件', 'skip', '今日无需动作')
|
||||
else this.step('任务中心·挂件', 'skip', '今日无需动作')
|
||||
if (st['9'] === 'N') await this.cosmetic()
|
||||
else this.step('装扮', 'skip', '今日无需动作')
|
||||
else this.step('任务中心·装扮', 'skip', '今日无需动作')
|
||||
if (st['30'] === 'N') {
|
||||
await this.api('music.lvz.LevelConfigSvr', 'RecordBehavior', { ID: '30' })
|
||||
log('[听书] 行为上报完成')
|
||||
this.step('听书行为', 'done', '已上报')
|
||||
log('[任务中心·听书] 行为上报完成')
|
||||
this.step('任务中心·听书', 'done', '已上报')
|
||||
} else {
|
||||
this.step('听书行为', 'skip', '今日无需动作')
|
||||
this.step('任务中心·听书', 'skip', '今日无需动作')
|
||||
}
|
||||
if (st['20'] === 'N' || !signed) {
|
||||
// 任务20跨天不重置:当日未签时 20:Y 是昨日残留,仍挂开始标记
|
||||
const r = (await this.api('music.lvz.LevelConfigSvr', 'StartTask', { ID: '20' })).data || {}
|
||||
if (Number(r.code) === 1) {
|
||||
log(PLAY_REPORT
|
||||
? '[节目] 开始标记已挂(播放上报未实现于本版,App 内正常收听计入)'
|
||||
: '[节目] 开始标记已挂(播放上报未启用,App 内正常收听计入)')
|
||||
if (r.count != null) log(`[节目] 当前播放计数: ${r.count}`)
|
||||
this.step('节目开始标记', 'done', r.count != null ? `计数 ${r.count}` : '已挂')
|
||||
? '[任务中心·节目] 开始标记已挂(播放上报未实现于本版,App 内正常收听计入)'
|
||||
: '[任务中心·节目] 开始标记已挂(播放上报未启用,App 内正常收听计入)')
|
||||
if (r.count != null) log(`[任务中心·节目] 当前播放计数: ${r.count}`)
|
||||
this.step('任务中心·节目', 'done', r.count != null ? `计数 ${r.count}` : '已挂')
|
||||
} else {
|
||||
log(`[节目] StartTask 返回 code:${r.code},无法开始`)
|
||||
this.step('节目开始标记', 'warn', `code ${r.code}`)
|
||||
log(`[任务中心·节目] StartTask 返回 code:${r.code},无法开始`)
|
||||
this.step('任务中心·节目', 'warn', `code ${r.code}`)
|
||||
}
|
||||
} else {
|
||||
this.step('节目开始标记', 'skip', '今日无需动作')
|
||||
this.step('任务中心·节目', 'skip', '今日无需动作')
|
||||
}
|
||||
|
||||
const stNow = await this.state()
|
||||
out.total += await this.claimAll(stNow)
|
||||
|
||||
log('\n───── ② 会员福利站(成长值 + 权益)─────')
|
||||
try { out.total += await this.vipStation() } catch (e) {
|
||||
if (e instanceof QmDead) throw e // 失效语义不被福利站吞掉
|
||||
log(`[福利站] 运行异常: ${e.message}`)
|
||||
this.step('福利站', 'fail', e.message)
|
||||
}
|
||||
|
||||
// 金币中心:独立协议(musicu.fcg + 小程序 comm),给金币不给成长值。
|
||||
// 放在福利站之后、分享卡之前 —— 金币体系挂了不影响前两套,也不拖累取卡。
|
||||
log('\n───── ③ 金币中心(金币,不计入成长值)─────')
|
||||
try { await this.coinCenter() } catch (e) {
|
||||
if (e instanceof QmDead) throw e
|
||||
log(`[金币] 运行异常: ${e.message}`)
|
||||
this.step('金币签到', 'fail', e.message)
|
||||
log(`[金币·签到] 运行异常: ${e.message}`)
|
||||
this.step('金币·签到', 'fail', e.message)
|
||||
}
|
||||
|
||||
log('\n───── ④ VIP 体验券(分享卡)─────')
|
||||
try { await this.shareCard() } catch (e) {
|
||||
if (e instanceof QmDead) throw e // 登录态失效照常上抛
|
||||
log(`[分享卡] 运行异常: ${e.message}`) // 接口改版/风控不拖垮主流程
|
||||
@@ -865,7 +874,8 @@ class QMClient {
|
||||
out.cardUrl = this.shareCardUrl || ''
|
||||
out.steps = this.steps
|
||||
out.verdict = this.verdict()
|
||||
log(`[QQ音乐] 本次新增成长值合计: ${out.total}`)
|
||||
log(`\n[QQ音乐] 本次新增成长值合计: ${out.total}`)
|
||||
log(`[QQ音乐] 金币中心为独立体系,不产成长值(明细见上方 ③ 段)`)
|
||||
} catch (e) {
|
||||
if (e instanceof QmDead) {
|
||||
out.dead = true
|
||||
@@ -984,30 +994,47 @@ function pushCardResult(results) {
|
||||
* 全部 skip 的项折叠成一行,避免通知被十几行「今日已完成」淹没。 */
|
||||
function renderAccount(uin, r) {
|
||||
const icon = { done: '✓', warn: '△', fail: '✗', skip: '·' }
|
||||
// 金币体系的 step 名字都以「金币」开头 —— 单独成组,别混进成长值里让人误读
|
||||
const isCoin = s => /^金币/.test(s.name)
|
||||
const coin = r.steps.filter(isCoin)
|
||||
const rest = r.steps.filter(s => !isCoin(s))
|
||||
const did = rest.filter(s => s.status === 'done')
|
||||
const warn = rest.filter(s => s.status === 'warn' || s.status === 'fail')
|
||||
const skip = rest.filter(s => s.status === 'skip')
|
||||
// step 名统一成「体系·子项」两段(2026-11-07 改),按体系分组渲染 ——
|
||||
// 通知里必须一眼看出「哪个签到完成了」,不能把所有 done 平铺成一堆。
|
||||
const group = s => {
|
||||
const m = /^(任务中心|福利站|金币|分享卡)/.exec(s.name)
|
||||
return m ? m[1] : '其他'
|
||||
}
|
||||
const line = s => ` ${icon[s.status] || '·'} ${s.name.replace(/^[^·]+·/, '')}${s.detail ? ` ${s.detail}` : ''}`
|
||||
|
||||
const lines = [`${r.verdict === 'done' ? '🟢' : '🟡'} ${uin}:成长值 +${r.total}`]
|
||||
for (const s of did) lines.push(` ${icon.done} ${s.name}${s.detail ? ` ${s.detail}` : ''}`)
|
||||
for (const s of warn) lines.push(` ${icon[s.status]} ${s.name}${s.detail ? ` ${s.detail}` : ''}`)
|
||||
if (skip.length) {
|
||||
lines.push(` ${icon.skip} 无需处理 ${skip.length} 项:${skip.map(s => s.name).join('、')}`)
|
||||
}
|
||||
// 金币单独一行:它不加成长值,混在上面会被当成成长值的一部分
|
||||
if (coin.length) {
|
||||
const doneCoin = coin.filter(s => s.status === 'done')
|
||||
const badCoin = coin.filter(s => s.status === 'warn' || s.status === 'fail')
|
||||
if (doneCoin.length) {
|
||||
lines.push(` 💰 金币:${doneCoin.map(s => `${s.name.replace(/^金币/, '') || '签到'}${s.detail ? ` ${s.detail}` : ''}`).join(' / ')}`)
|
||||
|
||||
const ORDER = [
|
||||
['任务中心', '📈 任务中心(成长值)'],
|
||||
['福利站', '🎁 会员福利站(成长值 + 权益)'],
|
||||
['金币', '💰 金币中心(金币,不计成长值)'],
|
||||
['分享卡', '🎫 VIP 体验券'],
|
||||
['其他', '📌 其他'],
|
||||
]
|
||||
|
||||
for (const [key, title] of ORDER) {
|
||||
const items = r.steps.filter(s => group(s) === key)
|
||||
if (!items.length) continue
|
||||
|
||||
const done = items.filter(s => s.status === 'done')
|
||||
const bad = items.filter(s => s.status === 'warn' || s.status === 'fail')
|
||||
const skip = items.filter(s => s.status === 'skip')
|
||||
|
||||
// 整个体系都没做事(全 skip)→ 收成一行,别占屏
|
||||
if (!done.length && !bad.length && skip.length) {
|
||||
lines.push(` ${title}:${skip.map(s => s.name.replace(/^[^·]+·/, '')).join('、')} 今日无需处理`)
|
||||
continue
|
||||
}
|
||||
|
||||
lines.push(` ${title}`)
|
||||
for (const s of done) lines.push(line(s))
|
||||
for (const s of bad) lines.push(line(s))
|
||||
if (skip.length) {
|
||||
lines.push(` · 无需处理 ${skip.length} 项:${skip.map(s => s.name.replace(/^[^·]+·/, '')).join('、')}`)
|
||||
}
|
||||
for (const s of badCoin) lines.push(` ${icon[s.status]} ${s.name}${s.detail ? ` ${s.detail}` : ''}`)
|
||||
if (!doneCoin.length && !badCoin.length) lines.push(` · 金币:今日已签`)
|
||||
}
|
||||
if (r.cardUrl) lines.push(` 🎁 分享卡 ${r.cardUrl}`)
|
||||
|
||||
if (r.cardUrl) lines.push(` 🔗 领卡链接 ${r.cardUrl}`)
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user