增加兑换重试链路

This commit is contained in:
yml2213
2026-04-13 22:54:14 +08:00
parent bc0d6fc8aa
commit 0c2f2e0a12
12 changed files with 661 additions and 31 deletions
@@ -63,6 +63,7 @@ export async function runTencentBrowserRedeem({
code,
verifyCode,
})
const classification = classifyTencentRedeemResult(redeem)
const attemptRecord = {
attempt,
@@ -70,6 +71,7 @@ export async function runTencentBrowserRedeem({
verifysession: captcha.verifysession,
ocrSample: ocr?.data?.saved || null,
redeem,
classification,
role: activityInfo.role,
}
@@ -111,7 +113,7 @@ export async function runTencentBrowserRedeem({
finishedAt: new Date().toISOString(),
}
session.status = 'redeemed'
session.notice = String(finalResult.redeem?.sMsg || '兑换完成')
session.notice = resolveTencentRedeemMessage(finalResult?.redeem)
session.updatedAt = new Date().toISOString()
await persistSessionState(session)
@@ -277,22 +279,42 @@ export async function refreshPageCaptcha(page, verifyImgId) {
}
export function isCaptchaRejectedResult(result) {
const retCode = Number(result?.iRet)
if (Number.isFinite(retCode) && retCode === -100) {
if (classifyTencentRedeemResult(result).outcome === 'captcha_rejected') {
return true
}
const text = [
String(result?.sMsg || ''),
String(result?.msg || ''),
String(result?.popup?.text || ''),
String(result?.popup?.detail || ''),
]
.join(' ')
.trim()
return false
}
return /验证码|校验码/.test(text)
export function classifyTencentRedeemResult(result) {
const retCode = Number(result?.iRet)
const message = resolveTencentRedeemMessage(result)
const normalized = normalizeTencentRedeemText(message)
if (Number.isFinite(retCode) && retCode === -100) {
return buildTencentRedeemClassification('captcha_rejected', retCode, message)
}
if (/兑换码已使用|cdk已使用|cdkey已使用|已被使用/.test(normalized)) {
return buildTencentRedeemClassification('code_used', retCode, message || '兑换码已使用')
}
if (/兑换码错误|cdk错误|cdkey错误|请确认兑换码信息是否准确|兑换码信息是否准确/.test(normalized)) {
return buildTencentRedeemClassification('code_invalid', retCode, message || '兑换码错误,请确认兑换码信息是否准确')
}
if (/验证码|校验码/.test(normalized)) {
return buildTencentRedeemClassification('captcha_rejected', retCode, message || '验证码错误,请稍后重试')
}
if (
retCode === 0 ||
/成功|已兑换|领取成功|恭喜您获得了礼包|查看邮件|到账/.test(normalized)
) {
return buildTencentRedeemClassification('success', retCode, message || '兑换成功')
}
return buildTencentRedeemClassification('failed', retCode, message || '兑换失败')
}
function resetRedeemPopup(page) {
@@ -508,3 +530,34 @@ function inferRedeemCodeFromPopup(popup) {
return 1
}
export function resolveTencentRedeemMessage(result) {
const candidates = [
result?.popup?.detail,
result?.popup?.text,
result?.sMsg,
result?.msg,
]
.map((item) => String(item || '').trim())
.filter(Boolean)
return candidates[0] || '兑换完成'
}
function buildTencentRedeemClassification(outcome, retCode, message) {
return {
outcome,
success: outcome === 'success',
retryWithSameCode: outcome === 'captcha_rejected',
retryWithReplacementCode: outcome === 'code_used' || outcome === 'code_invalid',
retCode: Number.isFinite(retCode) ? retCode : null,
message: String(message || '').trim(),
}
}
function normalizeTencentRedeemText(value) {
return String(value || '')
.replace(/\s+/g, '')
.trim()
.toLowerCase()
}
@@ -0,0 +1,78 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {
classifyTencentRedeemResult,
isCaptchaRejectedResult,
resolveTencentRedeemMessage,
} from './session-redeem.js'
test('resolveTencentRedeemMessage prefers popup detail over generic title', () => {
assert.equal(
resolveTencentRedeemMessage({
popup: {
text: '兑换结果',
detail: '兑换码错误,请确认兑换码信息是否准确。',
},
}),
'兑换码错误,请确认兑换码信息是否准确。',
)
})
test('classifyTencentRedeemResult marks used codes as replacement retries', () => {
const result = classifyTencentRedeemResult({
popup: {
text: '兑换结果',
detail: '兑换码已使用。',
},
})
assert.equal(result.outcome, 'code_used')
assert.equal(result.success, false)
assert.equal(result.retryWithReplacementCode, true)
})
test('classifyTencentRedeemResult marks invalid codes as replacement retries', () => {
const result = classifyTencentRedeemResult({
popup: {
text: '兑换结果',
detail: '兑换码错误,请确认兑换码信息是否准确。',
},
})
assert.equal(result.outcome, 'code_invalid')
assert.equal(result.success, false)
assert.equal(result.retryWithReplacementCode, true)
})
test('classifyTencentRedeemResult recognizes successful gift popup', () => {
const result = classifyTencentRedeemResult({
popup: {
text: '恭喜您获得了礼包:动作-干员庆生,请注意:游戏虚拟道具奖品将会在24小时内到账,请登录游戏查看邮件。',
detail: '',
},
})
assert.equal(result.outcome, 'success')
assert.equal(result.success, true)
})
test('isCaptchaRejectedResult only treats captcha problems as same-code retries', () => {
assert.equal(
isCaptchaRejectedResult({
iRet: -100,
sMsg: '验证码错误',
}),
true,
)
assert.equal(
isCaptchaRejectedResult({
popup: {
text: '兑换结果',
detail: '兑换码已使用。',
},
}),
false,
)
})