From 8bbd91ff9de9b71e27d179f23a513304dcc956bd Mon Sep 17 00:00:00 2001 From: yanbing Date: Sat, 23 May 2026 11:52:13 +0800 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E7=99=BB=E5=BD=95=E5=B7=B2=E6=B3=A8=E5=86=8C=20Plus?= =?UTF-8?q?=20=E7=9A=84=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 212 +++++++++++++++++++++++++++ background/message-router.js | 14 ++ background/steps/fetch-login-code.js | 4 + background/steps/open-chatgpt.js | 2 +- content/signup-page.js | 50 +++++++ sidepanel/hotmail-manager.js | 32 ++++ sidepanel/sidepanel.html | 6 + sidepanel/sidepanel.js | 141 ++++++++++++++++++ 8 files changed, 460 insertions(+), 1 deletion(-) diff --git a/background.js b/background.js index b107b68..81079e3 100644 --- a/background.js +++ b/background.js @@ -13948,6 +13948,216 @@ async function executeReloginBoundEmail(state = {}) { }); } +function validateHotmailSub2ApiLoginImportState(state = {}, account = null) { + if (!account) { + throw new Error('未找到对应的 Hotmail 账号。'); + } + if (!account.used) { + throw new Error('只有已用 Hotmail 账号可以执行登录并导入。'); + } + if (!String(account.email || '').trim()) { + throw new Error('目标 Hotmail 账号缺少邮箱地址。'); + } + if (!String(account.password || '')) { + throw new Error(`Hotmail 账号 ${account.email || account.id} 没有保存可用于 ChatGPT 登录的密码。`); + } + if (getPanelMode(state) !== 'sub2api') { + throw new Error('请先将“导出至”切换为 SUB2API。'); + } + if (!String(state.sub2apiUrl || '').trim()) { + throw new Error('请先填写 SUB2API URL。'); + } + if (!String(state.sub2apiEmail || '').trim()) { + throw new Error('请先填写 SUB2API 登录邮箱。'); + } + if (!String(state.sub2apiPassword || '')) { + throw new Error('请先填写 SUB2API 登录密码。'); + } +} + +function isChatGptSessionPageUrl(rawUrl = '') { + try { + const parsed = new URL(String(rawUrl || '')); + const host = String(parsed.hostname || '').trim().toLowerCase(); + if (!['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com'].includes(host)) { + return false; + } + const path = String(parsed.pathname || ''); + return !/^\/(?:auth|log-in|create-account|email-verification|phone-verification|add-phone)(?:[/?#]|$)/i.test(path); + } catch { + return false; + } +} + +async function waitForChatGptSessionPage(tabId, options = {}) { + const timeoutMs = Math.max(1000, Math.floor(Number(options.timeoutMs) || 90000)); + const retryDelayMs = Math.max(250, Math.floor(Number(options.retryDelayMs) || 700)); + const startedAt = Date.now(); + while (Date.now() - startedAt < timeoutMs) { + throwIfStopped(); + const tab = await chrome.tabs.get(tabId).catch(() => null); + if (!tab) { + throw new Error('ChatGPT 登录标签页已关闭,无法继续导入。'); + } + if (isChatGptSessionPageUrl(tab.url)) { + return tab; + } + await sleepWithStop(retryDelayMs); + } + throw new Error('登录后未能进入 ChatGPT 会话页,请确认账号已登录成功。'); +} + +async function ensureChatGptSessionTabForImport(tabId) { + let tab = await chrome.tabs.get(tabId).catch(() => null); + if (!tab?.id) { + throw new Error('ChatGPT 登录标签页已关闭,无法继续导入。'); + } + if (!isChatGptSessionPageUrl(tab.url)) { + await chrome.tabs.update(tab.id, { url: 'https://chatgpt.com/', active: true }); + tab = await waitForChatGptSessionPage(tab.id, { timeoutMs: 90000 }); + } + await waitForTabCompleteUntilStopped(tab.id, { timeoutMs: 45000 }).catch(() => tab); + await registerTab('plus-checkout', tab.id); + await setState({ plusCheckoutTabId: tab.id }); + return tab; +} + +async function runLoginImportVerificationStep(stateOverride = {}) { + const previousState = await getState(); + await setState({ + plusModeEnabled: true, + plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH, + }); + try { + const verificationState = await getState(); + const verificationStep = getStepIdByNodeIdForState('fetch-login-code', verificationState) || 8; + await step8Executor.executeStep8({ + ...verificationState, + ...stateOverride, + nodeId: 'fetch-login-code', + visibleStep: verificationStep, + }); + } catch (err) { + const message = String(err?.message || ''); + if (/back\/forward cache|message channel closed|Receiving end does not exist|port closed/i.test(message)) { + const tabId = await getTabId('signup-page'); + if (tabId) { + const tab = await chrome.tabs.get(tabId).catch(() => null); + if (tab && isChatGptSessionPageUrl(tab.url)) { + await addLog('登录并导入:验证码已提交,页面已跳转到 ChatGPT 会话页。', 'info'); + return; + } + } + await addLog('登录并导入:验证码提交后页面连接中断,但未检测到登录成功,将继续尝试导入。', 'warn'); + return; + } + throw err; + } finally { + await setState({ + plusModeEnabled: previousState.plusModeEnabled, + plusAccountAccessStrategy: previousState.plusAccountAccessStrategy, + }); + } +} + +async function runHotmailChatGptLogin(account) { + await addLog(`登录并导入:正在打开 ChatGPT 登录页,账号 ${account.email}。`, 'info'); + const tabId = await reuseOrCreateTab('signup-page', 'https://chatgpt.com/auth/login', { forceNew: true }); + await waitForTabCompleteUntilStopped(tabId, { timeoutMs: 45000 }).catch(() => null); + await ensureContentScriptReadyOnTabUntilStopped('signup-page', tabId, { + inject: SIGNUP_PAGE_INJECT_FILES, + injectSource: 'signup-page', + timeoutMs: 45000, + logMessage: '登录并导入:ChatGPT 登录页内容脚本未就绪,正在等待页面恢复...', + }); + + const loginPayload = { + email: account.email, + accountIdentifier: account.email, + loginIdentifierType: 'email', + password: account.password, + visibleStep: 7, + }; + const loginResult = await sendToContentScriptResilient('signup-page', { + type: 'EXECUTE_NODE', + nodeId: 'oauth-login', + step: 7, + source: 'background', + payload: loginPayload, + }, { + timeoutMs: 180000, + responseTimeoutMs: 180000, + retryDelayMs: 700, + logMessage: '登录并导入:认证页正在切换,等待页面重新就绪后继续登录...', + logStep: 7, + logStepKey: 'oauth-login', + }); + if (loginResult?.error) { + throw new Error(loginResult.error); + } + if (!isStep6SuccessResult(loginResult)) { + throw new Error(loginResult?.message || 'ChatGPT 登录页未返回可识别的登录结果。'); + } + + if (!loginResult.directOAuthConsentPage && !loginResult.addEmailPage) { + const alreadyOnSessionPage = loginResult.url && isChatGptSessionPageUrl(loginResult.url); + if (!alreadyOnSessionPage) { + await runLoginImportVerificationStep(); + } + } + + await ensureChatGptSessionTabForImport(tabId); + return tabId; +} + +async function loginHotmailAndImportSub2Api(accountId) { + const state = await getState(); + const account = findHotmailAccount(normalizeHotmailAccounts(state.hotmailAccounts), accountId); + validateHotmailSub2ApiLoginImportState(state, account); + + await addLog(`登录并导入:准备使用 Hotmail 已用账号 ${account.email} 重新登录 ChatGPT。`, 'info'); + await setCurrentHotmailAccount(account.id, { markUsed: false, syncEmail: true }); + await setPasswordState(account.password); + await setState({ + accountIdentifierType: 'email', + accountIdentifier: account.email, + signupMethod: 'email', + resolvedSignupMethod: 'email', + mailProvider: HOTMAIL_PROVIDER, + panelMode: 'sub2api', + plusModeEnabled: true, + plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION, + step8VerificationTargetEmail: account.email, + loginVerificationRequestedAt: null, + lastLoginCode: null, + }); + + await step1Executor.clearOpenAiCookiesBeforeStep1(); + const loginTabId = await runHotmailChatGptLogin(account); + const sessionTab = await ensureChatGptSessionTabForImport(loginTabId); + await addLog('登录并导入:ChatGPT 登录完成,正在读取当前会话并导入 SUB2API。', 'info'); + + try { + await sub2ApiSessionImportExecutor.executeSub2ApiSessionImport({ + ...(await getState()), + nodeId: 'sub2api-session-import', + visibleStep: 10, + plusCheckoutTabId: sessionTab.id, + }); + } catch (err) { + const message = String(err?.message || ''); + if (/未读取到有效的.*ChatGPT.*会话|未返回可用.*accessToken|请先登录.*ChatGPT|当前.*标签页不在.*ChatGPT/i.test(message)) { + throw new Error(`ACCOUNT_DELETED::${message}`); + } + throw err; + } + + return { + account, + imported: true, + }; +} + const stepExecutorsByKey = { 'open-chatgpt': () => step1Executor.executeStep1(), 'submit-signup-email': (state) => step2Executor.executeStep2(state), @@ -14063,6 +14273,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter runIpProxyAutoSync: null, listIcloudAliases, listLuckmailPurchasesForManagement, + loginHotmailAndImportSub2Api, markCurrentCustomEmailPoolEntryUsed, markCurrentRegistrationAccountUsed, getCurrentMail2925Account, @@ -14931,6 +15142,7 @@ async function ensureStep8VerificationPageReady(options = {}) { if ( pageState.state === 'verification_page' || pageState.state === 'oauth_consent_page' + || pageState.state === 'logged_in_home' || (options.allowPhoneVerificationPage && pageState.state === 'phone_verification_page') || (options.allowAddEmailPage && pageState.state === 'add_email_page') ) { diff --git a/background/message-router.js b/background/message-router.js index 18f6a91..6ab8853 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -169,6 +169,7 @@ setSignupPhoneState, setSignupPhoneStateSilently, setIcloudAliasPreservedState, + loginHotmailAndImportSub2Api, setIcloudAliasUsedState, setLuckmailPurchaseDisabledState, setLuckmailPurchasePreservedState, @@ -1775,6 +1776,19 @@ return { ok: true, ...result }; } + case 'LOGIN_HOTMAIL_AND_IMPORT_SUB2API': { + clearStopRequest(); + if (message.source === 'sidepanel') { + await lockAutomationWindowFromMessage(message, sender); + await ensureManualInteractionAllowed('登录并导入 SUB2API'); + } + if (typeof loginHotmailAndImportSub2Api !== 'function') { + throw new Error('登录并导入 SUB2API 能力未接入。'); + } + const result = await loginHotmailAndImportSub2Api(String(message.payload?.accountId || '')); + return { ok: true, ...result }; + } + case 'UPSERT_MAIL2925_ACCOUNT': { const account = await upsertMail2925Account(message.payload || {}); return { ok: true, account }; diff --git a/background/steps/fetch-login-code.js b/background/steps/fetch-login-code.js index 5ecd1e7..f3d2730 100644 --- a/background/steps/fetch-login-code.js +++ b/background/steps/fetch-login-code.js @@ -785,6 +785,10 @@ await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: state?.nodeId }); return; } + if (pageState?.state === 'logged_in_home') { + await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: state?.nodeId }); + return; + } const phoneLoginCodeMode = isPhoneLoginCodeMode(state); if (phoneLoginCodeMode) { if (pageState?.state === 'phone_verification_page') { diff --git a/background/steps/open-chatgpt.js b/background/steps/open-chatgpt.js index 5db5e65..2279514 100644 --- a/background/steps/open-chatgpt.js +++ b/background/steps/open-chatgpt.js @@ -156,7 +156,7 @@ await completeNodeFromBackground('open-chatgpt', {}); } - return { executeStep1 }; + return { clearOpenAiCookiesBeforeStep1, executeStep1 }; } return { createStep1Executor }; diff --git a/content/signup-page.js b/content/signup-page.js index 42ae3e1..745bf01 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -4196,6 +4196,9 @@ function inspectLoginAuthState() { const phoneVerificationPage = isPhoneVerificationPageReady(); const consentReady = isStep8Ready(); const oauthConsentPage = isOAuthConsentPage(); + const postVerificationState = typeof getStep4PostVerificationState === 'function' + ? getStep4PostVerificationState({ ignoreVerificationVisibility: true }) + : null; const baseState = { state: 'unknown', url: location.href, @@ -4231,6 +4234,14 @@ function inspectLoginAuthState() { }; } + if (postVerificationState?.state === 'logged_in_home') { + return { + ...baseState, + state: 'logged_in_home', + url: postVerificationState.url || location.href, + }; + } + if (phoneVerificationPage) { return { ...baseState, @@ -4358,6 +4369,8 @@ function getLoginAuthStateLabel(snapshot) { return '手机号页'; case 'add_email_page': return '添加邮箱页'; + case 'logged_in_home': + return 'ChatGPT 已登录页'; default: return '未知页面'; } @@ -5023,6 +5036,18 @@ async function waitForVerificationSubmitOutcome(step, timeout, options = {}) { return { invalidCode: true, errorText }; } + if (step === 8) { + const postVerificationState = typeof getStep4PostVerificationState === 'function' + ? getStep4PostVerificationState({ ignoreVerificationVisibility: true }) + : null; + if (postVerificationState?.state === 'logged_in_home') { + return { + success: true, + url: postVerificationState.url || location.href, + }; + } + } + if (step === 8 && isStep8Ready()) { return { success: true }; } @@ -5192,6 +5217,13 @@ async function fillVerificationCode(step, payload) { } } if (step === 8) { + const postVerificationState = typeof getStep4PostVerificationState === 'function' + ? getStep4PostVerificationState({ ignoreVerificationVisibility: true }) + : null; + if (postVerificationState?.state === 'logged_in_home') { + log(`步骤 ${step}:检测到页面已进入 ChatGPT 已登录态,本次验证码提交按成功处理。`, 'ok'); + return { success: true, assumed: true, alreadyAdvanced: true, url: postVerificationState.url || location.href }; + } if (isStep8Ready()) { log(`步骤 ${step}:检测到页面已进入 OAuth 同意页,本次验证码提交按成功处理。`, 'ok'); return { success: true, assumed: true, alreadyAdvanced: true }; @@ -5388,6 +5420,16 @@ async function resolveStep6PostSubmitSnapshot(snapshot, options = {}) { addPhoneMessage, } = options; + if (normalizedSnapshot.state === 'logged_in_home') { + return { + action: 'done', + result: createStep6SuccessResult(normalizedSnapshot, { + via: `${via}_logged_in_home`, + loginVerificationRequestedAt: null, + }), + }; + } + if (normalizedSnapshot.state === 'verification_page' || (allowPhoneVerificationPage && normalizedSnapshot.state === 'phone_verification_page')) { return { action: 'done', @@ -6096,6 +6138,14 @@ async function step6_login(payload) { const snapshot = normalizeStep6Snapshot(await waitForKnownLoginAuthState(15000)); + if (snapshot.state === 'logged_in_home') { + log('检测到 ChatGPT 已登录态,登录阶段按成功处理。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); + return createStep6SuccessResult(snapshot, { + via: 'already_logged_in_home', + loginVerificationRequestedAt: null, + }); + } + if (snapshot.state === 'verification_page' || snapshot.state === 'phone_verification_page') { log('认证页已在登录验证码页,开始确认页面是否稳定。', 'info', { step: visibleStep, stepKey: 'oauth-login' }); return finalizeStep6VerificationReady({ diff --git a/sidepanel/hotmail-manager.js b/sidepanel/hotmail-manager.js index 2492730..19ae8e4 100644 --- a/sidepanel/hotmail-manager.js +++ b/sidepanel/hotmail-manager.js @@ -280,6 +280,7 @@ + ${account.used ? `` : ''} @@ -449,6 +450,7 @@ actionInFlight = true; actionButton.disabled = true; + const originalActionButtonText = actionButton.textContent; try { if (action === 'copy-email') { @@ -505,6 +507,17 @@ } else { helpers.showToast('当前没有可读取的最新邮件。', 'warn', 2600); } + } else if (action === 'login-import-sub2api') { + if (!targetAccount) throw new Error('未找到目标 Hotmail 账号。'); + if (!targetAccount.used) throw new Error('只有已用账号可以执行登录并导入。'); + if (typeof helpers.loginAndImportHotmailAccountToSub2Api !== 'function') { + throw new Error('登录并导入能力未接入,请刷新扩展后重试。'); + } + actionButton.textContent = '登录中...'; + const result = await helpers.loginAndImportHotmailAccountToSub2Api(accountId); + if (result?.account) { + applyHotmailAccountMutation(result.account, { preserveCurrentSelection: true }); + } } else if (action === 'delete') { const confirmed = await helpers.openConfirmModal({ title: '删除账号', @@ -528,6 +541,7 @@ } finally { actionInFlight = false; actionButton.disabled = false; + actionButton.textContent = originalActionButtonText; } } @@ -567,6 +581,24 @@ } }); + dom.btnBatchLoginImportSub2Api?.addEventListener('click', async () => { + if (actionInFlight) return; + if (typeof helpers.batchLoginAndImportHotmailAccountsToSub2Api !== 'function') { + helpers.showToast('批量登录并导入能力未接入,请刷新扩展后重试。', 'error'); + return; + } + actionInFlight = true; + dom.btnBatchLoginImportSub2Api.disabled = true; + try { + await helpers.batchLoginAndImportHotmailAccountsToSub2Api(); + } catch (err) { + helpers.showToast(err.message, 'error'); + } finally { + actionInFlight = false; + dom.btnBatchLoginImportSub2Api.disabled = false; + } + }); + dom.btnDeleteAllHotmailAccounts?.addEventListener('click', async () => { if (actionInFlight) return; actionInFlight = true; diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 59fb73f..be1fcae 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -930,12 +930,18 @@ + +
+ 批量邮箱 + +
接码模式
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 377e615..ebcef5d 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -361,6 +361,8 @@ const inputHotmailEmail = document.getElementById('input-hotmail-email'); const inputHotmailClientId = document.getElementById('input-hotmail-client-id'); const inputHotmailPassword = document.getElementById('input-hotmail-password'); const inputHotmailRefreshToken = document.getElementById('input-hotmail-refresh-token'); +const rowHotmailBatchEmailList = document.getElementById('row-hotmail-batch-email-list'); +const inputHotmailBatchEmailList = document.getElementById('input-hotmail-batch-email-list'); const inputHotmailImport = document.getElementById('input-hotmail-import'); const inputHotmailSearch = document.getElementById('input-hotmail-search'); const selectHotmailFilter = document.getElementById('select-hotmail-filter'); @@ -369,6 +371,7 @@ const btnImportHotmailAccounts = document.getElementById('btn-import-hotmail-acc const btnToggleHotmailForm = document.getElementById('btn-toggle-hotmail-form'); const btnHotmailUsageGuide = document.getElementById('btn-hotmail-usage-guide'); const btnClearUsedHotmailAccounts = document.getElementById('btn-clear-used-hotmail-accounts'); +const btnBatchLoginImportSub2Api = document.getElementById('btn-batch-login-import-sub2api'); const btnDeleteAllHotmailAccounts = document.getElementById('btn-delete-all-hotmail-accounts'); const btnToggleHotmailList = document.getElementById('btn-toggle-hotmail-list'); const hotmailFormShell = document.getElementById('hotmail-form-shell'); @@ -12590,6 +12593,141 @@ async function copyTextToClipboard(text) { await navigator.clipboard.writeText(value); } +function validateSub2ApiLoginImportConfig() { + const fail = (input, message) => { + input?.classList?.add('is-invalid'); + input?.focus?.(); + return { valid: false, message }; + }; + [inputSub2ApiUrl, inputSub2ApiEmail, inputSub2ApiPassword, inputSub2ApiGroup].forEach((input) => { + input?.classList?.remove('is-invalid'); + }); + + if (getSelectedPanelMode() !== 'sub2api') { + selectPanelMode?.focus?.(); + return { valid: false, message: '请先将“导出至”切换为 SUB2API。' }; + } + if (!String(inputSub2ApiUrl?.value || '').trim()) { + return fail(inputSub2ApiUrl, '请先填写 SUB2API URL。'); + } + if (!String(inputSub2ApiEmail?.value || '').trim()) { + return fail(inputSub2ApiEmail, '请先填写 SUB2API 登录邮箱。'); + } + if (!String(inputSub2ApiPassword?.value || '')) { + return fail(inputSub2ApiPassword, '请先填写 SUB2API 登录密码。'); + } + if (!getSelectedSub2ApiGroupName()) { + return fail(inputSub2ApiGroup, '请先选择或填写 SUB2API 分组。'); + } + return { valid: true }; +} + +async function loginAndImportHotmailAccountToSub2Api(accountId) { + const validation = validateSub2ApiLoginImportConfig(); + if (!validation.valid) { + throw new Error(validation.message || 'SUB2API 配置不完整。'); + } + if (!(await maybeTakeoverAutoRun('登录并导入 SUB2API'))) { + return null; + } + await persistCurrentSettingsForAction(); + showToast('开始自动登录该账号并导入 SUB2API,请查看执行日志。', 'info', 2400); + const response = await chrome.runtime.sendMessage({ + type: 'LOGIN_HOTMAIL_AND_IMPORT_SUB2API', + source: 'sidepanel', + payload: { accountId }, + }); + if (response?.error) { + throw new Error(response.error); + } + showToast('登录并导入 SUB2API 已完成。', 'success', 2600); + return response || null; +} + +async function batchLoginAndImportHotmailAccountsToSub2Api() { + const validation = validateSub2ApiLoginImportConfig(); + if (!validation.valid) { + showToast(validation.message || 'SUB2API 配置不完整。', 'warn'); + return; + } + + const rawText = String(inputHotmailBatchEmailList?.value || '').trim(); + if (!rawText) { + showToast('请先在“批量邮箱”文本框中粘贴要处理的邮箱列表(每行一个)。', 'warn'); + inputHotmailBatchEmailList?.focus?.(); + return; + } + const targetEmails = rawText.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean); + const targetEmailSet = new Set(targetEmails.map((e) => e.toLowerCase())); + + const allHotmailAccounts = getHotmailAccounts(); + const matchedAccounts = allHotmailAccounts.filter((a) => { + if (!a.email || !a.password) return false; + return targetEmailSet.has(String(a.email).trim().toLowerCase()); + }); + + if (!matchedAccounts.length) { + showToast('批量邮箱列表中的邮箱未匹配到任何 Hotmail 账号(需要有邮箱和密码)。', 'warn'); + return; + } + + const notFound = targetEmails.filter((e) => !allHotmailAccounts.some((a) => String(a.email || '').trim().toLowerCase() === e.toLowerCase())); + if (notFound.length) { + showToast(`以下邮箱未在 Hotmail 账号池中找到:${notFound.join('、')}`, 'warn', 4000); + } + + if (!(await maybeTakeoverAutoRun('批量登录并导入 SUB2API'))) { + return; + } + await persistCurrentSettingsForAction(); + let successCount = 0; + let deletedCount = 0; + let failedCount = 0; + const failedList = []; + const deletedList = []; + showToast(`开始批量登录并导入 ${matchedAccounts.length} 个账号,请查看执行日志。`, 'info', 3200); + for (let i = 0; i < matchedAccounts.length; i++) { + const account = matchedAccounts[i]; + try { + const response = await chrome.runtime.sendMessage({ + type: 'LOGIN_HOTMAIL_AND_IMPORT_SUB2API', + source: 'sidepanel', + payload: { accountId: account.id }, + }); + if (response?.error) { + const msg = String(response.error || ''); + if (/ACCOUNT_DELETED/i.test(msg)) { + deletedCount++; + deletedList.push(account.email); + } else { + throw new Error(response.error); + } + } else { + successCount++; + } + } catch (err) { + const msg = String(err?.message || ''); + if (/ACCOUNT_DELETED|账号.*已删除|账号.*不存在/i.test(msg)) { + deletedCount++; + deletedList.push(account.email); + } else { + failedCount++; + failedList.push(`${account.email}: ${msg}`); + } + } + } + let summary = `批量完成:成功 ${successCount}`; + if (deletedCount > 0) summary += `,已删除/无效 ${deletedCount}`; + if (failedCount > 0) summary += `,失败 ${failedCount}`; + showToast(summary, failedCount > 0 ? 'warn' : 'success', 5000); + if (deletedList.length) { + showToast(`已删除/无效账号:${deletedList.join('、')}`, 'info', 4000); + } + if (failedList.length) { + showToast(`失败账号:${failedList.join(';')}`, 'error', 5000); + } +} + const hotmailManager = window.SidepanelHotmailManager?.createHotmailManager({ state: { getLatestState: () => latestState, @@ -12598,6 +12736,7 @@ const hotmailManager = window.SidepanelHotmailManager?.createHotmailManager({ dom: { btnAddHotmailAccount, btnClearUsedHotmailAccounts, + btnBatchLoginImportSub2Api, btnDeleteAllHotmailAccounts, btnHotmailUsageGuide, btnImportHotmailAccounts, @@ -12621,6 +12760,8 @@ const hotmailManager = window.SidepanelHotmailManager?.createHotmailManager({ escapeHtml, getCurrentHotmailEmail, getHotmailAccounts, + loginAndImportHotmailAccountToSub2Api, + batchLoginAndImportHotmailAccountsToSub2Api, openConfirmModal, showToast, }, From 04d7bb05e13b84d478251900078dbc70690a1c39 Mon Sep 17 00:00:00 2001 From: yanbing Date: Sat, 23 May 2026 12:19:36 +0800 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E6=96=87?= =?UTF-8?q?=E6=A1=88=E8=A1=A8=E8=BE=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background/message-router.js | 4 ++-- sidepanel/hotmail-manager.js | 10 +++++----- sidepanel/sidepanel.html | 6 +++--- sidepanel/sidepanel.js | 14 +++++++------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/background/message-router.js b/background/message-router.js index 6ab8853..f6fadb9 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -1780,10 +1780,10 @@ clearStopRequest(); if (message.source === 'sidepanel') { await lockAutomationWindowFromMessage(message, sender); - await ensureManualInteractionAllowed('登录并导入 SUB2API'); + await ensureManualInteractionAllowed('重新登录导入 SUB2API'); } if (typeof loginHotmailAndImportSub2Api !== 'function') { - throw new Error('登录并导入 SUB2API 能力未接入。'); + throw new Error('重新登录导入 SUB2API 能力未接入。'); } const result = await loginHotmailAndImportSub2Api(String(message.payload?.accountId || '')); return { ok: true, ...result }; diff --git a/sidepanel/hotmail-manager.js b/sidepanel/hotmail-manager.js index 19ae8e4..2eab40f 100644 --- a/sidepanel/hotmail-manager.js +++ b/sidepanel/hotmail-manager.js @@ -280,7 +280,7 @@ - ${account.used ? `` : ''} + ${account.used ? `` : ''}
@@ -509,11 +509,11 @@ } } else if (action === 'login-import-sub2api') { if (!targetAccount) throw new Error('未找到目标 Hotmail 账号。'); - if (!targetAccount.used) throw new Error('只有已用账号可以执行登录并导入。'); + if (!targetAccount.used) throw new Error('只有已用账号可以执行重新登录导入。'); if (typeof helpers.loginAndImportHotmailAccountToSub2Api !== 'function') { - throw new Error('登录并导入能力未接入,请刷新扩展后重试。'); + throw new Error('重新登录导入能力未接入,请刷新扩展后重试。'); } - actionButton.textContent = '登录中...'; + actionButton.textContent = '重新登录中...'; const result = await helpers.loginAndImportHotmailAccountToSub2Api(accountId); if (result?.account) { applyHotmailAccountMutation(result.account, { preserveCurrentSelection: true }); @@ -584,7 +584,7 @@ dom.btnBatchLoginImportSub2Api?.addEventListener('click', async () => { if (actionInFlight) return; if (typeof helpers.batchLoginAndImportHotmailAccountsToSub2Api !== 'function') { - helpers.showToast('批量登录并导入能力未接入,请刷新扩展后重试。', 'error'); + helpers.showToast('批量重新登录导入能力未接入,请刷新扩展后重试。', 'error'); return; } actionInFlight = true; diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index be1fcae..23e1e96 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -930,7 +930,7 @@ - + @@ -263,7 +324,43 @@ item.querySelector('[data-action="copy-phone"]')?.addEventListener('click', async () => { await helpers.copyTextToClipboard?.(entry.phone || ''); - helpers.showToast?.('号码已复制', 'success', 1600); + helpers.showToast?.(copySuccessText, 'success', 1600); + }); + + item.querySelector('[data-action="disable"]')?.addEventListener('click', async () => { + await patchPool(({ entries: entriesList, usage }) => { + const nextUsage = { ...usage }; + nextUsage[entry.key] = { + ...(nextUsage[entry.key] || {}), + useCount: Math.max(0, Number(nextUsage[entry.key]?.useCount) || 0), + usedAt: Math.max(0, Number(nextUsage[entry.key]?.usedAt) || 0), + lastAttemptAt: Math.max(0, Number(nextUsage[entry.key]?.lastAttemptAt) || 0), + lastError: normalizeText(nextUsage[entry.key]?.lastError), + enabled: false, + disabledReason: '手动禁用', + disabledAt: Date.now(), + failureCount: Math.max(0, Math.floor(Number(nextUsage[entry.key]?.failureCount) || 0)), + }; + return { entries: entriesList, usage: nextUsage }; + }); + }); + + item.querySelector('[data-action="enable"]')?.addEventListener('click', async () => { + await patchPool(({ entries: entriesList, usage }) => { + const nextUsage = { ...usage }; + nextUsage[entry.key] = { + ...(nextUsage[entry.key] || {}), + useCount: Math.max(0, Number(nextUsage[entry.key]?.useCount) || 0), + usedAt: Math.max(0, Number(nextUsage[entry.key]?.usedAt) || 0), + lastAttemptAt: Math.max(0, Number(nextUsage[entry.key]?.lastAttemptAt) || 0), + lastError: normalizeText(nextUsage[entry.key]?.lastError), + enabled: true, + disabledReason: '', + disabledAt: 0, + failureCount: 0, + }; + return { entries: entriesList, usage: nextUsage }; + }); }); item.querySelector('[data-action="increment-usage"]')?.addEventListener('click', async () => { @@ -275,6 +372,10 @@ usedAt: Date.now(), lastAttemptAt: Math.max(0, Number(nextUsage[entry.key]?.lastAttemptAt) || 0), lastError: normalizeText(nextUsage[entry.key]?.lastError), + enabled: nextUsage[entry.key]?.enabled !== false, + disabledReason: normalizeText(nextUsage[entry.key]?.disabledReason), + disabledAt: Math.max(0, Number(nextUsage[entry.key]?.disabledAt) || 0), + failureCount: Math.max(0, Math.floor(Number(nextUsage[entry.key]?.failureCount) || 0)), }; return { entries: entriesList, usage: nextUsage }; }); @@ -288,6 +389,11 @@ useCount: 0, usedAt: 0, lastError: '', + lastAttemptAt: 0, + enabled: nextUsage[entry.key]?.enabled !== false, + disabledReason: normalizeText(nextUsage[entry.key]?.disabledReason), + disabledAt: Math.max(0, Number(nextUsage[entry.key]?.disabledAt) || 0), + failureCount: 0, }; return { entries: entriesList, usage: nextUsage }; }); @@ -295,7 +401,7 @@ item.querySelector('[data-action="delete"]')?.addEventListener('click', async () => { const confirmed = await helpers.openConfirmModal?.({ - title: '删除 PayPal 号码', + title: deleteTitle, message: `确认删除 ${entry.phone} 吗?此操作不可撤销。`, confirmLabel: '确认删除', confirmVariant: 'btn-danger', @@ -329,7 +435,7 @@ const nextUsage = normalizeUsage(result.usage || previousUsage); const nextText = entriesToText(nextEntries); - setLoading(true, '正在更新 PayPal 号池...'); + setLoading(true, updateLoadingText); state.setText?.(nextText); state.setUsage?.(nextUsage); render(nextEntries); @@ -340,7 +446,7 @@ state.setText?.(previousText); state.setUsage?.(previousUsage); render(previousEntries); - helpers.showToast?.(`更新 PayPal 号池失败:${error.message}`, 'error'); + helpers.showToast?.(`${updateFailedPrefix}:${error.message}`, 'error'); return false; } finally { setLoading(false); @@ -350,7 +456,7 @@ async function importEntries() { const text = normalizePoolText(dom.inputHostedSmsPoolImport?.value || ''); if (!text) { - helpers.showToast?.('请先粘贴 PayPal 号码,每行一个号码和验证码接口。', 'warn'); + helpers.showToast?.(importEmptyWarning, 'warn'); return; } @@ -392,18 +498,30 @@ async function clearUsedState() { const confirmed = await helpers.openConfirmModal?.({ - title: '清空使用次数', - message: '确认清空 PayPal 号池的使用次数吗?号码本身会保留。', + title: clearUsageTitle, + message: clearUsageMessage, confirmLabel: '清空次数', }); if (!confirmed) return; - await patchPool(({ entries }) => ({ entries, usage: {} })); + await patchPool(({ entries, usage }) => ({ + entries, + usage: Object.fromEntries(Object.entries(normalizeUsage(usage)).map(([key, item]) => [key, { + enabled: item.enabled !== false, + disabledReason: normalizeText(item.disabledReason), + disabledAt: Math.max(0, Number(item.disabledAt) || 0), + useCount: 0, + usedAt: 0, + lastAttemptAt: 0, + lastError: '', + failureCount: 0, + }])), + })); } async function deleteAll() { const confirmed = await helpers.openConfirmModal?.({ - title: '删除 PayPal 号池', - message: '确认删除当前全部 PayPal 号码吗?此操作不可撤销。', + title: deleteAllTitle, + message: deleteAllMessage, confirmLabel: '确认删除', confirmVariant: 'btn-danger', }); @@ -416,7 +534,7 @@ if (state.isVisible && !state.isVisible()) { return; } - if (!silent) setLoading(true, '正在刷新 PayPal 号池...'); + if (!silent) setLoading(true, refreshLoadingText); render(parseEntries(state.getText?.())); if (!silent) setLoading(false); } @@ -464,7 +582,7 @@ if (dom.selectHostedSmsPoolFilter) dom.selectHostedSmsPoolFilter.value = 'all'; if (dom.hostedSmsPoolList) dom.hostedSmsPoolList.innerHTML = ''; if (dom.hostedSmsPoolSummary) { - dom.hostedSmsPoolSummary.textContent = '导入 PayPal 接码号码,每行一个号码和验证码接口。'; + dom.hostedSmsPoolSummary.textContent = emptySummary; } updateControls([]); } diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css index 2c74a16..51270cf 100644 --- a/sidepanel/sidepanel.css +++ b/sidepanel/sidepanel.css @@ -5,8 +5,7 @@ Themes: Light (default) + Dark (toggle) ============================================================ */ -#ip-proxy-section, -#phone-verification-section { +#ip-proxy-section { display: none !important; } @@ -1148,6 +1147,30 @@ header { min-width: 0; } +.hosted-checkout-resend-settings-inline { + flex-wrap: wrap; + justify-content: flex-start; + row-gap: 6px; +} + +#row-hosted-checkout-resend-settings > .data-label { + width: 76px; + text-transform: none; + letter-spacing: 0.02em; +} + +.hosted-checkout-setting-field, +.hosted-checkout-setting-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + flex: 0 0 auto; +} + +.hosted-checkout-setting-toggle .toggle-switch { + flex: 0 0 auto; +} + .plus-checkout-conversion-proxy-test-inline { flex: 1; min-width: 0; @@ -1541,6 +1564,15 @@ header { justify-content: flex-end; } +.hosted-sms-pool-import-actions { + justify-content: space-between; + flex-wrap: wrap; +} + +.hosted-sms-pool-auto-disable-toggle { + margin-left: auto; +} + .hosted-sms-pool-phone { display: flex; align-items: center; @@ -1565,6 +1597,15 @@ header { font-weight: 600; } +.luckmail-item.is-disabled { + opacity: 0.72; +} + +.hosted-sms-pool-disabled-reason { + color: var(--red); + font-size: 11px; +} + .luckmail-tag { display: inline-flex; align-items: center; @@ -2180,6 +2221,27 @@ header { white-space: nowrap; } +.auto-run-retry-setting-pair { + justify-content: flex-start; + flex-wrap: wrap; + row-gap: 8px; +} + +.auto-run-retry-setting-pair .setting-group-primary { + flex: 0 0 auto; + min-width: 0; +} + +.auto-run-retry-setting-pair .auto-run-retry-non-free-trial-setting, +.auto-run-retry-setting-pair .auto-run-retry-paypal-callback-setting { + flex: 0 0 auto; +} + +.auto-run-retry-setting-pair .auto-step-delay-setting { + flex: 0 0 auto; + min-width: 0; +} + .auto-run-delay-setting { margin-left: auto !important; } diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 59fb73f..7363733 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -4,7 +4,7 @@ - GuJumpgate V0.1.4 + GuJumpgate V0.1.5
+ aria-label="打开 GitHub Releases 页面" title="打开 GitHub Releases 页面">GuJumpgate V0.1.5
@@ -130,6 +130,8 @@
@@ -367,14 +369,6 @@ 未获取
-