// flows/openai/content/openai-auth.js — Content script for ChatGPT signup entry + OpenAI auth pages // Injected on: auth0.openai.com, auth.openai.com, accounts.openai.com // Dynamically injected on: chatgpt.com console.log('[MultiPage:openai-auth] Content script loaded on', location.href); const OPENAI_AUTH_LISTENER_SENTINEL = 'data-multipage-openai-auth-listener'; function getOperationDelayRunner() { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate : async (_metadata, operation) => operation(); } if (document.documentElement.getAttribute(OPENAI_AUTH_LISTENER_SENTINEL) !== '1') { document.documentElement.setAttribute(OPENAI_AUTH_LISTENER_SENTINEL, '1'); // Listen for commands from Background chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if ( message.type === 'EXECUTE_NODE' || message.type === 'FILL_CODE' || message.type === 'STEP8_FIND_AND_CLICK' || message.type === 'STEP8_GET_STATE' || message.type === 'STEP8_TRIGGER_CONTINUE' || message.type === 'GET_LOGIN_AUTH_STATE' || message.type === 'SUBMIT_ADD_EMAIL' || message.type === 'GET_STEP5_SUBMIT_STATE' || message.type === 'PREPARE_SIGNUP_VERIFICATION' || message.type === 'RECOVER_AUTH_RETRY_PAGE' || message.type === 'RECOVER_STEP5_SUBMIT_RETRY_PAGE' || message.type === 'RESEND_VERIFICATION_CODE' || message.type === 'SUBMIT_PHONE_NUMBER' || message.type === 'SUBMIT_PHONE_VERIFICATION_CODE' || message.type === 'RESEND_PHONE_VERIFICATION_CODE' || message.type === 'CHECK_PHONE_RESEND_ERROR' || message.type === 'RETURN_TO_ADD_PHONE' || message.type === 'ENSURE_SIGNUP_ENTRY_READY' || message.type === 'ENSURE_SIGNUP_PHONE_ENTRY_READY' || message.type === 'ENSURE_SIGNUP_PASSWORD_PAGE_READY' ) { resetStopState(); handleCommand(message).then((result) => { sendResponse({ ok: true, ...(result || {}) }); }).catch(err => { const reportedStep = Number(message.payload?.visibleStep) || message.step; const reportedNodeId = resolveCommandNodeId(message); if (isStopError(err)) { if (reportedStep) { log(`步骤 ${reportedStep || 8}:已被用户停止。`, 'warn'); } sendResponse({ stopped: true, error: err.message }); return; } if (message.type === 'STEP8_FIND_AND_CLICK') { log(err.message, 'error', { step: reportedStep || 9, stepKey: 'confirm-oauth' }); sendResponse({ error: err.message }); return; } if (reportedStep) { reportError(reportedNodeId || reportedStep, err.message); } sendResponse({ error: err.message }); }); return true; } }); } else { console.log('[MultiPage:openai-auth] 消息监听已存在,跳过重复注册'); } const OPENAI_AUTH_NODE_HANDLERS = Object.freeze({ 'submit-signup-email': (payload) => step2_clickRegister(payload), 'fill-password': (payload) => step3_fillEmailPassword(payload), 'fill-profile': (payload) => step5_fillNameBirthday(payload), 'oauth-login': (payload) => step6_login(payload), 'relogin-bound-email': (payload) => step6_login(payload), 'confirm-oauth': (_payload) => step8_findAndClick(), }); function resolveCommandNodeId(message = {}) { const directNodeId = String(message.nodeId || message.payload?.nodeId || '').trim(); if (directNodeId) { return directNodeId; } const visibleStep = Number(message.payload?.visibleStep || message.step) || 0; if (visibleStep === 4) return 'fetch-signup-code'; if (message.type === 'FILL_CODE' && (visibleStep === 12 || visibleStep === 15)) { return 'fetch-bound-email-login-code'; } if ( ( message.type === 'SUBMIT_PHONE_NUMBER' || message.type === 'SUBMIT_PHONE_VERIFICATION_CODE' || message.type === 'RESEND_PHONE_VERIFICATION_CODE' || message.type === 'CHECK_PHONE_RESEND_ERROR' || message.type === 'RETURN_TO_ADD_PHONE' ) && (visibleStep === 13 || visibleStep === 16) ) { return 'post-bound-email-phone-verification'; } if (visibleStep === 8 || visibleStep === 11) return 'fetch-login-code'; if (visibleStep === 9 || visibleStep === 12) return 'post-login-phone-verification'; if (visibleStep === 10 || visibleStep === 13) return 'confirm-oauth'; if (visibleStep === 16) return 'confirm-oauth'; if (visibleStep === 14 || visibleStep === 15 || visibleStep === 17) return 'platform-verify'; if (visibleStep === 7) return 'oauth-login'; if (visibleStep === 5) return 'fill-profile'; if (visibleStep === 3) return 'fill-password'; if (visibleStep === 2) return 'submit-signup-email'; return ''; } async function handleCommand(message) { switch (message.type) { case 'EXECUTE_NODE': { const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim(); const handler = OPENAI_AUTH_NODE_HANDLERS[nodeId]; if (!handler) { throw new Error(`openai-auth.js 不处理节点 ${nodeId}`); } return await handler(message.payload || {}); } case 'FILL_CODE': // Step 4 = signup code, Step 7 = login code (same handler) return await fillVerificationCode(message.step, message.payload); case 'GET_LOGIN_AUTH_STATE': return serializeLoginAuthState(inspectLoginAuthState()); case 'SUBMIT_ADD_EMAIL': return await submitAddEmailAndContinue(message.payload); case 'GET_STEP5_SUBMIT_STATE': return getStep5SubmitState(); case 'PREPARE_SIGNUP_VERIFICATION': return await prepareSignupVerificationFlow(message.payload); case 'RECOVER_AUTH_RETRY_PAGE': return await recoverCurrentAuthRetryPage(message.payload); case 'RECOVER_STEP5_SUBMIT_RETRY_PAGE': return await recoverStep5SubmitRetryPage(message.payload); case 'RESEND_VERIFICATION_CODE': return await resendVerificationCode(message.step); case 'SUBMIT_PHONE_NUMBER': return await phoneAuthHelpers.submitPhoneNumber(message.payload); case 'SUBMIT_PHONE_VERIFICATION_CODE': if (message.payload?.purpose === 'signup') { return await fillVerificationCode(message.step || 4, message.payload); } if (message.payload?.purpose === 'login') { return await fillVerificationCode(message.step || 8, message.payload); } return await submitPhoneVerificationCodeWithProfileFallback(message.payload); case 'RESEND_PHONE_VERIFICATION_CODE': return await phoneAuthHelpers.resendPhoneVerificationCode(undefined, message.payload || {}); case 'CHECK_PHONE_RESEND_ERROR': return phoneAuthHelpers.checkPhoneResendError(); case 'RETURN_TO_ADD_PHONE': return await phoneAuthHelpers.returnToAddPhone(); case 'ENSURE_SIGNUP_ENTRY_READY': return await ensureSignupEntryReady(); case 'ENSURE_SIGNUP_PHONE_ENTRY_READY': return await ensureSignupPhoneEntryReady(); case 'ENSURE_SIGNUP_PASSWORD_PAGE_READY': return await ensureSignupPasswordPageReady(); case 'STEP8_FIND_AND_CLICK': return await step8_findAndClick(message.payload); case 'STEP8_GET_STATE': return getStep8State(); case 'STEP8_TRIGGER_CONTINUE': return await step8_triggerContinue(message.payload); } } function resolveVisibleStep(payload = {}, fallback = 0) { const step = Math.floor(Number(payload?.visibleStep) || 0); return step > 0 ? step : fallback; } function stepLog(step, message, level = 'info', stepKey = '') { return log(message, level, { step, stepKey }); } const VERIFICATION_CODE_INPUT_SELECTOR = [ 'input[name="code"]', 'input[name="otp"]', 'input[autocomplete="one-time-code"]', 'input[type="text"][maxlength="6"]', 'input[type="tel"][maxlength="6"]', 'input[aria-label*="code" i]', 'input[aria-label*="コード"]', 'input[aria-label*="認証"]', 'input[aria-label*="確認"]', 'input[placeholder*="code" i]', 'input[placeholder*="コード"]', 'input[placeholder*="認証"]', 'input[placeholder*="確認"]', 'input[inputmode="numeric"]', ].join(', '); const ONE_TIME_CODE_LOGIN_PATTERN = /使用一次性验证码登录|改用(?:一次性)?验证码(?:登录)?|使用验证码登录|一次性验证码|验证码登录|ワンタイム(?:コード|パスコード)|一回限りの?(?:コード|パスコード)|(?:認証|確認)?コード(?:で|を使用して)?(?:ログイン|サインイン)|one[-\s]*time\s*(?:passcode|password|code)|use\s+(?:a\s+)?one[-\s]*time\s*(?:passcode|password|code)(?:\s+instead)?|use\s+(?:a\s+)?code(?:\s+instead)?|sign\s+in\s+with\s+(?:email|code)|email\s+(?:me\s+)?(?:a\s+)?code/i; const LOGIN_ENTRY_ACTION_PATTERN = /(?:^|\b)(?:log\s*in|sign\s*in|continue\s+(?:with|using)\s+(?:email|chatgpt)|use\s+(?:an?\s+)?email|email\s+address)(?:\b|$)|登录|登陆|邮箱|电子邮件|ログイン|サインイン|メールアドレス|メール|電子メール/i; const LOGIN_SWITCH_TO_PHONE_PATTERN = /继续使用(?:手机|手机号|电话)(?:号码)?登录|改用(?:手机|手机号|电话)(?:号码)?登录|手机号登录|(?:電話番号|電話|携帯電話|携帯)(?:で|を使用して)?(?:続行|続ける|ログイン|サインイン)|(?:続行|続ける|使用|ログイン|サインイン).*(?:電話番号|電話|携帯電話|携帯)|continue\s+(?:with|using)\s+(?:a\s+)?phone(?:\s+number)?|use\s+(?:a\s+)?phone(?:\s+number)?(?:\s+instead)?|sign\s*in\s+with\s+(?:a\s+)?phone/i; const LOGIN_PHONE_ACTION_PATTERN = /手机|电话|phone|telephone|電話番号|電話|携帯電話|携帯/i; const LOGIN_PHONE_ENTRY_PAGE_PATTERN = /(?:\+\s*\(?\d{1,4}\)?\s*)?(?:手机号码|手机号|电话号码|電話番号|携帯電話番号|携帯番号)|(?:phone|mobile)\s+number|telephone/i; const LOGIN_MORE_OPTIONS_PATTERN = /更多(?:选项|登录方式|方式)|其他(?:登录方式|选项|方式)|显示更多|その他|他の(?:ログイン)?方法|別の(?:ログイン)?方法|もっと見る|オプション|more\s+(?:login\s+|sign[-\s]*in\s+)?options|other\s+(?:login\s+|sign[-\s]*in\s+)?(?:options|ways)|show\s+more/i; const LOGIN_EXTERNAL_IDP_PATTERN = /google|microsoft|apple|sso|single\s+sign[-\s]*on|企业|工作区|workspace/i; const LOGIN_CODE_ONLY_ACTION_PATTERN = /one[-\s]*time|passcode|use\s+(?:a\s+)?code|验证码|一次性|ワンタイム|パスコード|認証コード|確認コード/i; const RESEND_VERIFICATION_CODE_PATTERN = /重新发送(?:验证码)?|再次发送(?:验证码)?|重发(?:验证码)?|未收到(?:验证码|邮件)|(?:コード|メール|確認コード|認証コード)(?:を)?再送信|再送信|新しい(?:コード|確認コード|認証コード)|届かない|受信していません|resend(?:\s+code)?|send\s+(?:a\s+)?new\s+code|send\s+(?:it\s+)?again|request\s+(?:a\s+)?new\s+code|didn'?t\s+receive/i; const CHOOSE_ACCOUNT_PAGE_PATTERN = new RegExp([ String.raw`choose\s+(?:an?\s+)?account`, String.raw`select\s+(?:an?\s+)?account`, String.raw`welcome\s+back`, String.raw`\u9009\u62e9(?:\u4e00\u4e2a)?(?:\u5e10\u6237|\u8d26\u6237|\u8d26\u53f7)`, String.raw`\u6b22\u8fce\u56de\u6765`, String.raw`\u30a2\u30ab\u30a6\u30f3\u30c8.*(?:\u9078\u629e|\u9078\u3093\u3067)`, ].join('|'), 'i'); const CHOOSE_ACCOUNT_REMOVE_ACTION_PATTERN = /remove|delete|forget|close|dismiss|trash|\u79fb\u9664|\u5220\u9664|\u522a\u9664|\u524a\u9664|\u9589\u3058\u308b|\u524a\u9664/i; const CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN = new RegExp([ String.raw`another\s+account`, String.raw`different\s+account`, String.raw`other\s+account`, String.raw`use\s+(?:a\s+)?different`, String.raw`sign\s*in\s+(?:with\s+)?(?:another|different)`, String.raw`log\s*in\s+(?:with\s+)?(?:another|different)`, String.raw`\u5176\u4ed6(?:\u5e10\u6237|\u8d26\u6237|\u8d26\u53f7)`, String.raw`\u53e6\u4e00\u4e2a(?:\u5e10\u6237|\u8d26\u6237|\u8d26\u53f7)`, String.raw`\u5225\u306e\u30a2\u30ab\u30a6\u30f3\u30c8`, ].join('|'), 'i'); const CHOOSE_ACCOUNT_ACTION_SELECTOR = 'button, a, [role="button"], [role="link"], [tabindex]:not([tabindex="-1"])'; const CHOOSE_ACCOUNT_CARD_SELECTOR = [ '[data-testid*="account" i]', '[data-test-id*="account" i]', '[class*="account" i]', '[class*="user" i]', '[class*="card" i]', '[class*="option" i]', '[class*="select" i]', '[class*="list" i] > *', 'li', ].join(', '); const PHONE_RESEND_SERVER_ERROR_PREFIX = 'PHONE_RESEND_SERVER_ERROR::'; const CONTACT_VERIFICATION_SERVER_ERROR_PATTERN = /this\s+page\s+isn['’]?t\s+working|currently\s+unable\s+to\s+handle\s+this\s+request|http\s+error\s+500|500\s+internal\s+server\s+error/i; function isVisibleElement(el) { if (!el) return false; const style = window.getComputedStyle(el); const rect = el.getBoundingClientRect(); return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0; } function getVisibleSplitVerificationInputs() { return Array.from(document.querySelectorAll('input[maxlength="1"]')) .filter(isVisibleElement); } function getVerificationCodeTarget() { const splitInputs = getVisibleSplitVerificationInputs(); const codeInput = document.querySelector(VERIFICATION_CODE_INPUT_SELECTOR); if (codeInput && isVisibleElement(codeInput)) { const maxLength = Number(codeInput.getAttribute?.('maxlength') || codeInput.maxLength || 0); if (maxLength === 1 && splitInputs.length >= 6) { return { type: 'split', elements: splitInputs }; } return { type: 'single', element: codeInput }; } if (splitInputs.length >= 6) { return { type: 'split', elements: splitInputs }; } return null; } function getActionText(el) { return [ el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title'), ] .filter(Boolean) .join(' ') .replace(/\s+/g, ' ') .trim(); } function isActionEnabled(el) { return Boolean(el) && !el.disabled && el.getAttribute('aria-disabled') !== 'true'; } function findOneTimeCodeLoginTrigger() { const candidates = document.querySelectorAll( 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' ); for (const el of candidates) { if (!isVisibleElement(el)) continue; if (el.disabled || el.getAttribute('aria-disabled') === 'true') continue; const text = [ el.textContent, el.value, el.getAttribute('aria-label'), el.getAttribute('title'), ] .filter(Boolean) .join(' ') .replace(/\s+/g, ' ') .trim(); if (text && ONE_TIME_CODE_LOGIN_PATTERN.test(text)) { return el; } } return null; } function findResendVerificationCodeTrigger({ allowDisabled = false } = {}) { const candidates = document.querySelectorAll( 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' ); for (const el of candidates) { if (!isVisibleElement(el)) continue; if (!allowDisabled && !isActionEnabled(el)) continue; const text = getActionText(el); if (text && RESEND_VERIFICATION_CODE_PATTERN.test(text)) { return el; } } return null; } function isEmailVerificationPage() { return /\/email-verification(?:[/?#]|$)/i.test(location.pathname || ''); } function getContactVerificationServerErrorText() { const path = String(location?.pathname || ''); if (!/\/contact-verification(?:[/?#]|$)/i.test(path)) { return ''; } const text = String(getPageTextSnapshot?.() || document?.body?.textContent || '').replace(/\s+/g, ' ').trim(); const title = String(document?.title || '').replace(/\s+/g, ' ').trim(); const combined = `${title} ${text}`.trim(); if (!CONTACT_VERIFICATION_SERVER_ERROR_PATTERN.test(combined)) { return ''; } return combined || 'OpenAI contact-verification page returned HTTP ERROR 500 after resend.'; } function buildContactVerificationServerError(errorText = '') { const serverErrorPrefix = typeof PHONE_RESEND_SERVER_ERROR_PREFIX === 'string' ? PHONE_RESEND_SERVER_ERROR_PREFIX : 'PHONE_RESEND_SERVER_ERROR::'; const resolvedText = String(errorText || '').trim() || 'OpenAI contact-verification 页面返回 HTTP ERROR 500。'; return new Error( resolvedText.startsWith(serverErrorPrefix) ? resolvedText : `${serverErrorPrefix}${resolvedText}` ); } function throwIfContactVerificationServerError() { const serverErrorText = getContactVerificationServerErrorText(); if (serverErrorText) { throw buildContactVerificationServerError(serverErrorText); } } async function resendVerificationCode(step, timeout = 45000) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; if (step === 8) { await waitForLoginVerificationPageReady(10000, step); } const start = Date.now(); let action = null; let loggedWaiting = false; while (Date.now() - start < timeout) { throwIfStopped(); throwIfContactVerificationServerError(); // Check for 405 error page and recover by clicking "Try again" if (is405MethodNotAllowedPage()) { await handle405ResendError(step, timeout - (Date.now() - start)); // After recovery, loop back to find the resend button again loggedWaiting = false; continue; } action = findResendVerificationCodeTrigger({ allowDisabled: true }); if (action && isActionEnabled(action)) { log(`步骤 ${step}:重新发送验证码按钮已可用。`); await humanPause(350, 900); await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'click', label: 'resend-verification-code' }, async () => { simulateClick(action); }); await sleep(1200); // After clicking resend, check if 405 error appeared if (is405MethodNotAllowedPage()) { log(`步骤 ${step}:点击重新发送后出现 405 错误,正在恢复...`, 'warn'); await handle405ResendError(step, timeout - (Date.now() - start)); loggedWaiting = false; continue; } throwIfContactVerificationServerError(); return { resent: true, buttonText: getActionText(action), }; } if (action && !loggedWaiting) { loggedWaiting = true; log(`步骤 ${step}:正在等待重新发送验证码按钮变为可点击...`); } await sleep(250); } throwIfContactVerificationServerError(); throw new Error('无法点击重新发送验证码按钮。URL: ' + location.href); } function is405MethodNotAllowedPage() { const pageText = document.body?.textContent || ''; return AUTH_ROUTE_ERROR_PATTERN.test(pageText); } function getStep405RecoveryStateKey(step) { return `__MULTIPAGE_STEP_${Number(step) || '?'}_405_RECOVERY_COUNT__`; } function getStep405StorageScope() { if (typeof window !== 'undefined' && window) { return window; } if (typeof globalThis !== 'undefined' && globalThis) { return globalThis; } return {}; } function getStep405RecoveryLimit(step) { if (Number(step) !== 4) { return 0; } return typeof STEP4_405_RECOVERY_LIMIT !== 'undefined' ? STEP4_405_RECOVERY_LIMIT : 3; } function getStep405RecoveryErrorPrefix(step) { if (Number(step) !== 4) { return ''; } return typeof STEP4_405_RECOVERY_ERROR_PREFIX !== 'undefined' ? STEP4_405_RECOVERY_ERROR_PREFIX : 'STEP4_405_RECOVERY_LIMIT::'; } function getStep405RecoveryCount(step) { const key = getStep405RecoveryStateKey(step); let value = ''; try { if (typeof sessionStorage !== 'undefined' && sessionStorage?.getItem) { value = sessionStorage.getItem(key) || ''; } } catch {} if (!value) { value = getStep405StorageScope()[key]; } return Math.max(0, Math.floor(Number(value) || 0)); } function setStep405RecoveryCount(step, count) { const key = getStep405RecoveryStateKey(step); const value = String(Math.max(0, Math.floor(Number(count) || 0))); try { if (typeof sessionStorage !== 'undefined' && sessionStorage?.setItem) { sessionStorage.setItem(key, value); } } catch {} getStep405StorageScope()[key] = value; } function clearStep405RecoveryCount(step) { const key = getStep405RecoveryStateKey(step); try { if (typeof sessionStorage !== 'undefined' && sessionStorage?.removeItem) { sessionStorage.removeItem(key); } } catch {} try { delete getStep405StorageScope()[key]; } catch {} } function createStep405RecoveryLimitError(step, count) { const normalizedStep = Number(step) || step || '?'; const limit = getStep405RecoveryLimit(normalizedStep) || count; const message = `步骤 ${normalizedStep}:检测到 405 错误页面,已连续点击“重试”恢复 ${count}/${limit} 次仍未恢复,当前轮将结束并进入下一轮。URL: ${location.href}`; return new Error(`${getStep405RecoveryErrorPrefix(normalizedStep)}${message}`); } async function handle405ResendError(step, remainingTimeout = 30000) { const currentCount = getStep405RecoveryCount(step); if (Number(step) === 4 && currentCount >= getStep405RecoveryLimit(step)) { throw createStep405RecoveryLimitError(step, currentCount); } const nextCount = currentCount + 1; setStep405RecoveryCount(step, nextCount); const maxClickAttempts = Number(step) === 4 ? 1 : 5; await recoverCurrentAuthRetryPage({ logLabel: Number(step) === 4 ? `步骤 ${step}:检测到 405 错误页面,正在点击“重试”恢复(总计 ${nextCount}/${getStep405RecoveryLimit(step)})` : `步骤 ${step}:检测到 405 错误页面,正在点击“重试”恢复`, maxClickAttempts, pathPatterns: [], step, timeoutMs: Math.max(1000, remainingTimeout), }); if (is405MethodNotAllowedPage()) { throw createStep405RecoveryLimitError(step, nextCount); } if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); log(`步骤 ${step}:405 错误已恢复,页面已返回验证码页面。`); } // ============================================================ // Signup Entry Helpers // ============================================================ const SIGNUP_ENTRY_TRIGGER_PATTERN = /免费注册|立即注册|注册|無料でサインアップ|サインアップ|新規登録|登録する|登録|アカウントを作成|アカウント作成|sign\s*up|register|create\s*account|create\s+account/i; const SIGNUP_EMAIL_INPUT_SELECTOR = [ 'input[type="email"]', 'input[autocomplete="email"]', 'input[autocomplete="username"]', 'input[name="email"]', 'input[name="username"]', 'input[id*="email"]', 'input[placeholder*="email" i]', 'input[placeholder*="电子邮件"]', 'input[placeholder*="邮箱"]', 'input[placeholder*="メール"]', 'input[aria-label*="email" i]', 'input[aria-label*="电子邮件"]', 'input[aria-label*="邮箱"]', 'input[aria-label*="メール"]', ].join(', '); const SIGNUP_PHONE_INPUT_SELECTOR = [ 'input[type="tel"]:not([maxlength="6"])', 'input[name*="phone" i]', 'input[id*="phone" i]', 'input[autocomplete="tel"]', 'input[placeholder*="手机"]', 'input[placeholder*="電話"]', 'input[placeholder*="携帯"]', 'input[aria-label*="手机"]', 'input[aria-label*="電話"]', 'input[aria-label*="携帯"]', ].join(', '); const SIGNUP_SWITCH_TO_EMAIL_PATTERN = new RegExp([ String.raw`\u7ee7\u7eed\u4f7f\u7528(?:\u7535\u5b50\u90ae\u4ef6\u5730\u5740|\u90ae\u7bb1)\u767b\u5f55`, String.raw`\u6539\u7528(?:\u7535\u5b50\u90ae\u4ef6\u5730\u5740|\u90ae\u7bb1)\u767b\u5f55`, String.raw`(?:メールアドレス|メール|電子メール)(?:で|を使用して)?(?:続行|続ける|ログイン|サインイン|サインアップ)`, String.raw`(?:続行|続ける|使用|ログイン|サインイン|サインアップ).*(?:メールアドレス|メール|電子メール)`, String.raw`continue\s+using\s+(?:an?\s+)?email(?:\s+address)?(?:\s+(?:to\s+)?(?:log\s*in|sign\s*in|sign\s*up))?`, String.raw`continue\s+with\s+email(?:\s+address)?`, String.raw`use\s+(?:an?\s+)?email(?:\s+address)?(?:\s+instead)?`, String.raw`sign\s*(?:in|up)\s+with\s+email`, ].join('|'), 'i'); const SIGNUP_SWITCH_ACTION_PATTERN = /\u7ee7\u7eed\u4f7f\u7528|\u6539\u7528|continue|use|sign\s*(?:in|up)|続行|続ける|使用|ログイン|サイン(?:イン|アップ)/i; const SIGNUP_EMAIL_ACTION_PATTERN = /\u7535\u5b50\u90ae\u4ef6|\u90ae\u7bb1|email|メールアドレス|メール|電子メール/i; const SIGNUP_PHONE_ACTION_PATTERN = /手机|手机号|电话号码|phone|telephone|mobile|電話番号|電話|携帯電話|携帯/i; const SIGNUP_SWITCH_TO_PHONE_PATTERN = new RegExp([ String.raw`\u7ee7\u7eed\u4f7f\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?\u767b\u5f55`, String.raw`\u6539\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?\u767b\u5f55`, String.raw`\u7ee7\u7eed\u4f7f\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u624b\u673a\u53f7\u7801|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?`, String.raw`\u6539\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u624b\u673a\u53f7\u7801|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?`, String.raw`\u4f7f\u7528(?:\u624b\u673a|\u624b\u673a\u53f7|\u624b\u673a\u53f7\u7801|\u7535\u8bdd\u53f7\u7801)(?:\u53f7\u7801)?`, String.raw`(?:電話番号|電話|携帯電話|携帯)(?:で|を使用して)?(?:続行|続ける|ログイン|サインイン|サインアップ)`, String.raw`(?:続行|続ける|使用|ログイン|サインイン|サインアップ).*(?:電話番号|電話|携帯電話|携帯)`, String.raw`continue\s+(?:with|using)\s+(?:a\s+)?phone(?:\s+number)?`, String.raw`use\s+(?:a\s+)?phone(?:\s+number)?(?:\s+instead)?`, String.raw`sign\s*(?:in|up)\s+with\s+(?:a\s+)?phone`, ].join('|'), 'i'); const SIGNUP_MORE_OPTIONS_PATTERN = /更多选项|其它方式|其他方式|その他|他の方法|別の方法|もっと見る|オプション|more\s+options|show\s+more|other\s+(?:options|ways)/i; const SIGNUP_WORK_EMAIL_PATTERN = /\u5de5\u4f5c|business|work\s+email/i; function getSignupEmailInput() { const input = document.querySelector(SIGNUP_EMAIL_INPUT_SELECTOR); if (input && isVisibleElement(input)) { return input; } const fallback = Array.from(document.querySelectorAll('input')).find((el) => { if (!isVisibleElement(el)) return false; const type = String(el.getAttribute?.('type') || '').trim().toLowerCase(); const name = String(el.getAttribute?.('name') || '').trim().toLowerCase(); const id = String(el.getAttribute?.('id') || '').trim().toLowerCase(); const placeholder = String(el.getAttribute?.('placeholder') || '').trim(); const ariaLabel = String(el.getAttribute?.('aria-label') || '').trim(); const autocomplete = String(el.getAttribute?.('autocomplete') || '').trim().toLowerCase(); const combinedText = `${placeholder} ${ariaLabel}`; return type === 'email' || autocomplete === 'email' || autocomplete === 'username' || /email|username|mail/i.test(`${name} ${id}`) || /email|电子邮件|邮箱|メール|電子メール/i.test(combinedText); }); return fallback || null; } function getSignupPhoneInput() { const input = document.querySelector(SIGNUP_PHONE_INPUT_SELECTOR); if (input && isVisibleElement(input)) { return input; } const fallback = Array.from(document.querySelectorAll('input')).find((el) => { if (!isVisibleElement(el)) return false; const type = String(el.getAttribute?.('type') || '').trim().toLowerCase(); const name = String(el.getAttribute?.('name') || '').trim().toLowerCase(); const id = String(el.getAttribute?.('id') || '').trim().toLowerCase(); const placeholder = String(el.getAttribute?.('placeholder') || '').trim(); const ariaLabel = String(el.getAttribute?.('aria-label') || '').trim(); const autocomplete = String(el.getAttribute?.('autocomplete') || '').trim().toLowerCase(); const combinedText = `${placeholder} ${ariaLabel}`; return type === 'tel' || autocomplete === 'tel' || /phone|tel/i.test(`${name} ${id}`) || /手机|电话|手机号|電話|電話番号|携帯|携帯電話/.test(combinedText); }); return fallback || null; } function findSignupUseEmailTrigger() { const candidates = document.querySelectorAll('button, a, [role="button"], [role="link"]'); return Array.from(candidates).find((el) => { if (!isVisibleElement(el) || !isActionEnabled(el)) return false; const text = getActionText(el); if (!text) return false; if (SIGNUP_WORK_EMAIL_PATTERN.test(text)) return false; return SIGNUP_SWITCH_TO_EMAIL_PATTERN.test(text) || (SIGNUP_SWITCH_ACTION_PATTERN.test(text) && SIGNUP_EMAIL_ACTION_PATTERN.test(text)); }) || null; } function findSignupUsePhoneTrigger() { const candidates = document.querySelectorAll('button, a, [role="button"], [role="link"]'); return Array.from(candidates).find((el) => { if (!isVisibleElement(el) || !isActionEnabled(el)) return false; const text = getActionText(el); if (!text) return false; return SIGNUP_SWITCH_TO_PHONE_PATTERN.test(text) || (SIGNUP_SWITCH_ACTION_PATTERN.test(text) && SIGNUP_PHONE_ACTION_PATTERN.test(text)); }) || null; } function findSignupMoreOptionsTrigger() { const candidates = document.querySelectorAll('button, a, [role="button"], [role="link"]'); return Array.from(candidates).find((el) => { if (!isVisibleElement(el) || !isActionEnabled(el)) return false; const text = getActionText(el); if (!text || !SIGNUP_MORE_OPTIONS_PATTERN.test(text)) return false; const expanded = String(el.getAttribute?.('aria-expanded') || '').trim().toLowerCase(); const state = String(el.getAttribute?.('data-state') || '').trim().toLowerCase(); return expanded !== 'true' && state !== 'open'; }) || null; } function getSignupEmailContinueButton({ allowDisabled = false } = {}) { const direct = document.querySelector('button[type="submit"], input[type="submit"]'); if (direct && isVisibleElement(direct) && (allowDisabled || isActionEnabled(direct))) { return direct; } const candidates = document.querySelectorAll( 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' ); return Array.from(candidates).find((el) => { if (!isVisibleElement(el) || (!allowDisabled && !isActionEnabled(el))) return false; return /continue|next|submit|继续|下一步|続行|続ける|次へ|送信/i.test(getActionText(el)); }) || null; } function findSignupEntryTrigger(options = {}) { const { allowHiddenFallback = true } = options || {}; const candidates = document.querySelectorAll('a, button, [role="button"], [role="link"]'); let hiddenSignupTrigger = null; for (const el of Array.from(candidates)) { if (!isActionEnabled(el)) continue; if (!SIGNUP_ENTRY_TRIGGER_PATTERN.test(getActionText(el))) continue; if (isVisibleElement(el)) { return el; } if (!hiddenSignupTrigger) { hiddenSignupTrigger = el; } } if (!allowHiddenFallback || !hiddenSignupTrigger) { return null; } const view = typeof window !== 'undefined' ? window : globalThis; const collapsedViewport = Boolean( Math.round(Number(view?.innerWidth) || 0) < 240 || Math.round(Number(view?.innerHeight) || 0) < 160 || Math.round(Number(view?.outerWidth) || 0) < 320 || Math.round(Number(view?.outerHeight) || 0) < 180 ); const pageText = typeof getPageTextSnapshot === 'function' ? getPageTextSnapshot() : ''; const looksLikeLoggedOutHome = /登录|登入|ログイン|サインイン|log\s*in|sign\s*in/i.test(pageText); return collapsedViewport || looksLikeLoggedOutHome ? hiddenSignupTrigger : null; } function getSignupPasswordDisplayedEmail() { const text = (document.body?.innerText || document.body?.textContent || '') .replace(/\s+/g, ' ') .trim(); const matches = text.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig); return matches?.[0] ? String(matches[0]).trim().toLowerCase() : ''; } function inspectSignupEntryState() { if (typeof isPhoneVerificationPageReady === 'function' && isPhoneVerificationPageReady()) { return { state: 'phone_verification_page', verificationTarget: typeof getVerificationCodeTarget === 'function' ? getVerificationCodeTarget() : null, displayedPhone: typeof getPhoneVerificationDisplayedPhone === 'function' ? getPhoneVerificationDisplayedPhone() : '', url: location.href, }; } const postVerificationState = typeof getStep4PostVerificationState === 'function' ? getStep4PostVerificationState() : null; if (postVerificationState?.state === 'step5') { return { state: 'profile_page', url: postVerificationState.url || location.href, }; } if (postVerificationState?.state === 'logged_in_home') { return { state: 'logged_in_home', skipProfileStep: true, url: postVerificationState.url || location.href, }; } if (typeof isVerificationPageStillVisible === 'function' && isVerificationPageStillVisible()) { return { state: 'verification_page', verificationTarget: typeof getVerificationCodeTarget === 'function' ? getVerificationCodeTarget() : null, url: location.href, }; } const passwordInput = getSignupPasswordInput(); if (isSignupPasswordPage() && passwordInput) { return { state: 'password_page', passwordInput, submitButton: getSignupPasswordSubmitButton({ allowDisabled: true }), displayedEmail: getSignupPasswordDisplayedEmail(), passwordErrorText: getSignupPasswordFieldErrorText(), url: location.href, }; } const emailInput = getSignupEmailInput(); if (emailInput) { return { state: 'email_entry', emailInput, continueButton: getSignupEmailContinueButton({ allowDisabled: true }), switchToPhoneTrigger: findSignupUsePhoneTrigger(), url: location.href, }; } const phoneInput = getSignupPhoneInput(); if (phoneInput) { return { state: 'phone_entry', phoneInput, switchToEmailTrigger: findSignupUseEmailTrigger(), url: location.href, }; } const signupTrigger = findSignupEntryTrigger(); if (signupTrigger) { return { state: 'entry_home', signupTrigger, url: location.href, }; } return { state: 'unknown', url: location.href, }; } function getSignupEntryStateSummary(snapshot = inspectSignupEntryState()) { const summary = { state: snapshot?.state || 'unknown', url: snapshot?.url || location.href, hasEmailInput: Boolean(snapshot?.emailInput || getSignupEmailInput()), hasPhoneInput: Boolean(snapshot?.phoneInput || getSignupPhoneInput()), hasPasswordInput: Boolean(snapshot?.passwordInput || getSignupPasswordInput()), }; if (snapshot?.displayedEmail) { summary.displayedEmail = snapshot.displayedEmail; } if (snapshot?.signupTrigger) { summary.signupTrigger = { tag: (snapshot.signupTrigger.tagName || '').toLowerCase(), text: getActionText(snapshot.signupTrigger).slice(0, 80), visible: isVisibleElement(snapshot.signupTrigger), }; } if (snapshot?.continueButton) { summary.continueButton = { tag: (snapshot.continueButton.tagName || '').toLowerCase(), text: getActionText(snapshot.continueButton).slice(0, 80), enabled: isActionEnabled(snapshot.continueButton), }; } if (snapshot?.switchToEmailTrigger) { summary.switchToEmailTrigger = { tag: (snapshot.switchToEmailTrigger.tagName || '').toLowerCase(), text: getActionText(snapshot.switchToEmailTrigger).slice(0, 80), enabled: isActionEnabled(snapshot.switchToEmailTrigger), }; } if (snapshot?.switchToPhoneTrigger) { summary.switchToPhoneTrigger = { tag: (snapshot.switchToPhoneTrigger.tagName || '').toLowerCase(), text: getActionText(snapshot.switchToPhoneTrigger).slice(0, 80), enabled: isActionEnabled(snapshot.switchToPhoneTrigger), }; } return summary; } function getSignupEntryDiagnostics() { const view = typeof window !== 'undefined' ? window : globalThis; const safeGetComputedStyle = (el) => { if (!el || typeof view?.getComputedStyle !== 'function') { return null; } try { return view.getComputedStyle(el); } catch { return null; } }; const buildRectSummary = (el) => { const rect = typeof el?.getBoundingClientRect === 'function' ? el.getBoundingClientRect() : null; return rect ? { width: Math.round(rect.width || 0), height: Math.round(rect.height || 0), } : null; }; const buildVisibilityMeta = (el) => { const style = safeGetComputedStyle(el); return { className: String(el?.className || '').slice(0, 200), hidden: Boolean(el?.hidden), ariaHidden: el?.getAttribute?.('aria-hidden') || '', inert: typeof el?.hasAttribute === 'function' ? el.hasAttribute('inert') : false, display: style?.display || '', visibility: style?.visibility || '', opacity: style?.opacity || '', pointerEvents: style?.pointerEvents || '', }; }; const findBlockingAncestor = (el) => { let current = el?.parentElement || null; while (current) { const style = safeGetComputedStyle(current); const rect = buildRectSummary(current); const hidden = Boolean(current.hidden); const ariaHidden = current.getAttribute?.('aria-hidden') || ''; const inert = typeof current.hasAttribute === 'function' ? current.hasAttribute('inert') : false; const blockedByStyle = Boolean( style && ( style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0' || style.pointerEvents === 'none' ) ); const blockedByRect = Boolean(rect && (rect.width === 0 || rect.height === 0)); if (hidden || ariaHidden === 'true' || inert || blockedByStyle || blockedByRect) { return { tag: (current.tagName || '').toLowerCase(), id: current.id || '', className: String(current.className || '').slice(0, 200), hidden, ariaHidden, inert, display: style?.display || '', visibility: style?.visibility || '', opacity: style?.opacity || '', pointerEvents: style?.pointerEvents || '', rect, }; } current = current.parentElement; } return null; }; const actionCandidates = document.querySelectorAll( 'a, button, [role="button"], [role="link"], input[type="button"], input[type="submit"]' ); const allActions = Array.from(actionCandidates).map((el) => { const text = getActionText(el); return { tag: (el.tagName || '').toLowerCase(), type: el.getAttribute?.('type') || '', text: text.slice(0, 80), visible: isVisibleElement(el), enabled: isActionEnabled(el), rect: buildRectSummary(el), }; }); const visibleActions = Array.from(actionCandidates) .filter(isVisibleElement) .slice(0, 12) .map((el) => ({ tag: (el.tagName || '').toLowerCase(), type: el.getAttribute?.('type') || '', text: getActionText(el).slice(0, 80), enabled: isActionEnabled(el), })) .filter((item) => item.text); const signupLikeActions = Array.from(actionCandidates) .map((el) => { const text = getActionText(el); return { tag: (el.tagName || '').toLowerCase(), type: el.getAttribute?.('type') || '', text: text.slice(0, 80), visible: isVisibleElement(el), enabled: isActionEnabled(el), rect: buildRectSummary(el), ...buildVisibilityMeta(el), blockingAncestor: findBlockingAncestor(el), }; }) .filter((item) => item.text && SIGNUP_ENTRY_TRIGGER_PATTERN.test(item.text)) .slice(0, 12); return { url: location.href, title: document.title || '', readyState: document.readyState || '', viewport: { innerWidth: Math.round(Number(view?.innerWidth) || 0), innerHeight: Math.round(Number(view?.innerHeight) || 0), outerWidth: Math.round(Number(view?.outerWidth) || 0), outerHeight: Math.round(Number(view?.outerHeight) || 0), devicePixelRatio: Number(view?.devicePixelRatio) || 0, }, hasEmailInput: Boolean(getSignupEmailInput()), hasPhoneInput: Boolean(getSignupPhoneInput()), hasPasswordInput: Boolean(getSignupPasswordInput()), hasSwitchToEmailAction: Boolean(findSignupUseEmailTrigger()), bodyContainsSignupText: SIGNUP_ENTRY_TRIGGER_PATTERN.test(getPageTextSnapshot()), signupLikeActionCounts: { total: signupLikeActions.length, visible: signupLikeActions.filter((item) => item.visible).length, hidden: signupLikeActions.filter((item) => !item.visible).length, }, signupLikeActions, visibleActions, bodyTextPreview: getPageTextSnapshot().slice(0, 240), }; } function getSignupPasswordDiagnostics() { const view = typeof window !== 'undefined' ? window : globalThis; const safeGetComputedStyle = (el) => { if (!el || typeof view?.getComputedStyle !== 'function') { return null; } try { return view.getComputedStyle(el); } catch { return null; } }; const buildRectSummary = (el) => { const rect = typeof el?.getBoundingClientRect === 'function' ? el.getBoundingClientRect() : null; return rect ? { width: Math.round(rect.width || 0), height: Math.round(rect.height || 0), } : null; }; const buildInputSummary = (el) => { const style = safeGetComputedStyle(el); return { tag: (el?.tagName || '').toLowerCase(), type: el?.getAttribute?.('type') || el?.type || '', name: el?.getAttribute?.('name') || el?.name || '', id: el?.id || '', autocomplete: el?.getAttribute?.('autocomplete') || '', placeholder: String(el?.getAttribute?.('placeholder') || '').slice(0, 80), visible: isVisibleElement(el), enabled: isActionEnabled(el), valueLength: String(el?.value || '').length, rect: buildRectSummary(el), className: String(el?.className || '').slice(0, 200), display: style?.display || '', visibility: style?.visibility || '', opacity: style?.opacity || '', pointerEvents: style?.pointerEvents || '', formAction: el?.form?.action || '', }; }; const buildActionSummary = (el) => { const style = safeGetComputedStyle(el); return { tag: (el?.tagName || '').toLowerCase(), type: el?.getAttribute?.('type') || el?.type || '', role: el?.getAttribute?.('role') || '', text: getActionText(el).slice(0, 120), visible: isVisibleElement(el), enabled: isActionEnabled(el), rect: buildRectSummary(el), className: String(el?.className || '').slice(0, 200), display: style?.display || '', visibility: style?.visibility || '', opacity: style?.opacity || '', pointerEvents: style?.pointerEvents || '', dataDdActionName: el?.getAttribute?.('data-dd-action-name') || '', formAction: el?.form?.action || '', }; }; const passwordInputs = Array.from(document.querySelectorAll( 'input[type="password"], input[name*="password" i], input[autocomplete="new-password"], input[autocomplete="current-password"]' )) .map(buildInputSummary) .slice(0, 8); const actionCandidates = Array.from(document.querySelectorAll( 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' )) .map(buildActionSummary) .filter((item) => item.text) .slice(0, 16); const visibleActions = actionCandidates.filter((item) => item.visible).slice(0, 12); const submitButton = getSignupPasswordSubmitButton({ allowDisabled: true }); const oneTimeCodeTrigger = findOneTimeCodeLoginTrigger(); const retryState = getSignupPasswordTimeoutErrorPageState(); return { url: location.href, title: document.title || '', readyState: document.readyState || '', displayedEmail: getSignupPasswordDisplayedEmail(), passwordErrorText: getSignupPasswordFieldErrorText(), hasVisiblePasswordInput: Boolean(getSignupPasswordInput()), passwordInputCount: passwordInputs.length, visiblePasswordInputCount: passwordInputs.filter((item) => item.visible).length, passwordInputs, submitButton: submitButton ? buildActionSummary(submitButton) : null, oneTimeCodeTrigger: oneTimeCodeTrigger ? buildActionSummary(oneTimeCodeTrigger) : null, retryPage: Boolean(retryState), retryEnabled: Boolean(retryState?.retryEnabled), userAlreadyExistsBlocked: Boolean(retryState?.userAlreadyExistsBlocked), visibleActions, bodyTextPreview: getPageTextSnapshot().slice(0, 240), }; } function logSignupPasswordDiagnostics(context, level = 'warn') { try { log(`${context}:密码页诊断快照:${JSON.stringify(getSignupPasswordDiagnostics())}`, level); } catch (error) { console.warn('[MultiPage:openai-auth] failed to build signup password diagnostics:', error?.message || error); } } async function waitForSignupEntryState(options = {}) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const { timeout = 15000, autoOpenEntry = false, step = 2, logDiagnostics = false, } = options; const start = Date.now(); const maxSignupEntryClickRetries = 5; const maxSignupEntryClickAttempts = maxSignupEntryClickRetries + 1; let lastTriggerClickAt = 0; let clickAttempts = 0; let lastState = ''; let slowSnapshotLogged = false; let lastSwitchToEmailAt = 0; let loggedMissingSwitchToEmail = false; while (Date.now() - start < timeout) { throwIfStopped(); const snapshot = inspectSignupEntryState(); if (logDiagnostics && snapshot.state !== lastState) { lastState = snapshot.state; log(`步骤 ${step}:注册入口状态切换为 ${snapshot.state},状态快照:${JSON.stringify(getSignupEntryStateSummary(snapshot))}`); } if (snapshot.state === 'password_page' || snapshot.state === 'email_entry') { return snapshot; } if (snapshot.state === 'phone_entry') { if (!autoOpenEntry) { return snapshot; } if (snapshot.switchToEmailTrigger && Date.now() - lastSwitchToEmailAt >= 1500) { lastSwitchToEmailAt = Date.now(); loggedMissingSwitchToEmail = false; if (logDiagnostics) { log(`步骤 ${step}:检测到手机号输入模式,准备点击切换邮箱入口:"${getActionText(snapshot.switchToEmailTrigger).slice(0, 80)}"`); } log('步骤 2:检测到手机号输入模式,正在切换到邮箱输入模式...'); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-entry', kind: 'click', label: 'switch-to-signup-email' }, async () => { simulateClick(snapshot.switchToEmailTrigger); }); } else if (!snapshot.switchToEmailTrigger && !loggedMissingSwitchToEmail) { loggedMissingSwitchToEmail = true; log('步骤 2:检测到手机号输入模式,但暂未识别到“改用邮箱/继续使用电子邮件地址登录”按钮,继续等待界面稳定...', 'warn'); } if (logDiagnostics && !slowSnapshotLogged && Date.now() - start >= 5000) { slowSnapshotLogged = true; log(`步骤 ${step}:等待手机号入口切换超过 5 秒,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } await sleep(250); continue; } if (snapshot.state === 'entry_home') { if (!autoOpenEntry) { return snapshot; } if (Date.now() - lastTriggerClickAt >= 1500) { if (clickAttempts >= maxSignupEntryClickAttempts) { log(`步骤 ${step}:官网注册入口已完成 ${maxSignupEntryClickRetries} 次重试,页面仍未进入邮箱输入页,停止重试。`, 'warn'); return snapshot; } lastTriggerClickAt = Date.now(); clickAttempts += 1; const retryAttempt = clickAttempts - 1; if (logDiagnostics) { log(`步骤 ${step}:正在点击官网注册入口(第 ${clickAttempts}/${maxSignupEntryClickAttempts} 次):"${getActionText(snapshot.signupTrigger).slice(0, 80)}"`); } log(retryAttempt > 0 ? `步骤 ${step}:上次点击后仍未进入邮箱输入页,等待 3 秒后重试点击官网注册入口(重试 ${retryAttempt}/${maxSignupEntryClickRetries})...` : `步骤 ${step}:已找到官网注册入口,等待 3 秒后点击...`); await sleep(3000); throwIfStopped(); const clickTarget = findSignupEntryTrigger({ allowHiddenFallback: false }) || snapshot.signupTrigger; if (!isVisibleElement(clickTarget)) { log(`步骤 ${step}:注册入口仍处于不可见状态,继续按入口重试节奏尝试恢复点击...`, 'warn'); } await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-entry', kind: 'click', label: 'open-signup-entry' }, async () => { simulateClick(clickTarget); }); } } if (logDiagnostics && !slowSnapshotLogged && Date.now() - start >= 5000) { slowSnapshotLogged = true; log(`步骤 ${step}:等待注册入口超过 5 秒,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } await sleep(250); } const finalSnapshot = inspectSignupEntryState(); if (logDiagnostics) { log(`步骤 ${step}:等待注册入口状态超时,最终状态快照:${JSON.stringify(getSignupEntryStateSummary(finalSnapshot))}`, 'warn'); } return finalSnapshot; } async function ensureSignupEntryReady(timeout = 15000) { const snapshot = await waitForSignupEntryState({ timeout, autoOpenEntry: false }); if (snapshot.state === 'entry_home' || snapshot.state === 'phone_entry' || snapshot.state === 'email_entry' || snapshot.state === 'password_page') { return { ready: true, state: snapshot.state, url: snapshot.url || location.href, }; } log(`注册入口识别失败,诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); throw new Error('当前页面没有可用的注册入口,也不在邮箱/密码页。URL: ' + location.href); } async function ensureSignupPhoneEntryReady(timeout = 25000) { const snapshot = await waitForSignupPhoneEntryState({ timeout, step: 2 }); if ( (snapshot.state === 'phone_entry' && snapshot.phoneInput) || snapshot.state === 'password_page' ) { return { ready: true, state: snapshot.state, url: snapshot.url || location.href, }; } log(`手机号注册入口识别失败,诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); throw new Error('当前页面没有可用的手机号注册入口,也不在密码页。URL: ' + location.href); } async function ensureSignupPasswordPageReady(timeout = 20000) { const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); const passwordInput = getSignupPasswordInput(); if (isSignupPasswordPage() && passwordInput) { return { ready: true, state: 'password_page', url: location.href, }; } await sleep(200); } throw new Error('等待进入密码页超时。URL: ' + location.href); } async function fillSignupEmailAndContinue(email, step) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; if (!email) throw new Error(`未提供邮箱地址,步骤 ${step} 无法继续。`); const normalizedEmail = String(email || '').trim().toLowerCase(); const snapshot = await waitForSignupEntryState({ timeout: 20000, autoOpenEntry: true, step, logDiagnostics: step === 2, }); if (snapshot.state === 'password_page') { if (snapshot.displayedEmail && snapshot.displayedEmail !== normalizedEmail) { throw new Error(`步骤 ${step}:当前密码页邮箱为 ${snapshot.displayedEmail},与目标邮箱 ${email} 不一致,请先回到步骤 1 重新开始。`); } log(`步骤 ${step}:当前已在密码页,无需重复提交邮箱。`); return { alreadyOnPasswordPage: true, url: snapshot.url || location.href, }; } if (snapshot.state !== 'email_entry' || !snapshot.emailInput) { if (step === 2) { log(`步骤 ${step}:未进入邮箱输入页,最终页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } throw new Error(`步骤 ${step}:未找到可用的邮箱输入入口。URL: ${location.href}`); } log(`步骤 ${step}:正在填写邮箱:${email}`); await humanPause(500, 1400); await performOperationWithDelay({ stepKey: step === 2 ? 'signup-entry' : 'fill-password', kind: 'fill', label: 'signup-email' }, async () => { fillInput(snapshot.emailInput, email); }); log(`步骤 ${step}:邮箱已填写`); const continueButton = snapshot.continueButton || getSignupEmailContinueButton({ allowDisabled: true }); if (!continueButton || !isActionEnabled(continueButton)) { throw new Error(`步骤 ${step}:未找到可点击的“继续”按钮。URL: ${location.href}`); } log(`步骤 ${step}:邮箱已准备提交,正在前往密码页...`); window.setTimeout(async () => { try { throwIfStopped(); await performOperationWithDelay({ stepKey: step === 2 ? 'signup-entry' : 'fill-password', kind: 'submit', label: 'submit-signup-email' }, async () => { simulateClick(continueButton); }); } catch (error) { if (!isStopError(error)) { console.error('[MultiPage:openai-auth] deferred signup email submit failed:', error?.message || error); } } }, 120); return { submitted: true, email, url: location.href, }; } function normalizePhoneDigits(value) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.normalizePhoneDigits === 'function') { return phoneCountryUtils.normalizePhoneDigits(value); } let digits = String(value || '').replace(/\D+/g, ''); if (digits.startsWith('00')) { digits = digits.slice(2); } return digits; } function extractDialCodeFromText(value) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.extractDialCodeFromText === 'function') { return phoneCountryUtils.extractDialCodeFromText(value); } const match = String(value || '').match(/\(\+\s*(\d{1,4})\s*\)|\+\s*\(\s*(\d{1,4})\s*\)|\+\s*(\d{1,4})\b/); return String(match?.[1] || match?.[2] || match?.[3] || '').trim(); } function dispatchSignupPhoneFieldEvents(element) { if (!element) return; element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); } function normalizeSignupCountryLabel(value) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.normalizeCountryLabel === 'function') { return phoneCountryUtils.normalizeCountryLabel(value); } return String(value || '') .normalize('NFKD') .replace(/[\u0300-\u036f]/g, '') .replace(/&/g, ' and ') .replace(/[^\p{L}\p{N}\s]/gu, ' ') .replace(/\s+/g, ' ') .trim() .toLowerCase(); } function getSignupCountryLabelAliases(value) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.getCountryLabelAliases === 'function') { return phoneCountryUtils.getCountryLabelAliases(value); } const aliases = new Set(); const addAlias = (alias) => { const normalized = normalizeSignupCountryLabel(alias); if (normalized) { aliases.add(normalized); } }; const raw = String(value || '').trim(); addAlias(raw); const normalized = normalizeSignupCountryLabel(raw); const compact = normalized.replace(/\s+/g, ''); if ( /(?:^|\s)(?:gb|uk)(?:\s|$)/i.test(raw) || /england|united\s*kingdom|great\s*britain|\bbritain\b/i.test(raw) || /英国|英格兰|大不列颠/.test(raw) || ['gb', 'uk', 'england', 'unitedkingdom', 'greatbritain', 'britain'].includes(compact) ) { [ 'GB', 'UK', 'United Kingdom', 'Great Britain', 'Britain', 'England', '英国', '英格兰', '大不列颠', ].forEach(addAlias); } return Array.from(aliases); } function getSignupPhoneOptionLabel(option) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.getOptionLabel === 'function') { return phoneCountryUtils.getOptionLabel(option); } return String(option?.textContent || option?.label || '') .replace(/\s+/g, ' ') .trim(); } function normalizeSignupCountryOptionValue(value) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.normalizeCountryOptionValue === 'function') { return phoneCountryUtils.normalizeCountryOptionValue(value); } return String(value || '').trim().toUpperCase(); } function getSignupRegionDisplayName(regionCode, locale) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.getRegionDisplayName === 'function') { return phoneCountryUtils.getRegionDisplayName(regionCode, locale); } const normalizedRegionCode = normalizeSignupCountryOptionValue(regionCode); const normalizedLocale = String(locale || '').trim(); if (!/^[A-Z]{2}$/.test(normalizedRegionCode) || !normalizedLocale || typeof Intl?.DisplayNames !== 'function') { return ''; } try { return String( new Intl.DisplayNames([normalizedLocale], { type: 'region' }).of(normalizedRegionCode) || '' ).trim(); } catch { return ''; } } function getSignupPhoneCountryMatchLabels(option) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.getOptionMatchLabels === 'function') { const rootScope = typeof self !== 'undefined' ? self : globalThis; return phoneCountryUtils.getOptionMatchLabels(option, { document: typeof document !== 'undefined' ? document : null, navigator: rootScope?.navigator || globalThis?.navigator || null, getOptionLabel: getSignupPhoneOptionLabel, }); } const labels = new Set(); const pushLabel = (value) => { const label = String(value || '').replace(/\s+/g, ' ').trim(); if (label) { labels.add(label); } }; pushLabel(getSignupPhoneOptionLabel(option)); const regionCode = normalizeSignupCountryOptionValue(option?.value); if (/^[A-Z]{2}$/.test(regionCode)) { pushLabel(regionCode); pushLabel(getSignupRegionDisplayName(regionCode, 'en')); const rootScope = typeof self !== 'undefined' ? self : globalThis; const pageLocale = String( document?.documentElement?.lang || document?.documentElement?.getAttribute?.('lang') || rootScope?.navigator?.language || '' ).trim(); if (pageLocale && !/^en(?:[-_]|$)/i.test(pageLocale)) { pushLabel(getSignupRegionDisplayName(regionCode, pageLocale)); } } return Array.from(labels); } function isSameSignupCountryOption(left, right) { if (!left || !right) { return false; } const leftValue = normalizeSignupCountryOptionValue(left.value); const rightValue = normalizeSignupCountryOptionValue(right.value); if (leftValue && rightValue) { return leftValue === rightValue; } return normalizeSignupCountryLabel(getSignupPhoneOptionLabel(left)) === normalizeSignupCountryLabel(getSignupPhoneOptionLabel(right)); } function getSignupPhoneForm(phoneInput = getSignupPhoneInput()) { return phoneInput?.closest?.('form') || null; } function getSignupPhoneControlRoots(phoneInput = getSignupPhoneInput()) { const roots = []; const addRoot = (root) => { if (root && !roots.includes(root)) { roots.push(root); } }; addRoot(phoneInput?.closest?.('form')); addRoot(phoneInput?.closest?.('fieldset')); addRoot(phoneInput?.closest?.('[data-rac]')); addRoot(phoneInput?.closest?.('[role="group"]')); addRoot(phoneInput?.parentElement); addRoot(phoneInput?.parentElement?.parentElement); addRoot(document); return roots; } function querySignupPhoneCountryElements(root, selector) { if (!root || !selector) { return []; } if (typeof root.querySelectorAll === 'function') { const directMatches = Array.from(root.querySelectorAll(selector)); if (directMatches.length > 0) { return directMatches; } } if (typeof root.querySelector === 'function') { const selectors = String(selector || '') .split(',') .map((part) => part.trim()) .filter(Boolean); const matches = []; for (const part of selectors) { const element = root.querySelector(part); if (element && !matches.includes(element)) { matches.push(element); } } return matches; } return []; } function isSignupPhoneCountrySelect(select) { if (!select) { return false; } return Array.from(select.options || []).some((option) => ( extractDialCodeFromText(getSignupPhoneOptionLabel(option)) || /^[A-Z]{2}$/.test(normalizeSignupCountryOptionValue(option?.value)) )); } function getSignupPhoneCountrySelect(phoneInput = getSignupPhoneInput()) { const selects = []; for (const root of getSignupPhoneControlRoots(phoneInput)) { for (const select of querySignupPhoneCountryElements(root, 'select')) { if (!selects.includes(select)) { selects.push(select); } } } return selects.find(isSignupPhoneCountrySelect) || selects[0] || null; } function getSignupPhoneSelectedCountryOption(phoneInput = getSignupPhoneInput()) { const select = getSignupPhoneCountrySelect(phoneInput); if (!select || select.selectedIndex < 0) { return null; } return select.options?.[select.selectedIndex] || null; } function getSignupPhoneCountryButtonText(phoneInput = getSignupPhoneInput()) { const button = getSignupPhoneCountryButton(phoneInput); if (!button) return ''; const valueNode = button.querySelector('.react-aria-SelectValue'); return String(valueNode?.textContent || button.textContent || '') .replace(/\s+/g, ' ') .trim(); } function getSignupPhoneCountryButton(phoneInput = getSignupPhoneInput()) { const candidates = []; for (const root of getSignupPhoneControlRoots(phoneInput)) { const buttons = querySignupPhoneCountryElements( root, 'button[aria-haspopup="listbox"], [role="button"][aria-haspopup="listbox"], [role="combobox"][aria-haspopup="listbox"], button[aria-expanded]' ); for (const button of buttons) { if (!candidates.includes(button)) { candidates.push(button); } } } return candidates.find((button) => isVisibleElement(button) && extractDialCodeFromText(getActionText(button))) || candidates.find(isVisibleElement) || null; } function getSignupPhoneDisplayedDialCode(phoneInput = getSignupPhoneInput()) { const buttonDialCode = extractDialCodeFromText(getSignupPhoneCountryButtonText(phoneInput)); if (buttonDialCode) { return buttonDialCode; } const inputRoot = phoneInput?.closest?.('fieldset, form, [data-rac], div') || document; const visibleText = String(inputRoot?.textContent || '').replace(/\s+/g, ' ').trim(); const rootDialCode = extractDialCodeFromText(visibleText); if (rootDialCode) { return rootDialCode; } const pageDialCode = extractDialCodeFromText(getPageTextSnapshot()); if (pageDialCode) { return pageDialCode; } return ''; } function getSignupPhoneHiddenNumberInput(phoneInput = getSignupPhoneInput()) { const form = getSignupPhoneForm(phoneInput); if (!form || typeof form.querySelector !== 'function') { return null; } return form.querySelector('input[name="phoneNumber"]'); } function resolveSignupPhoneDialCodeFromNumber(phoneNumber = '', texts = []) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.resolveDialCodeFromPhoneNumber === 'function') { return phoneCountryUtils.resolveDialCodeFromPhoneNumber(phoneNumber, texts); } const digits = normalizePhoneDigits(phoneNumber); if (!digits) { return ''; } const textDialCodes = texts .map((text) => normalizePhoneDigits(extractDialCodeFromText(text))) .filter((dialCode) => dialCode && digits.startsWith(dialCode) && digits.length > dialCode.length) .sort((left, right) => right.length - left.length); if (textDialCodes[0]) { return textDialCodes[0]; } const knownDialCodes = [ '1246', '1264', '1268', '1284', '1340', '1345', '1441', '1473', '1649', '1664', '1670', '1671', '1684', '1721', '1758', '1767', '1784', '1809', '1829', '1849', '1868', '1869', '1876', '971', '962', '886', '880', '856', '855', '852', '853', '673', '672', '670', '599', '598', '597', '596', '595', '594', '593', '592', '591', '590', '509', '508', '507', '506', '505', '504', '503', '502', '501', '423', '421', '420', '389', '387', '386', '385', '383', '382', '381', '380', '379', '378', '377', '376', '375', '374', '373', '372', '371', '370', '359', '358', '357', '356', '355', '354', '353', '352', '351', '350', '299', '298', '297', '291', '290', '269', '268', '267', '266', '265', '264', '263', '262', '261', '260', '258', '257', '256', '255', '254', '253', '252', '251', '250', '249', '248', '247', '246', '245', '244', '243', '242', '241', '240', '239', '238', '237', '236', '235', '234', '233', '232', '231', '230', '229', '228', '227', '226', '225', '224', '223', '222', '221', '220', '218', '216', '213', '212', '211', '98', '95', '94', '93', '92', '91', '90', '89', '88', '86', '84', '82', '81', '66', '65', '64', '63', '62', '61', '60', '58', '57', '56', '55', '54', '53', '52', '51', '49', '48', '47', '46', '45', '44', '43', '41', '40', '39', '36', '34', '33', '32', '31', '30', '27', '20', '7', '1', ]; return knownDialCodes.find((code) => digits.startsWith(code) && digits.length > code.length) || ''; } function resolveSignupPhoneTargetDialCode(options = {}, targetOption = null) { const optionDialCode = extractDialCodeFromText(getSignupPhoneOptionLabel(targetOption)); if (optionDialCode) { return optionDialCode; } const countryText = String(options.countryLabel || '').trim(); if (/australia|澳大利亚/i.test(countryText)) return '61'; if (/thailand|泰国/i.test(countryText)) return '66'; if (/vietnam|越南/i.test(countryText)) return '84'; if (/england|united\s*kingdom|great\s*britain|\bbritain\b|英国|英格兰|uk|gb/i.test(countryText)) return '44'; return resolveSignupPhoneDialCodeFromNumber(options.phoneNumber); } function getSignupPhoneCountryTargetLabels(targetOption, options = {}) { const labels = new Set(); const addLabel = (value) => { getSignupCountryLabelAliases(value).forEach((alias) => labels.add(alias)); }; addLabel(options.countryLabel); if (targetOption) { getSignupPhoneCountryMatchLabels(targetOption).forEach(addLabel); } return Array.from(labels); } function doesSignupPhoneCountryTextMatchTarget(text, targetOption, options = {}) { const normalizedText = normalizeSignupCountryLabel(text); if (!normalizedText) { return false; } const labels = getSignupPhoneCountryTargetLabels(targetOption, options); if (labels.some((label) => ( label && ( normalizedText === label || (label.length > 1 && normalizedText.includes(label)) || (normalizedText.length > 2 && label.includes(normalizedText)) ) ))) { return true; } const targetDialCode = resolveSignupPhoneTargetDialCode(options, targetOption); return Boolean(targetDialCode && extractDialCodeFromText(text) === targetDialCode); } function isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options = {}) { const targetDialCode = resolveSignupPhoneTargetDialCode(options, targetOption); const displayedText = getSignupPhoneCountryButtonText(phoneInput); const displayedDialCode = extractDialCodeFromText(displayedText); if (targetDialCode && displayedDialCode) { return displayedDialCode === targetDialCode && (!displayedText || doesSignupPhoneCountryTextMatchTarget(displayedText, targetOption, options)); } if (displayedText && doesSignupPhoneCountryTextMatchTarget(displayedText, targetOption, options)) { return true; } const selectedOption = getSignupPhoneSelectedCountryOption(phoneInput); if (selectedOption && targetOption && isSameSignupCountryOption(selectedOption, targetOption)) { return !displayedDialCode || !targetDialCode || displayedDialCode === targetDialCode; } return Boolean(selectedOption && !targetOption && targetDialCode && displayedDialCode === targetDialCode); } function findSignupPhoneCountryOptionByLabel(phoneInput, countryLabel) { const select = getSignupPhoneCountrySelect(phoneInput); if (!select) { return null; } const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.findOptionByCountryLabel === 'function') { return phoneCountryUtils.findOptionByCountryLabel(select.options, countryLabel, { document: typeof document !== 'undefined' ? document : null, navigator: (typeof self !== 'undefined' ? self : globalThis)?.navigator || globalThis?.navigator || null, getOptionLabel: getSignupPhoneOptionLabel, }); } const normalizedTargets = getSignupCountryLabelAliases(countryLabel); if (normalizedTargets.length === 0) { return null; } const options = Array.from(select.options || []); return options.find((option) => ( getSignupPhoneCountryMatchLabels(option).some((label) => normalizedTargets.includes(normalizeSignupCountryLabel(label))) )) || options.find((option) => { const normalizedLabels = getSignupPhoneCountryMatchLabels(option) .map((label) => normalizeSignupCountryLabel(label)) .filter(Boolean); return normalizedLabels.some((optionLabel) => normalizedTargets.some((normalizedTarget) => ( optionLabel.length > 2 && normalizedTarget.length > 2 && (optionLabel.includes(normalizedTarget) || normalizedTarget.includes(optionLabel)) ))); }) || null; } function findSignupPhoneCountryOptionByPhoneNumber(phoneInput, phoneNumber) { const select = getSignupPhoneCountrySelect(phoneInput); if (!select) { return null; } const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.findOptionByPhoneNumber === 'function') { return phoneCountryUtils.findOptionByPhoneNumber(select.options, phoneNumber, { getOptionLabel: getSignupPhoneOptionLabel, }); } const digits = normalizePhoneDigits(phoneNumber); if (!digits) { return null; } let bestMatch = null; let bestDialCodeLength = 0; for (const option of Array.from(select.options || [])) { const dialCode = normalizePhoneDigits(extractDialCodeFromText(getSignupPhoneOptionLabel(option))); if (!dialCode || !digits.startsWith(dialCode)) { continue; } if (dialCode.length > bestDialCodeLength) { bestMatch = option; bestDialCodeLength = dialCode.length; } } return bestMatch; } async function trySelectSignupPhoneCountryOption(select, targetOption, phoneInput = getSignupPhoneInput(), options = {}) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; if (!select || !targetOption) { return false; } const selectedOption = select.selectedIndex >= 0 ? (select.options?.[select.selectedIndex] || null) : null; if (selectedOption && isSameSignupCountryOption(selectedOption, targetOption)) { await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'select', label: 'signup-phone-country-select' }, async () => { dispatchSignupPhoneFieldEvents(select); }); await sleep(120); return isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options); } await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'select', label: 'signup-phone-country-select' }, async () => { select.value = String(targetOption.value || ''); dispatchSignupPhoneFieldEvents(select); }); await sleep(250); return isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options); } function getVisibleSignupPhoneCountryListboxOptions() { const seen = new Set(); return Array.from(document.querySelectorAll('[role="listbox"] [role="option"], [role="option"]')) .filter((option) => { if (!option || seen.has(option)) { return false; } seen.add(option); return isVisibleElement(option); }); } function findSignupPhoneCountryListboxOption(targetOption, options = {}) { const candidates = getVisibleSignupPhoneCountryListboxOptions(); const byLabel = candidates.find((option) => doesSignupPhoneCountryTextMatchTarget(getActionText(option), targetOption, options)); if (byLabel) { return byLabel; } const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.findElementByDialCode === 'function') { const byPhoneNumber = phoneCountryUtils.findElementByDialCode(candidates, options.phoneNumber, { getText: getActionText, }); if (byPhoneNumber) { return byPhoneNumber; } } const targetDialCode = resolveSignupPhoneTargetDialCode(options, targetOption); if (!targetDialCode) { const digits = normalizePhoneDigits(options.phoneNumber); let bestMatch = null; let bestDialCodeLength = 0; for (const option of candidates) { const dialCode = normalizePhoneDigits(extractDialCodeFromText(getActionText(option))); if (!dialCode || !digits.startsWith(dialCode) || dialCode.length <= bestDialCodeLength) { continue; } bestMatch = option; bestDialCodeLength = dialCode.length; } return bestMatch; } return candidates.find((option) => extractDialCodeFromText(getActionText(option)) === targetDialCode) || null; } async function trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption, options = {}) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const button = getSignupPhoneCountryButton(phoneInput); if (!button) { return false; } const getScrollableTargets = () => { const seen = new Set(); const targets = []; const pushTarget = (element) => { if (!element || seen.has(element)) { return; } seen.add(element); const scrollHeight = Number(element.scrollHeight) || 0; const clientHeight = Number(element.clientHeight) || 0; if (scrollHeight > clientHeight + 2) { targets.push(element); } }; getVisibleSignupPhoneCountryListboxOptions().forEach((option) => { let current = option.parentElement || null; let depth = 0; while (current && depth < 6) { pushTarget(current); if (current === document.body || current === document.documentElement) { break; } current = current.parentElement || null; depth += 1; } }); Array.from(document.querySelectorAll('[role="listbox"]')) .filter((listbox) => isVisibleElement(listbox)) .forEach(pushTarget); return targets; }; const dispatchListboxScroll = (element) => { if (!element || typeof element.dispatchEvent !== 'function') { return; } try { element.dispatchEvent(typeof Event === 'function' ? new Event('scroll', { bubbles: true }) : { type: 'scroll' }); } catch { try { element.dispatchEvent({ type: 'scroll' }); } catch { } } }; const resetListboxScroll = () => { getScrollableTargets().forEach((target) => { if ((Number(target.scrollTop) || 0) > 0) { target.scrollTop = 0; dispatchListboxScroll(target); } }); }; const scrollListboxDown = () => { let scrolled = false; getScrollableTargets().forEach((target) => { const before = Number(target.scrollTop) || 0; const maxScrollTop = Math.max(0, (Number(target.scrollHeight) || 0) - (Number(target.clientHeight) || 0)); if (maxScrollTop <= before + 1) { return; } const step = Math.max(360, Math.floor((Number(target.clientHeight) || 0) * 0.85)); target.scrollTop = Math.min(maxScrollTop, before + step); dispatchListboxScroll(target); scrolled = true; }); return scrolled; }; await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'open-signup-phone-country-listbox' }, async () => { simulateClick(button); }); await sleep(200); resetListboxScroll(); const start = Date.now(); let reachedListEndAt = 0; while (Date.now() - start < 8000) { throwIfStopped(); const option = findSignupPhoneCountryListboxOption(targetOption, options); if (option) { await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'select', label: 'signup-phone-country-listbox-option' }, async () => { simulateClick(option); }); await sleep(450); if (isSignupPhoneCountrySelectionSynced(phoneInput, targetOption, options)) { return true; } } if (!scrollListboxDown()) { reachedListEndAt += 1; if (reachedListEndAt >= 6) { break; } await sleep(150); continue; } reachedListEndAt = 0; await sleep(220); } return false; } async function ensureSignupPhoneCountrySelected(phoneInput, options = {}) { const select = getSignupPhoneCountrySelect(phoneInput); const hasCountryControl = Boolean(select || getSignupPhoneCountryButton(phoneInput)); if (!hasCountryControl) { return { hasSelect: false, hasCountryControl: false, matched: false, selectedOption: null, }; } const byLabel = findSignupPhoneCountryOptionByLabel(phoneInput, options.countryLabel); const byPhoneNumber = findSignupPhoneCountryOptionByPhoneNumber(phoneInput, options.phoneNumber); const targets = [byLabel, byPhoneNumber, null].filter((target, index, list) => ( index === list.findIndex((item) => ( (!item && !target) || (item && target && isSameSignupCountryOption(item, target)) )) )); for (const targetOption of targets) { if (await trySelectSignupPhoneCountryOption(select, targetOption, phoneInput, options)) { return { hasSelect: Boolean(select), hasCountryControl: true, matched: true, selectedOption: getSignupPhoneSelectedCountryOption(phoneInput), }; } if (await trySelectSignupPhoneCountryListboxOption(phoneInput, targetOption, options)) { return { hasSelect: Boolean(select), hasCountryControl: true, matched: true, selectedOption: getSignupPhoneSelectedCountryOption(phoneInput), }; } } return { hasSelect: Boolean(select), hasCountryControl: true, matched: false, selectedOption: getSignupPhoneSelectedCountryOption(phoneInput), }; } function toNationalPhoneNumber(value, dialCode) { const digits = normalizePhoneDigits(value); const normalizedDialCode = normalizePhoneDigits(dialCode); const isExplicitInternational = /^\s*(?:\+|00)\s*\d/.test(String(value || '').trim()); if (!digits) { return ''; } if (normalizedDialCode && digits.startsWith(normalizedDialCode) && digits.length > normalizedDialCode.length) { return digits.slice(normalizedDialCode.length); } if (isExplicitInternational) { return digits; } return digits; } function toE164PhoneNumber(value, dialCode) { const digits = normalizePhoneDigits(value); const normalizedDialCode = normalizePhoneDigits(dialCode); const isExplicitInternational = /^\s*(?:\+|00)\s*\d/.test(String(value || '').trim()); if (!digits) { return ''; } if (isExplicitInternational) { return `+${digits}`; } if (!normalizedDialCode) { return `+${digits}`; } if (digits.startsWith(normalizedDialCode)) { return `+${digits}`; } if (digits.startsWith('0')) { return `+${normalizedDialCode}${digits.slice(1)}`; } return `+${normalizedDialCode}${digits}`; } function getPhoneInputRenderedValue(phoneInput) { return String(phoneInput?.value ?? phoneInput?.getAttribute?.('value') ?? '').trim(); } function isPhoneInputValueVerified(actualValue, expectedValue, options = {}) { const actualDigits = normalizePhoneDigits(actualValue); const expectedDigits = normalizePhoneDigits(expectedValue); if (!actualDigits || !expectedDigits) { return false; } if (actualDigits === expectedDigits) { return true; } const dialDigits = normalizePhoneDigits(options.dialCode); const fullDigits = normalizePhoneDigits(options.phoneNumber); if (fullDigits && actualDigits === fullDigits) { return true; } if (!dialDigits) { return false; } if (actualDigits === `${dialDigits}${expectedDigits}`) { return true; } const localDigits = fullDigits && fullDigits.startsWith(dialDigits) ? fullDigits.slice(dialDigits.length) : expectedDigits; return dialDigits === '44' && actualDigits === `${dialDigits}0${localDigits}`; } async function waitForPhoneInputValue(phoneInput, expectedValue, options = {}) { const { timeout = 1800, pollInterval = 100, resolvePhoneInput = null, phoneNumber = '', dialCode = '', } = options; const startedAt = Date.now(); let currentInput = phoneInput; while (Date.now() - startedAt < timeout) { throwIfStopped(); currentInput = (typeof resolvePhoneInput === 'function' && resolvePhoneInput()) || currentInput; if (isPhoneInputValueVerified(getPhoneInputRenderedValue(currentInput), expectedValue, { phoneNumber, dialCode })) { return { ok: true, input: currentInput, value: getPhoneInputRenderedValue(currentInput), }; } await sleep(pollInterval); } currentInput = (typeof resolvePhoneInput === 'function' && resolvePhoneInput()) || currentInput; return { ok: false, input: currentInput, value: getPhoneInputRenderedValue(currentInput), }; } function formatPhoneHiddenFormValue({ phoneNumber = '', dialCode = '', inputValue = '' } = {}) { const fullDigits = normalizePhoneDigits(phoneNumber); if (fullDigits) { return `+${fullDigits}`; } const localDigits = normalizePhoneDigits(inputValue); if (!localDigits) { return ''; } const dialDigits = normalizePhoneDigits(dialCode); return dialDigits ? `+${dialDigits}${localDigits}` : localDigits; } function getPhoneHiddenValueInput(phoneInput) { if (typeof getLoginPhoneHiddenValueInput === 'function') { const loginHiddenInput = getLoginPhoneHiddenValueInput(phoneInput); if (loginHiddenInput) { return loginHiddenInput; } } const form = phoneInput?.form || phoneInput?.closest?.('form') || null; const root = form || phoneInput?.closest?.('fieldset, form, [data-rac], div') || document; const candidates = Array.from(root?.querySelectorAll?.('input[name="phone"], input[name="phoneNumber"], input[type="hidden"][id*="phone" i]') || []); return candidates.find((input) => { if (!input || input === phoneInput) return false; const type = String(input.getAttribute?.('type') || input.type || '').trim().toLowerCase(); return type === 'hidden' || !isVisibleElement(input); }) || null; } function setPhoneHiddenValue(input, value) { const normalizedValue = String(value || ''); try { const nativeInputValueSetter = typeof window !== 'undefined' ? Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set : null; if (nativeInputValueSetter) { nativeInputValueSetter.call(input, normalizedValue); } else { input.value = normalizedValue; } } catch { input.value = normalizedValue; } input.dispatchEvent?.(new Event('input', { bubbles: true })); input.dispatchEvent?.(new Event('change', { bubbles: true })); } function syncPhoneHiddenFormValue(phoneInput, options = {}) { const hiddenInput = getPhoneHiddenValueInput(phoneInput); const hiddenValue = formatPhoneHiddenFormValue(options); if (!hiddenInput || !hiddenValue) { return null; } setPhoneHiddenValue(hiddenInput, hiddenValue); return { input: hiddenInput, value: hiddenInput.value || '', }; } function isPhoneInputValueComplete(phoneInput, phoneNumber, dialCode, expectedLocalNumber = '') { return isPhoneInputValueVerified(getPhoneInputRenderedValue(phoneInput), expectedLocalNumber || toNationalPhoneNumber(phoneNumber, dialCode), { phoneNumber, dialCode, }); } function getLoginPhoneFillCandidates(phoneNumber, dialCode, phoneInput = null) { const inputValue = toNationalPhoneNumber(phoneNumber, dialCode); const e164Value = toE164PhoneNumber(phoneNumber, dialCode); const dialDigits = normalizePhoneDigits(dialCode); const currentRenderedValue = getPhoneInputRenderedValue(phoneInput); const currentDigits = normalizePhoneDigits(currentRenderedValue); const shouldKeepDialPrefix = Boolean( e164Value && ( String(currentRenderedValue || '').trim().startsWith('+') || (dialDigits && currentDigits === dialDigits) ) ); const candidates = []; const addCandidate = (value) => { const normalizedValue = String(value || '').trim(); if (normalizedValue && !candidates.includes(normalizedValue)) { candidates.push(normalizedValue); } }; if (shouldKeepDialPrefix) { addCandidate(e164Value); } addCandidate(inputValue); addCandidate(e164Value); return candidates; } function getLoginPhoneSubmitButtonDiagnostics(button) { if (!button) { return { present: false, }; } return { present: true, tag: (button.tagName || '').toLowerCase(), type: String(button.getAttribute?.('type') || button.type || '').trim().toLowerCase(), text: getActionText(button).slice(0, 80), visible: isVisibleElement(button), enabled: isActionEnabled(button), disabled: Boolean(button.disabled), ariaDisabled: String(button.getAttribute?.('aria-disabled') || '').trim(), }; } function getLoginPhoneInputCandidateDiagnostics(limit = 12) { return collectPhoneInputCandidates('input', { allowGenericText: true }).slice(0, limit); } async function fillLoginPhoneInputAndConfirm(phoneInput, options = {}) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const { phoneNumber = '', dialCode = '', visibleStep = 7, resolvePhoneInput = null, maxAttempts = 3, } = options; const inputValue = toNationalPhoneNumber(phoneNumber, dialCode); if (!inputValue) { throw new Error(`\u6b65\u9aa4 ${visibleStep}\uff1a\u624b\u673a\u53f7\u4e3a\u7a7a\uff0c\u65e0\u6cd5\u586b\u5199\u3002`); } let currentInput = phoneInput; let lastVerification = { ok: false, input: currentInput, value: getPhoneInputRenderedValue(currentInput) }; for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { throwIfStopped(); currentInput = (typeof resolvePhoneInput === 'function' && resolvePhoneInput()) || currentInput; if (!currentInput) { break; } const fillCandidates = getLoginPhoneFillCandidates(phoneNumber, dialCode, currentInput); for (const attemptedValue of fillCandidates) { currentInput.focus?.(); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-phone-number' }, async () => { fillInput(currentInput, attemptedValue); }); lastVerification = await waitForPhoneInputValue(currentInput, inputValue, { resolvePhoneInput, phoneNumber, dialCode, timeout: 1600, pollInterval: 100, }); if (lastVerification.ok) { const verifiedInput = lastVerification.input || currentInput; const hiddenSync = await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'hidden-sync', label: 'login-phone-hidden-sync' }, async () => ( syncPhoneHiddenFormValue(verifiedInput, { phoneNumber, dialCode, inputValue }) )); const expectedHiddenDigits = normalizePhoneDigits(phoneNumber) || `${normalizePhoneDigits(dialCode)}${normalizePhoneDigits(inputValue)}`; if (hiddenSync && expectedHiddenDigits && normalizePhoneDigits(hiddenSync.value) !== expectedHiddenDigits) { throw new Error(`\u6b65\u9aa4 ${visibleStep}\uff1a\u624b\u673a\u53f7\u9690\u85cf\u63d0\u4ea4\u5b57\u6bb5\u540c\u6b65\u5931\u8d25\uff0c\u671f\u671b ${expectedHiddenDigits}\uff0c\u5b9e\u9645 ${normalizePhoneDigits(hiddenSync.value) || '\u7a7a'}\u3002`); } log( `\u6b65\u9aa4 ${visibleStep}\uff1a\u624b\u673a\u53f7\u8f93\u5165\u6821\u9a8c\u901a\u8fc7 ${JSON.stringify({ attemptedValue, renderedValue: lastVerification.value, input: getLoginPhoneInputDiagnostics(verifiedInput), hidden: getLoginPhoneHiddenValueDiagnostics(hiddenSync?.input || getPhoneHiddenValueInput(verifiedInput)), })}`, 'info', { step: visibleStep, stepKey: 'oauth-login' } ); return { input: verifiedInput, inputValue, attemptedValue, renderedValue: lastVerification.value, hiddenInput: hiddenSync?.input || null, hiddenValue: hiddenSync?.value || '', }; } } const currentDigits = normalizePhoneDigits(lastVerification.value); log( `\u6b65\u9aa4 ${visibleStep}\uff1a\u624b\u673a\u53f7\u8f93\u5165\u6846\u672a\u7a33\u5b9a\u5199\u5165\uff08\u7b2c ${attempt}/${maxAttempts} \u6b21\uff09\uff0c\u671f\u671b\u672c\u5730\u53f7 ${inputValue}\uff0c\u5f53\u524d\u503c ${currentDigits || '\u7a7a'}\uff0c\u51c6\u5907\u91cd\u8bd5\u3002`, 'warn', { step: visibleStep, stepKey: 'oauth-login' } ); await sleep(200); } const actualDigits = normalizePhoneDigits(lastVerification.value); throw new Error(`\u6b65\u9aa4 ${visibleStep}\uff1a\u624b\u673a\u53f7\u586b\u5199\u540e\u6821\u9a8c\u5931\u8d25\uff0c\u5b8c\u6574\u53f7\u7801 ${phoneNumber}\uff0c\u533a\u53f7 +${dialCode || '\u672a\u8bc6\u522b'}\uff0c\u671f\u671b\u8f93\u5165\u672c\u5730\u53f7 ${inputValue}\uff0c\u5b9e\u9645\u8f93\u5165\u6846\u4e3a ${actualDigits || '\u7a7a'}\uff0c\u5df2\u505c\u6b62\u63d0\u4ea4\u3002`); } function resolveSignupPhoneDialCode(phoneInput, options = {}) { const { phoneNumber = '', countryLabel = '' } = options; const displayedDialCode = getSignupPhoneDisplayedDialCode(phoneInput); if (displayedDialCode) { return displayedDialCode; } const countryText = String(countryLabel || '').trim(); if (/australia|澳大利亚/i.test(countryText)) return '61'; if (/thailand|泰国/i.test(countryText)) return '66'; if (/vietnam|越南/i.test(countryText)) return '84'; if (/england|united kingdom|英国|uk/i.test(countryText)) return '44'; const digits = normalizePhoneDigits(phoneNumber); const knownDialCodes = ['66', '84', '61', '44', '1', '81', '82', '86', '852', '855', '856', '60', '62', '63', '65']; return knownDialCodes.find((code) => digits.startsWith(code) && digits.length > code.length) || ''; } async function waitForSignupPhoneEntryState(options = {}) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const { timeout = 20000, step = 2, } = options; const start = Date.now(); const maxSignupEntryClickRetries = 5; const maxSignupEntryClickAttempts = maxSignupEntryClickRetries + 1; let lastTriggerClickAt = 0; let clickAttempts = 0; let lastSwitchToPhoneAt = 0; let lastMoreOptionsClickAt = 0; let slowSnapshotLogged = false; while (Date.now() - start < timeout) { throwIfStopped(); const snapshot = inspectSignupEntryState(); if (snapshot.state === 'password_page') { return snapshot; } if (snapshot.state === 'phone_entry' && snapshot.phoneInput) { return snapshot; } if (snapshot.state === 'email_entry') { const switchToPhone = snapshot.switchToPhoneTrigger || findSignupUsePhoneTrigger(); if (switchToPhone && Date.now() - lastSwitchToPhoneAt >= 1500) { lastSwitchToPhoneAt = Date.now(); log(`步骤 ${step}:检测到邮箱输入模式,正在切换到手机号注册入口...`); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'switch-to-signup-phone' }, async () => { simulateClick(switchToPhone); }); } else { const moreOptionsTrigger = findSignupMoreOptionsTrigger(); if (moreOptionsTrigger && Date.now() - lastMoreOptionsClickAt >= 1500) { lastMoreOptionsClickAt = Date.now(); log(`步骤 ${step}:手机号入口可能隐藏在更多选项中,正在展开更多选项...`); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'signup-phone-more-options' }, async () => { simulateClick(moreOptionsTrigger); }); } else if (!switchToPhone && !slowSnapshotLogged && Date.now() - start >= 5000) { slowSnapshotLogged = true; log(`步骤 ${step}:尚未找到手机号入口,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } } await sleep(250); continue; } if (snapshot.state === 'entry_home' && snapshot.signupTrigger) { if (Date.now() - lastTriggerClickAt >= 1500) { if (clickAttempts >= maxSignupEntryClickAttempts) { log(`步骤 ${step}:官网注册入口已完成 ${maxSignupEntryClickRetries} 次重试,页面仍未进入手机号输入页,停止重试。`, 'warn'); return snapshot; } lastTriggerClickAt = Date.now(); clickAttempts += 1; const retryAttempt = clickAttempts - 1; log(retryAttempt > 0 ? `步骤 ${step}:上次点击后仍未进入手机号输入页,等待 3 秒后重试点击官网注册入口(重试 ${retryAttempt}/${maxSignupEntryClickRetries})...` : `步骤 ${step}:已找到官网注册入口,等待 3 秒后点击...`); await sleep(3000); throwIfStopped(); const clickTarget = findSignupEntryTrigger({ allowHiddenFallback: false }) || snapshot.signupTrigger; if (!isVisibleElement(clickTarget)) { log(`步骤 ${step}:注册入口仍处于不可见状态,继续按入口重试节奏尝试恢复点击...`, 'warn'); } await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'click', label: 'open-signup-entry' }, async () => { simulateClick(clickTarget); }); } await sleep(250); continue; } if (!slowSnapshotLogged && Date.now() - start >= 5000) { slowSnapshotLogged = true; log(`步骤 ${step}:等待手机号注册入口超过 5 秒,页面诊断快照:${JSON.stringify(getSignupEntryDiagnostics())}`, 'warn'); } await sleep(250); } const finalSnapshot = inspectSignupEntryState(); log(`步骤 ${step}:等待手机号注册入口超时,最终状态快照:${JSON.stringify(getSignupEntryStateSummary(finalSnapshot))}`, 'warn'); return finalSnapshot; } async function submitSignupPhoneNumberAndContinue(payload = {}) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const phoneNumber = String(payload.phoneNumber || '').trim(); const countryLabel = String(payload.countryLabel || '').trim(); if (!phoneNumber) { throw new Error('未提供手机号,步骤 2 无法继续。'); } const snapshot = await waitForSignupPhoneEntryState({ timeout: 25000, step: 2 }); if (snapshot.state === 'password_page') { log('步骤 2:当前已在密码页,无需重复提交手机号。'); return { alreadyOnPasswordPage: true, url: snapshot.url || location.href, }; } if (snapshot.state !== 'phone_entry' || !snapshot.phoneInput) { throw new Error(`步骤 2:未找到可用的手机号输入入口。URL: ${location.href}`); } const countrySelection = await ensureSignupPhoneCountrySelected(snapshot.phoneInput, { countryLabel, phoneNumber, }); if (countrySelection.hasCountryControl && !countrySelection.matched) { const currentCountryText = getSignupPhoneCountryButtonText(snapshot.phoneInput) || '未知'; const targetDialCode = resolveSignupPhoneTargetDialCode({ countryLabel, phoneNumber }, countrySelection.selectedOption); const targetLabel = targetDialCode ? `目标区号 +${targetDialCode}(号码 ${phoneNumber}${countryLabel ? `,国家 ${countryLabel}` : ''})` : (countryLabel || phoneNumber); throw new Error(`步骤 2:手机号国家下拉框未能自动切换到 ${targetLabel},当前显示为 ${currentCountryText},已停止提交以避免区号不匹配。`); } const dialCode = resolveSignupPhoneDialCode(snapshot.phoneInput, { phoneNumber, countryId: payload.countryId, countryLabel, }); const inputValue = toNationalPhoneNumber(phoneNumber, dialCode); if (!inputValue) { throw new Error('步骤 2:手机号为空,无法填写。'); } log(`步骤 2:正在填写手机号:${phoneNumber}`); await humanPause(500, 1400); await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'fill', label: 'signup-phone-number' }, async () => { fillInput(snapshot.phoneInput, inputValue); }); const hiddenPhoneNumberInput = getSignupPhoneHiddenNumberInput(snapshot.phoneInput); const e164PhoneNumber = toE164PhoneNumber(phoneNumber, dialCode); if (hiddenPhoneNumberInput && e164PhoneNumber) { await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'hidden-sync', label: 'signup-phone-hidden-sync' }, async () => { fillInput(hiddenPhoneNumberInput, e164PhoneNumber); }); } log(`步骤 2:手机号已填写:${phoneNumber}${dialCode ? `(区号 +${dialCode},本地号 ${inputValue})` : ''}`); const continueButton = getSignupEmailContinueButton({ allowDisabled: true }); if (!continueButton || !isActionEnabled(continueButton)) { throw new Error(`步骤 2:未找到可点击的“继续”按钮。URL: ${location.href}`); } log('步骤 2:手机号已准备提交,正在前往下一页...'); window.setTimeout(async () => { try { throwIfStopped(); await performOperationWithDelay({ stepKey: 'signup-phone-entry', kind: 'submit', label: 'submit-signup-phone' }, async () => { simulateClick(continueButton); }); } catch (error) { if (!isStopError(error)) { console.error('[MultiPage:openai-auth] deferred signup phone submit failed:', error?.message || error); } } }, 120); return { submitted: true, deferredSubmit: true, phoneNumber, phoneInputValue: snapshot.phoneInput?.value || inputValue, url: location.href, }; } // ============================================================ // Step 2: Click Register, fill email, then continue to password page // ============================================================ async function step2_clickRegister(payload = {}) { if (payload?.signupMethod === 'phone' || payload?.phoneNumber) { return submitSignupPhoneNumberAndContinue(payload); } const { email } = payload; return fillSignupEmailAndContinue(email, 2); } // ============================================================ // Step 3: Fill Password // ============================================================ async function step3_fillEmailPassword(payload) { const resolvePasswordPageInfo = (rawUrl = '') => { const url = String(rawUrl || '').trim() || String(location.href || ''); let path = String(location.pathname || '').trim(); try { path = new URL(url, 'https://auth.openai.com').pathname || path; } catch { // Keep the current pathname fallback; this only tags the completion payload. } const mode = /\/log-in\/password(?:[/?#]|$)/i.test(path) ? 'login' : (/\/(?:create-account|u\/signup|signup)\/password(?:[/?#]|$)/i.test(path) ? 'signup' : ''); return { url, path, mode }; }; const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const { email, password } = payload; if (!password) throw new Error('未提供密码,步骤 3 需要可用密码。'); const normalizedEmail = String(email || '').trim().toLowerCase(); const accountIdentifierType = String(payload?.accountIdentifierType || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'; const accountIdentifier = String(payload?.accountIdentifier || email || payload?.phoneNumber || '').trim(); let snapshot = inspectSignupEntryState(); if (snapshot.state === 'entry_home') { throw new Error('当前仍停留在 ChatGPT 官网首页,请先完成步骤 2。'); } if ( snapshot.state === 'phone_verification_page' || snapshot.state === 'verification_page' || snapshot.state === 'profile_page' || snapshot.state === 'logged_in_home' ) { const completionPayload = { email: email || '', phoneNumber: String(payload?.phoneNumber || '').trim(), accountIdentifierType, accountIdentifier, signupVerificationRequestedAt: ( snapshot.state === 'phone_verification_page' || snapshot.state === 'verification_page' ) ? Date.now() : null, skippedPasswordPage: true, deferredSubmit: false, ...(snapshot.skipProfileStep ? { skipProfileStep: true } : {}), }; log('步骤 3:当前页面已进入验证码或后续阶段,密码页按已跳过处理。', 'warn'); reportComplete(3, completionPayload); return completionPayload; } if (snapshot.state === 'email_entry') { const transition = await fillSignupEmailAndContinue(email, 3); if (!transition.alreadyOnPasswordPage) { await sleep(1200); await ensureSignupPasswordPageReady(); } snapshot = inspectSignupEntryState(); } if (snapshot.state !== 'password_page' || !snapshot.passwordInput) { await ensureSignupPasswordPageReady(); snapshot = inspectSignupEntryState(); } if (snapshot.state !== 'password_page' || !snapshot.passwordInput) { logSignupPasswordDiagnostics('步骤 3:未能识别可填写的密码输入框'); } if (snapshot.state !== 'password_page' || !snapshot.passwordInput) { throw new Error('在密码页未找到密码输入框。URL: ' + location.href); } if (normalizedEmail && snapshot.displayedEmail && snapshot.displayedEmail !== normalizedEmail) { throw new Error(`当前密码页邮箱为 ${snapshot.displayedEmail},与目标邮箱 ${email} 不一致,请先回到步骤 1 重新开始。`); } const passwordPageInfo = resolvePasswordPageInfo(snapshot.url || location.href); await humanPause(600, 1500); await performOperationWithDelay({ stepKey: 'fill-password', kind: 'fill', label: 'signup-password' }, async () => { fillInput(snapshot.passwordInput, password); }); log('步骤 3:密码已填写'); const submitBtn = snapshot.submitButton || getSignupPasswordSubmitButton({ allowDisabled: true }) || await waitForElementByText('button', /continue|sign\s*up|submit|注册|创建|続行|続ける|次へ|サインアップ|登録|作成|create/i, 5000).catch(() => null); if (!submitBtn) { logSignupPasswordDiagnostics('步骤 3:未找到可提交的密码页按钮'); } else if (typeof findOneTimeCodeLoginTrigger === 'function' && findOneTimeCodeLoginTrigger()) { logSignupPasswordDiagnostics('步骤 3:当前密码页同时存在一次性验证码入口', 'info'); } const isLoginPasswordPage = passwordPageInfo.mode === 'login'; const signupVerificationRequestedAt = submitBtn && !isLoginPasswordPage ? Date.now() : null; const completionPayload = { email, phoneNumber: String(payload?.phoneNumber || '').trim(), accountIdentifierType, accountIdentifier, signupVerificationRequestedAt, deferredSubmit: Boolean(submitBtn), passwordPageUrl: passwordPageInfo.url, passwordPagePath: passwordPageInfo.path, passwordPageMode: passwordPageInfo.mode, ...(isLoginPasswordPage ? { passwordLoginFlow: true } : {}), }; reportComplete(3, completionPayload); if (submitBtn) { window.setTimeout(async () => { try { throwIfStopped(); await sleep(500); await humanPause(500, 1300); await performOperationWithDelay({ stepKey: 'fill-password', kind: 'submit', label: 'submit-signup-password' }, async () => { simulateClick(submitBtn); }); log('步骤 3:表单已提交'); } catch (error) { if (!isStopError(error)) { console.error('[MultiPage:openai-auth] deferred step 3 submit failed:', error?.message || error); } } }, 120); } return completionPayload; } // ============================================================ // Fill Verification Code (used by step 4 and step 7) // ============================================================ const INVALID_VERIFICATION_CODE_PATTERN = /代码不正确|验证码不正确|验证码错误|コードが正しくありません|コードが間違っています|無効なコード|認証コードが正しくありません|確認コードが正しくありません|code\s+(?:is\s+)?incorrect|invalid\s+code|incorrect\s+code|try\s+again/i; const VERIFICATION_PAGE_PATTERN = /检查您的收件箱|输入我们刚刚向|重新发送电子邮件|重新发送验证码|代码不正确|受信(?:トレイ|箱)を確認|(?:認証|確認)?コードを入力|送信した(?:認証|確認)?コード|メールで送信|再送信|email\s+verification|check\s+your\s+inbox|enter\s+the\s+code|we\s+just\s+sent|we\s+emailed|resend/i; const OAUTH_CONSENT_PAGE_PATTERN = /使用\s*ChatGPT\s*登录到\s*Codex|ChatGPT\s*(?:を使用して|で)\s*Codex\s*(?:に)?(?:ログイン|サインイン)|Codex\s*(?:に)?(?:ログイン|サインイン)|認可|承認|sign\s+in\s+to\s+codex(?:\s+with\s+chatgpt)?|login\s+to\s+codex|log\s+in\s+to\s+codex|authorize|授权/i; const OAUTH_CONSENT_FORM_SELECTOR = 'form[action*="/sign-in-with-chatgpt/" i][action*="/consent" i]'; const CONTINUE_ACTION_PATTERN = /继续|続行|続ける|次へ|continue/i; const ADD_PHONE_PAGE_PATTERN = /add[\s-]*(?:a\s+)?phone|添加(?:手机|手机号|电话号码)|绑定(?:手机|手机号|电话号码)|验证(?:你的|您)?(?:手机|手机号|电话号码)|需要(?:手机|手机号|电话号码)|提供(?:手机|手机号|电话号码)|電話番号(?:を)?(?:追加|登録|確認|認証)|電話番号が必要|携帯(?:電話)?番号(?:を)?(?:追加|登録|確認|認証)|provide\s+(?:a\s+)?phone\s+number|phone\s+number\s+(?:required|verification)|verify\s+(?:your\s+)?phone|confirm\s+(?:your\s+)?phone/i; const ADD_EMAIL_PAGE_PATTERN = /add[\s-]*email|添加(?:电子邮件|邮箱)|要求提供(?:电子邮件|邮箱)地址|提供(?:电子邮件|邮箱)地址|(?:メールアドレス|メール|電子メール)(?:を)?(?:追加|登録|提供)|メールアドレスが必要|provide\s+(?:an?\s+)?email\s+address|email\s+address\s+required/i; const STEP5_SUBMIT_ERROR_PATTERN = /无法根据该信息创建帐户|请重试|アカウントを作成できません|アカウント作成に失敗|もう一度お試し|問題が発生しました|無効な(?:生年月日|誕生日|日付)|生年月日|誕生日|unable\s+to\s+create\s+(?:your\s+)?account|couldn'?t\s+create\s+(?:your\s+)?account|something\s+went\s+wrong|invalid\s+(?:birthday|birth|date)|生日|出生日期/i; const AUTH_TIMEOUT_ERROR_TITLE_PATTERN = /糟糕,出错了|問題が発生しました|エラーが発生しました|something\s+went\s+wrong|oops/i; const AUTH_TIMEOUT_ERROR_DETAIL_PATTERN = /operation\s+timed\s+out|timed\s+out|请求超时|操作超时|タイムアウト|failed\s+to\s+fetch|network\s+error|fetch\s+failed|ネットワークエラー|取得に失敗/i; const AUTH_ROUTE_ERROR_PATTERN = /405\s+method\s+not\s+allowed|route\s+error.*405|did\s+not\s+provide\s+an?\s+[`'"]?action|post\s+request\s+to\s+["']?\/email-verification/i; const STEP4_405_RECOVERY_ERROR_PREFIX = 'STEP4_405_RECOVERY_LIMIT::'; const STEP4_405_RECOVERY_LIMIT = 3; const SIGNUP_USER_ALREADY_EXISTS_ERROR_PREFIX = 'SIGNUP_USER_ALREADY_EXISTS::'; const SIGNUP_PHONE_PASSWORD_MISMATCH_ERROR_PREFIX = 'SIGNUP_PHONE_PASSWORD_MISMATCH::'; const AUTH_MAX_CHECK_ATTEMPTS_ERROR_PREFIX = 'AUTH_MAX_CHECK_ATTEMPTS::'; const STEP8_EMAIL_IN_USE_ERROR_PREFIX = 'STEP8_EMAIL_IN_USE::'; const SIGNUP_EMAIL_EXISTS_PATTERN = /与此电子邮件地址相关联的帐户已存在|この(?:メールアドレス|メール|電子メール)(?:に関連付けられた)?アカウントは(?:既に|すでに)存在|メールアドレス.*(?:既に|すでに)存在|account\s+associated\s+with\s+this\s+email\s+address\s+already\s+exists|email\s+address.*already\s+exists/i; const SIGNUP_PHONE_PASSWORD_MISMATCH_PATTERN = /incorrect\s+phone\s+number\s+or\s+password|phone\s+number\s+or\s+password|電話番号またはパスワード|電話番号.*アカウントは(?:既に|すでに)存在|与此(?:电话|手机)号码相关联的帐户已存在|account\s+associated\s+with\s+this\s+phone\s+number\s+already\s+exists/i; const authPageRecovery = self.MultiPageAuthPageRecovery?.createAuthPageRecovery?.({ detailPattern: AUTH_TIMEOUT_ERROR_DETAIL_PATTERN, getActionText, getPageTextSnapshot, humanPause, isActionEnabled, isVisibleElement, log, routeErrorPattern: AUTH_ROUTE_ERROR_PATTERN, simulateClick, sleep, throwIfStopped, titlePattern: AUTH_TIMEOUT_ERROR_TITLE_PATTERN, }) || null; function getVerificationErrorText() { const messages = []; const selectors = [ '.react-aria-FieldError', '[slot="errorMessage"]', '[id$="-error"]', '[data-invalid="true"] + *', '[aria-invalid="true"] + *', '[class*="error"]', ]; for (const selector of selectors) { document.querySelectorAll(selector).forEach((el) => { const text = (el.textContent || '').replace(/\s+/g, ' ').trim(); if (text) { messages.push(text); } }); } const invalidInput = document.querySelector(`${VERIFICATION_CODE_INPUT_SELECTOR}[aria-invalid="true"], ${VERIFICATION_CODE_INPUT_SELECTOR}[data-invalid="true"]`); if (invalidInput) { const wrapper = invalidInput.closest('form, [data-rac], ._root_18qcl_51, div'); if (wrapper) { const text = (wrapper.textContent || '').replace(/\s+/g, ' ').trim(); if (text) { messages.push(text); } } } return messages.find((text) => INVALID_VERIFICATION_CODE_PATTERN.test(text)) || ''; } function createSignupUserAlreadyExistsError() { return new Error( `${SIGNUP_USER_ALREADY_EXISTS_ERROR_PREFIX}步骤 4:检测到 user_already_exists,说明当前用户已存在,当前轮将直接停止。` ); } function createSignupPhonePasswordMismatchError(detailText = '') { const detail = String(detailText || '').replace(/\s+/g, ' ').trim(); const suffix = detail ? `页面提示:${detail}` : '页面提示注册手机号不可继续使用,需重新开始当前轮。'; return new Error( `${SIGNUP_PHONE_PASSWORD_MISMATCH_ERROR_PREFIX}步骤 3:检测到注册手机号异常,需要重新开始当前轮。${suffix}` ); } function createAuthMaxCheckAttemptsError() { return new Error(`${AUTH_MAX_CHECK_ATTEMPTS_ERROR_PREFIX}max_check_attempts on auth retry page; restart the current auth step without clicking Retry.`); } function createStep8EmailInUseError() { return new Error(`${STEP8_EMAIL_IN_USE_ERROR_PREFIX}email_in_use on add-email verification page; choose a different email.`); } function getVisibleFieldErrorText() { const selectors = [ '.react-aria-FieldError', '[slot="errorMessage"]', '[id$="-error"]', '[data-invalid="true"] + *', '[aria-invalid="true"] + *', '[class*="error"]', '[role="alert"]', ]; for (const selector of selectors) { const match = Array.from(document.querySelectorAll(selector)).find((el) => { if (!isVisibleElement(el)) return false; const text = (el.textContent || '').replace(/\s+/g, ' ').trim(); return Boolean(text); }); if (match) { return (match.textContent || '').replace(/\s+/g, ' ').trim(); } } return ''; } function getSignupPasswordFieldErrorText() { const text = getVisibleFieldErrorText(); if (text && SIGNUP_PHONE_PASSWORD_MISMATCH_PATTERN.test(text)) { return text; } const passwordInput = getSignupPasswordInput(); if (passwordInput) { const wrapper = passwordInput.closest('form, [data-rac], [role="group"], section, div'); const wrapperText = (wrapper?.textContent || '').replace(/\s+/g, ' ').trim(); if (wrapperText && SIGNUP_PHONE_PASSWORD_MISMATCH_PATTERN.test(wrapperText)) { return wrapperText; } } return ''; } function isStep5Ready() { return Boolean( document.querySelector('input[name="name"], input[autocomplete="name"], input[name="birthday"], input[name="age"], [role="spinbutton"][data-type="year"]') ); } function isSignupProfilePageUrl(rawUrl = location.href) { const url = String(rawUrl || '').trim(); if (!url) { return false; } try { const parsed = new URL(url); const host = String(parsed.hostname || '').toLowerCase(); if (!['auth.openai.com', 'auth0.openai.com', 'accounts.openai.com'].includes(host)) { return false; } return /\/(?:create-account\/profile|u\/signup\/profile|signup\/profile|about-you)(?:[/?#]|$)/i.test(String(parsed.pathname || '')); } catch { return false; } } function isLikelyLoggedInChatgptHomeUrl(rawUrl = location.href) { const url = String(rawUrl || '').trim(); if (!url) { return false; } try { const parsed = new URL(url); const host = String(parsed.hostname || '').toLowerCase(); if (!['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com'].includes(host)) { return false; } const path = String(parsed.pathname || ''); if (/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path)) { return false; } const signupTrigger = typeof findSignupEntryTrigger === 'function' ? findSignupEntryTrigger() : null; if (signupTrigger) { return false; } if (typeof document !== 'undefined' && document && typeof document.querySelectorAll === 'function') { const loginActionPattern = /登录|ログイン|サインイン|log\s*in|sign\s*in/i; const candidates = document.querySelectorAll( 'a, button, [role="button"], [role="link"], input[type="button"], input[type="submit"]' ); for (const el of candidates) { const text = typeof getActionText === 'function' ? getActionText(el) : [ el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title'), ] .filter(Boolean) .join(' ') .replace(/\s+/g, ' ') .trim(); if (!text || !loginActionPattern.test(text)) { continue; } const visible = typeof isVisibleElement === 'function' ? isVisibleElement(el) : true; if (!visible) { continue; } const enabled = typeof isActionEnabled === 'function' ? isActionEnabled(el) : (Boolean(el) && !el.disabled && el?.getAttribute?.('aria-disabled') !== 'true'); if (enabled) { return false; } } } return true; } catch { return false; } } function isStep5CompletionChatgptUrl(rawUrl = location.href) { const url = String(rawUrl || '').trim(); if (!url) { return false; } try { const parsed = new URL(url); const protocol = String(parsed.protocol || '').toLowerCase(); const host = String(parsed.hostname || '').toLowerCase(); if (protocol !== 'https:' || !['chatgpt.com', 'www.chatgpt.com'].includes(host)) { return false; } const path = String(parsed.pathname || ''); return !/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path); } catch { return false; } } function getStep4PostVerificationState(options = {}) { const { ignoreVerificationVisibility = false } = options; // Newer auth flows can briefly render profile fields before the email-verification // form fully exits. Do not advance to Step 5 while verification UI is still present. if (!ignoreVerificationVisibility && isVerificationPageStillVisible()) { return null; } if (isStep5Ready() || isSignupProfilePageUrl()) { return { state: 'step5', url: location.href, }; } if (isLikelyLoggedInChatgptHomeUrl()) { return { state: 'logged_in_home', skipProfileStep: true, url: location.href, }; } return null; } function getPageTextSnapshot() { return (document.body?.innerText || document.body?.textContent || '') .replace(/\s+/g, ' ') .trim(); } function getLoginVerificationDisplayedEmail() { const pageText = getPageTextSnapshot(); const matches = pageText.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig) || []; return matches[0] ? String(matches[0]).trim().toLowerCase() : ''; } function getPhoneVerificationDisplayedPhone() { const pageText = getPageTextSnapshot(); const matches = pageText.match(/\+\d[\d\s-]{6,}\d/g) || []; return matches[0] ? String(matches[0]).replace(/\s+/g, ' ').trim() : ''; } function normalizeAuthAccountIdentifier(value) { return String(value || '').replace(/\s+/g, ' ').trim().toLowerCase(); } function getChooseAccountCandidateText(element) { const parts = [ element?.textContent, element?.value, element?.getAttribute?.('aria-label'), element?.getAttribute?.('title'), element?.getAttribute?.('data-testid'), element?.getAttribute?.('data-test-id'), ]; return parts .filter(Boolean) .join(' ') .replace(/\s+/g, ' ') .trim(); } function isChooseAccountPage() { const path = `${location.pathname || ''} ${location.href || ''}`; if (/\/choose-an-account(?:[/?#]|$)/i.test(path)) { return true; } const pageText = getPageTextSnapshot(); if (!CHOOSE_ACCOUNT_PAGE_PATTERN.test(pageText)) { return false; } return Boolean(findChooseAccountButtonForEmail(getLoginVerificationDisplayedEmail())); } function isChooseAccountRemovalAction(element) { if (!element) return false; const text = getChooseAccountCandidateText(element); const role = String(element.getAttribute?.('role') || '').trim().toLowerCase(); return CHOOSE_ACCOUNT_REMOVE_ACTION_PATTERN.test(text) || role === 'menuitem' || role === 'switch'; } function resolveChooseAccountClickTarget(element) { if (!element || isChooseAccountRemovalAction(element)) { return null; } const tag = String(element.tagName || '').trim().toLowerCase(); const role = String(element.getAttribute?.('role') || '').trim().toLowerCase(); const tabIndex = Number(element.getAttribute?.('tabindex') ?? element.tabIndex ?? NaN); const actionable = tag === 'button' || tag === 'a' || role === 'button' || role === 'link' || (Number.isFinite(tabIndex) && tabIndex >= 0); if (actionable && isActionEnabled(element) && isVisibleElement(element)) { return element; } const closestAction = element.closest?.(CHOOSE_ACCOUNT_ACTION_SELECTOR) || null; if ( closestAction && closestAction !== element && isActionEnabled(closestAction) && isVisibleElement(closestAction) && !isChooseAccountRemovalAction(closestAction) ) { return closestAction; } return null; } function resolveChooseAccountCardTarget(element, normalizedEmail = '') { let current = element; let bestTarget = null; while (current && current !== document.body) { if (isChooseAccountRemovalAction(current) || !isVisibleElement(current)) { current = current.parentElement; continue; } const actionTarget = resolveChooseAccountClickTarget(current); if (actionTarget) { return actionTarget; } const text = normalizeAuthAccountIdentifier(getChooseAccountCandidateText(current)); if (text.includes(normalizedEmail) && !CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN.test(text)) { bestTarget = current; } const closestCard = current.closest?.(CHOOSE_ACCOUNT_CARD_SELECTOR) || null; if ( closestCard && closestCard !== current && closestCard !== document.body && isVisibleElement(closestCard) && !isChooseAccountRemovalAction(closestCard) ) { const cardText = normalizeAuthAccountIdentifier(getChooseAccountCandidateText(closestCard)); if (cardText.includes(normalizedEmail) && !CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN.test(cardText)) { const cardActionTarget = resolveChooseAccountClickTarget(closestCard); return cardActionTarget || closestCard; } } current = current.parentElement; } return bestTarget; } function findChooseAccountButtonForEmail(email) { const normalizedEmail = normalizeAuthAccountIdentifier(email); if (!normalizedEmail || !normalizedEmail.includes('@')) { return null; } const candidates = Array.from(document.querySelectorAll(CHOOSE_ACCOUNT_ACTION_SELECTOR)) .filter((element) => isVisibleElement(element) && isActionEnabled(element)); for (const candidate of candidates) { if (isChooseAccountRemovalAction(candidate)) continue; const text = normalizeAuthAccountIdentifier(getChooseAccountCandidateText(candidate)); if (!text || !text.includes(normalizedEmail)) continue; if (CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN.test(text)) continue; const target = resolveChooseAccountClickTarget(candidate); if (target) { return target; } } const emailNodes = Array.from(document.querySelectorAll('body *')) .filter((element) => { if (!isVisibleElement(element)) return false; const text = normalizeAuthAccountIdentifier(getChooseAccountCandidateText(element)); return text.includes(normalizedEmail); }); for (const node of emailNodes) { const target = resolveChooseAccountCardTarget(node, normalizedEmail); if (target) { return target; } } return null; } function findChooseAccountOtherAccountButton() { const candidates = Array.from(document.querySelectorAll(CHOOSE_ACCOUNT_ACTION_SELECTOR)) .filter((element) => isVisibleElement(element) && isActionEnabled(element)); return candidates.find((candidate) => { if (isChooseAccountRemovalAction(candidate)) { return false; } const text = getChooseAccountCandidateText(candidate); return CHOOSE_ACCOUNT_OTHER_ACCOUNT_PATTERN.test(text); }) || null; } function getChooseAccountListedEmails() { const emailPattern = /[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9-]+)+/ig; const emails = new Set(); const nodes = [ ...Array.from(document.querySelectorAll(CHOOSE_ACCOUNT_ACTION_SELECTOR)), ...Array.from(document.querySelectorAll('body *')).filter((element) => isVisibleElement(element)), ]; for (const node of nodes) { if (isChooseAccountRemovalAction(node)) { continue; } const text = getChooseAccountCandidateText(node); for (const match of text.matchAll(emailPattern)) { emails.add(normalizeAuthAccountIdentifier(match[0])); } } return Array.from(emails).filter(Boolean); } async function resolveChooseAccountAction(email, maxRounds = 8) { let otherAccountButton = null; let latestSnapshot = normalizeStep6Snapshot(inspectLoginAuthState()); for (let round = 0; round < maxRounds; round += 1) { throwIfStopped(); latestSnapshot = normalizeStep6Snapshot(inspectLoginAuthState()); if (latestSnapshot.state !== 'unknown' && latestSnapshot.state !== 'choose_account_page') { return { snapshot: latestSnapshot, }; } const target = findChooseAccountButtonForEmail(email); if (target) { return { target, snapshot: latestSnapshot, }; } otherAccountButton = findChooseAccountOtherAccountButton() || otherAccountButton; const listedEmails = getChooseAccountListedEmails(); if (otherAccountButton && round >= 2 && (listedEmails.length > 0 || round >= 4)) { return { otherAccountButton, snapshot: latestSnapshot, }; } if (round < maxRounds - 1) { await sleep(round === 0 ? 300 : 500); } } return { otherAccountButton, snapshot: latestSnapshot, }; } function getOAuthConsentForm() { return document.querySelector(OAUTH_CONSENT_FORM_SELECTOR); } function getPrimaryContinueButton() { const consentForm = getOAuthConsentForm(); if (consentForm) { const formButtons = Array.from( consentForm.querySelectorAll('button[type="submit"], input[type="submit"], [role="button"]') ); const formContinueButton = formButtons.find((el) => { if (!isVisibleElement(el)) return false; const ddActionName = el.getAttribute?.('data-dd-action-name') || ''; return ddActionName === 'Continue' || CONTINUE_ACTION_PATTERN.test(getActionText(el)); }); if (formContinueButton) { return formContinueButton; } const firstVisibleSubmit = formButtons.find(isVisibleElement); if (firstVisibleSubmit) { return firstVisibleSubmit; } } const continueBtn = document.querySelector( `${OAUTH_CONSENT_FORM_SELECTOR} button[type="submit"], button[type="submit"][data-dd-action-name="Continue"], button[type="submit"]._primary_3rdp0_107` ); if (continueBtn && isVisibleElement(continueBtn)) { return continueBtn; } const buttons = document.querySelectorAll('button, [role="button"]'); return Array.from(buttons).find((el) => { if (!isVisibleElement(el)) return false; const ddActionName = el.getAttribute?.('data-dd-action-name') || ''; return ddActionName === 'Continue' || CONTINUE_ACTION_PATTERN.test(getActionText(el)); }) || null; } function isOAuthConsentPage() { const pageText = getPageTextSnapshot(); if (OAUTH_CONSENT_PAGE_PATTERN.test(pageText)) { return true; } if (getOAuthConsentForm()) { return true; } return /\bcodex\b/i.test(pageText) && /\bchatgpt\b/i.test(pageText) && Boolean(getPrimaryContinueButton()); } function isVerificationPageStillVisible() { if (getCurrentAuthRetryPageState('signup_password') || getCurrentAuthRetryPageState('login')) { return false; } if (isPhoneVerificationPageReady()) { return false; } if (getVerificationCodeTarget()) return true; if (findResendVerificationCodeTrigger({ allowDisabled: true })) return true; if (document.querySelector('form[action*="email-verification" i]')) return true; if (!isEmailVerificationPage()) { return false; } return VERIFICATION_PAGE_PATTERN.test(getPageTextSnapshot()); } function isAddPhonePageReady() { const path = `${location.pathname || ''} ${location.href || ''}`; if (/\/add-phone(?:[/?#]|$)/i.test(path)) return true; const addPhoneForm = document.querySelector('form[action*="/add-phone" i]'); if (addPhoneForm && isVisibleElement(addPhoneForm)) { return true; } return ADD_PHONE_PAGE_PATTERN.test(getPageTextSnapshot()); } function isAddEmailPageReady() { const path = `${location.pathname || ''} ${location.href || ''}`; if (/\/add-email(?:[/?#]|$)/i.test(path)) { return true; } const emailInput = getLoginEmailInput(); if (!emailInput) { return false; } const form = emailInput.form || emailInput.closest?.('form') || null; const formAction = String(form?.getAttribute?.('action') || form?.action || ''); if (/\/add-email(?:[/?#]|$)/i.test(formAction)) { return true; } const pageText = getPageTextSnapshot(); return ADD_EMAIL_PAGE_PATTERN.test(pageText) && !/继续使用(?:电子邮件地址|邮箱)登录|(?:メールアドレス|メール|電子メール)(?:で|を使用して)?(?:続行|続ける|ログイン|サインイン)|continue\s+using\s+(?:an?\s+)?email(?:\s+address)?\s+(?:to\s+)?(?:log\s*in|sign\s*in)|continue\s+with\s+email/i.test(pageText); } function isPhoneVerificationPageReady() { const path = `${location.pathname || ''} ${location.href || ''}`; const isPhoneVerificationRoute = /\/phone-verification(?:[/?#]|$)/i.test(path); const isContactVerificationRoute = /\/contact-verification(?:[/?#]|$)/i.test(path); if (isContactVerificationRoute && getContactVerificationServerErrorText()) { return false; } if (isPhoneVerificationRoute || isContactVerificationRoute) { return true; } const form = document.querySelector('form[action*="/phone-verification" i]'); if (form && isVisibleElement(form)) { return true; } if (document.querySelector('button[name="intent"][value="resend"]') && getPhoneVerificationDisplayedPhone()) { return true; } const pageText = getPageTextSnapshot(); const displayedPhone = getPhoneVerificationDisplayedPhone(); return Boolean(getVerificationCodeTarget()) && Boolean(displayedPhone) && /check\s+your\s+phone|phone\s+verification|verify\s+your\s+phone|sms|text\s+message|code\s+to\s+\+/.test(pageText); } function getDocumentReadyStateSnapshot() { const readyState = typeof document !== 'undefined' && document ? String(document.readyState || '').trim().toLowerCase() : ''; return readyState || 'complete'; } function isDocumentLoadComplete() { return getDocumentReadyStateSnapshot() === 'complete'; } async function waitForDocumentLoadComplete(timeout = 15000, label = '页面') { const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); if (isDocumentLoadComplete()) { return true; } await sleep(150); } throw new Error(`${label}长时间未完成加载,当前 readyState=${getDocumentReadyStateSnapshot()}。URL: ${location.href}`); } function isSignupVerificationPageInteractiveReady(snapshot = null) { if (!isDocumentLoadComplete()) { return false; } const resolvedSnapshot = snapshot || inspectSignupVerificationState(); if (resolvedSnapshot?.state !== 'verification') { return false; } return Boolean(getVerificationCodeTarget()); } function isStep8Ready() { const continueBtn = getPrimaryContinueButton(); if (!continueBtn) return false; if (isVerificationPageStillVisible()) return false; if (isPhoneVerificationPageReady()) return false; if (isAddPhonePageReady()) return false; if (isAddEmailPageReady()) return false; return isOAuthConsentPage(); } const phoneAuthHelpers = self.MultiPagePhoneAuth?.createPhoneAuthHelpers?.({ fillInput, getActionText, getPageTextSnapshot, getVerificationErrorText, humanPause, isActionEnabled, isAddPhonePageReady, isConsentReady: isStep8Ready, isPhoneVerificationPageReady, isVisibleElement, simulateClick, sleep, throwIfStopped, waitForElement, }) || { submitPhoneNumber: async () => { throw new Error('Phone auth helpers are unavailable.'); }, submitPhoneVerificationCode: async () => { throw new Error('Phone auth helpers are unavailable.'); }, resendPhoneVerificationCode: async () => { throw new Error('Phone auth helpers are unavailable.'); }, checkPhoneResendError: () => ({ hasError: false, reason: '', message: '', url: location.href }), returnToAddPhone: async () => { throw new Error('Phone auth helpers are unavailable.'); }, }; async function waitForPhoneVerificationProfileCompletion(timeout = 30000) { const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); if (isStep8Ready()) { return { success: true, consentReady: true, url: location.href, }; } if (isAddPhonePageReady()) { return { returnedToAddPhone: true, url: location.href, }; } await sleep(150); } if (isStep8Ready()) { return { success: true, consentReady: true, url: location.href, }; } return { success: true, assumed: true, url: location.href, }; } async function submitPhoneVerificationCodeWithProfileFallback(payload = {}) { const result = await phoneAuthHelpers.submitPhoneVerificationCode(payload); if (!(isStep5Ready() || isSignupProfilePageUrl(result?.url || location.href))) { return result; } const signupProfile = payload?.signupProfile || {}; if (!signupProfile.firstName || !signupProfile.lastName) { throw new Error('手机号验证后进入资料页,但未提供步骤 5 所需的姓名数据。'); } await step5_fillNameBirthday(signupProfile); const nextState = await waitForPhoneVerificationProfileCompletion(); const mergedResult = { ...result, ...nextState, profileCompleted: true, }; if (nextState.consentReady || nextState.returnedToAddPhone) { delete mergedResult.assumed; } return mergedResult; } function normalizeInlineText(text) { return (text || '').replace(/\s+/g, ' ').trim(); } function isStep5AllConsentText(text) { const normalizedText = normalizeInlineText(text).toLowerCase(); if (!normalizedText) return false; return /i\s+agree\s+to\s+all\s+of\s+the\s+following/i.test(normalizedText) || normalizedText.includes('\u4ee5\u4e0b\u306e\u3059\u3079\u3066\u306b\u540c\u610f') || normalizedText.includes('\u3059\u3079\u3066\u306b\u540c\u610f') || normalizedText.includes('\u540c\u610f\u3057\u307e\u3059') || normalizedText.includes('\u6211\u540c\u610f\u4ee5\u4e0b\u6240\u6709\u5404\u9879') || normalizedText.includes('\u540c\u610f\u4ee5\u4e0b\u6240\u6709\u5404\u9879') || normalizedText.includes('\u6211\u540c\u610f\u6240\u6709') || normalizedText.includes('\u5168\u90e8\u540c\u610f'); } function findStep5AllConsentCheckbox() { const namedCandidates = Array.from(document.querySelectorAll('input[name="allCheckboxes"][type="checkbox"]')) .filter((el) => { const checkboxLabel = el.closest?.('label') || null; return isVisibleElement(el) || (checkboxLabel && isVisibleElement(checkboxLabel)); }); const namedMatch = namedCandidates.find((el) => { const checkboxLabel = el.closest?.('label') || null; const checkboxText = normalizeInlineText([ checkboxLabel?.textContent || '', el.getAttribute?.('aria-label') || '', el.getAttribute?.('title') || '', el.getAttribute?.('name') || '', ].filter(Boolean).join(' ')); return isStep5AllConsentText(checkboxText); }); if (namedMatch) { return namedMatch; } if (namedCandidates.length > 0) { return namedCandidates[0]; } return Array.from(document.querySelectorAll('input[type="checkbox"]')) .find((el) => { const checkboxLabel = el.closest?.('label') || null; if (!isVisibleElement(el) && !(checkboxLabel && isVisibleElement(checkboxLabel))) { return false; } const checkboxText = normalizeInlineText([ checkboxLabel?.textContent || '', el.getAttribute?.('aria-label') || '', el.getAttribute?.('title') || '', el.getAttribute?.('name') || '', ].filter(Boolean).join(' ')); return isStep5AllConsentText(checkboxText); }) || null; } function isStep5CheckboxChecked(checkbox) { if (!checkbox) return false; if (checkbox.checked === true) return true; const ariaChecked = String( checkbox.getAttribute?.('aria-checked') || checkbox.closest?.('[role="checkbox"]')?.getAttribute?.('aria-checked') || '' ).toLowerCase(); return ariaChecked === 'true'; } function findBirthdayReactAriaSelect(labelText) { const normalizedLabels = (Array.isArray(labelText) ? labelText : [labelText]) .map((text) => normalizeInlineText(text)) .filter(Boolean); const roots = document.querySelectorAll('.react-aria-Select'); for (const root of roots) { const labelEl = Array.from(root.querySelectorAll('span')).find((el) => normalizedLabels.includes(normalizeInlineText(el.textContent))); if (!labelEl) continue; const item = root.closest('[class*="selectItem"], ._selectItem_ppsls_113') || root.parentElement; const nativeSelect = item?.querySelector('[data-testid="hidden-select-container"] select') || null; const button = root.querySelector('button[aria-haspopup="listbox"]') || null; const valueEl = root.querySelector('.react-aria-SelectValue') || null; return { root, item, labelEl, nativeSelect, button, valueEl }; } return null; } async function setReactAriaBirthdaySelect(control, value) { if (!control?.nativeSelect) { throw new Error('未找到可写入的生日下拉框。'); } const desiredValue = String(value); const option = Array.from(control.nativeSelect.options).find((item) => item.value === desiredValue); if (!option) { throw new Error(`生日下拉框中不存在值 ${desiredValue}。`); } control.nativeSelect.value = desiredValue; option.selected = true; control.nativeSelect.dispatchEvent(new Event('input', { bubbles: true })); control.nativeSelect.dispatchEvent(new Event('change', { bubbles: true })); await sleep(120); } function getStep5ErrorText() { const messages = []; const selectors = [ '.react-aria-FieldError', '[slot="errorMessage"]', '[id$="-error"]', '[id$="-errors"]', '[role="alert"]', '[aria-live="assertive"]', '[aria-live="polite"]', '[class*="error"]', ]; for (const selector of selectors) { document.querySelectorAll(selector).forEach((el) => { if (!isVisibleElement(el)) return; const text = normalizeInlineText(el.textContent); if (text) { messages.push(text); } }); } const invalidField = Array.from(document.querySelectorAll('[aria-invalid="true"], [data-invalid="true"]')) .find((el) => isVisibleElement(el)); if (invalidField) { const wrapper = invalidField.closest('form, fieldset, [data-rac], div'); if (wrapper) { const text = normalizeInlineText(wrapper.textContent); if (text) { messages.push(text); } } } return messages.find((text) => STEP5_SUBMIT_ERROR_PATTERN.test(text)) || ''; } function isSignupPasswordPage() { return /\/(?:create-account|log-in)\/password(?:[/?#]|$)/i.test(location.pathname || ''); } function getSignupPasswordInput() { const input = document.querySelector('input[type="password"]'); return input && isVisibleElement(input) ? input : null; } function getSignupPasswordSubmitButton({ allowDisabled = false } = {}) { const direct = document.querySelector('button[type="submit"]'); if (direct && isVisibleElement(direct) && (allowDisabled || isActionEnabled(direct))) { return direct; } const candidates = document.querySelectorAll('button, [role="button"]'); return Array.from(candidates).find((el) => { if (!isVisibleElement(el) || (!allowDisabled && !isActionEnabled(el))) return false; const text = getActionText(el); return /继续|continue|submit|创建|続行|続ける|次へ|サインアップ|登録|作成|create/i.test(text); }) || null; } function getAuthRetryButton({ allowDisabled = false } = {}) { if (authPageRecovery?.getAuthRetryButton) { return authPageRecovery.getAuthRetryButton({ allowDisabled }); } const direct = document.querySelector('button[data-dd-action-name="Try again"]'); if (direct && isVisibleElement(direct) && (allowDisabled || isActionEnabled(direct))) { return direct; } const candidates = document.querySelectorAll('button, [role="button"]'); return Array.from(candidates).find((el) => { if (!isVisibleElement(el) || (!allowDisabled && !isActionEnabled(el))) return false; const text = getActionText(el); return /重试|try\s+again/i.test(text); }) || null; } function getAuthTimeoutErrorPageState(options = {}) { if (authPageRecovery?.getAuthTimeoutErrorPageState) { return authPageRecovery.getAuthTimeoutErrorPageState(options); } const { pathPatterns = [] } = options; const path = location.pathname || ''; if (pathPatterns.length && !pathPatterns.some((pattern) => pattern.test(path))) { return null; } const retryButton = getAuthRetryButton({ allowDisabled: true }); if (!retryButton) { return null; } const text = getPageTextSnapshot(); const titleMatched = AUTH_TIMEOUT_ERROR_TITLE_PATTERN.test(text) || AUTH_TIMEOUT_ERROR_TITLE_PATTERN.test(document.title || ''); const detailMatched = AUTH_TIMEOUT_ERROR_DETAIL_PATTERN.test(text); const routeErrorMatched = AUTH_ROUTE_ERROR_PATTERN.test(text); const fetchFailedMatched = /failed\s+to\s+fetch|network\s+error|fetch\s+failed/i.test(text); const maxCheckAttemptsBlocked = /max_check_attempts/i.test(text); const emailInUseBlocked = /email_in_use/i.test(text); const userAlreadyExistsBlocked = /user_already_exists/i.test(text); if (!titleMatched && !detailMatched && !routeErrorMatched && !fetchFailedMatched && !maxCheckAttemptsBlocked && !emailInUseBlocked && !userAlreadyExistsBlocked) { return null; } return { path, url: location.href, retryButton, retryEnabled: isActionEnabled(retryButton), titleMatched, detailMatched, routeErrorMatched, fetchFailedMatched, maxCheckAttemptsBlocked, emailInUseBlocked, userAlreadyExistsBlocked, }; } function getSignupAuthRetryPathPatterns() { return [ /\/create-account\/password(?:[/?#]|$)/i, /\/email-verification(?:[/?#]|$)/i, ]; } function getLoginAuthRetryPathPatterns() { return [ /\/log-in(?:[/?#]|$)/i, /\/email-verification(?:[/?#]|$)/i, ]; } function getAuthRetryPathPatternsForFlow(flow = 'auth') { switch (flow) { case 'signup': case 'signup_password': return getSignupAuthRetryPathPatterns(); case 'login': return getLoginAuthRetryPathPatterns(); default: return []; } } function getCurrentAuthRetryPageState(flow = 'auth') { return getAuthTimeoutErrorPageState({ pathPatterns: getAuthRetryPathPatternsForFlow(flow), }); } async function recoverCurrentAuthRetryPage(payload = {}) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const { flow = 'auth', logLabel = '', maxClickAttempts = 5, pathPatterns = null, step = null, timeoutMs = 12000, waitAfterClickMs = 3000, } = payload; const resolvedPathPatterns = Array.isArray(pathPatterns) ? pathPatterns : getAuthRetryPathPatternsForFlow(flow); if (authPageRecovery?.recoverAuthRetryPage) { return authPageRecovery.recoverAuthRetryPage({ logLabel, maxClickAttempts, pathPatterns: resolvedPathPatterns, step, stepKey: step === 8 || flow === 'login' ? 'oauth-login' : 'fetch-signup-code', timeoutMs, waitAfterClickMs, }); } const maxIdlePolls = timeoutMs > 0 ? Math.max(1, Math.ceil(timeoutMs / Math.max(1, 250))) : Number.POSITIVE_INFINITY; let clickCount = 0; let idlePollCount = 0; while (clickCount < maxClickAttempts) { throwIfStopped(); const retryState = getAuthTimeoutErrorPageState({ pathPatterns: resolvedPathPatterns }); if (!retryState) { return { recovered: clickCount > 0, clickCount, url: location.href, }; } if (retryState.maxCheckAttemptsBlocked) { throw new Error('CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器'); } if (retryState.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } if (retryState.retryButton && retryState.retryEnabled) { idlePollCount = 0; clickCount += 1; log(`${logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`}(第 ${clickCount} 次)...`, 'warn'); await humanPause(300, 800); await performOperationWithDelay({ stepKey: step === 8 || flow === 'login' ? 'oauth-login' : 'fetch-signup-code', kind: 'click', label: 'auth-retry-click' }, async () => { simulateClick(retryState.retryButton); }); const settleStart = Date.now(); while (Date.now() - settleStart < waitAfterClickMs) { throwIfStopped(); if (!getAuthTimeoutErrorPageState({ pathPatterns: resolvedPathPatterns })) { return { recovered: true, clickCount, url: location.href, }; } await sleep(250); } continue; } idlePollCount += 1; if (idlePollCount >= maxIdlePolls) { throw new Error(`${logLabel || `步骤 ${step || '?'}:重试页恢复`}超时:重试按钮长时间不可点击。URL: ${location.href}`); } await sleep(250); } const finalRetryState = getAuthTimeoutErrorPageState({ pathPatterns: resolvedPathPatterns }); if (!finalRetryState) { return { recovered: clickCount > 0, clickCount, url: location.href, }; } if (finalRetryState.maxCheckAttemptsBlocked) { throw new Error('CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器'); } if (finalRetryState.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } throw new Error(`${logLabel || `步骤 ${step || '?'}:重试页恢复`}失败:已连续点击“重试” ${maxClickAttempts} 次,页面仍未恢复。URL: ${location.href}`); } function getSignupPasswordTimeoutErrorPageState() { return getAuthTimeoutErrorPageState({ pathPatterns: getSignupAuthRetryPathPatterns(), }); } function getLoginTimeoutErrorPageState() { return getAuthTimeoutErrorPageState({ pathPatterns: getLoginAuthRetryPathPatterns(), }); } function isLoginPhoneUsernameKind(rawUrl = location.href) { const url = String(rawUrl || '').trim(); if (!url) { return false; } try { const parsed = new URL(url); return /\/log-in(?:[/?#]|$)/i.test(parsed.pathname || '') && String(parsed.searchParams.get('usernameKind') || '').toLowerCase() === 'phone_number'; } catch { return /\/log-in(?:[/?#]|$)/i.test(url) && /[?&]usernameKind=phone_number(?:[&#]|$)/i.test(url); } } function isLoginPhoneEntryPageText(pageText = getPageTextSnapshot()) { const normalizedText = String(pageText || '').replace(/\s+/g, ' ').trim(); if (!normalizedText) { return false; } if (isAddPhonePageReady() || isPhoneVerificationPageReady()) { return false; } return LOGIN_PHONE_ENTRY_PAGE_PATTERN.test(normalizedText); } function isInsideHiddenPhoneControl(element) { if (!element) { return true; } return Boolean(element.closest?.('[aria-hidden="true"], [hidden], [data-testid="hidden-select-container"], [data-react-aria-prevent-focus="true"]')); } function summarizePhoneInputCandidate(element, options = {}) { const summary = { tag: (element?.tagName || '').toLowerCase(), type: '', name: '', id: '', autocomplete: '', placeholder: '', ariaLabel: '', visible: false, hiddenControl: true, readOnly: false, maxLength: 0, usable: false, skipReason: '', }; if (!element) { summary.skipReason = 'missing_element'; return summary; } summary.visible = isVisibleElement(element); summary.hiddenControl = isInsideHiddenPhoneControl(element); summary.type = String(element.getAttribute?.('type') || element.type || '').trim().toLowerCase(); summary.name = String(element.getAttribute?.('name') || element.name || '').trim(); summary.id = String(element.getAttribute?.('id') || element.id || '').trim(); summary.autocomplete = String(element.getAttribute?.('autocomplete') || '').trim().toLowerCase(); summary.placeholder = String(element.getAttribute?.('placeholder') || '').trim().slice(0, 80); summary.ariaLabel = String(element.getAttribute?.('aria-label') || '').trim().slice(0, 80); const hasReadonlyAttribute = typeof element.hasAttribute === 'function' ? element.hasAttribute('readonly') : element.readOnly === true; summary.readOnly = element.readOnly === true || hasReadonlyAttribute || String(element.getAttribute?.('aria-readonly') || '').trim().toLowerCase() === 'true'; summary.maxLength = Number(element.getAttribute?.('maxlength') || element.maxLength || 0); if (summary.hiddenControl) { summary.skipReason = 'inside_hidden_control'; return summary; } if (!summary.visible) { summary.skipReason = 'not_visible'; return summary; } if (summary.type === 'hidden') { summary.skipReason = 'hidden_type'; return summary; } if (summary.readOnly) { summary.skipReason = 'readonly'; return summary; } if (summary.maxLength === 6) { summary.skipReason = 'verification_code_input'; return summary; } const normalizedName = summary.name.toLowerCase(); const normalizedId = summary.id.toLowerCase(); const combinedText = `${normalizedName} ${normalizedId} ${summary.placeholder} ${summary.ariaLabel}`; if (isLoginEmailLikeInput(element)) { summary.skipReason = 'email_like'; return summary; } if ( summary.type === 'tel' || summary.autocomplete === 'tel' || /phone|tel/i.test(`${normalizedName} ${normalizedId}`) || /手机|电话|手机号|电话号码|国家号码|phone|mobile|telephone/i.test(combinedText) ) { summary.usable = true; return summary; } if (options.allowGenericText && (!summary.type || summary.type === 'text')) { summary.usable = true; return summary; } summary.skipReason = 'not_phone_like'; return summary; } function isUsablePhoneInputElement(element, options = {}) { return summarizePhoneInputCandidate(element, options).usable; } function collectPhoneInputCandidates(selector, options = {}) { return Array.from(document.querySelectorAll(selector)) .map((element) => summarizePhoneInputCandidate(element, options)); } function findUsablePhoneInput(selector, options = {}) { return Array.from(document.querySelectorAll(selector)) .find((element) => isUsablePhoneInputElement(element, options)) || null; } function getLoginInputAttributeText(input) { return { type: String(input?.getAttribute?.('type') || input?.type || '').trim().toLowerCase(), autocomplete: String(input?.getAttribute?.('autocomplete') || '').trim().toLowerCase(), name: String(input?.getAttribute?.('name') || input?.name || '').trim(), id: String(input?.getAttribute?.('id') || input?.id || '').trim(), placeholder: String(input?.getAttribute?.('placeholder') || '').trim(), ariaLabel: String(input?.getAttribute?.('aria-label') || '').trim(), }; } function isLoginEmailLikeInput(input) { const summary = getLoginInputAttributeText(input); const nameId = `${summary.name} ${summary.id}`; const labelText = `${summary.placeholder} ${summary.ariaLabel}`; return summary.type === 'email' || summary.autocomplete === 'email' || /email|mail/i.test(nameId) || /email|电子邮件|邮箱|メールアドレス|メール|電子メール/i.test(labelText); } function getLoginEmailInput() { const input = Array.from(document.querySelectorAll([ 'input[type="email"]', 'input[autocomplete="email"]', 'input[name="email"]', 'input[name="username"]', 'input[autocomplete="username"]', 'input[id*="email" i]', 'input[placeholder*="email" i]', 'input[placeholder*="Email"]', 'input[placeholder*="电子邮件"]', 'input[placeholder*="邮箱"]', 'input[placeholder*="メール"]', 'input[aria-label*="email" i]', 'input[aria-label*="电子邮件"]', 'input[aria-label*="邮箱"]', 'input[aria-label*="メール"]', ].join(', '))).find((candidate) => isVisibleElement(candidate)) || null; if (!input) { return null; } if ((isLoginPhoneUsernameKind() || isLoginPhoneEntryPageText()) && !isLoginEmailLikeInput(input)) { return null; } return input; } function getLoginPhoneInput() { const phonePage = isLoginPhoneUsernameKind() || isLoginPhoneEntryPageText(); const selector = [ 'input[type="tel"]:not([maxlength="6"])', 'input[name*="phone" i]:not([type="hidden"])', 'input[id*="phone" i]:not([type="hidden"])', 'input[autocomplete="tel"]', 'input[inputmode="tel"]', 'input[placeholder*="phone" i]', 'input[aria-label*="phone" i]', 'input[placeholder*="telephone" i]', 'input[aria-label*="telephone" i]', 'input[placeholder*="手机"]', 'input[aria-label*="手机"]', 'input[placeholder*="电话"]', 'input[aria-label*="电话"]', 'input[placeholder*="電話"]', 'input[aria-label*="電話"]', 'input[placeholder*="携帯"]', 'input[aria-label*="携帯"]', phonePage ? 'input[name="username"]:not([maxlength="6"])' : '', phonePage ? 'input[id*="username" i]:not([maxlength="6"])' : '', phonePage ? 'input[autocomplete="username"]:not([maxlength="6"])' : '', phonePage ? 'input[type="text"]:not([maxlength="6"])' : '', ].filter(Boolean).join(', '); return findUsablePhoneInput(selector, { allowGenericText: phonePage }); } function getLoginPhoneInputDiagnostics(phoneInput) { return { tag: (phoneInput?.tagName || '').toLowerCase(), type: String(phoneInput?.getAttribute?.('type') || phoneInput?.type || '').trim().toLowerCase(), name: String(phoneInput?.getAttribute?.('name') || phoneInput?.name || '').trim(), id: String(phoneInput?.getAttribute?.('id') || phoneInput?.id || '').trim(), autocomplete: String(phoneInput?.getAttribute?.('autocomplete') || '').trim().toLowerCase(), placeholder: String(phoneInput?.getAttribute?.('placeholder') || '').trim().slice(0, 80), ariaLabel: String(phoneInput?.getAttribute?.('aria-label') || '').trim().slice(0, 80), value: getPhoneInputRenderedValue(phoneInput), }; } function getLoginPhoneHiddenValueInput(phoneInput) { const form = phoneInput?.form || phoneInput?.closest?.('form') || null; const root = form || phoneInput?.closest?.('fieldset, form, [data-rac], div') || document; const candidates = Array.from(root?.querySelectorAll?.([ 'input[name="phone"]', 'input[name*="phone" i]', 'input[name="phoneNumber"]', 'input[name*="telephone" i]', 'input[type="hidden"][id*="phone" i]', 'input[type="hidden"][id*="telephone" i]', 'input[type="hidden"][name*="phone" i]', 'input[type="hidden"][name*="telephone" i]', ].join(', ')) || []); return candidates.find((input) => { if (!input || input === phoneInput) return false; const type = String(input.getAttribute?.('type') || input.type || '').trim().toLowerCase(); return type === 'hidden' || !isVisibleElement(input); }) || null; } function getLoginPhoneHiddenValueDiagnostics(hiddenInput) { return { tag: (hiddenInput?.tagName || '').toLowerCase(), type: String(hiddenInput?.getAttribute?.('type') || hiddenInput?.type || '').trim().toLowerCase(), name: String(hiddenInput?.getAttribute?.('name') || hiddenInput?.name || '').trim(), id: String(hiddenInput?.getAttribute?.('id') || hiddenInput?.id || '').trim(), value: String(hiddenInput?.value || hiddenInput?.getAttribute?.('value') || '').trim(), }; } function getLoginPasswordInput() { const input = document.querySelector('input[type="password"]'); return input && isVisibleElement(input) ? input : null; } function getLoginSubmitButton({ allowDisabled = false } = {}) { const direct = document.querySelector('button[type="submit"], input[type="submit"]'); if (direct && isVisibleElement(direct) && (allowDisabled || isActionEnabled(direct))) { return direct; } const candidates = document.querySelectorAll( 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' ); return Array.from(candidates).find((el) => { if (!isVisibleElement(el) || (!allowDisabled && !isActionEnabled(el))) return false; const text = getActionText(el); if (!text || ONE_TIME_CODE_LOGIN_PATTERN.test(text)) return false; return /continue|next|submit|sign\s*in|log\s*in|继续|下一步|登录|続行|続ける|次へ|ログイン|サインイン|送信/i.test(text); }) || null; } function normalizeCountryLabel(value) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.normalizeCountryLabel === 'function') { return phoneCountryUtils.normalizeCountryLabel(value); } return String(value || '') .normalize('NFKD') .replace(/[\u0300-\u036f]/g, '') .replace(/&/g, ' and ') .replace(/[^\p{L}\p{N}\s]/gu, ' ') .replace(/\s+/g, ' ') .trim() .toLowerCase(); } function getLoginPhoneCountrySelect(phoneInput) { const scope = phoneInput?.closest?.('fieldset, form, [data-rac], div') || document; const select = scope.querySelector?.('select'); return select && isVisibleElement(select) ? select : null; } function getLoginPhoneCountryOptionLabel(option) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.getOptionLabel === 'function') { return phoneCountryUtils.getOptionLabel(option); } return String(option?.textContent || option?.label || '') .replace(/\s+/g, ' ') .trim(); } function getLoginPhoneCountryOptionMatchLabels(option) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.getOptionMatchLabels === 'function') { const rootScope = typeof self !== 'undefined' ? self : globalThis; return phoneCountryUtils.getOptionMatchLabels(option, { document: typeof document !== 'undefined' ? document : null, navigator: rootScope?.navigator || globalThis?.navigator || null, getOptionLabel: getLoginPhoneCountryOptionLabel, }); } const labels = new Set(); const pushLabel = (value) => { const label = String(value || '').replace(/\s+/g, ' ').trim(); if (label) { labels.add(label); } }; pushLabel(getLoginPhoneCountryOptionLabel(option)); pushLabel(option?.value); return Array.from(labels); } function findLoginPhoneCountryOptionByLabel(select, countryLabel) { const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (select && typeof phoneCountryUtils.findOptionByCountryLabel === 'function') { return phoneCountryUtils.findOptionByCountryLabel(select.options, countryLabel, { document: typeof document !== 'undefined' ? document : null, navigator: (typeof self !== 'undefined' ? self : globalThis)?.navigator || globalThis?.navigator || null, getOptionLabel: getLoginPhoneCountryOptionLabel, }); } const normalizedTarget = normalizeCountryLabel(countryLabel); if (!select || !normalizedTarget) { return null; } const options = Array.from(select.options || []); return options.find((option) => ( getLoginPhoneCountryOptionMatchLabels(option) .some((label) => normalizeCountryLabel(label) === normalizedTarget) )) || options.find((option) => { const normalizedLabels = getLoginPhoneCountryOptionMatchLabels(option) .map((label) => normalizeCountryLabel(label)) .filter(Boolean); return normalizedLabels.some((optionLabel) => ( optionLabel.length > 2 && normalizedTarget.length > 2 && (optionLabel.includes(normalizedTarget) || normalizedTarget.includes(optionLabel)) )); }) || null; } function findLoginPhoneCountryOptionByNumber(select, phoneNumber) { if (!select) { return null; } const phoneCountryUtils = (typeof self !== 'undefined' ? self : globalThis)?.MultiPagePhoneCountryUtils || globalThis?.MultiPagePhoneCountryUtils || {}; if (typeof phoneCountryUtils.findOptionByPhoneNumber === 'function') { return phoneCountryUtils.findOptionByPhoneNumber(select.options, phoneNumber, { getOptionLabel: getLoginPhoneCountryOptionLabel, }); } const digits = normalizePhoneDigits(phoneNumber); if (!digits) { return null; } let bestMatch = null; let bestDialCodeLength = 0; for (const option of Array.from(select.options || [])) { const dialCode = normalizePhoneDigits(extractDialCodeFromText(getLoginPhoneCountryOptionLabel(option))); if (!dialCode || !digits.startsWith(dialCode)) { continue; } if (dialCode.length > bestDialCodeLength) { bestMatch = option; bestDialCodeLength = dialCode.length; } } return bestMatch; } async function selectCountryForPhoneInput(phoneInput, phoneNumber = '', countryLabel = '', options = {}) { const visibleStep = Math.floor(Number(options?.visibleStep) || 0) || 7; const selection = await ensureSignupPhoneCountrySelected(phoneInput, { countryLabel, phoneNumber, }); const selectedOption = selection.selectedOption || getSignupPhoneSelectedCountryOption(phoneInput); const targetDialCode = resolveSignupPhoneTargetDialCode({ countryLabel, phoneNumber }, selectedOption); const displayedDialCode = getSignupPhoneDisplayedDialCode(phoneInput); if (selection.hasCountryControl && targetDialCode) { if (!selection.matched || (displayedDialCode && displayedDialCode !== targetDialCode)) { const currentCountryText = getSignupPhoneCountryButtonText(phoneInput) || displayedDialCode || '未知'; const targetLabel = `目标区号 +${targetDialCode}(号码 ${phoneNumber}${countryLabel ? `,国家 ${countryLabel}` : ''})`; throw new Error(`步骤 ${visibleStep}:手机号登录国家下拉框未能自动切换到 ${targetLabel},当前显示为 ${currentCountryText},已停止提交以避免区号不匹配。`); } return targetDialCode; } const select = getLoginPhoneCountrySelect(phoneInput); const fallbackSelectedOption = select?.options?.[select.selectedIndex] || null; return extractDialCodeFromText(getLoginPhoneCountryOptionLabel(fallbackSelectedOption)) || displayedDialCode || resolveSignupPhoneDialCodeFromNumber(phoneNumber); } function resolveLoginPhoneDialCode(phoneInput, options = {}) { return resolveSignupPhoneDialCode(phoneInput, options); } function findLoginEntryTrigger() { const candidates = Array.from(document.querySelectorAll( 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' )).filter((el) => isVisibleElement(el) && isActionEnabled(el)); const preferred = candidates.find((el) => { const text = getActionText(el); if (!text || LOGIN_CODE_ONLY_ACTION_PATTERN.test(text) || LOGIN_EXTERNAL_IDP_PATTERN.test(text)) return false; return /continue\s+(?:with|using)\s+email|use\s+(?:an?\s+)?email|email\s+address|邮箱|电子邮件|メールアドレス|メール|電子メール/i.test(text); }); if (preferred) return preferred; return candidates.find((el) => { const text = getActionText(el); if (!text || LOGIN_CODE_ONLY_ACTION_PATTERN.test(text) || LOGIN_EXTERNAL_IDP_PATTERN.test(text)) return false; return LOGIN_ENTRY_ACTION_PATTERN.test(text); }) || null; } function findLoginPhoneEntryTrigger() { const candidates = Array.from(document.querySelectorAll( 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' )).filter((el) => isVisibleElement(el) && isActionEnabled(el)); return candidates.find((el) => { const text = getActionText(el); if (!text || LOGIN_CODE_ONLY_ACTION_PATTERN.test(text) || LOGIN_EXTERNAL_IDP_PATTERN.test(text)) return false; return LOGIN_SWITCH_TO_PHONE_PATTERN.test(text) || ( LOGIN_PHONE_ACTION_PATTERN.test(text) && !/email|邮箱|电子邮件|メールアドレス|メール|電子メール/i.test(text) ); }) || null; } function findLoginMoreOptionsTrigger() { const candidates = Array.from(document.querySelectorAll( 'button, a, [role="button"], [role="link"], input[type="button"], input[type="submit"]' )).filter((el) => isVisibleElement(el) && isActionEnabled(el)); return candidates.find((el) => { const text = getActionText(el); if (!text || LOGIN_EXTERNAL_IDP_PATTERN.test(text)) return false; return LOGIN_MORE_OPTIONS_PATTERN.test(text); }) || null; } function inspectLoginAuthState() { const retryState = getLoginTimeoutErrorPageState(); const verificationTarget = getVerificationCodeTarget(); const passwordInput = getLoginPasswordInput(); const emailInput = getLoginEmailInput(); const phoneInput = getLoginPhoneInput(); const switchTrigger = findOneTimeCodeLoginTrigger(); const loginEntryTrigger = findLoginEntryTrigger(); const phoneEntryTrigger = findLoginPhoneEntryTrigger(); const moreOptionsTrigger = findLoginMoreOptionsTrigger(); const submitButton = getLoginSubmitButton({ allowDisabled: true }); const verificationVisible = isVerificationPageStillVisible(); const addPhonePage = isAddPhonePageReady(); const addEmailPage = isAddEmailPageReady(); const phoneVerificationPage = isPhoneVerificationPageReady(); const consentReady = isStep8Ready(); const oauthConsentPage = isOAuthConsentPage(); const chooseAccountPage = isChooseAccountPage(); const baseState = { state: 'unknown', url: location.href, path: location.pathname || '', displayedEmail: getLoginVerificationDisplayedEmail(), retryButton: retryState?.retryButton || null, retryEnabled: Boolean(retryState?.retryEnabled), titleMatched: Boolean(retryState?.titleMatched), detailMatched: Boolean(retryState?.detailMatched), maxCheckAttemptsBlocked: Boolean(retryState?.maxCheckAttemptsBlocked), emailInUseBlocked: Boolean(retryState?.emailInUseBlocked), verificationTarget, passwordInput, emailInput, phoneInput, submitButton, switchTrigger, loginEntryTrigger, phoneEntryTrigger, moreOptionsTrigger, verificationVisible, addPhonePage, addEmailPage, phoneVerificationPage, oauthConsentPage, consentReady, chooseAccountPage, }; if (retryState) { return { ...baseState, state: 'login_timeout_error_page', }; } if (phoneVerificationPage) { return { ...baseState, state: 'phone_verification_page', displayedPhone: getPhoneVerificationDisplayedPhone(), }; } if (verificationTarget) { return { ...baseState, state: 'verification_page', }; } if (addPhonePage) { return { ...baseState, state: 'add_phone_page', }; } if (addEmailPage) { return { ...baseState, state: 'add_email_page', }; } if (passwordInput || switchTrigger) { return { ...baseState, state: 'password_page', }; } if (phoneInput) { return { ...baseState, state: 'phone_entry_page', }; } if (emailInput) { return { ...baseState, state: 'email_page', }; } if (chooseAccountPage) { return { ...baseState, state: 'choose_account_page', }; } if (verificationVisible) { return { ...baseState, state: 'verification_page', }; } if (consentReady) { return { ...baseState, state: 'oauth_consent_page', }; } if (loginEntryTrigger) { return { ...baseState, state: 'entry_page', }; } return baseState; } function serializeLoginAuthState(snapshot) { return { state: snapshot?.state || 'unknown', url: snapshot?.url || location.href, path: snapshot?.path || location.pathname || '', displayedEmail: snapshot?.displayedEmail || '', verificationErrorText: getVerificationErrorText(), retryEnabled: Boolean(snapshot?.retryEnabled), titleMatched: Boolean(snapshot?.titleMatched), detailMatched: Boolean(snapshot?.detailMatched), maxCheckAttemptsBlocked: Boolean(snapshot?.maxCheckAttemptsBlocked), emailInUseBlocked: Boolean(snapshot?.emailInUseBlocked), hasVerificationTarget: Boolean(snapshot?.verificationTarget), hasPasswordInput: Boolean(snapshot?.passwordInput), hasEmailInput: Boolean(snapshot?.emailInput), hasPhoneInput: Boolean(snapshot?.phoneInput), hasSubmitButton: Boolean(snapshot?.submitButton), hasSwitchTrigger: Boolean(snapshot?.switchTrigger), hasLoginEntryTrigger: Boolean(snapshot?.loginEntryTrigger), hasPhoneEntryTrigger: Boolean(snapshot?.phoneEntryTrigger), hasMoreOptionsTrigger: Boolean(snapshot?.moreOptionsTrigger), verificationVisible: Boolean(snapshot?.verificationVisible), addPhonePage: Boolean(snapshot?.addPhonePage), addEmailPage: Boolean(snapshot?.addEmailPage), phoneVerificationPage: Boolean(snapshot?.phoneVerificationPage), oauthConsentPage: Boolean(snapshot?.oauthConsentPage), consentReady: Boolean(snapshot?.consentReady), chooseAccountPage: Boolean(snapshot?.chooseAccountPage), }; } function getLoginAuthStateLabel(snapshot) { const state = snapshot?.state; switch (state) { case 'verification_page': return '登录验证码页'; case 'password_page': return '密码页'; case 'email_page': return '邮箱输入页'; case 'phone_entry_page': return '手机号输入页'; case 'phone_verification_page': return '手机验证码页'; case 'login_timeout_error_page': return '登录超时报错页'; case 'oauth_consent_page': return 'OAuth 授权页'; case 'choose_account_page': return 'OpenAI choose account page'; case 'entry_page': return '登录入口页'; case 'add_phone_page': return '手机号页'; case 'add_email_page': return '添加邮箱页'; default: return '未知页面'; } } async function waitForKnownLoginAuthState(timeout = 15000) { const start = Date.now(); let snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); while (Date.now() - start < timeout) { throwIfStopped(); snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); if (snapshot.state !== 'unknown') { return snapshot; } await sleep(200); } return snapshot; } function getAuthLoginStepForLoginCodeStep(step = 8) { return Number(step) >= 11 ? 10 : 7; } async function waitForLoginVerificationPageReady(timeout = 10000, visibleStep = 8, options = {}) { const start = Date.now(); let snapshot = inspectLoginAuthState(); const allowPhoneVerificationPage = Boolean(options?.allowPhoneVerificationPage); while (Date.now() - start < timeout) { throwIfStopped(); snapshot = inspectLoginAuthState(); if (snapshot.state === 'verification_page' || (allowPhoneVerificationPage && snapshot.state === 'phone_verification_page')) { return snapshot; } if (snapshot.state !== 'unknown') { break; } await sleep(200); } throw new Error( `当前未进入登录验证码页面,请先重新完成步骤 ${Number(visibleStep) >= 11 ? 10 : 7}。当前状态:${getLoginAuthStateLabel(snapshot)}。URL: ${snapshot?.url || location.href}` ); } function createStep6SuccessResult(snapshot, options = {}) { const result = { step6Outcome: 'success', state: snapshot?.state || 'verification_page', url: snapshot?.url || location.href, via: options.via || '', loginVerificationRequestedAt: options.loginVerificationRequestedAt || null, }; if (options.skipLoginVerificationStep) { result.skipLoginVerificationStep = true; } if (options.directOAuthConsentPage) { result.directOAuthConsentPage = true; } return result; } function createStep6OAuthConsentSuccessResult(snapshot, options = {}) { return createStep6SuccessResult(snapshot, { ...options, via: options.via || 'oauth_consent_page', loginVerificationRequestedAt: null, skipLoginVerificationStep: true, directOAuthConsentPage: true, }); } function createStep6AddEmailSuccessResult(snapshot, options = {}) { return { ...createStep6SuccessResult(snapshot, { ...options, via: options.via || 'add_email_page', loginVerificationRequestedAt: null, }), addEmailPage: true, }; } function createStep6RecoverableResult(reason, snapshot, options = {}) { return { step6Outcome: 'recoverable', reason, state: snapshot?.state || 'unknown', url: snapshot?.url || location.href, message: options.message || '', loginVerificationRequestedAt: options.loginVerificationRequestedAt || null, }; } async function createStep6LoginTimeoutRecoveryTransition(reason, snapshot, message, options = {}) { const { loginVerificationRequestedAt = null, visibleStep = 7, via = 'login_timeout_recovered', allowPhoneVerificationPage = false, } = options; let resolvedSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState()); let recovered = false; if (resolvedSnapshot?.state === 'login_timeout_error_page') { try { const recoveryResult = await recoverCurrentAuthRetryPage({ flow: 'login', logLabel: `步骤 ${visibleStep}:检测到登录超时报错,正在点击“重试”恢复当前页面`, step: visibleStep, timeoutMs: 12000, }); recovered = Boolean(recoveryResult?.recovered); if (recovered) { log('登录超时报错页已点击“重试”,正在按恢复后的页面状态继续当前流程。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); } } catch (error) { if (/CF_SECURITY_BLOCKED::/i.test(String(error?.message || error || ''))) { throw error; } log(`登录超时报错页自动点击“重试”失败:${error.message}`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); } } resolvedSnapshot = recovered ? normalizeStep6Snapshot(await waitForKnownLoginAuthState(4000)) : normalizeStep6Snapshot(inspectLoginAuthState()); if (resolvedSnapshot.state === 'verification_page' || (allowPhoneVerificationPage && resolvedSnapshot.state === 'phone_verification_page')) { return { action: 'done', result: createStep6SuccessResult(resolvedSnapshot, { via, loginVerificationRequestedAt, }), }; } if (resolvedSnapshot.state === 'oauth_consent_page') { return { action: 'done', result: createStep6OAuthConsentSuccessResult(resolvedSnapshot, { via, }), }; } if (resolvedSnapshot.state === 'add_email_page') { return { action: 'done', result: createStep6AddEmailSuccessResult(resolvedSnapshot, { via: `${via}_add_email`, }), }; } if (resolvedSnapshot.state === 'password_page') { log('登录超时报错页恢复后已进入密码页,继续当前登录流程。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return { action: 'password', snapshot: resolvedSnapshot }; } if (resolvedSnapshot.state === 'phone_entry_page') { log('登录超时报错页恢复后已进入手机号输入页,继续当前登录流程。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return { action: 'phone', snapshot: resolvedSnapshot }; } if (resolvedSnapshot.state === 'email_page') { log('登录超时报错页恢复后已回到邮箱输入页,继续当前登录流程。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return { action: 'email', snapshot: resolvedSnapshot }; } return { action: 'recoverable', result: createStep6RecoverableResult(reason, resolvedSnapshot, { message, loginVerificationRequestedAt, }), }; } async function createStep6LoginTimeoutRecoverableResult(reason, snapshot, message, options = {}) { const transition = await createStep6LoginTimeoutRecoveryTransition(reason, snapshot, message, options); if (transition?.action === 'done' || transition?.action === 'recoverable') { return transition.result; } return createStep6RecoverableResult(reason, transition?.snapshot || normalizeStep6Snapshot(inspectLoginAuthState()), { message, }); } async function finalizeStep6VerificationReady(options = {}) { const { visibleStep = 7, logLabel = `步骤 ${visibleStep} 收尾`, loginVerificationRequestedAt = null, timeout = 12000, via = 'verification_page_ready', allowPhoneVerificationPage = false, } = options; const start = Date.now(); const maxRounds = 3; const settleDelayMs = 3000; let round = 0; while (Date.now() - start < timeout && round < maxRounds) { throwIfStopped(); round += 1; log(`确认页面是否稳定停留在登录验证码阶段(第 ${round}/${maxRounds} 轮,先等待 3 秒)...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await sleep(settleDelayMs); const rawSnapshot = inspectLoginAuthState(); const snapshot = normalizeStep6Snapshot(rawSnapshot); if (snapshot.state === 'verification_page' || (allowPhoneVerificationPage && snapshot.state === 'phone_verification_page')) { log( snapshot.state === 'phone_verification_page' ? '登录手机验证码页面已稳定就绪。' : '登录验证码页面已稳定就绪。', 'ok', { step: visibleStep, stepKey: 'oauth-login' } ); return createStep6SuccessResult(snapshot, { via, loginVerificationRequestedAt, }); } if (snapshot.state === 'oauth_consent_page') { log('认证页已直接进入 OAuth 授权页,跳过登录验证码步骤。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6OAuthConsentSuccessResult(snapshot, { via: `${via}_oauth_consent`, }); } if (snapshot.state === 'add_email_page') { log('认证页已进入添加邮箱页,登录阶段完成。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6AddEmailSuccessResult(snapshot, { via: `${via}_add_email`, }); } if (snapshot.state === 'login_timeout_error_page') { log(`页面进入登录超时报错页,准备自动恢复后重试步骤 ${visibleStep}。`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6LoginTimeoutRecoverableResult( 'login_timeout_error_page', snapshot, '登录验证码页面准备就绪前进入登录超时报错页。', { visibleStep } ); } if (snapshot.state === 'password_page' || snapshot.state === 'email_page') { return createStep6RecoverableResult('verification_page_unstable', snapshot, { message: `页面曾进入登录验证码阶段,但又回到了${getLoginAuthStateLabel(snapshot)},准备重新执行步骤 ${visibleStep}。`, loginVerificationRequestedAt, }); } if (snapshot.state === 'add_phone_page') { throw new Error(`登录验证码页面准备过程中页面进入手机号页面。URL: ${snapshot.url}`); } } const rawSnapshot = inspectLoginAuthState(); const snapshot = normalizeStep6Snapshot(rawSnapshot); if (snapshot.state === 'verification_page' || (allowPhoneVerificationPage && snapshot.state === 'phone_verification_page')) { log( snapshot.state === 'phone_verification_page' ? '登录手机验证码页面已稳定就绪。' : '登录验证码页面已稳定就绪。', 'ok', { step: visibleStep, stepKey: 'oauth-login' } ); return createStep6SuccessResult(snapshot, { via, loginVerificationRequestedAt, }); } if (snapshot.state === 'oauth_consent_page') { log('认证页已直接进入 OAuth 授权页,跳过登录验证码步骤。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6OAuthConsentSuccessResult(snapshot, { via: `${via}_oauth_consent`, }); } if (snapshot.state === 'add_email_page') { log('认证页已进入添加邮箱页,登录阶段完成。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6AddEmailSuccessResult(snapshot, { via: `${via}_add_email`, }); } if (snapshot.state === 'login_timeout_error_page') { log(`页面进入登录超时报错页,准备自动恢复后重试步骤 ${visibleStep}。`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6LoginTimeoutRecoverableResult( 'login_timeout_error_page', snapshot, '登录验证码页面准备就绪前进入登录超时报错页。', { visibleStep } ); } if (snapshot.state === 'password_page' || snapshot.state === 'email_page') { return createStep6RecoverableResult('verification_page_unstable', snapshot, { message: `页面曾进入登录验证码阶段,但又回到了${getLoginAuthStateLabel(snapshot)},准备重新执行步骤 ${visibleStep}。`, loginVerificationRequestedAt, }); } return createStep6RecoverableResult('verification_page_finalize_unknown', snapshot, { message: `登录验证码页面状态在收尾确认阶段未稳定,准备重新执行步骤 ${visibleStep}。`, loginVerificationRequestedAt, }); } function normalizeStep6Snapshot(snapshot) { return snapshot; } function throwForStep6FatalState(snapshot, visibleStep = 7) { snapshot = normalizeStep6Snapshot(snapshot); switch (snapshot?.state) { case 'oauth_consent_page': return; case 'add_phone_page': throw new Error(`当前页面已进入手机号页面,未经过登录验证码页,无法完成步骤 ${visibleStep}。URL: ${snapshot.url}`); case 'unknown': throw new Error(`无法识别当前登录页面状态。URL: ${snapshot?.url || location.href}`); default: return; } } async function triggerLoginSubmitAction(button, fallbackField) { const form = button?.form || fallbackField?.form || button?.closest?.('form') || fallbackField?.closest?.('form') || null; const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; await humanPause(400, 1100); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'submit', label: 'login-submit' }, async () => { if (button && isActionEnabled(button)) { simulateClick(button); return; } if (form && typeof form.requestSubmit === 'function') { if (button && button.form === form) { form.requestSubmit(button); } else { form.requestSubmit(); } return; } if (button && typeof button.click === 'function') { button.click(); return; } throw new Error('未找到可用的登录提交按钮。URL: ' + location.href); }); } function isSignupPasswordErrorPage() { return Boolean(getSignupPasswordTimeoutErrorPageState()); } function isSignupEmailAlreadyExistsPage() { return isSignupPasswordPage() && SIGNUP_EMAIL_EXISTS_PATTERN.test(getPageTextSnapshot()); } function inspectSignupVerificationState() { const postVerificationState = getStep4PostVerificationState(); if (postVerificationState?.state === 'step5') { return { state: 'step5' }; } if (postVerificationState?.state === 'logged_in_home') { return { state: 'logged_in_home', skipProfileStep: true, url: postVerificationState.url || location.href, }; } if (typeof isOAuthConsentPage === 'function' && isOAuthConsentPage()) { return { state: 'oauth_consent_page', url: location.href, }; } if (isSignupPasswordErrorPage()) { const timeoutPage = getSignupPasswordTimeoutErrorPageState(); return { state: 'error', retryButton: timeoutPage?.retryButton || null, userAlreadyExistsBlocked: Boolean(timeoutPage?.userAlreadyExistsBlocked), }; } const contactVerificationServerErrorText = typeof getContactVerificationServerErrorText === 'function' ? getContactVerificationServerErrorText() : ''; if (contactVerificationServerErrorText) { return { state: 'contact_verification_server_error', serverErrorText: contactVerificationServerErrorText, }; } if (typeof isPhoneVerificationPageReady === 'function' && isPhoneVerificationPageReady()) { return { state: 'verification', phoneVerificationPage: true, }; } if (isVerificationPageStillVisible()) { return { state: 'verification' }; } if (isSignupEmailAlreadyExistsPage()) { return { state: 'email_exists' }; } const passwordInput = getSignupPasswordInput(); if (passwordInput) { return { state: 'password', passwordInput, submitButton: getSignupPasswordSubmitButton({ allowDisabled: true }), passwordErrorText: getSignupPasswordFieldErrorText(), }; } return { state: 'unknown' }; } async function waitForSignupVerificationTransition(timeout = 5000) { const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); const snapshot = inspectSignupVerificationState(); if (snapshot.state === 'verification' && !isSignupVerificationPageInteractiveReady(snapshot)) { await sleep(200); continue; } if ( snapshot.state === 'step5' || snapshot.state === 'logged_in_home' || snapshot.state === 'oauth_consent_page' || snapshot.state === 'verification' || snapshot.state === 'contact_verification_server_error' || snapshot.state === 'error' || snapshot.state === 'email_exists' ) { return snapshot; } await sleep(200); } return inspectSignupVerificationState(); } async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const { password } = payload; const prepareSource = String(payload?.prepareSource || '').trim() || 'step4_execute'; const prepareLogLabel = String(payload?.prepareLogLabel || '').trim() || (prepareSource === 'step3_finalize' ? '步骤 3 收尾' : '步骤 4 执行'); const start = Date.now(); let recoveryRound = 0; const maxRecoveryRounds = 3; let passwordPageDiagnosticsLogged = false; const isPasswordSubmitButtonReadyForRetry = (button) => { if (!button || !isActionEnabled(button)) { return false; } const ariaBusy = String(button.getAttribute?.('aria-busy') || '').trim().toLowerCase(); if (ariaBusy === 'true') { return false; } const pendingAttr = [ button.getAttribute?.('data-loading'), button.getAttribute?.('data-pending'), button.getAttribute?.('data-submitting'), button.getAttribute?.('data-state'), ] .map((value) => String(value || '').trim().toLowerCase()) .filter(Boolean) .join(' '); if (/\b(?:true|loading|pending|submitting|busy)\b/.test(pendingAttr)) { return false; } let style = null; try { style = typeof window !== 'undefined' && window.getComputedStyle ? window.getComputedStyle(button) : null; } catch { style = null; } if (style?.pointerEvents === 'none') { return false; } const opacity = Number.parseFloat(style?.opacity || ''); if (Number.isFinite(opacity) && opacity < 0.8) { return false; } return true; }; while (Date.now() - start < timeout && recoveryRound < maxRecoveryRounds) { throwIfStopped(); const roundNo = recoveryRound + 1; log(`${prepareLogLabel}:等待页面进入验证码阶段(第 ${roundNo}/${maxRecoveryRounds} 轮,先等待 5 秒)...`, 'info'); const snapshot = await waitForSignupVerificationTransition(5000); if (snapshot.state === 'step5') { log(`${prepareLogLabel}:页面已进入验证码后的下一阶段,本步骤按已完成处理。`, 'ok'); return { ready: true, alreadyVerified: true, retried: recoveryRound, prepareSource }; } if (snapshot.state === 'logged_in_home') { log(`${prepareLogLabel}:页面已直接进入 ChatGPT 已登录态,本步骤按已完成处理,并将跳过步骤 5。`, 'ok'); return { ready: true, alreadyVerified: true, skipProfileStep: true, retried: recoveryRound, prepareSource, }; } if (snapshot.state === 'oauth_consent_page') { log(`${prepareLogLabel}:页面已直接进入 OAuth 授权页,本步骤按已完成处理。`, 'ok'); return { ready: true, alreadyVerified: true, state: 'oauth_consent_page', skipLoginVerificationStep: true, directOAuthConsentPage: true, skipProfileStep: true, retried: recoveryRound, prepareSource, }; } if (snapshot.state === 'verification') { await waitForDocumentLoadComplete(15000, `${prepareLogLabel}:注册验证码页面`); await waitForVerificationCodeTarget(15000); log(`${prepareLogLabel}:验证码页面已完成加载并就绪${recoveryRound ? `(期间自动恢复 ${recoveryRound} 次)` : ''}。`, 'ok'); return { ready: true, retried: recoveryRound, prepareSource }; } if (snapshot.state === 'email_exists') { throw new Error('当前邮箱已存在,需要重新开始新一轮。'); } if (snapshot.state === 'contact_verification_server_error') { const serverErrorText = String(snapshot.serverErrorText || '').trim() || 'OpenAI contact-verification 页面返回 HTTP ERROR 500。'; log(`${prepareLogLabel}:检测到 contact-verification 500 错误页,当前轮直接报错。`, 'warn'); throw buildContactVerificationServerError(serverErrorText); } if (snapshot.state === 'error') { if (snapshot.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } recoveryRound += 1; await recoverCurrentAuthRetryPage({ flow: 'signup', logLabel: `${prepareLogLabel}:检测到注册认证重试页,正在点击“重试”恢复(第 ${recoveryRound}/${maxRecoveryRounds} 次)`, step: 4, timeoutMs: 12000, }); continue; } if (snapshot.state === 'password') { if (snapshot.passwordErrorText) { log(`${prepareLogLabel}:检测到密码页报错“${snapshot.passwordErrorText}”,当前轮将回到步骤 1 重新开始。`, 'warn'); throw createSignupPhonePasswordMismatchError(snapshot.passwordErrorText); } if (!passwordPageDiagnosticsLogged) { passwordPageDiagnosticsLogged = true; logSignupPasswordDiagnostics(`${prepareLogLabel}:页面仍停留在密码页`); } if (!password) { throw new Error('当前回到了密码页,但没有可用密码,无法自动重新提交。'); } if ((snapshot.passwordInput.value || '') !== password) { log(`${prepareLogLabel}:页面仍停留在密码页,正在重新填写密码...`, 'warn'); await humanPause(450, 1100); await performOperationWithDelay({ stepKey: 'fill-password', kind: 'fill', label: 'retry-signup-password' }, async () => { fillInput(snapshot.passwordInput, password); }); } if (snapshot.submitButton && isPasswordSubmitButtonReadyForRetry(snapshot.submitButton)) { recoveryRound += 1; log(`${prepareLogLabel}:页面仍停留在密码页,正在重新点击“继续”(第 ${recoveryRound}/${maxRecoveryRounds} 次)...`, 'warn'); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'fill-password', kind: 'submit', label: 'retry-submit-signup-password' }, async () => { simulateClick(snapshot.submitButton); }); await sleep(1200); continue; } log(`${prepareLogLabel}:页面仍停留在密码页,但“继续”按钮暂不可用,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); continue; } log(`${prepareLogLabel}:页面仍在切换中,准备继续等待(${recoveryRound}/${maxRecoveryRounds})...`, 'warn'); } throw new Error(`等待注册验证码页面就绪超时或自动恢复失败(已尝试 ${recoveryRound}/${maxRecoveryRounds} 轮)。URL: ${location.href}`); } async function waitForVerificationSubmitOutcome(step, timeout, options = {}) { const resolvedTimeout = timeout ?? (step === 8 ? 30000 : 12000); const purpose = options?.purpose || ''; const start = Date.now(); let recoveryCount = 0; const maxRecoveryCount = 2; while (Date.now() - start < resolvedTimeout) { throwIfStopped(); const retryFlow = step === 4 ? 'signup' : 'login'; const retryState = getCurrentAuthRetryPageState(retryFlow); if (retryState?.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } if (step === 8 && retryState?.emailInUseBlocked) { throw createStep8EmailInUseError(); } if (step === 8 && retryState?.maxCheckAttemptsBlocked) { throw createAuthMaxCheckAttemptsError(); } if (retryState) { if (recoveryCount >= maxRecoveryCount) { throw new Error(`步骤 ${step}:验证码提交后连续进入认证重试页 ${maxRecoveryCount} 次,页面仍未恢复。URL: ${location.href}`); } recoveryCount += 1; log(`步骤 ${step}:验证码提交后进入认证重试页,正在自动恢复(${recoveryCount}/${maxRecoveryCount})...`, 'warn'); await recoverCurrentAuthRetryPage({ flow: retryFlow, logLabel: `步骤 ${step}:验证码提交后检测到认证重试页,正在点击“重试”恢复`, step, timeoutMs: 12000, }); continue; } if (step === 4) { const postVerificationState = getStep4PostVerificationState({ ignoreVerificationVisibility: true }); if (postVerificationState?.state === 'logged_in_home') { return { success: true, skipProfileStep: true, url: postVerificationState.url || location.href, }; } if (postVerificationState?.state === 'step5') { return { success: true }; } if (purpose === 'signup' && isEmailVerificationPage()) { return { success: true, emailVerificationRequired: true, emailVerificationPage: true, url: location.href, }; } } const errorText = getVerificationErrorText(); if (errorText) { return { invalidCode: true, errorText }; } if (step === 8 && isStep8Ready()) { return { success: true }; } if (step === 8 && isAddPhonePageReady()) { return { success: true, addPhonePage: true, url: location.href }; } await sleep(150); } if (step === 4) { const signupRetryState = getCurrentAuthRetryPageState('signup'); if (signupRetryState?.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } const postVerificationState = getStep4PostVerificationState({ ignoreVerificationVisibility: true }); if (postVerificationState?.state === 'logged_in_home') { return { success: true, skipProfileStep: true, url: postVerificationState.url || location.href, }; } if (postVerificationState?.state === 'step5') { return { success: true }; } if (purpose === 'signup' && isEmailVerificationPage()) { return { success: true, emailVerificationRequired: true, emailVerificationPage: true, url: location.href, }; } } if (isVerificationPageStillVisible()) { return { invalidCode: true, errorText: getVerificationErrorText() || '提交后仍停留在验证码页面,准备重新发送验证码。', }; } return { success: true, assumed: true }; } function getVerificationSubmitButtonForTarget(codeInput, options = {}) { const { allowDisabled = false } = options; const form = codeInput?.form || codeInput?.closest?.('form') || null; const isUsableAction = (element) => { if (!element || !isVisibleElement(element)) return false; return allowDisabled || isActionEnabled(element); }; const findSubmitInRoot = (root) => { if (!root?.querySelectorAll) return null; const directCandidates = root.querySelectorAll('button[type="submit"], input[type="submit"]'); for (const element of directCandidates) { if (isUsableAction(element)) { return element; } } const textCandidates = root.querySelectorAll('button, [role="button"], input[type="button"], input[type="submit"]'); return Array.from(textCandidates).find((element) => { if (!isUsableAction(element)) return false; const text = getActionText(element); return /verify|confirm|submit|continue|确认|验证|继续|確認|認証|検証|続行|続ける|次へ|送信/i.test(text); }) || null; }; return findSubmitInRoot(form) || findSubmitInRoot(document); } async function waitForVerificationSubmitButton(codeInput, timeout = 5000) { const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); if (is405MethodNotAllowedPage()) { throw new Error('当前页面处于 405 错误恢复流程中,暂时无法定位验证码提交按钮。'); } const button = getVerificationSubmitButtonForTarget(codeInput, { allowDisabled: false }); if (button) { return button; } await sleep(150); } return null; } async function waitForVerificationCodeTarget(timeout = 10000) { const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); if (is405MethodNotAllowedPage()) { throw new Error('当前页面处于 405 错误恢复流程中,暂时无法定位验证码输入框。'); } const target = getVerificationCodeTarget(); if (target) { return target; } await sleep(150); } throw new Error('未找到验证码输入框。URL: ' + location.href); } async function waitForSplitVerificationInputsFilled(inputs, code, timeout = 2500) { const expected = String(code || '').slice(0, 6); const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); const current = Array.from(inputs || []) .slice(0, expected.length) .map((input) => String(input?.value || '').trim()) .join(''); if (current === expected) { return true; } await sleep(100); } return false; } async function fillVerificationCode(step, payload) { const { code, signupProfile } = payload; if (!code) throw new Error('未提供验证码。'); const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; if (step === 4) { const postVerificationState = getStep4PostVerificationState(); if (postVerificationState?.state === 'logged_in_home') { if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); log(`步骤 ${step}:检测到页面已进入 ChatGPT 已登录态,本次验证码提交按成功处理。`, 'ok'); return { success: true, assumed: true, alreadyAdvanced: true, skipProfileStep: true, url: postVerificationState.url || location.href, }; } if (postVerificationState?.state === 'step5') { if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); log(`步骤 ${step}:检测到页面已进入下一阶段,本次验证码提交按成功处理。`, 'ok'); return { success: true, assumed: true, alreadyAdvanced: true }; } } if (step === 8) { if (isStep8Ready()) { log(`步骤 ${step}:检测到页面已进入 OAuth 同意页,本次验证码提交按成功处理。`, 'ok'); return { success: true, assumed: true, alreadyAdvanced: true }; } if (isAddPhonePageReady()) { return { success: true, addPhonePage: true, url: location.href }; } } log(`步骤 ${step}:正在填写验证码:${code}`); if (step === 8) { await waitForLoginVerificationPageReady(10000, step, { allowPhoneVerificationPage: payload?.purpose === 'login' || payload?.loginIdentifierType === 'phone', }); } if (step === 4) { await waitForDocumentLoadComplete(15000, `步骤 ${step}:注册验证码页面`); } const combinedSignupProfilePage = step === 4 && await waitForCombinedSignupVerificationProfilePage(); if (combinedSignupProfilePage) { if (!signupProfile || !signupProfile.firstName || !signupProfile.lastName) { throw new Error('当前注册验证码页面要求同时填写资料,但未提供姓名或生日数据。'); } await step5_fillNameBirthday({ ...signupProfile, prefillOnly: true, }); } // Find code input — could be a single input or multiple separate inputs // Retry with 405 error recovery if needed const maxRetries = 3; let codeInput = null; let splitInputs = null; for (let retry = 0; retry <= maxRetries; retry++) { throwIfStopped(); // Before looking for input, check if page is in 405 error state if (is405MethodNotAllowedPage()) { log(`步骤 ${step}:检测到 405 错误页面,正在恢复...`, 'warn'); await handle405ResendError(step, 30000); continue; } try { const verificationTarget = await waitForVerificationCodeTarget(10000); if (verificationTarget.type === 'split') { splitInputs = verificationTarget.elements; } else { codeInput = verificationTarget.element; } break; // Found it } catch { // No input found — check if it's a 405 error and can be recovered if (is405MethodNotAllowedPage() && retry < maxRetries) { log(`步骤 ${step}:未找到验证码输入框且页面出现 405 错误,正在恢复...`, 'warn'); await handle405ResendError(step, 30000); continue; } throw new Error('未找到验证码输入框。URL: ' + location.href); } } if (splitInputs?.length >= 6) { log(`步骤 ${step}:发现分开的单字符验证码输入框,正在逐个填写...`); await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'grouped-code', label: 'split-code' }, async () => { for (let i = 0; i < 6 && i < splitInputs.length; i++) { const targetInput = splitInputs[i]; try { targetInput.focus?.(); } catch {} fillInput(splitInputs[i], code[i]); try { targetInput.dispatchEvent(new KeyboardEvent('keyup', { key: code[i], bubbles: true })); } catch {} } }); const filled = await waitForSplitVerificationInputsFilled(splitInputs, code, 2500); if (!filled) { const current = Array.from(splitInputs) .slice(0, 6) .map((input) => String(input?.value || '').trim() || '_') .join(''); log(`步骤 ${step}:分格验证码输入框未稳定呈现目标值,当前页面值为 ${current},准备继续观察提交流程。`, 'warn'); } else { log(`步骤 ${step}:分格验证码输入框已稳定显示 ${code}。`, 'info'); } await sleep(800); const splitSubmitBtn = await waitForVerificationSubmitButton(splitInputs[0], 2000).catch(() => null); if (splitSubmitBtn) { await humanPause(450, 1200); await performOperationWithDelay({ stepKey: 'fetch-signup-code', kind: 'submit', label: 'submit-code' }, async () => { simulateClick(splitSubmitBtn); }); log(`步骤 ${step}:分格验证码已提交`); } else { log(`步骤 ${step}:分格验证码页面未找到可点击提交按钮,继续等待页面自动推进。`, 'info'); } const outcome = await waitForVerificationSubmitOutcome(step, undefined, payload); if (outcome.invalidCode) { log(`步骤 ${step}:验证码被拒绝:${outcome.errorText}`, 'warn'); } else if (outcome.emailVerificationRequired) { log(`步骤 ${step}:手机验证码已通过,页面进入邮箱验证码验证。`, 'ok'); } else if (outcome.addPhonePage) { log(`步骤 ${step}:验证码提交后页面进入手机号页面,当前流程将停止自动授权。`, 'warn'); } else { if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); log(`步骤 ${step}:验证码已通过${outcome.assumed ? '(按成功推定)' : ''}。`, 'ok'); } if (combinedSignupProfilePage && !outcome.invalidCode) { outcome.skipProfileStep = true; outcome.skipProfileStepReason = 'combined_verification_profile'; } return outcome; } if (!codeInput) { throw new Error('未找到验证码输入框。URL: ' + location.href); } await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'fill', label: 'verification-code' }, async () => { fillInput(codeInput, code); }); log(`步骤 ${step}:验证码已填写`); // Submit await sleep(800); const submitBtn = await waitForVerificationSubmitButton(codeInput, 5000).catch(() => null); if (submitBtn) { await humanPause(450, 1200); await performOperationWithDelay({ stepKey: step === 8 ? 'oauth-login' : 'fetch-signup-code', kind: 'submit', label: 'submit-code' }, async () => { simulateClick(submitBtn); }); log(`步骤 ${step}:验证码已提交`); } else { log(`步骤 ${step}:未找到可提交的验证码按钮,先等待页面自动推进或反馈结果。`, 'warn'); } const outcome = await waitForVerificationSubmitOutcome(step, undefined, payload); if (outcome.invalidCode) { log(`步骤 ${step}:验证码被拒绝:${outcome.errorText}`, 'warn'); } else if (outcome.emailVerificationRequired) { log(`步骤 ${step}:手机验证码已通过,页面进入邮箱验证码验证。`, 'ok'); } else if (outcome.addPhonePage) { log(`步骤 ${step}:验证码提交后页面进入手机号页面,当前流程将停止自动授权。`, 'warn'); } else { if (typeof clearStep405RecoveryCount === 'function') clearStep405RecoveryCount(step); log(`步骤 ${step}:验证码已通过${outcome.assumed ? '(按成功推定)' : ''}。`, 'ok'); } if (combinedSignupProfilePage && !outcome.invalidCode) { outcome.skipProfileStep = true; outcome.skipProfileStepReason = 'combined_verification_profile'; } return outcome; } // ============================================================ // Step 7: Login with registered account (on OAuth auth page) // ============================================================ function getStep6OptionMessage(value, snapshot) { return typeof value === 'function' ? value(snapshot) : String(value || ''); } async function resolveStep6PostSubmitSnapshot(snapshot, options = {}) { const normalizedSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState()); const { via = 'post_submit', loginVerificationRequestedAt = null, oauthConsentVia = `${via}_oauth_consent`, timeoutRecoveryReason = 'login_timeout_error_page', timeoutRecoveryMessage = '登录提交后进入登录超时报错页。', timeoutRecoveryVia = `${via}_timeout_recovered`, allowPhoneVerificationPage = false, allowPhoneAction = false, allowPasswordAction = false, allowEmailAction = false, allowFinalPhoneAction = false, allowFinalPasswordAction = false, allowFinalEmailAction = false, allowFinalSwitchAction = false, visibleStep = 7, final = false, addPhoneMessage, } = options; if (normalizedSnapshot.state === 'verification_page' || (allowPhoneVerificationPage && normalizedSnapshot.state === 'phone_verification_page')) { return { action: 'done', result: createStep6SuccessResult(normalizedSnapshot, { via, loginVerificationRequestedAt, }), }; } if (normalizedSnapshot.state === 'oauth_consent_page') { return { action: 'done', result: createStep6OAuthConsentSuccessResult(normalizedSnapshot, { via: oauthConsentVia, }), }; } if (normalizedSnapshot.state === 'add_email_page') { return { action: 'done', result: createStep6AddEmailSuccessResult(normalizedSnapshot, { via: `${via}_add_email`, }), }; } if (normalizedSnapshot.state === 'login_timeout_error_page') { const transition = await createStep6LoginTimeoutRecoveryTransition( timeoutRecoveryReason, normalizedSnapshot, timeoutRecoveryMessage, { visibleStep, loginVerificationRequestedAt, via: timeoutRecoveryVia, allowPhoneVerificationPage, } ); if (transition.action === 'done') { return { action: 'done', result: transition.result, }; } if (transition.action === 'phone') { return { action: 'phone', snapshot: transition.snapshot }; } if (transition.action === 'password') { return { action: 'password', snapshot: transition.snapshot }; } if (transition.action === 'email') { return { action: 'email', snapshot: transition.snapshot }; } return { action: 'recoverable', result: transition.result, }; } if (normalizedSnapshot.state === 'phone_entry_page' && (allowPhoneAction || (final && allowFinalPhoneAction))) { return { action: 'phone', snapshot: normalizedSnapshot }; } if (normalizedSnapshot.state === 'password_page') { if (allowPasswordAction || (final && allowFinalPasswordAction)) { return { action: 'password', snapshot: normalizedSnapshot }; } if (final && allowFinalSwitchAction && normalizedSnapshot.switchTrigger) { return { action: 'switch', snapshot: normalizedSnapshot }; } } if (normalizedSnapshot.state === 'email_page' && (allowEmailAction || (final && allowFinalEmailAction))) { return { action: 'email', snapshot: normalizedSnapshot }; } if (normalizedSnapshot.state === 'add_phone_page') { const message = getStep6OptionMessage(addPhoneMessage, normalizedSnapshot) || `登录提交后页面进入手机号页面。URL: ${normalizedSnapshot.url || location.href}`; throw new Error(message); } return null; } async function waitForStep6PostSubmitTransition(options = {}) { const { timeout = 10000, stalledReason = 'post_submit_stalled', stalledMessage = '登录提交后未进入可识别的下一页。', } = options; const start = Date.now(); let snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); while (Date.now() - start < timeout) { throwIfStopped(); snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); const transition = await resolveStep6PostSubmitSnapshot(snapshot, { ...options, final: false, }); if (transition) { return transition; } await sleep(250); } snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); const transition = await resolveStep6PostSubmitSnapshot(snapshot, { ...options, final: true, }); if (transition) { return transition; } return { action: 'recoverable', result: createStep6RecoverableResult(stalledReason, snapshot, { message: stalledMessage, loginVerificationRequestedAt: options.loginVerificationRequestedAt || null, }), }; } async function waitForStep6EmailSubmitTransition(emailSubmittedAt, timeout = 12000, options = {}) { return waitForStep6PostSubmitTransition({ timeout, visibleStep: Math.floor(Number(options?.visibleStep) || 0) || 7, via: 'email_submit', oauthConsentVia: 'email_submit_oauth_consent', loginVerificationRequestedAt: emailSubmittedAt, timeoutRecoveryMessage: '提交邮箱后进入登录超时报错页。', timeoutRecoveryVia: 'email_submit_timeout_recovered', allowPasswordAction: true, stalledReason: 'email_submit_stalled', stalledMessage: '提交邮箱后长时间未进入密码页或登录验证码页。', addPhoneMessage: (snapshot) => `提交邮箱后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`, }); } async function waitForStep6PhoneSubmitTransition(phoneSubmittedAt, timeout = 12000, options = {}) { return waitForStep6PostSubmitTransition({ timeout, visibleStep: Math.floor(Number(options?.visibleStep) || 0) || 7, via: 'phone_submit', oauthConsentVia: 'phone_submit_oauth_consent', loginVerificationRequestedAt: phoneSubmittedAt, timeoutRecoveryMessage: '提交手机号后进入登录超时报错页。', timeoutRecoveryVia: 'phone_submit_timeout_recovered', allowPhoneVerificationPage: true, allowPasswordAction: true, allowFinalPhoneAction: true, stalledReason: 'phone_submit_stalled', stalledMessage: '提交手机号后长时间未进入密码页或手机验证码页。', addPhoneMessage: (snapshot) => `提交手机号后页面直接进入手机号补全页面,未经过登录验证码页。URL: ${snapshot.url}`, }); } async function waitForStep6PasswordSubmitTransition(passwordSubmittedAt, timeout = 10000, options = {}) { return waitForStep6PostSubmitTransition({ timeout, visibleStep: Math.floor(Number(options?.visibleStep) || 0) || 7, via: 'password_submit', oauthConsentVia: 'password_submit_oauth_consent', loginVerificationRequestedAt: passwordSubmittedAt, timeoutRecoveryMessage: '提交密码后进入登录超时报错页。', timeoutRecoveryVia: 'password_submit_timeout_recovered', allowFinalSwitchAction: true, stalledReason: 'password_submit_stalled', stalledMessage: '提交密码后仍未进入登录验证码页。', addPhoneMessage: (snapshot) => `提交密码后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`, }); } async function waitForStep6SwitchTransition(loginVerificationRequestedAt, timeout = 10000, options = {}) { const transition = await waitForStep6PostSubmitTransition({ timeout, visibleStep: Math.floor(Number(options?.visibleStep) || 0) || 7, via: 'switch_to_one_time_code_login', oauthConsentVia: 'switch_to_one_time_code_oauth_consent', loginVerificationRequestedAt, timeoutRecoveryMessage: '切换到一次性验证码登录后进入登录超时报错页。', timeoutRecoveryVia: 'switch_to_one_time_code_timeout_recovered', stalledReason: 'one_time_code_switch_stalled', stalledMessage: '点击一次性验证码登录后仍未进入登录验证码页。', addPhoneMessage: (snapshot) => `切换到一次性验证码登录后页面直接进入手机号页面,未经过登录验证码页。URL: ${snapshot.url}`, }); if (transition.action === 'done' || transition.action === 'recoverable') { return transition.result; } return transition; } async function waitForLoginEntryOpenTransition(timeout = 10000) { const start = Date.now(); let snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); while (Date.now() - start < timeout) { throwIfStopped(); snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); if (snapshot.state !== 'unknown' && snapshot.state !== 'entry_page') { return snapshot; } await sleep(250); } return snapshot; } function isOpenAiOAuthAuthorizationRoute(rawUrl = '') { const value = String(rawUrl || '').trim(); if (!value) { return false; } try { const parsed = new URL(value, location.href); const hostname = String(parsed.hostname || '').toLowerCase(); if (!['auth.openai.com', 'auth0.openai.com', 'accounts.openai.com'].includes(hostname)) { return false; } const pathname = parsed.pathname || '/'; return /^\/(?:authorize|oauth(?:2)?\/|api\/oauth\/oauth2\/auth|sign-in-with-chatgpt\/)/i.test(pathname); } catch { return false; } } function isPostChooseAccountOAuthRoute(snapshot = {}) { const url = String(snapshot?.url || location.href || '').trim(); if (!isOpenAiOAuthAuthorizationRoute(url)) { return false; } try { const parsed = new URL(url, location.href); return !/\/choose-an-account(?:[/?#]|$)/i.test(parsed.pathname || ''); } catch { return !/\/choose-an-account(?:[/?#]|$)/i.test(url); } } async function waitForChooseAccountTransition(timeout = 15000) { const start = Date.now(); let snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); while (Date.now() - start < timeout) { throwIfStopped(); snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); if (isPostChooseAccountOAuthRoute(snapshot)) { return { ...snapshot, oauthAuthorizationRoute: true, }; } if (snapshot.state !== 'unknown' && snapshot.state !== 'choose_account_page') { return snapshot; } await sleep(250); } return snapshot; } async function step6ChooseExistingAccount(payload, snapshot) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7; const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState()); const loginIdentifierType = String(payload?.loginIdentifierType || '').trim(); if (loginIdentifierType === 'phone') { return createStep6RecoverableResult('choose_account_requires_email_identifier', currentSnapshot, { message: 'OpenAI choose-account page requires an email account card; current login identifier is phone.', }); } const email = normalizeAuthAccountIdentifier(payload?.email || payload?.accountIdentifier || ''); if (!email || !email.includes('@')) { return createStep6RecoverableResult('missing_choose_account_email', currentSnapshot, { message: 'OpenAI choose-account page is visible, but the target email is missing.', }); } const chooseAccountAction = await resolveChooseAccountAction(email); if ( chooseAccountAction.snapshot && chooseAccountAction.snapshot.state !== 'unknown' && chooseAccountAction.snapshot.state !== 'choose_account_page' ) { const resolvedSnapshot = chooseAccountAction.snapshot; if (resolvedSnapshot.state === 'oauth_consent_page') { return createStep6OAuthConsentSuccessResult(resolvedSnapshot, { via: 'choose_account_oauth_consent_page', }); } if (resolvedSnapshot.oauthAuthorizationRoute || isPostChooseAccountOAuthRoute(resolvedSnapshot)) { return createStep6OAuthConsentSuccessResult(resolvedSnapshot, { via: 'choose_account_oauth_authorization_route', }); } if (resolvedSnapshot.state === 'email_page') { return step6LoginFromEmailPage(payload, resolvedSnapshot); } if (resolvedSnapshot.state === 'entry_page') { return step6OpenLoginEntry(payload, resolvedSnapshot); } if (resolvedSnapshot.state === 'password_page') { return step6LoginFromPasswordPage(payload, resolvedSnapshot); } if (resolvedSnapshot.state === 'phone_entry_page') { return step6LoginFromPhonePage(payload, resolvedSnapshot); } } const target = chooseAccountAction.target; if (!target) { const otherAccountButton = chooseAccountAction.otherAccountButton; if (otherAccountButton) { const listedEmails = getChooseAccountListedEmails(); const listedLabel = listedEmails.length ? `页面已有账号:${listedEmails.join(', ')}。` : '页面未读取到已有账号邮箱。'; log(`OpenAI 选择账号页未列出目标邮箱 ${email},${listedLabel} 将点击“登录至另一个帐户”继续目标邮箱登录。`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'choose-other-account' }, async () => { simulateClick(otherAccountButton); }); const otherAccountSnapshot = normalizeStep6Snapshot(await waitForChooseAccountTransition(15000)); if (otherAccountSnapshot.state === 'email_page') { return step6LoginFromEmailPage(payload, otherAccountSnapshot); } if (otherAccountSnapshot.state === 'entry_page') { return step6OpenLoginEntry(payload, otherAccountSnapshot); } if (otherAccountSnapshot.state === 'password_page') { return step6LoginFromPasswordPage(payload, otherAccountSnapshot); } if (otherAccountSnapshot.state === 'phone_entry_page') { return step6LoginFromPhonePage(payload, otherAccountSnapshot); } return createStep6RecoverableResult('choose_account_other_account_transition_stalled', otherAccountSnapshot, { message: `Clicked another-account login because ${email} was not listed, but the page did not enter a supported login state.`, }); } return createStep6RecoverableResult('choose_account_target_not_found', currentSnapshot, { message: `OpenAI choose-account page does not contain target email ${email}.`, }); } log(`Detected OpenAI choose-account page, selecting ${email}...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'choose-existing-account' }, async () => { simulateClick(target); }); const nextSnapshot = normalizeStep6Snapshot(await waitForChooseAccountTransition(15000)); if (nextSnapshot.state === 'oauth_consent_page') { return createStep6OAuthConsentSuccessResult(nextSnapshot, { via: 'choose_account_oauth_consent_page', }); } if (nextSnapshot.oauthAuthorizationRoute || isPostChooseAccountOAuthRoute(nextSnapshot)) { return createStep6OAuthConsentSuccessResult(nextSnapshot, { via: 'choose_account_oauth_authorization_route', }); } if (nextSnapshot.state === 'add_email_page') { return createStep6AddEmailSuccessResult(nextSnapshot, { via: 'choose_account_add_email_page', }); } if (nextSnapshot.state === 'verification_page') { return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: null, via: 'choose_account_verification_page', }); } if (nextSnapshot.state === 'phone_verification_page') { return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: null, via: 'choose_account_phone_verification_page', allowPhoneVerificationPage: true, }); } if (nextSnapshot.state === 'password_page') { return step6LoginFromPasswordPage(payload, nextSnapshot); } if (nextSnapshot.state === 'email_page') { return step6LoginFromEmailPage(payload, nextSnapshot); } if (nextSnapshot.state === 'phone_entry_page') { return step6LoginFromPhonePage(payload, nextSnapshot); } if (nextSnapshot.state === 'login_timeout_error_page') { const transition = await createStep6LoginTimeoutRecoveryTransition( 'login_timeout_after_choose_account', nextSnapshot, 'Clicking the existing OpenAI account entered the login timeout page.', { visibleStep } ); if (transition.action === 'done') return transition.result; if (transition.action === 'phone') return step6LoginFromPhonePage(payload, transition.snapshot); if (transition.action === 'email') return step6LoginFromEmailPage(payload, transition.snapshot); if (transition.action === 'password') return step6LoginFromPasswordPage(payload, transition.snapshot); return transition.result; } return createStep6RecoverableResult('choose_account_transition_stalled', nextSnapshot, { message: `Clicked ${email} on OpenAI choose-account page, but the page did not enter a supported next state.`, }); } async function waitForPhoneLoginEntrySwitchTransition(timeout = 10000) { const start = Date.now(); let snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); while (Date.now() - start < timeout) { throwIfStopped(); snapshot = normalizeStep6Snapshot(inspectLoginAuthState()); if (snapshot.state !== 'unknown' && snapshot.state !== 'email_page' && snapshot.state !== 'entry_page') { return snapshot; } await sleep(250); } return snapshot; } async function step6OpenLoginEntry(payload, snapshot) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7; const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState()); const preferPhoneLogin = String(payload?.loginIdentifierType || '').trim() === 'phone' || (!payload?.email && payload?.phoneNumber); const genericEntryTrigger = currentSnapshot.loginEntryTrigger || findLoginEntryTrigger(); const phoneEntryTrigger = currentSnapshot.phoneEntryTrigger || findLoginPhoneEntryTrigger(); const trigger = genericEntryTrigger || (preferPhoneLogin ? phoneEntryTrigger : null); if (!trigger || !isActionEnabled(trigger)) { return createStep6RecoverableResult('missing_login_entry_trigger', currentSnapshot, { message: preferPhoneLogin ? '当前登录入口页没有可点击的手机号登录入口。' : '当前登录入口页没有可点击的邮箱登录入口。', }); } log(`检测到登录入口页,正在点击 "${getActionText(trigger).slice(0, 80)}"...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'open-login-entry' }, async () => { simulateClick(trigger); }); const nextSnapshot = await waitForLoginEntryOpenTransition(); if (nextSnapshot.state === 'email_page') { if (preferPhoneLogin) { return switchFromEmailPageToPhoneLogin(payload, nextSnapshot); } return step6LoginFromEmailPage(payload, nextSnapshot); } if (nextSnapshot.state === 'password_page') { return step6LoginFromPasswordPage(payload, nextSnapshot); } if (nextSnapshot.state === 'phone_entry_page') { return step6LoginFromPhonePage(payload, nextSnapshot); } if (nextSnapshot.state === 'verification_page') { return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: null, via: 'entry_open_verification_page', }); } if (nextSnapshot.state === 'oauth_consent_page') { return createStep6OAuthConsentSuccessResult(nextSnapshot, { via: 'entry_open_oauth_consent_page', }); } if (nextSnapshot.state === 'add_email_page') { return createStep6AddEmailSuccessResult(nextSnapshot, { via: 'entry_open_add_email_page', }); } if (nextSnapshot.state === 'login_timeout_error_page') { const transition = await createStep6LoginTimeoutRecoveryTransition( 'login_timeout_after_entry_open', nextSnapshot, '点击登录入口后进入登录超时报错页。', { visibleStep } ); if (transition.action === 'done') return transition.result; if (transition.action === 'phone') return step6LoginFromPhonePage(payload, transition.snapshot); if (transition.action === 'email') return step6LoginFromEmailPage(payload, transition.snapshot); if (transition.action === 'password') return step6LoginFromPasswordPage(payload, transition.snapshot); return transition.result; } return createStep6RecoverableResult('login_entry_open_stalled', nextSnapshot, { message: '点击登录入口后仍未进入手机号/邮箱/密码/验证码页。', }); } async function step6SwitchToOneTimeCodeLogin(payload, snapshot) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7; const switchTrigger = snapshot?.switchTrigger || findOneTimeCodeLoginTrigger(); if (!switchTrigger || !isActionEnabled(switchTrigger)) { return createStep6RecoverableResult('missing_one_time_code_trigger', normalizeStep6Snapshot(inspectLoginAuthState()), { message: '当前登录页没有可用的一次性验证码登录入口。', }); } log('已检测到一次性验证码登录入口,准备切换...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); const loginVerificationRequestedAt = Date.now(); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'switch-one-time-code-login' }, async () => { simulateClick(switchTrigger); }); log('已点击一次性验证码登录', 'info', { step: visibleStep, stepKey: 'oauth-login' }); await sleep(1200); const result = await waitForStep6SwitchTransition(loginVerificationRequestedAt, 10000, { visibleStep }); if (result?.step6Outcome === 'success') { if (result.skipLoginVerificationStep || result.addEmailPage) { return result; } return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: result.loginVerificationRequestedAt || loginVerificationRequestedAt, via: result.via || 'switch_to_one_time_code_login', }); } if (result?.action === 'password') { return step6LoginFromPasswordPage(payload, result.snapshot); } if (result?.action === 'phone') { return step6LoginFromPhonePage(payload, result.snapshot); } if (result?.action === 'email') { return step6LoginFromEmailPage(payload, result.snapshot); } return result; } async function step6LoginFromPhonePage(payload, snapshot) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7; const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState()); const phoneInput = currentSnapshot.phoneInput || getLoginPhoneInput(); const phoneNumber = String(payload?.phoneNumber || payload?.accountIdentifier || '').trim(); const countryLabel = String(payload?.countryLabel || '').trim(); const countryId = payload?.countryId; if (!phoneNumber) { return createStep6RecoverableResult('missing_phone_number', currentSnapshot, { message: '手机号登录时缺少手机号,请重新执行步骤 2 获取号码。', }); } if (!phoneInput) { return createStep6RecoverableResult('missing_phone_input', currentSnapshot, { message: '当前登录页没有可用的手机号输入框。', }); } const dialCodeFromSelection = await selectCountryForPhoneInput(phoneInput, phoneNumber, countryLabel, { visibleStep }); const dialCode = dialCodeFromSelection || resolveLoginPhoneDialCode(phoneInput, { phoneNumber, countryId, countryLabel, }); const inputValue = toNationalPhoneNumber(phoneNumber, dialCode); if (!inputValue) { throw new Error(`步骤 ${visibleStep}:手机号为空,无法填写。`); } log( `步骤 ${visibleStep}:手机号登录填写前诊断 ${JSON.stringify({ phoneNumber, countryLabel, countryId, dialCode, input: getLoginPhoneInputDiagnostics(phoneInput), candidates: getLoginPhoneInputCandidateDiagnostics(), })}`, 'info', { step: visibleStep, stepKey: 'oauth-login' } ); log(`步骤 ${visibleStep}:正在填写手机号 ${phoneNumber}...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(500, 1400); const fillResult = await fillLoginPhoneInputAndConfirm(phoneInput, { phoneNumber, dialCode, visibleStep, resolvePhoneInput: () => getLoginPhoneInput() || phoneInput, }); log(`步骤 ${visibleStep}:手机号已填写${dialCode ? `(区号 +${dialCode},本地号 ${fillResult.inputValue},可见提交值 ${fillResult.attemptedValue})` : ''}。`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await sleep(500); const verifiedPhoneInput = fillResult.input || phoneInput; const hiddenSync = await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'hidden-sync', label: 'login-phone-pre-submit-hidden-sync' }, async () => ( syncPhoneHiddenFormValue(verifiedPhoneInput, { phoneNumber, dialCode, inputValue }) )); const submitButton = getLoginSubmitButton({ allowDisabled: true }) || currentSnapshot.submitButton; const preSubmitRenderedValue = getPhoneInputRenderedValue(verifiedPhoneInput); const preSubmitHiddenInput = hiddenSync?.input || getPhoneHiddenValueInput(verifiedPhoneInput); const preSubmitDiagnostics = { renderedValue: preSubmitRenderedValue, inputVerified: isPhoneInputValueComplete(verifiedPhoneInput, phoneNumber, dialCode, inputValue), input: getLoginPhoneInputDiagnostics(verifiedPhoneInput), hidden: getLoginPhoneHiddenValueDiagnostics(preSubmitHiddenInput), submitButton: getLoginPhoneSubmitButtonDiagnostics(submitButton), }; log( `步骤 ${visibleStep}:手机号提交前复查 ${JSON.stringify(preSubmitDiagnostics)}`, 'info', { step: visibleStep, stepKey: 'oauth-login' } ); if (!preSubmitDiagnostics.inputVerified) { throw new Error(`步骤 ${visibleStep}:提交前手机号输入框复查失败,完整号码 ${phoneNumber},区号 +${dialCode || '未识别'},期望本地号 ${inputValue},当前输入框为 ${normalizePhoneDigits(preSubmitRenderedValue) || '空'},已停止提交。`); } const phoneSubmittedAt = Date.now(); await triggerLoginSubmitAction(submitButton, verifiedPhoneInput); log(`步骤 ${visibleStep}:手机号已提交。`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); const transition = await waitForStep6PhoneSubmitTransition(phoneSubmittedAt, 12000, { visibleStep }); if (transition.action === 'done') { if (transition.result?.skipLoginVerificationStep || transition.result?.addEmailPage) { return transition.result; } return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: transition.result.loginVerificationRequestedAt || phoneSubmittedAt, via: transition.result.via || 'phone_submit', allowPhoneVerificationPage: true, }); } if (transition.action === 'recoverable') { log(transition.result.message || `提交手机号后仍未进入目标页面,准备重新执行步骤 ${visibleStep}。`, 'warn', { step: visibleStep, stepKey: 'oauth-login', }); return transition.result; } if (transition.action === 'phone') { return step6LoginFromPhonePage(payload, transition.snapshot); } if (transition.action === 'password') { return step6LoginFromPasswordPage(payload, transition.snapshot); } if (transition.action === 'email') { return step6LoginFromEmailPage(payload, transition.snapshot); } return createStep6RecoverableResult('phone_submit_unknown', normalizeStep6Snapshot(inspectLoginAuthState()), { message: '提交手机号后未得到可用的下一步状态。', }); } async function switchFromEmailPageToPhoneLogin(payload, snapshot) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7; let currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState()); let phoneEntryTrigger = currentSnapshot.phoneEntryTrigger || findLoginPhoneEntryTrigger(); if (!phoneEntryTrigger || !isActionEnabled(phoneEntryTrigger)) { const moreOptionsTrigger = currentSnapshot.moreOptionsTrigger || findLoginMoreOptionsTrigger(); if (moreOptionsTrigger && isActionEnabled(moreOptionsTrigger)) { log(`步骤 ${visibleStep}:手机号入口可能隐藏在更多选项中,正在展开更多选项...`, 'info', { step: visibleStep, stepKey: 'oauth-login', }); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'login-more-options' }, async () => { simulateClick(moreOptionsTrigger); }); await sleep(800); currentSnapshot = normalizeStep6Snapshot(inspectLoginAuthState()); phoneEntryTrigger = currentSnapshot.phoneEntryTrigger || findLoginPhoneEntryTrigger(); } } if (!phoneEntryTrigger || !isActionEnabled(phoneEntryTrigger)) { return createStep6RecoverableResult('missing_phone_login_entry_trigger', currentSnapshot, { message: '本轮要求使用手机号登录,但当前邮箱登录页没有可用的手机号登录入口。', }); } log(`步骤 ${visibleStep}:当前在邮箱入口,正在切换到手机号登录...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(350, 900); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'click', label: 'switch-phone-login' }, async () => { simulateClick(phoneEntryTrigger); }); const nextSnapshot = normalizeStep6Snapshot(await waitForPhoneLoginEntrySwitchTransition(20000)); if (nextSnapshot.state === 'phone_entry_page') { return step6LoginFromPhonePage(payload, nextSnapshot); } if (nextSnapshot.state === 'password_page') { return step6LoginFromPasswordPage(payload, nextSnapshot); } if (nextSnapshot.state === 'verification_page' || nextSnapshot.state === 'phone_verification_page') { return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: null, via: 'phone_entry_switch_verification_page', allowPhoneVerificationPage: true, }); } if (nextSnapshot.state === 'oauth_consent_page') { return createStep6OAuthConsentSuccessResult(nextSnapshot, { via: 'phone_entry_switch_oauth_consent_page', }); } if (nextSnapshot.state === 'add_email_page') { return createStep6AddEmailSuccessResult(nextSnapshot, { via: 'phone_entry_switch_add_email_page', }); } if (nextSnapshot.state === 'login_timeout_error_page') { const transition = await createStep6LoginTimeoutRecoveryTransition( 'login_timeout_after_phone_entry_switch', nextSnapshot, '点击手机号登录入口后进入登录超时报错页。', { visibleStep, allowPhoneVerificationPage: true, } ); if (transition.action === 'done') return transition.result; if (transition.action === 'phone') return step6LoginFromPhonePage(payload, transition.snapshot); if (transition.action === 'password') return step6LoginFromPasswordPage(payload, transition.snapshot); if (transition.action === 'email') return step6LoginFromEmailPage(payload, transition.snapshot); return transition.result; } return createStep6RecoverableResult('phone_login_entry_switch_stalled', nextSnapshot, { message: `点击手机号登录入口后仍未进入手机号或密码页,当前停留在${getLoginAuthStateLabel(nextSnapshot)}。`, }); } async function step6LoginFromPasswordPage(payload, snapshot) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7; const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState()); const hasPassword = Boolean(String(payload?.password || '').trim()); if (currentSnapshot.passwordInput) { if (!hasPassword) { if (currentSnapshot.switchTrigger) { log('当前未提供密码,改走一次性验证码登录。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return step6SwitchToOneTimeCodeLogin(payload, currentSnapshot); } return createStep6RecoverableResult('missing_password_and_one_time_code_trigger', currentSnapshot, { message: '登录时未提供密码,且当前页面没有可用的一次性验证码登录入口。', }); } log('已进入密码页,准备填写密码...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); await humanPause(550, 1450); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-password' }, async () => { fillInput(currentSnapshot.passwordInput, payload.password); }); log('已填写密码', 'info', { step: visibleStep, stepKey: 'oauth-login' }); await sleep(500); const passwordSubmittedAt = Date.now(); await triggerLoginSubmitAction(currentSnapshot.submitButton, currentSnapshot.passwordInput); log('已提交密码', 'info', { step: visibleStep, stepKey: 'oauth-login' }); const transition = await waitForStep6PasswordSubmitTransition(passwordSubmittedAt, 10000, { visibleStep }); if (transition.action === 'done') { if (transition.result?.skipLoginVerificationStep || transition.result?.addEmailPage) { return transition.result; } return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: transition.result.loginVerificationRequestedAt || passwordSubmittedAt, via: transition.result.via || 'password_submit', }); } if (transition.action === 'recoverable') { log(transition.result.message || `提交密码后仍未进入登录验证码页面,准备重新执行步骤 ${visibleStep}。`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return transition.result; } if (transition.action === 'password') { return step6LoginFromPasswordPage(payload, transition.snapshot); } if (transition.action === 'phone') { return step6LoginFromPhonePage(payload, transition.snapshot); } if (transition.action === 'email') { return step6LoginFromEmailPage(payload, transition.snapshot); } if (transition.action === 'switch') { return step6SwitchToOneTimeCodeLogin(payload, transition.snapshot); } return createStep6RecoverableResult('password_submit_unknown', normalizeStep6Snapshot(inspectLoginAuthState()), { message: '提交密码后未得到可用的下一步状态。', }); } if (currentSnapshot.switchTrigger) { return step6SwitchToOneTimeCodeLogin(payload, currentSnapshot); } return createStep6RecoverableResult('password_page_unactionable', currentSnapshot, { message: '当前停留在登录页,但没有可提交密码的输入框,也没有一次性验证码登录入口。', }); } async function step6LoginFromEmailPage(payload, snapshot) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7; const currentSnapshot = normalizeStep6Snapshot(snapshot || inspectLoginAuthState()); if (String(payload?.loginIdentifierType || '').trim() === 'phone' && payload?.phoneNumber) { return switchFromEmailPageToPhoneLogin(payload, currentSnapshot); } const emailInput = currentSnapshot.emailInput || getLoginEmailInput(); if (!emailInput) { throw new Error('在登录页未找到邮箱输入框。URL: ' + location.href); } if ((emailInput.value || '').trim() !== payload.email) { await humanPause(500, 1400); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'login-email' }, async () => { fillInput(emailInput, payload.email); }); log('已填写邮箱', 'info', { step: visibleStep, stepKey: 'oauth-login' }); } else { log('邮箱已在输入框中,准备提交...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); } await sleep(500); const emailSubmittedAt = Date.now(); await triggerLoginSubmitAction(currentSnapshot.submitButton, emailInput); log('已提交邮箱', 'info', { step: visibleStep, stepKey: 'oauth-login' }); const transition = await waitForStep6EmailSubmitTransition(emailSubmittedAt, 12000, { visibleStep }); if (transition.action === 'done') { if (transition.result?.skipLoginVerificationStep || transition.result?.addEmailPage) { return transition.result; } return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: transition.result.loginVerificationRequestedAt || emailSubmittedAt, via: transition.result.via || 'email_submit', }); } if (transition.action === 'recoverable') { log(transition.result.message || `提交邮箱后仍未进入目标页面,准备重新执行步骤 ${visibleStep}。`, 'warn', { step: visibleStep, stepKey: 'oauth-login' }); return transition.result; } if (transition.action === 'email') { return step6LoginFromEmailPage(payload, transition.snapshot); } if (transition.action === 'password') { return step6LoginFromPasswordPage(payload, transition.snapshot); } if (transition.action === 'phone') { return step6LoginFromPhonePage(payload, transition.snapshot); } return createStep6RecoverableResult('email_submit_unknown', normalizeStep6Snapshot(inspectLoginAuthState()), { message: '提交邮箱后未得到可用的下一步状态。', }); } async function step6_login(payload) { const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 7; const { email, phoneNumber } = payload; const loginIdentifierType = String(payload?.loginIdentifierType || '').trim(); if (!email && !phoneNumber) throw new Error('登录时缺少邮箱地址或手机号。'); const snapshot = normalizeStep6Snapshot(await waitForKnownLoginAuthState(15000)); if (snapshot.state === 'verification_page' || snapshot.state === 'phone_verification_page') { log('认证页已在登录验证码页,开始确认页面是否稳定。', 'info', { step: visibleStep, stepKey: 'oauth-login' }); return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: null, via: snapshot.state === 'phone_verification_page' ? 'already_on_phone_verification_page' : 'already_on_verification_page', allowPhoneVerificationPage: snapshot.state === 'phone_verification_page', }); } if (snapshot.state === 'oauth_consent_page') { log('认证页已直接进入 OAuth 授权页,跳过登录验证码步骤。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6OAuthConsentSuccessResult(snapshot, { via: 'already_on_oauth_consent_page', }); } if (snapshot.state === 'add_email_page') { log('认证页已在添加邮箱页,登录阶段完成。', 'ok', { step: visibleStep, stepKey: 'oauth-login' }); return createStep6AddEmailSuccessResult(snapshot, { via: 'already_on_add_email_page', }); } if (snapshot.state === 'login_timeout_error_page') { log('检测到登录超时报错页,先尝试恢复当前页面。', 'warn', { step: visibleStep, stepKey: 'oauth-login' }); const transition = await createStep6LoginTimeoutRecoveryTransition( 'login_timeout_error_page', snapshot, '当前页面处于登录超时报错页。', { visibleStep, loginVerificationRequestedAt: null, via: 'login_timeout_initial_recovered', allowPhoneVerificationPage: loginIdentifierType === 'phone' || Boolean(phoneNumber), } ); if (transition.action === 'done') { if (transition.result?.skipLoginVerificationStep || transition.result?.addEmailPage) { return transition.result; } return finalizeStep6VerificationReady({ visibleStep, loginVerificationRequestedAt: transition.result.loginVerificationRequestedAt || null, via: transition.result.via || 'login_timeout_initial_recovered', }); } if (transition.action === 'phone') { return step6LoginFromPhonePage(payload, transition.snapshot); } if (transition.action === 'email') { return step6LoginFromEmailPage(payload, transition.snapshot); } if (transition.action === 'password') { return step6LoginFromPasswordPage(payload, transition.snapshot); } return transition.result; } if (snapshot.state === 'email_page') { if (loginIdentifierType === 'phone' && phoneNumber) { return switchFromEmailPageToPhoneLogin(payload, snapshot); } log(`正在使用 ${email} 登录...`, 'info', { step: visibleStep, stepKey: 'oauth-login' }); return step6LoginFromEmailPage(payload, snapshot); } if (snapshot.state === 'phone_entry_page') { log('正在使用手机号登录...', 'info', { step: visibleStep, stepKey: 'oauth-login' }); return step6LoginFromPhonePage(payload, snapshot); } if (snapshot.state === 'password_page') { log('认证页已在密码页,继续当前登录流程。', 'info', { step: visibleStep, stepKey: 'oauth-login' }); return step6LoginFromPasswordPage(payload, snapshot); } if (snapshot.state === 'choose_account_page') { return step6ChooseExistingAccount(payload, snapshot); } if (snapshot.state === 'entry_page') { return step6OpenLoginEntry(payload, snapshot); } throwForStep6FatalState(snapshot, visibleStep); throw new Error(`无法识别当前登录页面状态。URL: ${snapshot?.url || location.href}`); } async function waitForAddEmailPageReady(timeout = 15000) { const start = Date.now(); let sawAddEmailPage = false; while (Date.now() - start < timeout) { throwIfStopped(); if (isAddEmailPageReady()) { sawAddEmailPage = true; const snapshot = inspectLoginAuthState(); if (snapshot.emailInput || getLoginEmailInput()) { return snapshot; } } await sleep(200); } if (sawAddEmailPage) { throw new Error('等待添加邮箱页面输入框就绪超时。URL: ' + location.href); } throw new Error('等待添加邮箱页面就绪超时。URL: ' + location.href); } async function waitForAddEmailSubmitOutcome(timeout = 45000) { const start = Date.now(); let lastState = inspectLoginAuthState(); while (Date.now() - start < timeout) { throwIfStopped(); lastState = inspectLoginAuthState(); if (lastState.state === 'verification_page') { return { success: true, verificationPage: true, displayedEmail: getLoginVerificationDisplayedEmail(), url: location.href, }; } if (lastState.state === 'oauth_consent_page') { return { success: true, directOAuthConsentPage: true, url: location.href, }; } if (lastState.state === 'login_timeout_error_page') { return { retryPage: true, maxCheckAttempts: Boolean(lastState.maxCheckAttemptsBlocked), emailInUse: Boolean(lastState.emailInUseBlocked), url: location.href, }; } const errorText = getVerificationErrorText(); if (errorText) { return { errorText, url: location.href, }; } const addEmailErrorText = isAddEmailPageReady() ? getVisibleFieldErrorText() : ''; if (addEmailErrorText) { return { errorText: addEmailErrorText, url: location.href, }; } await sleep(200); } throw new Error(`提交邮箱后未进入验证码页。当前状态:${getLoginAuthStateLabel(lastState)}。URL: ${lastState?.url || location.href}`); } async function submitAddEmailAndContinue(payload = {}) { const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const email = String(payload.email || '').trim().toLowerCase(); if (!email) { throw new Error('未提供邮箱地址,无法添加邮箱。'); } const snapshot = await waitForAddEmailPageReady(); const emailInput = snapshot.emailInput || getLoginEmailInput(); if (!emailInput) { throw new Error('添加邮箱页未找到邮箱输入框。URL: ' + location.href); } await humanPause(500, 1400); await performOperationWithDelay({ stepKey: 'oauth-login', kind: 'fill', label: 'add-email' }, async () => { fillInput(emailInput, email); }); log(`步骤 8:已填写邮箱:${email}`); await sleep(500); const submitButton = snapshot.submitButton || getLoginSubmitButton({ allowDisabled: true }); if (!submitButton || !isActionEnabled(submitButton)) { throw new Error('添加邮箱页未找到可点击的继续按钮。URL: ' + location.href); } await triggerLoginSubmitAction(submitButton, emailInput); log('步骤 8:已提交邮箱,正在等待邮箱验证码页...'); const outcome = await waitForAddEmailSubmitOutcome(); if (outcome.errorText && (SIGNUP_EMAIL_EXISTS_PATTERN.test(outcome.errorText) || /email_in_use/i.test(outcome.errorText))) { throw createStep8EmailInUseError(); } if (outcome.errorText) { throw new Error(`添加邮箱失败:${outcome.errorText}`); } if (outcome.emailInUse) { throw createStep8EmailInUseError(); } if (outcome.maxCheckAttempts) { throw createAuthMaxCheckAttemptsError(); } if (outcome.retryPage) { throw new Error(`添加邮箱后进入认证重试页,请重新执行步骤 8。URL: ${outcome.url}`); } return { submitted: true, email, ...outcome, }; } // ============================================================ // Step 9: Find "继续" on OAuth consent page for debugger click // ============================================================ // After login + verification, page shows: // "使用 ChatGPT 登录到 Codex" with a "继续" submit button. // Background performs the actual click through the debugger Input API. async function step8_findAndClick(payload = {}) { const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 9; log('正在查找 OAuth 同意页的“继续”按钮...', 'info', { step: visibleStep, stepKey: 'confirm-oauth' }); const continueBtn = await prepareStep8ContinueButton(); const rect = getSerializableRect(continueBtn); log('已找到“继续”按钮并准备好调试器点击坐标。', 'info', { step: visibleStep, stepKey: 'confirm-oauth' }); return { rect, buttonText: (continueBtn.textContent || '').trim(), url: location.href, }; } function getStep8State() { const continueBtn = getPrimaryContinueButton(); const retryState = getCurrentAuthRetryPageState('auth'); const state = { url: location.href, consentPage: isOAuthConsentPage(), consentReady: isStep8Ready(), verificationPage: isVerificationPageStillVisible(), addPhonePage: isAddPhonePageReady(), addEmailPage: isAddEmailPageReady(), phoneVerificationPage: isPhoneVerificationPageReady(), retryPage: Boolean(retryState), retryEnabled: Boolean(retryState?.retryEnabled), retryTitleMatched: Boolean(retryState?.titleMatched), retryDetailMatched: Boolean(retryState?.detailMatched), maxCheckAttemptsBlocked: Boolean(retryState?.maxCheckAttemptsBlocked), buttonFound: Boolean(continueBtn), buttonEnabled: isButtonEnabled(continueBtn), buttonText: continueBtn ? getActionText(continueBtn) : '', }; if (continueBtn) { try { state.rect = getSerializableRect(continueBtn); } catch { state.rect = null; } } return state; } async function step8_triggerContinue(payload = {}) { const visibleStep = Math.floor(Number(payload?.visibleStep) || 0) || 9; const strategy = payload?.strategy || 'requestSubmit'; const continueBtn = await prepareStep8ContinueButton({ findTimeoutMs: payload?.findTimeoutMs, enabledTimeoutMs: payload?.enabledTimeoutMs, }); const form = continueBtn.form || continueBtn.closest('form'); switch (strategy) { case 'requestSubmit': if (!form || typeof form.requestSubmit !== 'function') { throw new Error('“继续”按钮当前不在可提交的 form 中,无法使用 requestSubmit。URL: ' + location.href); } form.requestSubmit(continueBtn); break; case 'nativeClick': continueBtn.click(); break; case 'dispatchClick': simulateClick(continueBtn); break; default: throw new Error(`未知的 Step ${visibleStep} 触发策略:${strategy}`); } log(`continue button triggered via ${strategy}.`, 'info', { step: visibleStep, stepKey: 'confirm-oauth' }); return { strategy, ...getStep8State(), }; } async function prepareStep8ContinueButton(options = {}) { const { findTimeoutMs = 10000, enabledTimeoutMs = 8000, } = options; const continueBtn = await findContinueButton(findTimeoutMs); await waitForButtonEnabled(continueBtn, enabledTimeoutMs); await humanPause(250, 700); continueBtn.scrollIntoView({ behavior: 'auto', block: 'center' }); continueBtn.focus(); await waitForStableButtonRect(continueBtn); return continueBtn; } async function findContinueButton(timeout = 10000) { const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); if (isAddPhonePageReady()) { throw new Error('当前页面已进入手机号页面,不是 OAuth 授权同意页。URL: ' + location.href); } if (isAddEmailPageReady()) { throw new Error('当前页面已进入添加邮箱页面,不是 OAuth 授权同意页。URL: ' + location.href); } const button = getPrimaryContinueButton(); if (button && isStep8Ready()) { return button; } await sleep(150); } throw new Error('在 OAuth 同意页未找到“继续”按钮,或页面尚未进入授权同意状态。URL: ' + location.href); } async function waitForButtonEnabled(button, timeout = 8000) { const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); if (isButtonEnabled(button)) return; await sleep(150); } throw new Error('“继续”按钮长时间不可点击。URL: ' + location.href); } function isButtonEnabled(button) { return Boolean(button) && !button.disabled && button.getAttribute('aria-disabled') !== 'true'; } async function waitForStableButtonRect(button, timeout = 1500) { let previous = null; let stableSamples = 0; const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); const rect = button?.getBoundingClientRect?.(); if (rect && rect.width > 0 && rect.height > 0) { const snapshot = { left: rect.left, top: rect.top, width: rect.width, height: rect.height, }; if ( previous && Math.abs(snapshot.left - previous.left) < 1 && Math.abs(snapshot.top - previous.top) < 1 && Math.abs(snapshot.width - previous.width) < 1 && Math.abs(snapshot.height - previous.height) < 1 ) { stableSamples += 1; if (stableSamples >= 2) { return; } } else { stableSamples = 0; } previous = snapshot; } await sleep(80); } } function getSerializableRect(el) { const rect = el.getBoundingClientRect(); if (!rect.width || !rect.height) { throw new Error('滚动后“继续”按钮没有可点击尺寸。URL: ' + location.href); } return { left: rect.left, top: rect.top, width: rect.width, height: rect.height, centerX: rect.left + (rect.width / 2), centerY: rect.top + (rect.height / 2), }; } // ============================================================ // Step 5: Fill Name & Birthday / Age // ============================================================ function getStep5DirectCompletionPayload({ isAgeMode = false, navigationStarted = false, navigationEventType = '', outcome = null } = {}) { const payload = { profileSubmitted: true, postSubmitChecked: !navigationStarted, }; if (isAgeMode) { payload.ageMode = true; } if (navigationStarted) { payload.navigationStarted = true; payload.handoffToBackground = true; const resolvedNavigationEventType = String(navigationEventType || '').trim(); if (resolvedNavigationEventType) { payload.navigationEventType = resolvedNavigationEventType; } if (typeof location !== 'undefined' && location?.href) { payload.url = location.href; } } if (outcome?.state) { payload.postSubmitChecked = true; payload.outcome = outcome.state; } if (outcome?.url) { payload.url = outcome.url; } return payload; } function isCombinedSignupVerificationProfilePage() { if (!isEmailVerificationPage() || !isVerificationPageStillVisible()) { return false; } if (!document.querySelector('form[action*="email-verification/register" i]')) { return false; } const nameInput = document.querySelector('input[name="name"], input[autocomplete="name"]'); if (!nameInput || !isVisibleElement(nameInput)) { return false; } const ageInput = document.querySelector('input[name="age"]'); if (ageInput && isVisibleElement(ageInput)) { return true; } const yearSpinner = document.querySelector('[role="spinbutton"][data-type="year"]'); const monthSpinner = document.querySelector('[role="spinbutton"][data-type="month"]'); const daySpinner = document.querySelector('[role="spinbutton"][data-type="day"]'); return Boolean( yearSpinner && monthSpinner && daySpinner && isVisibleElement(yearSpinner) && isVisibleElement(monthSpinner) && isVisibleElement(daySpinner) ); } async function waitForCombinedSignupVerificationProfilePage(timeout = 2500) { const start = Date.now(); while (Date.now() - start < timeout) { if (isCombinedSignupVerificationProfilePage()) { return true; } await sleep(100); } return isCombinedSignupVerificationProfilePage(); } function getStep5ProfilePathPatterns() { return [ /\/create-account\/profile(?:[/?#]|$)/i, /\/u\/signup\/profile(?:[/?#]|$)/i, /\/signup\/profile(?:[/?#]|$)/i, /\/about-you(?:[/?#]|$)/i, ]; } function getStep5AuthRetryPathPatterns() { const signupPatterns = typeof getSignupAuthRetryPathPatterns === 'function' ? getSignupAuthRetryPathPatterns() : []; return [ ...signupPatterns, ...getStep5ProfilePathPatterns(), ]; } function isStep5ProfilePageUrl(rawUrl = location.href) { return isSignupProfilePageUrl(rawUrl); } function getStep5AuthRetryPageState() { if (typeof getAuthTimeoutErrorPageState === 'function') { return getAuthTimeoutErrorPageState({ pathPatterns: getStep5AuthRetryPathPatterns(), }); } if (typeof getCurrentAuthRetryPageState === 'function') { return getCurrentAuthRetryPageState('signup'); } return null; } function getStep5SubmitButton() { const direct = document.querySelector('button[type="submit"], input[type="submit"]'); if (direct && isVisibleElement(direct)) { return direct; } const candidates = document.querySelectorAll('button, [role="button"], input[type="button"], input[type="submit"]'); return Array.from(candidates).find((el) => { if (!isVisibleElement(el)) return false; const text = typeof getActionText === 'function' ? getActionText(el) : [ el?.textContent, el?.value, el?.getAttribute?.('aria-label'), el?.getAttribute?.('title'), ] .filter(Boolean) .join(' ') .replace(/\s+/g, ' ') .trim(); return /完成|创建|create|continue|finish|done|agree|完了|作成|アカウント作成|アカウントを作成|続行|続ける|次へ|同意/i.test(text); }) || null; } async function waitForStep5SubmitButton(timeout = 5000) { const start = Date.now(); while (Date.now() - start < timeout) { throwIfStopped(); const button = getStep5SubmitButton(); if (button) { return button; } await sleep(150); } return null; } function isStep5SubmitButtonClickable(button) { if ( !button || !isVisibleElement(button) || button.disabled || button.getAttribute?.('aria-disabled') === 'true' ) { return false; } const ariaBusy = String(button.getAttribute?.('aria-busy') || '').trim().toLowerCase(); if (ariaBusy === 'true') { return false; } const pendingAttr = [ button.getAttribute?.('data-loading'), button.getAttribute?.('data-pending'), button.getAttribute?.('data-submitting'), button.getAttribute?.('data-state'), ] .map((value) => String(value || '').trim().toLowerCase()) .filter(Boolean) .join(' '); if (/\b(?:true|loading|pending|submitting|busy)\b/.test(pendingAttr)) { return false; } const pendingAncestor = button.closest?.([ '[aria-busy="true"]', '[data-loading="true"]', '[data-pending="true"]', '[data-submitting="true"]', '[data-state="loading"]', '[data-state="pending"]', '[data-state="submitting"]', ].join(', ')); if (pendingAncestor) { return false; } let style = null; try { style = typeof window !== 'undefined' && window.getComputedStyle ? window.getComputedStyle(button) : null; } catch { style = null; } if (style?.pointerEvents === 'none') { return false; } const opacity = Number.parseFloat(style?.opacity || ''); if (Number.isFinite(opacity) && opacity < 0.8) { return false; } return true; } function isStep5ProfileStillVisible() { if (isStep5ProfilePageUrl()) { return true; } return typeof isStep5Ready === 'function' ? isStep5Ready() : false; } function getStep5PostSubmitSuccessState() { if (getStep5AuthRetryPageState()) { return null; } if (isStep5CompletionChatgptUrl()) { return { state: 'logged_in_home', url: location.href, }; } return null; } function getStep5SubmitState() { const retryState = getStep5AuthRetryPageState(); const successState = getStep5PostSubmitSuccessState(); const errorText = typeof getStep5ErrorText === 'function' ? getStep5ErrorText() : ''; let signupAuthHost = false; try { const parsed = new URL(String(location.href || '').trim()); signupAuthHost = ['auth.openai.com', 'auth0.openai.com', 'accounts.openai.com'] .includes(String(parsed.hostname || '').toLowerCase()); } catch { signupAuthHost = false; } return { url: location.href, retryPage: Boolean(retryState), retryEnabled: Boolean(retryState?.retryEnabled), maxCheckAttemptsBlocked: Boolean(retryState?.maxCheckAttemptsBlocked), userAlreadyExistsBlocked: Boolean(retryState?.userAlreadyExistsBlocked), successState: successState?.state || '', profileVisible: isStep5ProfileStillVisible(), errorText, unknownAuthPage: Boolean( signupAuthHost && !retryState && !successState && !isStep5ProfileStillVisible() ), }; } function logStep5SubmitDebug(message, options = {}) { const resolvedState = options?.state && typeof options.state === 'object' ? options.state : getStep5SubmitState(); const summary = [ `url=${resolvedState?.url || location.href}`, `retryPage=${Boolean(resolvedState?.retryPage)}`, `retryEnabled=${Boolean(resolvedState?.retryEnabled)}`, `successState=${resolvedState?.successState || 'none'}`, `profileVisible=${Boolean(resolvedState?.profileVisible)}`, `unknownAuthPage=${Boolean(resolvedState?.unknownAuthPage)}`, `maxCheckAttemptsBlocked=${Boolean(resolvedState?.maxCheckAttemptsBlocked)}`, `userAlreadyExistsBlocked=${Boolean(resolvedState?.userAlreadyExistsBlocked)}`, resolvedState?.errorText ? `errorText=${resolvedState.errorText}` : null, ] .filter(Boolean) .join(' | '); log(`步骤 5 [调试] ${message} | ${summary}`, options?.level || 'info', { step: 5, stepKey: 'fill-profile', }); } async function recoverStep5SubmitRetryPage(payload = {}) { return recoverCurrentAuthRetryPage({ ...payload, flow: 'signup', logLabel: payload?.logLabel || '步骤 5:资料提交后检测到认证重试页,正在点击“重试”恢复', maxClickAttempts: payload?.maxClickAttempts ?? 2, pathPatterns: Array.isArray(payload?.pathPatterns) ? payload.pathPatterns : getStep5AuthRetryPathPatterns(), step: 5, timeoutMs: payload?.timeoutMs ?? 12000, }); } function installStep5NavigationCompletionReporter(completeOnce) { if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') { return () => {}; } let reportedNavigation = false; const debugLog = typeof logStep5SubmitDebug === 'function' ? logStep5SubmitDebug : (message, options = {}) => { if (typeof log === 'function') { log(`步骤 5 [调试] ${message}`, options?.level || 'info', { step: 5, stepKey: 'fill-profile', }); } }; const onNavigationStarted = (event) => { const eventType = String(event?.type || 'navigation').trim() || 'navigation'; if (reportedNavigation) { return; } reportedNavigation = true; if (typeof completeOnce === 'function') { try { completeOnce({ navigationStarted: true, navigationEventType: eventType, }); } catch (error) { if (typeof log === 'function') { log(`步骤 5 [调试] 导航交棒信号发送失败:${error?.message || error}`, 'warn', { step: 5, stepKey: 'fill-profile', }); } } } debugLog(`检测到页面开始导航(event=${eventType})。`, { level: 'warn', }); }; window.addEventListener('pagehide', onNavigationStarted, { once: true }); window.addEventListener('beforeunload', onNavigationStarted, { once: true }); return () => { window.removeEventListener('pagehide', onNavigationStarted); window.removeEventListener('beforeunload', onNavigationStarted); }; } async function waitForStep5SubmitOutcome(options = {}) { const debugLog = typeof logStep5SubmitDebug === 'function' ? logStep5SubmitDebug : (message, logOptions = {}) => { if (typeof log === 'function') { log(`步骤 5 [调试] ${message}`, logOptions?.level || 'info', { step: 5, stepKey: 'fill-profile', }); } }; const { timeoutMs = 120000, maxAuthRetryRecoveries = 2, maxSubmitClicks = 3, retryClickIntervalMs = 3500, } = options; const start = Date.now(); let authRetryRecoveryCount = 0; let submitClickCount = 1; let lastSubmitClickAt = Date.now(); let lastStep5Error = ''; while (Date.now() - start < timeoutMs) { throwIfStopped(); const retryState = getStep5AuthRetryPageState(); if (retryState?.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } if (retryState?.maxCheckAttemptsBlocked) { throw createAuthMaxCheckAttemptsError(); } if (retryState) { if (authRetryRecoveryCount >= maxAuthRetryRecoveries) { throw new Error(`步骤 5:资料提交后连续进入认证重试页 ${maxAuthRetryRecoveries} 次,页面仍未恢复。URL: ${location.href}`); } authRetryRecoveryCount += 1; debugLog(`检测到资料提交后的认证重试页,准备执行恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries})。`, { level: 'warn', }); log(`步骤 5:资料提交后进入认证重试页,正在自动恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries})...`, 'warn'); await recoverCurrentAuthRetryPage({ flow: 'signup', logLabel: '步骤 5:资料提交后检测到认证重试页,正在点击“重试”恢复', maxClickAttempts: 2, pathPatterns: getStep5AuthRetryPathPatterns(), step: 5, timeoutMs: 12000, }); debugLog('认证重试页恢复动作已完成,准备继续等待最终结果。', { level: 'info', }); lastSubmitClickAt = Date.now(); continue; } const successState = getStep5PostSubmitSuccessState(); if (successState) { debugLog(`检测到资料提交成功状态:${successState.state || 'unknown'}`, { level: 'ok', }); return successState; } const step5Error = typeof getStep5ErrorText === 'function' ? getStep5ErrorText() : ''; if (step5Error) { lastStep5Error = step5Error; } if ( isStep5ProfileStillVisible() && submitClickCount < maxSubmitClicks && Date.now() - lastSubmitClickAt >= retryClickIntervalMs ) { const submitButton = getStep5SubmitButton(); if (isStep5SubmitButtonClickable(submitButton)) { submitClickCount += 1; log(`步骤 5:资料提交后仍停留在资料页,正在重新点击“完成帐户创建”(第 ${submitClickCount}/${maxSubmitClicks} 次)...`, 'warn'); await humanPause(350, 900); simulateClick(submitButton); lastSubmitClickAt = Date.now(); await sleep(1000); continue; } } await sleep(250); } const finalRetryState = getStep5AuthRetryPageState(); if (finalRetryState?.userAlreadyExistsBlocked) { throw createSignupUserAlreadyExistsError(); } if (finalRetryState?.maxCheckAttemptsBlocked) { throw createAuthMaxCheckAttemptsError(); } if (finalRetryState) { throw new Error(`步骤 5:资料提交后仍停留在认证重试页,自动恢复未完成。URL: ${location.href}`); } const finalSuccessState = getStep5PostSubmitSuccessState(); if (finalSuccessState) { return finalSuccessState; } const finalStep5Error = (typeof getStep5ErrorText === 'function' ? getStep5ErrorText() : '') || lastStep5Error; if (finalStep5Error) { throw new Error(`步骤 5:资料提交后页面返回错误:${finalStep5Error}。URL: ${location.href}`); } throw new Error(`步骤 5:资料提交后未检测到页面跳转或恢复成功(已点击提交 ${submitClickCount}/${maxSubmitClicks} 次)。URL: ${location.href}`); } async function step5_fillNameBirthday(payload) { const { firstName, lastName, age, year, month, day, prefillOnly = false } = payload; if (!firstName || !lastName) throw new Error('未提供姓名数据。'); const performOperationWithDelay = typeof getOperationDelayRunner === 'function' ? getOperationDelayRunner() : async (metadata, operation) => { const rootScope = typeof window !== 'undefined' ? window : globalThis; const gate = rootScope?.CodexOperationDelay?.performOperationWithDelay; return typeof gate === 'function' ? gate(metadata, operation) : operation(); }; const resolvedAge = age ?? (year ? new Date().getFullYear() - Number(year) : null); const hasBirthdayData = [year, month, day].every(value => value != null && !Number.isNaN(Number(value))); if (!hasBirthdayData && (resolvedAge == null || Number.isNaN(Number(resolvedAge)))) { throw new Error('未提供生日或年龄数据。'); } const fullName = `${firstName} ${lastName}`; log(`步骤 5:正在填写姓名:${fullName}`); // Actual DOM structure: // - Full name: // - Birthday: React Aria DateField or hidden input[name="birthday"] // - Age: // --- Full Name (single field, not first+last) --- let nameInput = null; try { nameInput = await waitForElement( 'input[name="name"], input[placeholder*="全名"], input[placeholder*="氏名"], input[placeholder*="名前"], input[placeholder*="お名前"], input[autocomplete="name"]', 10000 ); } catch { throw new Error('未找到姓名输入框。URL: ' + location.href); } await humanPause(500, 1300); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-name' }, async () => { fillInput(nameInput, fullName); }); log(`步骤 5:姓名已填写:${fullName}`); let birthdayMode = false; let ageInput = null; let yearSpinner = null; let monthSpinner = null; let daySpinner = null; let hiddenBirthday = null; let yearReactSelect = null; let monthReactSelect = null; let dayReactSelect = null; let visibleAgeInput = false; let visibleBirthdaySpinners = false; let visibleBirthdaySelects = false; const findBirthdaySelect = (...labels) => labels .map((label) => findBirthdayReactAriaSelect(label)) .find(Boolean) || null; for (let i = 0; i < 100; i++) { yearSpinner = document.querySelector('[role="spinbutton"][data-type="year"]'); monthSpinner = document.querySelector('[role="spinbutton"][data-type="month"]'); daySpinner = document.querySelector('[role="spinbutton"][data-type="day"]'); hiddenBirthday = document.querySelector('input[name="birthday"]'); ageInput = document.querySelector('input[name="age"]'); yearReactSelect = findBirthdaySelect('年', 'Year'); monthReactSelect = findBirthdaySelect('月', 'Month'); dayReactSelect = findBirthdaySelect('天', '日', 'Day'); visibleAgeInput = Boolean(ageInput && isVisibleElement(ageInput)); visibleBirthdaySpinners = Boolean( yearSpinner && monthSpinner && daySpinner && isVisibleElement(yearSpinner) && isVisibleElement(monthSpinner) && isVisibleElement(daySpinner) ); visibleBirthdaySelects = Boolean( yearReactSelect?.button && monthReactSelect?.button && dayReactSelect?.button && isVisibleElement(yearReactSelect.button) && isVisibleElement(monthReactSelect.button) && isVisibleElement(dayReactSelect.button) ); if (visibleAgeInput) break; if (visibleBirthdaySpinners || visibleBirthdaySelects) { birthdayMode = true; break; } await sleep(100); } if (birthdayMode) { if (!hasBirthdayData) { throw new Error('检测到生日字段,但未提供生日数据。'); } const yearSpinner = document.querySelector('[role="spinbutton"][data-type="year"]'); const monthSpinner = document.querySelector('[role="spinbutton"][data-type="month"]'); const daySpinner = document.querySelector('[role="spinbutton"][data-type="day"]'); const yearReactSelect = findBirthdaySelect('年', 'Year'); const monthReactSelect = findBirthdaySelect('月', 'Month'); const dayReactSelect = findBirthdaySelect('天', '日', 'Day'); if (yearReactSelect?.nativeSelect && monthReactSelect?.nativeSelect && dayReactSelect?.nativeSelect) { const desiredDate = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; const hiddenBirthday = document.querySelector('input[name="birthday"]'); log('步骤 5:检测到 React Aria 下拉生日字段,正在填写生日...'); await humanPause(450, 1100); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-year' }, async () => { await setReactAriaBirthdaySelect(yearReactSelect, year); }); await humanPause(250, 650); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-month' }, async () => { await setReactAriaBirthdaySelect(monthReactSelect, month); }); await humanPause(250, 650); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'select', label: 'select-birthday-day' }, async () => { await setReactAriaBirthdaySelect(dayReactSelect, day); }); if (hiddenBirthday) { const start = Date.now(); while (Date.now() - start < 2000) { if ((hiddenBirthday.value || '') === desiredDate) break; await sleep(100); } if ((hiddenBirthday.value || '') !== desiredDate) { throw new Error(`生日值未成功写入页面。期望 ${desiredDate},实际 ${(hiddenBirthday.value || '空')}。`); } } log(`步骤 5:React Aria 生日已填写:${desiredDate}`); } if (yearSpinner && monthSpinner && daySpinner) { log('步骤 5:检测到生日字段,正在填写生日...'); async function setSpinButton(el, value) { el.focus(); await sleep(100); document.execCommand('selectAll', false, null); await sleep(50); const valueStr = String(value); for (const char of valueStr) { el.dispatchEvent(new KeyboardEvent('keydown', { key: char, code: `Digit${char}`, bubbles: true })); el.dispatchEvent(new KeyboardEvent('keypress', { key: char, code: `Digit${char}`, bubbles: true })); el.dispatchEvent(new InputEvent('beforeinput', { inputType: 'insertText', data: char, bubbles: true })); el.dispatchEvent(new InputEvent('input', { inputType: 'insertText', data: char, bubbles: true })); await sleep(50); } el.dispatchEvent(new KeyboardEvent('keyup', { key: 'Tab', code: 'Tab', bubbles: true })); el.blur(); await sleep(100); } await humanPause(450, 1100); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-year' }, async () => { await setSpinButton(yearSpinner, year); }); await humanPause(250, 650); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-month' }, async () => { await setSpinButton(monthSpinner, String(month).padStart(2, '0')); }); await humanPause(250, 650); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday-day' }, async () => { await setSpinButton(daySpinner, String(day).padStart(2, '0')); }); log(`步骤 5:生日已填写:${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`); } const hiddenBirthday = document.querySelector('input[name="birthday"]'); if (hiddenBirthday) { const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'hidden-sync', label: 'profile-dom-sync' }, async () => { hiddenBirthday.value = dateStr; hiddenBirthday.dispatchEvent(new Event('input', { bubbles: true })); hiddenBirthday.dispatchEvent(new Event('change', { bubbles: true })); }); log(`步骤 5:已设置隐藏生日输入框:${dateStr}`); } } else if (ageInput) { if (resolvedAge == null || Number.isNaN(Number(resolvedAge))) { throw new Error('检测到年龄字段,但未提供年龄数据。'); } await humanPause(500, 1300); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'fill', label: 'fill-birthday' }, async () => { fillInput(ageInput, String(resolvedAge)); }); log(`步骤 5:年龄已填写:${resolvedAge}`); } else { throw new Error('未找到生日或年龄输入项。URL: ' + location.href); } // 韩国IP判断勾选框""I agree" const allConsentCheckbox = findStep5AllConsentCheckbox(); if (allConsentCheckbox) { if (!isStep5CheckboxChecked(allConsentCheckbox)) { const checkboxLabel = allConsentCheckbox.closest('label'); await humanPause(500, 1500); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'click', label: 'accept-profile-consent' }, async () => { if (checkboxLabel && isVisibleElement(checkboxLabel)) { simulateClick(checkboxLabel); } else { simulateClick(allConsentCheckbox); } }); await sleep(250); if (!isStep5CheckboxChecked(allConsentCheckbox)) { await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'click', label: 'accept-profile-consent-fallback' }, async () => { allConsentCheckbox.click(); }); await sleep(250); } if (!isStep5CheckboxChecked(allConsentCheckbox)) { throw new Error('未能勾选 “I agree to all of the following” 复选框。'); } log('步骤 5:已勾选 “I agree to all of the following”。'); } else { log('步骤 5:“I agree to all of the following” 已勾选,跳过。'); } } if (prefillOnly) { log('步骤 4:混合注册页资料已预填,继续填写验证码。', 'info'); return { prefilled: true }; } // Click "完成帐户创建" button await sleep(500); const completeBtn = await waitForStep5SubmitButton(5000) || await waitForElementByText('button', /完成|完了|作成|アカウント作成|アカウントを作成|続行|続ける|次へ|同意|create|continue|finish|done|agree/i, 5000).catch(() => null); if (!completeBtn) { throw new Error('未找到“完成帐户创建”按钮。URL: ' + location.href); } const isAgeMode = !birthdayMode && Boolean(ageInput); if (isAgeMode) { log('步骤 5:当前为年龄输入模式,点击“完成帐户创建”后将等待页面结果。', 'info'); } let reportedCompletionPayload = null; const debugLog = typeof logStep5SubmitDebug === 'function' ? logStep5SubmitDebug : (message, logOptions = {}) => { if (typeof log === 'function') { log(`步骤 5 [调试] ${message}`, logOptions?.level || 'info', { step: 5, stepKey: 'fill-profile', }); } }; function completeStep5Once(extra = {}) { const completionReason = extra?.outcome?.state || (extra?.navigationStarted ? `navigation_started:${extra?.navigationEventType || 'unknown'}` : 'direct_completion'); if (reportedCompletionPayload) { debugLog(`忽略重复完成信号(reason=${completionReason})。`, { level: 'warn', }); return reportedCompletionPayload; } const completionPayload = getStep5DirectCompletionPayload({ isAgeMode, navigationStarted: Boolean(extra.navigationStarted), navigationEventType: extra.navigationEventType || '', outcome: extra.outcome || null, }); reportedCompletionPayload = completionPayload; if (extra?.navigationStarted && typeof reportNodeComplete === 'function') { reportNodeComplete('fill-profile', completionPayload); } else { reportComplete(5, completionPayload); } debugLog(`准备发送完成信号(reason=${completionReason},isAgeMode=${isAgeMode})。`, { level: extra?.navigationStarted ? 'warn' : 'info', }); return completionPayload; } const cleanupNavigationReporter = installStep5NavigationCompletionReporter(completeStep5Once); await humanPause(500, 1300); await performOperationWithDelay({ stepKey: 'fill-profile', kind: 'submit', label: 'submit-profile' }, async () => { simulateClick(completeBtn); }); log('步骤 5:已点击“完成帐户创建”,正在等待页面跳转、重试页或提交结果。'); try { const outcome = await waitForStep5SubmitOutcome(); cleanupNavigationReporter(); const completionPayload = completeStep5Once({ outcome }); log(`步骤 5:资料提交结果已确认(${outcome.state || 'success'}),准备继续后续步骤。`, 'ok'); return completionPayload; } catch (error) { cleanupNavigationReporter(); throw error; } }