From 24f5b79bf129c13230c2b372f44852fb0e3db5ce Mon Sep 17 00:00:00 2001 From: QLHazyCoder Date: Tue, 5 May 2026 03:59:09 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=97=A5=E5=BF=97=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=EF=BC=8C=E5=90=88=E5=B9=B6=E5=90=8C=E4=B8=80=E8=BD=AE?= =?UTF-8?q?=E4=B8=AD=E5=AF=B9=E5=BA=94=E7=9A=84=E9=82=AE=E7=AE=B1=E5=92=8C?= =?UTF-8?q?=E6=89=8B=E6=9C=BA=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- background.js | 3 + background/account-run-history.js | 58 ++++++-- background/message-router.js | 1 + background/phone-verification-flow.js | 3 + sidepanel/account-records-manager.js | 89 +++++++++++-- sidepanel/sidepanel.css | 19 ++- sidepanel/sidepanel.html | 8 +- sidepanel/sidepanel.js | 1 + ...kground-account-run-history-module.test.js | 126 ++++++++++++++++++ tests/phone-verification-flow.test.js | 1 + .../sidepanel-account-records-manager.test.js | 74 ++++++++++ 项目完整链路说明.md | 10 +- 项目文件结构说明.md | 20 +-- 14 files changed, 377 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index aa14c05..b8afa3c 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ - 页面要求填写 `age` - 支持 `Auto` 多轮运行 - 支持中途 `Stop` -- 支持通过日志区的 `记录` 按钮查看邮箱记录面板,按邮箱展示最终状态、时间、失败标签和重试次数 -- 支持将邮箱记录完整快照同步到本地 helper,便于开发者直接查看 `data/account-run-history.json` +- 支持通过日志区的 `记录` 按钮查看账号记录面板,同一轮的邮箱和手机号会合并显示,按最终状态、时间、失败标签和重试次数筛选 +- 支持将账号记录完整快照同步到本地 helper,便于开发者直接查看 `data/account-run-history.json` - Step 8 会自动寻找 OAuth 同意页的“继续”按钮,并通过 Chrome debugger 输入事件发起点击,然后监听本地回调地址 @@ -308,7 +308,7 @@ python3 scripts/hotmail_helper.py Hotmail helper listening on http://127.0.0.1:17373 ``` -同时还会输出本地邮箱记录快照文件路径。看到这些输出后,再回到扩展里点 `校验`、`复制最新验证码`;邮箱记录快照会按默认本地 helper 地址自动同步,无需再手动开启本地同步。 +同时还会输出本地账号记录快照文件路径。看到这些输出后,再回到扩展里点 `校验`、`复制最新验证码`;账号记录快照会按默认本地 helper 地址自动同步,无需再手动开启本地同步。 #### 最小排错说明 diff --git a/background.js b/background.js index 77551f8..cf59b2d 100644 --- a/background.js +++ b/background.js @@ -801,6 +801,7 @@ const DEFAULT_STATE = { currentLuckmailPurchase: null, currentLuckmailMailCursor: null, currentPhoneActivation: null, + phoneNumber: '', currentPhoneVerificationCode: '', currentPhoneVerificationCountdownEndsAt: 0, currentPhoneVerificationCountdownWindowIndex: 0, @@ -2694,6 +2695,7 @@ async function setEmailStateSilently(email) { if (normalizedEmail) { updates.accountIdentifierType = 'email'; updates.accountIdentifier = normalizedEmail; + updates.phoneNumber = ''; updates.signupPhoneNumber = ''; updates.signupPhoneActivation = null; updates.signupPhoneCompletedActivation = null; @@ -2726,6 +2728,7 @@ async function setSignupPhoneStateSilently(phoneNumber) { if (normalizedPhoneNumber) { updates.accountIdentifierType = 'phone'; updates.accountIdentifier = normalizedPhoneNumber; + updates.phoneNumber = ''; if (!isPhoneActivationForNumber(currentState?.signupPhoneActivation, normalizedPhoneNumber)) { updates.signupPhoneActivation = null; updates.signupPhoneVerificationRequestedAt = null; diff --git a/background/account-run-history.js b/background/account-run-history.js index 7fb20be..0a73f29 100644 --- a/background/account-run-history.js +++ b/background/account-run-history.js @@ -104,26 +104,64 @@ : normalizedValue.toLowerCase(); } + function getActivationPhoneNumber(activation = null) { + if (!activation || typeof activation !== 'object' || Array.isArray(activation)) { + return ''; + } + return String( + activation.phoneNumber + ?? activation.number + ?? activation.phone + ?? '' + ).trim(); + } + + function resolveStatePhoneNumber(state = {}) { + const identifierType = String(state?.accountIdentifierType || '').trim().toLowerCase(); + const accountIdentifierPhone = identifierType === 'phone' + ? String(state?.accountIdentifier || '').trim() + : ''; + + return String( + state?.phoneNumber + || state?.signupPhoneNumber + || accountIdentifierPhone + || getActivationPhoneNumber(state?.signupPhoneCompletedActivation) + || getActivationPhoneNumber(state?.signupPhoneActivation) + || getActivationPhoneNumber(state?.currentPhoneActivation) + || '' + ).trim(); + } + + function normalizePhoneRecordKey(value = '') { + const rawValue = String(value || '').trim(); + const digits = rawValue.replace(/\D+/g, ''); + return digits || rawValue.toLowerCase(); + } + function resolveRecordIdentity(record = {}) { - const email = String(record.email || '').trim().toLowerCase(); - const phoneNumber = String(record.phoneNumber ?? record.phone ?? record.number ?? '').trim(); + const rawEmail = String(record.email || '').trim().toLowerCase(); + const rawPhoneNumber = String(record.phoneNumber ?? record.phone ?? record.number ?? '').trim(); const rawIdentifierType = String(record.accountIdentifierType || '').trim().toLowerCase(); const inferredIdentifierType = rawIdentifierType === 'phone' - || (!email && phoneNumber) ? 'phone' - : 'email'; + : (rawIdentifierType === 'email' + ? 'email' + : ((!rawEmail && rawPhoneNumber) ? 'phone' : 'email')); const rawAccountIdentifier = String( record.accountIdentifier - || (inferredIdentifierType === 'phone' ? phoneNumber : email) + || (inferredIdentifierType === 'phone' ? rawPhoneNumber : rawEmail) || '' ).trim(); const accountIdentifierType = rawAccountIdentifier ? normalizeAccountIdentifierType(inferredIdentifierType) - : (email ? 'email' : (phoneNumber ? 'phone' : '')); + : (rawEmail ? 'email' : (rawPhoneNumber ? 'phone' : '')); const accountIdentifier = normalizeAccountIdentifierValue( - rawAccountIdentifier || (accountIdentifierType === 'phone' ? phoneNumber : email), + rawAccountIdentifier || (accountIdentifierType === 'phone' ? rawPhoneNumber : rawEmail), accountIdentifierType || inferredIdentifierType ); + const email = rawEmail || (accountIdentifierType === 'email' ? accountIdentifier : ''); + const phoneNumber = rawPhoneNumber || (accountIdentifierType === 'phone' ? accountIdentifier : ''); return { email, @@ -279,7 +317,7 @@ accountIdentifierType: state.accountIdentifierType, accountIdentifier: state.accountIdentifier, email: state.email, - phoneNumber: state.phoneNumber || state.signupPhoneNumber, + phoneNumber: resolveStatePhoneNumber(state), }); const email = identity.email; const phoneNumber = identity.phoneNumber; @@ -329,7 +367,7 @@ const recordId = String(record.recordId || '').trim(); const emailKey = String(record.email || '').trim().toLowerCase(); - const phoneKey = String(record.phoneNumber || '').trim().toLowerCase(); + const phoneKey = normalizePhoneRecordKey(record.phoneNumber); const identifierKey = buildRecordId( record.accountIdentifier || record.email || record.phoneNumber, record.accountIdentifierType || (phoneKey && !emailKey ? 'phone' : 'email') @@ -337,7 +375,7 @@ const nextHistory = normalizedHistory.filter((item) => { const itemRecordId = String(item.recordId || '').trim(); const itemEmailKey = String(item.email || '').trim().toLowerCase(); - const itemPhoneKey = String(item.phoneNumber || '').trim().toLowerCase(); + const itemPhoneKey = normalizePhoneRecordKey(item.phoneNumber); const itemIdentifierKey = buildRecordId( item.accountIdentifier || item.email || item.phoneNumber, item.accountIdentifierType || (itemPhoneKey && !itemEmailKey ? 'phone' : 'email') diff --git a/background/message-router.js b/background/message-router.js index 1777f3f..a0f0a1c 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -216,6 +216,7 @@ await setEmailState(email); } const updates = { + phoneNumber: '', signupPhoneNumber: '', signupPhoneActivation: null, signupPhoneCompletedActivation: null, diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index c087af1..41cb6db 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -5312,6 +5312,9 @@ clearCountrySmsFailure(activation.countryId, activation.provider); shouldCancelActivation = false; await clearCurrentActivation(); + await setPhoneRuntimeState({ + phoneNumber: activation.phoneNumber, + }); addPhoneReentryWithSameActivation = 0; await addLog('步骤 9:手机号验证已完成,等待 OAuth 授权页。', 'ok'); return submitResult; diff --git a/sidepanel/account-records-manager.js b/sidepanel/account-records-manager.js index fe04a65..0a5f476 100644 --- a/sidepanel/account-records-manager.js +++ b/sidepanel/account-records-manager.js @@ -72,8 +72,15 @@ if (rawRecordId) { return rawRecordId.toLowerCase(); } - const identifierType = String(record.accountIdentifierType || '').trim().toLowerCase() === 'phone' - || (!record.email && (record.accountIdentifier || record.phoneNumber)) + const rawIdentifierType = String(record.accountIdentifierType || '').trim().toLowerCase(); + const hasPhoneOnlyIdentifier = !record.email && ( + record.phoneNumber + || record.phone + || record.number + || (record.accountIdentifier && !/@/.test(String(record.accountIdentifier || ''))) + ); + const identifierType = rawIdentifierType === 'phone' + || (!rawIdentifierType && hasPhoneOnlyIdentifier) ? 'phone' : 'email'; const identifier = String( @@ -89,12 +96,71 @@ : identifier.toLowerCase(); } - function getRecordPrimaryIdentifier(record = {}) { - const email = String(record.email || '').trim(); - if (email) { - return email; + function getRecordIdentifierType(record = {}) { + const rawType = String(record.accountIdentifierType || '').trim().toLowerCase(); + if (rawType === 'phone') { + return 'phone'; } - return String(record.accountIdentifier || record.phoneNumber || record.phone || record.number || '').trim(); + if (rawType === 'email') { + return 'email'; + } + if (!record.email && (record.phoneNumber || record.phone || record.number)) { + return 'phone'; + } + if (!record.email && record.accountIdentifier && !/@/.test(String(record.accountIdentifier || ''))) { + return 'phone'; + } + return 'email'; + } + + function getRecordEmail(record = {}) { + const identifierType = getRecordIdentifierType(record); + return String( + record.email + || (identifierType === 'email' ? record.accountIdentifier : '') + || '' + ).trim(); + } + + function getRecordPhoneNumber(record = {}) { + const identifierType = getRecordIdentifierType(record); + return String( + record.phoneNumber + || record.phone + || record.number + || (identifierType === 'phone' ? record.accountIdentifier : '') + || '' + ).trim(); + } + + function getRecordPrimaryIdentifier(record = {}) { + const identifierType = getRecordIdentifierType(record); + const email = getRecordEmail(record); + const phoneNumber = getRecordPhoneNumber(record); + return identifierType === 'phone' + ? (phoneNumber || String(record.accountIdentifier || '').trim() || email) + : (email || String(record.accountIdentifier || '').trim() || phoneNumber); + } + + function getRecordSecondaryIdentifier(record = {}) { + const identifierType = getRecordIdentifierType(record); + const email = getRecordEmail(record); + const phoneNumber = getRecordPhoneNumber(record); + if (identifierType === 'phone' && email) { + return `邮箱 ${email}`; + } + if (identifierType !== 'phone' && phoneNumber) { + return `绑定手机号 ${phoneNumber}`; + } + return ''; + } + + function getRecordTitle(record = {}) { + const primaryIdentifier = getRecordPrimaryIdentifier(record) || '(空账号)'; + const secondaryIdentifier = getRecordSecondaryIdentifier(record); + return secondaryIdentifier + ? `${primaryIdentifier} / ${secondaryIdentifier}` + : primaryIdentifier; } function getAccountRunRecords(currentState = state.getLatestState()) { @@ -382,6 +448,8 @@ dom.accountRecordsList.innerHTML = visibleRecords.map((record) => { const recordId = buildRecordId(record); const primaryIdentifier = getRecordPrimaryIdentifier(record) || '(空账号)'; + const secondaryIdentifier = getRecordSecondaryIdentifier(record); + const recordTitle = getRecordTitle(record); const statusMeta = getStatusMeta(record); const summaryText = getRecordSummaryText(record); const retryCount = normalizeRetryCount(record.retryCount); @@ -408,12 +476,15 @@