diff --git a/README.md b/README.md index e64c46b..5fd471a 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,8 @@ Step 3 使用的注册邮箱。 根据 `Mail` 配置,轮询邮箱并提取 6 位验证码。 +进入邮箱轮询前,脚本会先确认认证页是否已经进入验证码页面;如果密码页出现 `糟糕,出错了 / Operation timed out` 并带有 `重试` 按钮,会先自动点击 `重试`、回到密码页重新提交,再继续等待验证码页面。 + 支持: - `content/qq-mail.js` diff --git a/background.js b/background.js index b396eab..60f5115 100644 --- a/background.js +++ b/background.js @@ -1583,6 +1583,28 @@ async function resolveVerificationStep(step, state, mail) { async function executeStep4(state) { const mail = getMailConfig(state); if (mail.error) throw new Error(mail.error); + const signupTabId = await getTabId('signup-page'); + if (!signupTabId) { + throw new Error('认证页面标签页已关闭,无法继续步骤 4。'); + } + + await chrome.tabs.update(signupTabId, { active: true }); + await addLog('步骤 4:正在确认注册验证码页面是否就绪,必要时自动恢复密码页超时报错...'); + const prepareResult = await sendToContentScript('signup-page', { + type: 'PREPARE_SIGNUP_VERIFICATION', + step: 4, + source: 'background', + payload: { password: state.password || state.customPassword || '' }, + }); + + if (prepareResult && prepareResult.error) { + throw new Error(prepareResult.error); + } + if (prepareResult?.alreadyVerified) { + await completeStepFromBackground(4, {}); + return; + } + await addLog(`步骤 4:正在打开${mail.label}...`); // For mail tabs, only create if not alive — don't navigate (preserves login session) diff --git a/content/signup-page.js b/content/signup-page.js index 00cffe6..ed383e2 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -10,6 +10,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { || message.type === 'FILL_CODE' || message.type === 'STEP8_FIND_AND_CLICK' || message.type === 'PREPARE_LOGIN_CODE' + || message.type === 'PREPARE_SIGNUP_VERIFICATION' || message.type === 'RESEND_VERIFICATION_CODE' ) { resetStopState(); @@ -49,6 +50,8 @@ async function handleCommand(message) { case 'FILL_CODE': // Step 4 = signup code, Step 7 = login code (same handler) return await fillVerificationCode(message.step, message.payload); + case 'PREPARE_SIGNUP_VERIFICATION': + return await prepareSignupVerificationFlow(message.payload); case 'PREPARE_LOGIN_CODE': return await prepareLoginCodeFlow(); case 'RESEND_VERIFICATION_CODE': @@ -353,6 +356,8 @@ const INVALID_VERIFICATION_CODE_PATTERN = /代码不正确|验证码不正确| const VERIFICATION_PAGE_PATTERN = /检查您的收件箱|输入我们刚刚向|重新发送电子邮件|重新发送验证码|验证码|代码不正确|email\s+verification/i; const OAUTH_CONSENT_PAGE_PATTERN = /使用\s*ChatGPT\s*登录到\s*Codex|login\s+to\s+codex|log\s+in\s+to\s+codex|authorize|授权/i; const ADD_PHONE_PAGE_PATTERN = /add[\s-]*phone|添加手机号|手机号码|手机号|phone\s+number|telephone/i; +const SIGNUP_PASSWORD_ERROR_TITLE_PATTERN = /糟糕,出错了|something\s+went\s+wrong|oops/i; +const SIGNUP_PASSWORD_ERROR_DETAIL_PATTERN = /operation\s+timed\s+out|timed\s+out|请求超时|操作超时/i; function getVerificationErrorText() { const messages = []; @@ -443,6 +448,119 @@ function isStep8Ready() { return OAUTH_CONSENT_PAGE_PATTERN.test(getPageTextSnapshot()); } +function isSignupPasswordPage() { + return /\/create-account\/password(?:[/?#]|$)/i.test(location.pathname || ''); +} + +function getSignupPasswordInput() { + const input = document.querySelector('input[type="password"]'); + return input && isVisibleElement(input) ? input : null; +} + +function getSignupPasswordSubmitButton() { + const direct = document.querySelector('button[type="submit"]'); + if (direct && isVisibleElement(direct) && isActionEnabled(direct)) { + return direct; + } + + const candidates = document.querySelectorAll('button, [role="button"]'); + return Array.from(candidates).find((el) => { + if (!isVisibleElement(el) || !isActionEnabled(el)) return false; + const text = getActionText(el); + return /继续|continue|submit|创建|create/i.test(text); + }) || null; +} + +function getSignupRetryButton() { + const direct = document.querySelector('button[data-dd-action-name="Try again"]'); + if (direct && isVisibleElement(direct) && isActionEnabled(direct)) { + return direct; + } + + const candidates = document.querySelectorAll('button, [role="button"]'); + return Array.from(candidates).find((el) => { + if (!isVisibleElement(el) || !isActionEnabled(el)) return false; + const text = getActionText(el); + return /重试|try\s+again/i.test(text); + }) || null; +} + +function isSignupPasswordErrorPage() { + if (!isSignupPasswordPage()) return false; + const text = getPageTextSnapshot(); + return Boolean( + getSignupRetryButton() + && (SIGNUP_PASSWORD_ERROR_TITLE_PATTERN.test(text) + || SIGNUP_PASSWORD_ERROR_DETAIL_PATTERN.test(text) + || SIGNUP_PASSWORD_ERROR_TITLE_PATTERN.test(document.title || '')) + ); +} + +async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { + const { password } = payload; + const start = Date.now(); + let retried = 0; + let lastSubmitAt = 0; + + while (Date.now() - start < timeout) { + throwIfStopped(); + + if (isStep5Ready()) { + log('步骤 4:页面已进入验证码后的下一阶段,本步骤按已完成处理。', 'ok'); + return { ready: true, alreadyVerified: true, retried }; + } + + if (isVerificationPageStillVisible()) { + log(`步骤 4:验证码页面已就绪${retried ? `(期间自动重试 ${retried} 次)` : ''}。`, 'ok'); + return { ready: true, retried }; + } + + if (isSignupPasswordErrorPage()) { + const retryBtn = getSignupRetryButton(); + if (!retryBtn) { + throw new Error('检测到密码页超时报错,但未找到可点击的“重试”按钮。URL: ' + location.href); + } + retried += 1; + log(`步骤 4:检测到密码页超时报错,正在点击“重试”(第 ${retried} 次)...`, 'warn'); + await humanPause(350, 900); + simulateClick(retryBtn); + await sleep(1500); + continue; + } + + const passwordInput = getSignupPasswordInput(); + if (passwordInput) { + if (!password) { + throw new Error('当前回到了密码页,但没有可用密码,无法自动重新提交。'); + } + + if ((passwordInput.value || '') !== password) { + log('步骤 4:已回到密码页,正在重新填写密码...', 'warn'); + await humanPause(450, 1100); + fillInput(passwordInput, password); + } + + const submitBtn = getSignupPasswordSubmitButton(); + if (!submitBtn) { + throw new Error('密码页存在,但未找到“继续”提交按钮。URL: ' + location.href); + } + + if (Date.now() - lastSubmitAt > 1800) { + log('步骤 4:正在重新提交密码,等待验证码页面...', 'warn'); + lastSubmitAt = Date.now(); + await humanPause(350, 900); + simulateClick(submitBtn); + await sleep(1800); + continue; + } + } + + await sleep(200); + } + + throw new Error('等待注册验证码页面就绪超时。URL: ' + location.href); +} + async function waitForVerificationSubmitOutcome(step, timeout) { const resolvedTimeout = timeout ?? (step === 7 ? 30000 : 12000);