From 3718575bff27705b1f518733e46fcf752523fdf1 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Tue, 21 Apr 2026 13:31:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D2925=E5=8F=B7=E6=B1=A0?= =?UTF-8?q?=E4=B8=8D=E8=83=BD=E8=87=AA=E5=8A=A8=E7=99=BB=E9=99=86=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 2 + background/mail-2925-session.js | 36 ++++++- background/steps/fetch-login-code.js | 7 +- background/steps/fetch-signup-code.js | 7 +- background/verification-flow.js | 10 +- content/mail-2925.js | 14 +++ .../background-mail2925-relogin-wait.test.js | 18 +++- tests/background-step4-filter-window.test.js | 57 +++++++++++ tests/background-step7-recovery.test.js | 19 ++-- tests/mail-2925-content.test.js | 98 ++++++++++++++++++- tests/verification-flow-polling.test.js | 15 ++- 项目完整链路说明.md | 5 + 12 files changed, 263 insertions(+), 25 deletions(-) create mode 100644 tests/background-step4-filter-window.test.js diff --git a/background.js b/background.js index a970285..43b89dc 100644 --- a/background.js +++ b/background.js @@ -5394,6 +5394,7 @@ const mail2925SessionManager = self.MultiPageBackgroundMail2925Session?.createMa normalizeMail2925Accounts, pickMail2925AccountForRun, requestStop, + ensureContentScriptReadyOnTab, reuseOrCreateTab, sendToContentScriptResilient, sendToMailContentScriptResilient, @@ -5402,6 +5403,7 @@ const mail2925SessionManager = self.MultiPageBackgroundMail2925Session?.createMa sleepWithStop, throwIfStopped, upsertMail2925AccountInList, + waitForTabUrlMatch, }); async function upsertMail2925Account(input = {}) { diff --git a/background/mail-2925-session.js b/background/mail-2925-session.js index 3bdc609..4649231 100644 --- a/background/mail-2925-session.js +++ b/background/mail-2925-session.js @@ -15,6 +15,7 @@ pickMail2925AccountForRun, getState, isAutoRunLockedState, + ensureContentScriptReadyOnTab, requestStop, reuseOrCreateTab, sendToContentScriptResilient, @@ -24,11 +25,12 @@ sleepWithStop, throwIfStopped, upsertMail2925AccountInList, + waitForTabUrlMatch, } = deps; const MAIL2925_SOURCE = 'mail-2925'; const MAIL2925_URL = 'https://2925.com/#/mailList'; - const MAIL2925_LOGIN_URL = 'https://2925.com/'; + const MAIL2925_LOGIN_URL = 'https://2925.com/login/'; const MAIL2925_INJECT = ['content/utils.js', 'content/mail-2925.js']; const MAIL2925_INJECT_SOURCE = 'mail-2925'; const MAIL2925_COOKIE_DOMAINS = [ @@ -387,7 +389,8 @@ } throwIfStopped(); - await reuseOrCreateTab(MAIL2925_SOURCE, forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL, { + const targetUrl = forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL; + const tabId = await reuseOrCreateTab(MAIL2925_SOURCE, targetUrl, { inject: MAIL2925_INJECT, injectSource: MAIL2925_INJECT_SOURCE, }); @@ -497,13 +500,40 @@ throwIfStopped(); await addLog(`2925:准备打开登录页 ${MAIL2925_LOGIN_URL}(forceRelogin=${forceRelogin ? 'true' : 'false'})`, 'info'); - await reuseOrCreateTab(MAIL2925_SOURCE, forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL, { + const targetUrl = forceRelogin ? MAIL2925_LOGIN_URL : MAIL2925_URL; + const tabId = await reuseOrCreateTab(MAIL2925_SOURCE, targetUrl, { inject: MAIL2925_INJECT, injectSource: MAIL2925_INJECT_SOURCE, }); const openedUrl = await getMail2925CurrentTabUrl(); await addLog(`2925:打开页后当前标签地址:${openedUrl || 'unknown'}`, 'info'); + if (forceRelogin && typeof waitForTabUrlMatch === 'function') { + const matchedLoginTab = await waitForTabUrlMatch( + tabId, + (url) => { + try { + const parsed = new URL(String(url || '')); + return (parsed.hostname === '2925.com' || parsed.hostname === 'www.2925.com') + && /^\/login\/?$/.test(parsed.pathname); + } catch { + return false; + } + }, + { timeoutMs: 15000, retryDelayMs: 300 } + ); + await addLog(`2925:等待最终落到登录页结果:${matchedLoginTab?.url || 'timeout'}`, matchedLoginTab ? 'info' : 'warn'); + if (matchedLoginTab && typeof ensureContentScriptReadyOnTab === 'function') { + await ensureContentScriptReadyOnTab(MAIL2925_SOURCE, tabId, { + inject: MAIL2925_INJECT, + injectSource: MAIL2925_INJECT_SOURCE, + timeoutMs: 20000, + retryDelayMs: 800, + logMessage: '步骤 0:2925 登录页内容脚本未就绪,正在等待页面稳定后继续登录...', + }); + } + } + if (forceRelogin && typeof sleepWithStop === 'function') { await addLog('2925:登录页已打开,等待 3 秒后开始检查输入框并执行登录...', 'info'); await sleepWithStop(3000); diff --git a/background/steps/fetch-login-code.js b/background/steps/fetch-login-code.js index 40dec9a..9e16ab6 100644 --- a/background/steps/fetch-login-code.js +++ b/background/steps/fetch-login-code.js @@ -1,6 +1,8 @@ (function attachBackgroundStep8(root, factory) { root.MultiPageBackgroundStep8 = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() { + const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000; + function createStep8Executor(deps = {}) { const { addLog, @@ -61,6 +63,9 @@ if (mail.error) throw new Error(mail.error); const stepStartedAt = Date.now(); + const verificationFilterAfterTimestamp = mail.provider === '2925' + ? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS) + : stepStartedAt; const verificationSessionKey = `8:${stepStartedAt}`; const authTabId = await getTabId('signup-page'); @@ -140,7 +145,7 @@ ...state, step8VerificationTargetEmail: displayedVerificationEmail || '', }, mail, { - filterAfterTimestamp: mail.provider === '2925' ? 0 : stepStartedAt, + filterAfterTimestamp: verificationFilterAfterTimestamp, sessionKey: verificationSessionKey, disableTimeBudgetCap: mail.provider === '2925', getRemainingTimeMs: getStep8RemainingTimeResolver(state?.oauthUrl || ''), diff --git a/background/steps/fetch-signup-code.js b/background/steps/fetch-signup-code.js index fb5a1d6..e85b250 100644 --- a/background/steps/fetch-signup-code.js +++ b/background/steps/fetch-signup-code.js @@ -1,6 +1,8 @@ (function attachBackgroundStep4(root, factory) { root.MultiPageBackgroundStep4 = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep4Module() { + const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000; + function createStep4Executor(deps = {}) { const { addLog, @@ -26,6 +28,9 @@ const mail = getMailConfig(state); if (mail.error) throw new Error(mail.error); const stepStartedAt = Date.now(); + const verificationFilterAfterTimestamp = mail.provider === '2925' + ? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS) + : stepStartedAt; const verificationSessionKey = `4:${stepStartedAt}`; const signupTabId = await getTabId('signup-page'); if (!signupTabId) { @@ -101,7 +106,7 @@ } await resolveVerificationStep(4, state, mail, { - filterAfterTimestamp: mail.provider === '2925' ? 0 : stepStartedAt, + filterAfterTimestamp: verificationFilterAfterTimestamp, sessionKey: verificationSessionKey, disableTimeBudgetCap: mail.provider === '2925', requestFreshCodeFirst: mail.provider === HOTMAIL_PROVIDER ? false : true, diff --git a/background/verification-flow.js b/background/verification-flow.js index 9e20dd6..fc4f208 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -239,12 +239,16 @@ return requestedAt; } - function shouldPreclear2925Mailbox(step, mail) { - return mail?.provider === '2925' && (step === 4 || step === 8); + function shouldPreclear2925Mailbox(step, mail, options = {}) { + if (mail?.provider !== '2925' || (step !== 4 && step !== 8)) { + return false; + } + + return !(Number(options.filterAfterTimestamp) > 0); } async function clear2925MailboxBeforePolling(step, mail, options = {}) { - if (!shouldPreclear2925Mailbox(step, mail)) { + if (!shouldPreclear2925Mailbox(step, mail, options)) { return; } diff --git a/content/mail-2925.js b/content/mail-2925.js index 4c036b1..54970d2 100644 --- a/content/mail-2925.js +++ b/content/mail-2925.js @@ -629,6 +629,13 @@ function extractVerificationCode(text, strictChatGPTCodeOnly = false) { return null; } +function normalizeMinuteTimestamp(timestamp) { + if (!Number.isFinite(timestamp) || timestamp <= 0) return 0; + const date = new Date(timestamp); + date.setSeconds(0, 0); + return date.getTime(); +} + function parseMailItemTimestamp(item) { const timeText = getMailItemTimeText(item); if (!timeText) return null; @@ -920,10 +927,12 @@ async function handlePollEmail(step, payload) { subjectFilters, maxAttempts, intervalMs, + filterAfterTimestamp = 0, excludeCodes = [], strictChatGPTCodeOnly = false, } = payload || {}; const excludedCodeSet = new Set(excludeCodes.filter(Boolean)); + const filterAfterMinute = normalizeMinuteTimestamp(Number(filterAfterTimestamp) || 0); if (typeof throwIfMail2925LimitReached === 'function') { throwIfMail2925LimitReached(); } @@ -975,6 +984,11 @@ async function handlePollEmail(step, payload) { for (let index = 0; index < items.length; index += 1) { const item = items[index]; const itemTimestamp = parseMailItemTimestamp(item); + const itemMinute = normalizeMinuteTimestamp(itemTimestamp || 0); + + if (filterAfterMinute && (!itemMinute || itemMinute < filterAfterMinute)) { + continue; + } const previewText = getMailItemText(item); if (!matchesMailFilters(previewText, senderFilters, subjectFilters)) { diff --git a/tests/background-mail2925-relogin-wait.test.js b/tests/background-mail2925-relogin-wait.test.js index fe7634b..1f3cd4a 100644 --- a/tests/background-mail2925-relogin-wait.test.js +++ b/tests/background-mail2925-relogin-wait.test.js @@ -3,6 +3,11 @@ const assert = require('node:assert/strict'); const fs = require('node:fs'); const mail2925Utils = require('../mail2925-utils.js'); +test('background mail2925 session uses /login/ as relogin entry url', () => { + const source = fs.readFileSync('background/mail-2925-session.js', 'utf8'); + assert.match(source, /const MAIL2925_LOGIN_URL = 'https:\/\/2925\.com\/login\/';/); +}); + test('ensureMail2925MailboxSession waits 3 seconds before and after opening login page on force relogin', async () => { const source = fs.readFileSync('background/mail-2925-session.js', 'utf8'); const globalScope = {}; @@ -17,6 +22,8 @@ test('ensureMail2925MailboxSession waits 3 seconds before and after opening logi }; const events = { sleeps: [], + openedUrls: [], + readyCalls: 0, }; const manager = api.createMail2925SessionManager({ @@ -41,7 +48,13 @@ test('ensureMail2925MailboxSession waits 3 seconds before and after opening logi normalizeMail2925Accounts: mail2925Utils.normalizeMail2925Accounts, pickMail2925AccountForRun: mail2925Utils.pickMail2925AccountForRun, requestStop: async () => {}, - reuseOrCreateTab: async () => 1, + ensureContentScriptReadyOnTab: async () => { + events.readyCalls += 1; + }, + reuseOrCreateTab: async (_source, url) => { + events.openedUrls.push(url); + return 1; + }, sendToMailContentScriptResilient: async () => ({ loggedIn: true }), setPersistentSettings: async (payload) => { currentState = { ...currentState, ...payload }; @@ -54,6 +67,7 @@ test('ensureMail2925MailboxSession waits 3 seconds before and after opening logi }, throwIfStopped: () => {}, upsertMail2925AccountInList: mail2925Utils.upsertMail2925AccountInList, + waitForTabUrlMatch: async () => ({ url: 'https://2925.com/login/' }), }); await manager.ensureMail2925MailboxSession({ @@ -62,5 +76,7 @@ test('ensureMail2925MailboxSession waits 3 seconds before and after opening logi actionLabel: '步骤 4:确认 2925 邮箱登录态', }); + assert.deepStrictEqual(events.openedUrls, ['https://2925.com/login/']); assert.deepStrictEqual(events.sleeps, [3000, 3000]); + assert.equal(events.readyCalls, 1); }); diff --git a/tests/background-step4-filter-window.test.js b/tests/background-step4-filter-window.test.js new file mode 100644 index 0000000..cced4bc --- /dev/null +++ b/tests/background-step4-filter-window.test.js @@ -0,0 +1,57 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +const source = fs.readFileSync('background/steps/fetch-signup-code.js', 'utf8'); +const globalScope = {}; +const api = new Function('self', `${source}; return self.MultiPageBackgroundStep4;`)(globalScope); + +test('step 4 passes a fixed 10-minute lookback window to 2925 mailbox polling', async () => { + let capturedOptions = null; + const realDateNow = Date.now; + Date.now = () => 700000; + + const executor = api.createStep4Executor({ + addLog: async () => {}, + chrome: { + tabs: { + update: async () => {}, + }, + }, + completeStepFromBackground: async () => {}, + confirmCustomVerificationStepBypass: async () => {}, + ensureMail2925MailboxSession: async () => {}, + getMailConfig: () => ({ + provider: '2925', + label: '2925 邮箱', + source: 'mail-2925', + url: 'https://2925.com', + }), + getTabId: async () => 1, + HOTMAIL_PROVIDER: 'hotmail-api', + isTabAlive: async () => true, + LUCKMAIL_PROVIDER: 'luckmail-api', + CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email', + resolveVerificationStep: async (_step, _state, _mail, options) => { + capturedOptions = options; + }, + reuseOrCreateTab: async () => {}, + sendToContentScriptResilient: async () => ({}), + shouldUseCustomRegistrationEmail: () => false, + STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000, + throwIfStopped: () => {}, + }); + + try { + await executor.executeStep4({ + email: 'user@example.com', + password: 'secret', + mail2925UseAccountPool: false, + }); + } finally { + Date.now = realDateNow; + } + + assert.equal(capturedOptions.filterAfterTimestamp, 100000); + assert.equal(capturedOptions.resendIntervalMs, 0); +}); diff --git a/tests/background-step7-recovery.test.js b/tests/background-step7-recovery.test.js index c117f69..2503e7a 100644 --- a/tests/background-step7-recovery.test.js +++ b/tests/background-step7-recovery.test.js @@ -89,8 +89,10 @@ test('step 8 submits login verification directly without replaying step 7', asyn ]); }); -test('step 8 disables resend interval for 2925 mailbox polling', async () => { +test('step 8 uses a fixed 10-minute lookback window and disables resend interval for 2925 mailbox polling', async () => { let capturedOptions = null; + const realDateNow = Date.now; + Date.now = () => 900000; const executor = api.createStep8Executor({ addLog: async () => {}, @@ -130,12 +132,17 @@ test('step 8 disables resend interval for 2925 mailbox polling', async () => { throwIfStopped: () => {}, }); - await executor.executeStep8({ - email: 'user@example.com', - password: 'secret', - oauthUrl: 'https://oauth.example/latest', - }); + try { + await executor.executeStep8({ + email: 'user@example.com', + password: 'secret', + oauthUrl: 'https://oauth.example/latest', + }); + } finally { + Date.now = realDateNow; + } + assert.equal(capturedOptions.filterAfterTimestamp, 300000); assert.equal(capturedOptions.resendIntervalMs, 0); assert.equal(capturedOptions.targetEmail, ''); assert.equal(capturedOptions.beforeSubmit, undefined); diff --git a/tests/mail-2925-content.test.js b/tests/mail-2925-content.test.js index 098fab8..09730be 100644 --- a/tests/mail-2925-content.test.js +++ b/tests/mail-2925-content.test.js @@ -56,7 +56,10 @@ function extractFunction(name) { } test('handlePollEmail establishes a baseline after opening from detail view and only picks mail from a later refresh', async () => { - const bundle = extractFunction('handlePollEmail'); + const bundle = [ + extractFunction('normalizeMinuteTimestamp'), + extractFunction('handlePollEmail'), + ].join('\n'); const api = new Function(` let state = 'detail'; @@ -162,7 +165,10 @@ return { }); test('handlePollEmail ignores targetEmail and still tests any matching ChatGPT mail', async () => { - const bundle = extractFunction('handlePollEmail'); + const bundle = [ + extractFunction('normalizeMinuteTimestamp'), + extractFunction('handlePollEmail'), + ].join('\n'); const api = new Function(` let state = 'empty'; @@ -242,6 +248,94 @@ return { assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-1']); }); +test('handlePollEmail only accepts 2925 mails inside the fixed lookback window', async () => { + const bundle = [ + extractFunction('normalizeMinuteTimestamp'), + extractFunction('handlePollEmail'), + ].join('\n'); + + const api = new Function(` +let state = 'ready'; +const seenCodes = new Set(); +const readAndDeleteCalls = []; +const oldMail = { + id: 'mail-old', + text: 'OpenAI verification code 111111', + timestamp: 1000, +}; +const windowMail = { + id: 'mail-window', + text: 'OpenAI verification code 222222', + timestamp: 301000, +}; + +function findMailItems() { + return state === 'ready' ? [oldMail, windowMail] : []; +} + +function getMailItemId(item) { + return item.id; +} + +function getCurrentMailIds(items = []) { + return new Set(items.map((item) => item.id)); +} + +function parseMailItemTimestamp(item) { + return item.timestamp; +} + +function matchesMailFilters(text) { + return /openai|verification/i.test(String(text || '')); +} + +function getMailItemText(item) { + return item.text; +} + +function extractVerificationCode(text) { + const match = String(text || '').match(/(\\d{6})/); + return match ? match[1] : null; +} + +async function sleep() {} +async function sleepRandom() {} +async function returnToInbox() { + return true; +} +async function refreshInbox() {} + +async function openMailAndDeleteAfterRead(item) { + readAndDeleteCalls.push(item.id); + return item.text; +} + +async function ensureSeenCodesSession() {} +function persistSeenCodes() {} +function log() {} + +${bundle} + +return { + handlePollEmail, + getReadAndDeleteCalls() { + return readAndDeleteCalls.slice(); + }, +}; +`)(); + + const result = await api.handlePollEmail(4, { + senderFilters: ['openai'], + subjectFilters: ['verification'], + maxAttempts: 1, + intervalMs: 1, + filterAfterTimestamp: 120000, + }); + + assert.equal(result.code, '222222'); + assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-window']); +}); + test('ensureSeenCodesSession resets tried codes only when a new verification step session starts', async () => { const bundle = [ extractFunction('buildSeenCodeSessionKey'), diff --git a/tests/verification-flow-polling.test.js b/tests/verification-flow-polling.test.js index 70314ed..77ce17b 100644 --- a/tests/verification-flow-polling.test.js +++ b/tests/verification-flow-polling.test.js @@ -6,7 +6,7 @@ const source = fs.readFileSync('background/verification-flow.js', 'utf8'); const globalScope = {}; const api = new Function('self', `${source}; return self.MultiPageBackgroundVerificationFlow;`)(globalScope); -test('verification flow extends 2925 polling window', () => { +test('verification flow keeps 2925 polling cadence in the default payload', () => { const helpers = api.createVerificationFlowHelpers({ addLog: async () => {}, chrome: { tabs: { update: async () => {} } }, @@ -37,10 +37,8 @@ test('verification flow extends 2925 polling window', () => { const step4Payload = helpers.getVerificationPollPayload(4, { email: 'user@example.com', mailProvider: '2925' }); const step8Payload = helpers.getVerificationPollPayload(8, { email: 'user@example.com', mailProvider: '2925' }); - assert.equal(step4Payload.filterAfterTimestamp, 0); assert.equal(step4Payload.maxAttempts, 15); assert.equal(step4Payload.intervalMs, 15000); - assert.equal(step8Payload.filterAfterTimestamp, 0); assert.equal(step8Payload.maxAttempts, 15); assert.equal(step8Payload.intervalMs, 15000); }); @@ -111,7 +109,7 @@ test('verification flow runs beforeSubmit hook before filling the code', async ( ]); }); -test('verification flow clears 2925 mailbox before polling and after successful login code submission', async () => { +test('verification flow skips 2925 mailbox preclear when using a fixed login mail window and still clears after success', async () => { const mailMessages = []; const helpers = api.createVerificationFlowHelpers({ @@ -164,15 +162,15 @@ test('verification flow clears 2925 mailbox before polling and after successful lastLoginCode: null, }, { provider: '2925', label: '2925 邮箱' }, - {} + { filterAfterTimestamp: 123456 } ); await new Promise((resolve) => setTimeout(resolve, 0)); - assert.deepStrictEqual(mailMessages, ['DELETE_ALL_EMAILS', 'POLL_EMAIL', 'DELETE_ALL_EMAILS']); + assert.deepStrictEqual(mailMessages, ['POLL_EMAIL', 'DELETE_ALL_EMAILS']); }); -test('verification flow clears 2925 mailbox before polling and after successful signup code submission', async () => { +test('verification flow skips 2925 mailbox preclear when using a fixed signup mail window and still clears after success', async () => { const mailMessages = []; const helpers = api.createVerificationFlowHelpers({ @@ -229,13 +227,14 @@ test('verification flow clears 2925 mailbox before polling and after successful }, { provider: '2925', label: '2925 邮箱' }, { + filterAfterTimestamp: 123456, requestFreshCodeFirst: false, } ); await new Promise((resolve) => setTimeout(resolve, 0)); - assert.deepStrictEqual(mailMessages, ['DELETE_ALL_EMAILS', 'POLL_EMAIL', 'DELETE_ALL_EMAILS']); + assert.deepStrictEqual(mailMessages, ['POLL_EMAIL', 'DELETE_ALL_EMAILS']); }); test('verification flow treats add-phone after login code submit as fatal instead of completing step 8', async () => { diff --git a/项目完整链路说明.md b/项目完整链路说明.md index cbd413c..8dab6c4 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -620,3 +620,8 @@ - Step 8 如果发现认证页已经进入登录超时报错/重试页,会直接报错并回到 Step 7 重新开始,而不是在 Step 8 内部点击 `重试`。 - 任意认证页重试页如果正文中出现 `max_check_attempts`,会被视为 Cloudflare 风控触发:后台立刻完全停止流程,侧边栏会复用现有确认弹窗提示等待 15~30 分钟后再试,避免继续刷新或反复重试加重风控,确认按钮显示为“我知道了”。 - Step 9 在点击 OAuth 同意页 `继续` 后,会额外检查是否进入认证页重试页;若命中则先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再重新执行当前轮的 `继续` 点击。 +## 2026-04-21 2925 邮件时间窗补充 + +- `2925` 在 Step 4 / Step 8 现在会携带固定的步骤开始时间窗口,实际筛选下限为“步骤开始时间向前回看 10 分钟”。 +- 为了保留这段固定时间窗内已经到达的验证码邮件,后台不再在轮询开始前预先清空 2925 邮箱。 +- `2925` 验证码最终提交成功后,后台仍会异步发送 `DELETE_ALL_EMAILS` 做收尾清理。