diff --git a/scripts/api/checkInState.test.cjs b/scripts/api/checkInState.test.cjs index 6445653..334df47 100644 --- a/scripts/api/checkInState.test.cjs +++ b/scripts/api/checkInState.test.cjs @@ -40,6 +40,8 @@ function streak(updatedAt = '2026-10-23T17:26:19.6377348+00:00') { } } const recordedLegacy = legacy({ hidden: 'True', last_updated: '2024-10-18T08:21:27.1990138+00:00' }) +// 签到推广自身记录了今天(北京时间 10-24)的签到 +const checkedInToday = legacy({ last_updated: '2026-10-23T17:26:19.6377348+00:00' }) test('Main leaves the App remaining total unknown when only stale check-in data is available', async t => { t.mock.timers.enable({ apis: ['Date'], now: now.getTime() }) @@ -53,7 +55,9 @@ test('Main leaves the App remaining total unknown when only stale check-in data }) for (const [name, promotions, status, remaining] of [ - ['recorded current check-in overrides stale hidden promotion', [recordedLegacy, streak()], 'complete', 0], + // 连胜计数器会被每日任务等活动刷新到今天,签到前不能据此判定已签 + ['streak updated today by other activities is not a check-in', [recordedLegacy, streak()], 'unknown', null], + ['streak alone is not a check-in', [streak()], 'unknown', null], ['stale hidden promotion alone does not invent five points', [recordedLegacy], 'unknown', null], ['missing timestamp remains unknown', [legacy({ last_updated: '' })], 'unknown', null], ['invalid timestamp remains unknown', [legacy({ last_updated: 'invalid' })], 'unknown', null], @@ -69,7 +73,7 @@ for (const [name, promotions, status, remaining] of [ 'pending', 5 ], - ['China date uses the date after UTC midnight offset', [streak()], 'complete', 0], + ['China date uses the date after UTC midnight offset', [checkedInToday], 'complete', 0], ['previous China date is not already checked in', [legacy(), streak('2026-10-23T15:59:00Z')], 'pending', 5], ['seventh day reads its explicit reward', [legacy({ progress: '6', day_7_points: '20' })], 'pending', 20], ['new seven-day cycle returns to the first day', [legacy({ progress: '7' })], 'pending', 5], @@ -96,7 +100,7 @@ for (const completed of [true, false]) { bot.http.request = t.mock.fn(async () => ({ data: { response: { - promotions: completed ? [recordedLegacy, streak()] : [recordedLegacy] + promotions: completed ? [checkedInToday] : [recordedLegacy] } } })) @@ -116,7 +120,7 @@ function checkInBot(t, promotions = [legacy()]) { } test('already checked-in account skips the repeated request', async t => { - const bot = checkInBot(t, [recordedLegacy, streak()]) + const bot = checkInBot(t, [checkedInToday]) const activity = new DailyCheckIn(bot) await activity.doDailyCheckIn() @@ -124,6 +128,24 @@ test('already checked-in account skips the repeated request', async t => { assert.equal(bot.userData.gainedPoints, 0) }) +test('streak refreshed by the daily set still submits the check-in and credits it', async t => { + const bot = checkInBot(t, [recordedLegacy, streak()]) + bot.http.request = t.mock.fn(async () => ({ status: 200, data: { code: 0, response: { balance: 110 } } })) + await new DailyCheckIn(bot).doDailyCheckIn() + + assert.equal(bot.http.request.mock.callCount(), 1) + assert.equal(bot.userData.gainedPoints, 10) +}) + +test('a repeated check-in with zero gain is confirmed by the streak record', async t => { + const bot = checkInBot(t, [recordedLegacy, streak()]) + await new DailyCheckIn(bot).doDailyCheckIn() + + assert.equal(bot.http.request.mock.callCount(), 1) + assert.equal(bot.userData.gainedPoints, 0) + assert.ok(bot.logger.info.mock.calls.some(call => /本次无新增积分/.test(call.arguments[2]))) +}) + test('a pending check-in credits only the observed balance change', async t => { const bot = checkInBot(t) bot.http.request = t.mock.fn(async () => ({ status: 200, data: { code: 0, response: { balance: 105 } } })) diff --git a/src/functions/activities/app/CheckInState.ts b/src/functions/activities/app/CheckInState.ts index 706ded3..4df3c86 100644 --- a/src/functions/activities/app/CheckInState.ts +++ b/src/functions/activities/app/CheckInState.ts @@ -21,11 +21,16 @@ function dateKey(value: string | Date, country: string): string | null { export function resolveCheckInState( promotions: Pick[], country: string, - now = new Date() + now = new Date(), + // 连胜计数器在完成每日任务等活动时也会刷新到今天,不能据此判断"签到前已签"; + // 只在签到请求之后作为到账佐证使用 + trustStreak = false ): CheckInState { const today = dateKey(now, country) const checkIn = promotions.find(item => item.attributes.offerid === 'Gamification_Sapphire_DailyCheckIn') - const streak = promotions.find(item => item.name === 'Gamification_Streak_Counter_Promotion') + const streak = trustStreak + ? promotions.find(item => item.name === 'Gamification_Streak_Counter_Promotion') + : undefined for (const item of [streak, checkIn]) { const updatedAt = item?.attributes.last_updated if (updatedAt && dateKey(updatedAt, country) === today) { diff --git a/src/functions/activities/app/DailyCheckIn.ts b/src/functions/activities/app/DailyCheckIn.ts index b0469e1..d49174f 100644 --- a/src/functions/activities/app/DailyCheckIn.ts +++ b/src/functions/activities/app/DailyCheckIn.ts @@ -76,7 +76,7 @@ export class DailyCheckIn extends BaseActivity { 'green' ) } else { - const after = await this.getCheckInState() + const after = await this.getCheckInState(true) if (after.status !== 'complete') { throw new Error( `签到未确认到账 | pointsGained=${this.gainedPoints} | currentBalance=${newBalance} | state=${after.status}` @@ -99,10 +99,15 @@ export class DailyCheckIn extends BaseActivity { } } - private async getCheckInState(): Promise { + private async getCheckInState(trustStreak = false): Promise { try { const data = await this.bot.browser.func.getAppDashboardData('SAAndroid') - return resolveCheckInState(data.response?.promotions ?? [], this.bot.userData.geoLocale) + return resolveCheckInState( + data.response?.promotions ?? [], + this.bot.userData.geoLocale, + new Date(), + trustStreak + ) } catch (error) { this.bot.logger.warn( this.bot.isMobile,