diff --git a/background.js b/background.js index 2dec324..7b7cb7f 100644 --- a/background.js +++ b/background.js @@ -719,6 +719,7 @@ const HERO_SMS_COUNTRY_BY_PHONE_PREFIX = Object.freeze([ const FIVE_SIM_OPERATOR = DEFAULT_FIVE_SIM_OPERATOR; const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted'; +const PLUS_PAYMENT_METHOD_NONE = 'none'; const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL_HOSTED; @@ -823,6 +824,12 @@ function normalizePlusPaymentMethod(value = '') { const paypalHostedValue = typeof PLUS_PAYMENT_METHOD_PAYPAL_HOSTED !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL_HOSTED : 'paypal-hosted'; + const noneValue = typeof PLUS_PAYMENT_METHOD_NONE !== 'undefined' + ? PLUS_PAYMENT_METHOD_NONE + : 'none'; + if (normalized === noneValue || normalized === 'no-payment' || normalized === 'skip-payment') { + return noneValue; + } if (normalized === paypalHostedValue || normalized === 'paypal_direct' || normalized === 'paypal-direct') { return paypalHostedValue; } @@ -1958,6 +1965,12 @@ function normalizePlusPaymentMethod(value = '') { const paypalHostedValue = typeof PLUS_PAYMENT_METHOD_PAYPAL_HOSTED !== 'undefined' ? PLUS_PAYMENT_METHOD_PAYPAL_HOSTED : 'paypal-hosted'; + const noneValue = typeof PLUS_PAYMENT_METHOD_NONE !== 'undefined' + ? PLUS_PAYMENT_METHOD_NONE + : 'none'; + if (normalized === noneValue || normalized === 'no-payment' || normalized === 'skip-payment') { + return noneValue; + } if (normalized === paypalHostedValue || normalized === 'paypal_direct' || normalized === 'paypal-direct') { return paypalHostedValue; } @@ -8608,6 +8621,17 @@ function isLikelyLoggedInChatgptHomeUrl(rawUrl) { return !/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(parsed.pathname || ''); } +function isStep5CompletionChatgptUrl(rawUrl) { + const parsed = parseUrlSafely(rawUrl); + if (!parsed) return false; + const protocol = String(parsed.protocol || '').toLowerCase(); + const hostname = String(parsed.hostname || '').toLowerCase(); + if (protocol !== 'https:' || !['chatgpt.com', 'www.chatgpt.com'].includes(hostname)) { + return false; + } + return !/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(parsed.pathname || ''); +} + function isSignupPasswordPageUrl(rawUrl) { if (typeof navigationUtils !== 'undefined' && navigationUtils?.isSignupPasswordPageUrl) { return navigationUtils.isSignupPasswordPageUrl(rawUrl); @@ -9372,13 +9396,9 @@ function isGpcCheckoutRestartRequiredFailure(error) { function isPlusCheckoutRestartStep(step, stepExecutionKey = '', state = {}) { const normalizedKey = String(stepExecutionKey || '').trim(); - if (normalizedKey === 'plus-checkout-create' + return normalizedKey === 'plus-checkout-create' || normalizedKey === 'plus-checkout-billing' - || normalizedKey === 'gopay-subscription-confirm') { - return true; - } - const numericStep = Number(step); - return Boolean(state?.plusModeEnabled) && (numericStep === 6 || numericStep === 7); + || normalizedKey === 'gopay-subscription-confirm'; } function isPlusCheckoutRestartRequiredFailure(error) { @@ -9633,10 +9653,23 @@ function getDownstreamStateResets(step, state = {}) { currentPhoneVerificationCountdownWindowTotal: 0, }; } - if (step === 5 || step === 6 || step === 7 || step === 8) { + const normalizedStepKey = String(stepKey || '').trim(); + const isEarlyRegistrationNode = [ + 'fill-profile', + 'wait-registration-success', + 'plus-checkout-create', + ].includes(normalizedStepKey); + const isBillingNode = normalizedStepKey === 'plus-checkout-billing'; + const isApprovalNode = normalizedStepKey === 'paypal-approve' + || normalizedStepKey === 'gopay-subscription-confirm' + || normalizedStepKey === 'paypal-hosted-email' + || normalizedStepKey === 'paypal-hosted-card' + || normalizedStepKey === 'paypal-hosted-create-account' + || normalizedStepKey === 'paypal-hosted-review'; + if (isEarlyRegistrationNode || isBillingNode || isApprovalNode) { return { - ...(step <= 6 ? plusRuntimeResets : {}), - ...(step === 7 ? { + ...(isEarlyRegistrationNode ? plusRuntimeResets : {}), + ...(isBillingNode ? { plusBillingCountryText: '', plusBillingAddress: null, plusPaypalApprovedAt: null, @@ -9653,7 +9686,7 @@ function getDownstreamStateResets(step, state = {}) { gopayHelperOtpRequestId: '', gopayHelperOtpReferenceId: '', } : {}), - ...(step === 8 ? { + ...(isApprovalNode ? { plusPaypalApprovedAt: null, plusGoPayApprovedAt: null, plusReturnUrl: '', @@ -9670,7 +9703,7 @@ function getDownstreamStateResets(step, state = {}) { currentPhoneVerificationCountdownWindowTotal: 0, }; } - if (step === 9) { + if (stepKey === 'plus-checkout-return' || stepKey === 'confirm-oauth') { return { pendingPhoneActivationConfirmation: null, plusReturnUrl: '', @@ -11556,6 +11589,13 @@ async function executeNodeAndWait(nodeId, delayAfter = 2000) { const completionSignalTimeoutMs = getNodeCompletionSignalTimeoutMs(normalizedNodeId, executionState); await addLog(`自动运行:节点 ${normalizedNodeId} 已发起,正在等待完成信号(超时 ${Math.round(completionSignalTimeoutMs / 1000)} 秒)。`, 'info'); completionPayload = await executeNodeViaCompletionSignal(normalizedNodeId, completionSignalTimeoutMs); + if (normalizedNodeId === 'fill-profile') { + await addLog( + `步骤 5 [调试] 已收到资料页完成信号 | outcome=${String(completionPayload?.outcome || 'none')} | navigationStarted=${Boolean(completionPayload?.navigationStarted)} | url=${String(completionPayload?.url || '') || 'unknown'}`, + 'info', + { step: 5, stepKey: 'fill-profile' } + ); + } await addLog(`自动运行:节点 ${normalizedNodeId} 已收到完成信号,准备继续后续节点。`, 'info'); } else { await executeNode(normalizedNodeId); @@ -11573,6 +11613,12 @@ async function executeNodeAndWait(nodeId, delayAfter = 2000) { }); try { await validateStep5PostCompletion(signupTabId, completionPayload || {}); + await setNodeStatus(normalizedNodeId, 'completed'); + await addLog('已完成', 'ok', { nodeId: normalizedNodeId }); + await addLog('步骤 5 [调试] 资料页完成信号已通过后台复核。', 'ok', { + step: 5, + stepKey: 'fill-profile', + }); } catch (step5ValidationError) { await setNodeStatus(normalizedNodeId, 'failed'); await addLog(`失败:${getErrorMessage(step5ValidationError)}`, 'error', { nodeId: normalizedNodeId }); @@ -13548,6 +13594,7 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({ resolveSignupEmailForFlow, persistRegistrationEmailState, phoneVerificationHelpers, + getStepIdByKeyForState, rerunStep7ForStep8Recovery: (...args) => rerunStep7ForStep8Recovery(...args), resolveSignupMethod, reuseOrCreateTab, @@ -13942,6 +13989,21 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter 3 ); }, + finalizeStep5Completion: async (completionPayload = {}) => { + const signupTabId = await getTabId('openai-auth'); + if (!signupTabId) { + throw new Error('步骤 5:缺少认证页标签页,无法确认是否已跳转到 https://chatgpt.com。'); + } + await waitForTabStableComplete(signupTabId, { + timeoutMs: 120000, + retryDelayMs: 300, + stableMs: 1000, + initialDelayMs: 800, + }); + await validateStep5PostCompletion(signupTabId, completionPayload || {}); + await setNodeStatus('fill-profile', 'completed'); + await addLog('已完成', 'ok', { nodeId: 'fill-profile' }); + }, finalizeIcloudAliasAfterSuccessfulFlow, findHotmailAccount, flushCommand, @@ -14753,18 +14815,69 @@ async function recoverStep5SubmitRetryPageOnTab(options = {}) { return result || {}; } +async function addStep5PostCompletionDebugLog(message, details = {}) { + const pageState = details?.pageState && typeof details.pageState === 'object' + ? details.pageState + : null; + const summary = [ + `步骤 5 [调试] ${message}`, + details?.completionOutcome ? `completionOutcome=${details.completionOutcome}` : null, + `navigationStarted=${Boolean(details?.navigationStarted)}`, + details?.tabUrl ? `tabUrl=${details.tabUrl}` : null, + details?.completionUrl ? `completionUrl=${details.completionUrl}` : null, + pageState?.url ? `contentUrl=${pageState.url}` : null, + pageState ? `retryPage=${Boolean(pageState.retryPage)}` : null, + pageState ? `retryEnabled=${Boolean(pageState.retryEnabled)}` : null, + pageState ? `successState=${pageState.successState || 'none'}` : null, + pageState ? `profileVisible=${Boolean(pageState.profileVisible)}` : null, + pageState ? `unknownAuthPage=${Boolean(pageState.unknownAuthPage)}` : null, + pageState ? `maxCheckAttemptsBlocked=${Boolean(pageState.maxCheckAttemptsBlocked)}` : null, + pageState ? `userAlreadyExistsBlocked=${Boolean(pageState.userAlreadyExistsBlocked)}` : null, + pageState?.errorText ? `errorText=${pageState.errorText}` : null, + ] + .filter(Boolean) + .join(' | '); + + await addLog(summary, details?.level || 'info', { + step: 5, + stepKey: 'fill-profile', + }); +} + async function validateStep5PostCompletion(tabId, completionPayload = {}) { if (!Number.isInteger(tabId)) { throw new Error('步骤 5:缺少有效的资料页标签页,无法确认提交后的最终状态。'); } + const debugLog = typeof addStep5PostCompletionDebugLog === 'function' + ? addStep5PostCompletionDebugLog + : async (message, details = {}) => { + if (typeof addLog === 'function') { + await addLog(`步骤 5 [调试] ${message}`, details?.level || 'info', { + step: 5, + stepKey: 'fill-profile', + }); + } + }; const maxAuthRetryRecoveries = Math.max(1, Number(completionPayload?.maxAuthRetryRecoveries) || 2); let authRetryRecoveryCount = 0; + await debugLog('后台已收到资料页完成信号,准备开始最终状态复核。', { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + }); while (true) { const tab = await chrome.tabs.get(tabId).catch(() => null); const currentUrl = String(tab?.url || completionPayload?.url || '').trim(); - if (currentUrl && isLikelyLoggedInChatgptHomeUrl(currentUrl)) { + if (currentUrl && isStep5CompletionChatgptUrl(currentUrl)) { + await debugLog('后台直接通过标签页 URL 确认已进入 chatgpt.com,步骤 5 完成。', { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + tabUrl: currentUrl, + level: 'ok', + }); return { successState: 'logged_in_home', url: currentUrl, @@ -14777,6 +14890,13 @@ async function validateStep5PostCompletion(tabId, completionPayload = {}) { retryDelayMs: 500, logMessage: '步骤 5:资料提交已触发页面跳转,正在确认最终页面状态...', }); + await debugLog('后台复核当前页面状态。', { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + tabUrl: currentUrl, + pageState, + }); if (pageState.userAlreadyExistsBlocked) { throw new Error('SIGNUP_USER_ALREADY_EXISTS::步骤 5:检测到 user_already_exists,当前轮将直接停止。'); @@ -14790,6 +14910,14 @@ async function validateStep5PostCompletion(tabId, completionPayload = {}) { throw new Error(`步骤 5:资料提交后连续进入认证重试页 ${maxAuthRetryRecoveries} 次,页面仍未恢复。URL: ${pageState.url || currentUrl || 'unknown'}`); } authRetryRecoveryCount += 1; + await debugLog(`后台复核检测到认证重试页,准备恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries})。`, { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + tabUrl: currentUrl, + pageState, + level: 'warn', + }); await addLog(`步骤 5:提交完成信号后检测到认证重试页,正在自动恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries})...`, 'warn', { step: 5, stepKey: 'fill-profile', @@ -14808,22 +14936,74 @@ async function validateStep5PostCompletion(tabId, completionPayload = {}) { continue; } - if (pageState.successState === 'logged_in_home' || pageState.successState === 'oauth_consent' || pageState.successState === 'add_phone') { + if (pageState.successState === 'logged_in_home' && isStep5CompletionChatgptUrl(pageState.url)) { + await debugLog(`后台复核确认成功状态:${pageState.successState}`, { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + tabUrl: currentUrl, + pageState, + level: 'ok', + }); return pageState; } + if (pageState.successState) { + await debugLog('后台复核发现非 chatgpt.com 的步骤 5 完成候选,按未完成处理。', { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + tabUrl: currentUrl, + pageState, + level: 'error', + }); + throw new Error(`步骤 5:资料提交后尚未跳转到 https://chatgpt.com,不能标记完成。当前状态:${pageState.successState},URL: ${pageState.url || currentUrl || 'unknown'}`); + } + if (pageState.errorText) { + await debugLog('后台复核发现页面错误文本,准备按失败结束。', { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + tabUrl: currentUrl, + pageState, + level: 'error', + }); throw new Error(`步骤 5:资料提交后页面返回错误:${pageState.errorText}。URL: ${pageState.url || currentUrl || 'unknown'}`); } if (pageState.profileVisible) { + await debugLog('后台复核发现页面仍停留在资料页,准备按失败结束。', { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + tabUrl: currentUrl, + pageState, + level: 'error', + }); throw new Error(`步骤 5:资料提交完成信号已收到,但页面仍停留在资料页,当前流程将直接报错。URL: ${pageState.url || currentUrl || 'unknown'}`); } if (pageState.unknownAuthPage) { + await debugLog('后台复核进入未知认证页,无法确认成功。', { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + tabUrl: currentUrl, + pageState, + level: 'error', + }); throw new Error(`步骤 5:资料提交后进入未识别的认证页,无法确认成功。URL: ${pageState.url || currentUrl || 'unknown'}`); } + await debugLog('后台复核未识别到成功或明确失败状态,准备按失败结束。', { + completionOutcome: String(completionPayload?.outcome || '').trim(), + completionUrl: String(completionPayload?.url || '').trim(), + navigationStarted: Boolean(completionPayload?.navigationStarted), + tabUrl: currentUrl, + pageState, + level: 'error', + }); throw new Error(`步骤 5:资料提交后未能确认最终状态。URL: ${pageState.url || currentUrl || 'unknown'}`); } } @@ -15615,6 +15795,7 @@ const step9Executor = self.MultiPageBackgroundStep9?.createStep9Executor({ getStep8CallbackUrlFromNavigation, getStep8CallbackUrlFromTabUpdate, getStep8EffectLabel, + getStepIdByKeyForState, getTabId, getWebNavCommittedListener, getWebNavListener, diff --git a/background/message-router.js b/background/message-router.js index bc3b576..5de76ed 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -40,6 +40,7 @@ testKiroRsConnection, finalizePhoneActivationAfterSuccessfulFlow, finalizeStep3Completion, + finalizeStep5Completion = null, finalizeIcloudAliasAfterSuccessfulFlow, findHotmailAccount, findPayPalAccount, @@ -575,6 +576,12 @@ function normalizePlusPaymentMethodForDisplay(value = '') { const normalized = String(value || '').trim().toLowerCase(); + if (normalized === 'none' || normalized === 'no-payment' || normalized === 'skip-payment') { + return 'none'; + } + if (normalized === 'paypal-hosted' || normalized === 'paypal_direct' || normalized === 'paypal-direct') { + return 'paypal-hosted'; + } if (normalized === 'gpc-helper') { return 'gpc-helper'; } @@ -583,6 +590,12 @@ function getPlusPaymentMethodLabel(value = '') { const method = normalizePlusPaymentMethodForDisplay(value); + if (method === 'none') { + return '无需支付'; + } + if (method === 'paypal-hosted') { + return 'PayPal 无卡直绑'; + } if (method === 'gpc-helper') { return 'GPC'; } @@ -995,13 +1008,21 @@ return { ok: true, error: errorMessage }; } + const deferCompletionUntilBackgroundValidation = nodeId === 'fill-profile'; const completionStateCandidate = await getState(); const nodeIds = typeof getNodeIdsForState === 'function' ? getNodeIdsForState(completionStateCandidate) : []; const lastNodeId = nodeIds[nodeIds.length - 1] || ''; const isFinalNode = nodeId === lastNodeId; const completionState = isFinalNode ? completionStateCandidate : null; - await setNodeStatus(nodeId, 'completed'); - await addLog('已完成', 'ok', { nodeId }); + if (!deferCompletionUntilBackgroundValidation) { + await setNodeStatus(nodeId, 'completed'); + await addLog('已完成', 'ok', { nodeId }); + } else { + await addLog('步骤 5:已收到资料页完成信号,等待后台最终复核后再标记完成。', 'info', { + step: 5, + stepKey: nodeId, + }); + } await handleStepData(resolvedStep, message.payload); if (isFinalNode && typeof appendAccountRunRecord === 'function') { await appendAccountRunRecord('success', completionState); @@ -1272,7 +1293,10 @@ } const executionState = await getState(); if (doesNodeUseCompletionSignal(nodeId, executionState)) { - await executeNodeViaCompletionSignal(nodeId); + const completionPayload = await executeNodeViaCompletionSignal(nodeId); + if (nodeId === 'fill-profile' && typeof finalizeStep5Completion === 'function') { + await finalizeStep5Completion(completionPayload || {}); + } } else { await executeNode(nodeId); } diff --git a/data/step-definitions.js b/data/step-definitions.js index e610744..5f8f01e 100644 --- a/data/step-definitions.js +++ b/data/step-definitions.js @@ -225,8 +225,8 @@ ); const NORMAL_STEP_DEFINITIONS = STEP_DEFINITIONS; const PLUS_STEP_DEFINITIONS = cloneSteps( - defaultWorkflowBuilder?.getVariantStepDefinitions - ? defaultWorkflowBuilder.getVariantStepDefinitions('plusPaypal') + defaultWorkflowBuilder?.getModeStepDefinitions + ? defaultWorkflowBuilder.getModeStepDefinitions({ plusModeEnabled: true }) : [], { plusModeEnabled: true }, DEFAULT_ACTIVE_FLOW_ID diff --git a/flows/openai/background/steps/confirm-oauth.js b/flows/openai/background/steps/confirm-oauth.js index 74abd5c..1ba322a 100644 --- a/flows/openai/background/steps/confirm-oauth.js +++ b/flows/openai/background/steps/confirm-oauth.js @@ -34,6 +34,7 @@ setStep8PendingReject, setStep8TabUpdatedListener, shouldDeferStep9CallbackTimeout, + getStepIdByKeyForState = null, } = deps; const LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS = 240000; @@ -45,7 +46,17 @@ } function getAuthLoginStepForVisibleStep(visibleStep) { - return visibleStep >= 12 ? 10 : 7; + return visibleStep >= 12 ? Math.max(1, visibleStep - 3) : 7; + } + + function getAuthLoginStepForState(state = {}, visibleStep = 9) { + const authStep = typeof getStepIdByKeyForState === 'function' + ? Number(getStepIdByKeyForState('oauth-login', state)) + : 0; + if (Number.isInteger(authStep) && authStep > 0) { + return authStep; + } + return getAuthLoginStepForVisibleStep(visibleStep); } function addStepLog(step, message, level = 'info') { @@ -57,7 +68,7 @@ let activeState = state; if (!activeState.oauthUrl) { - const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep); + const authLoginStep = getAuthLoginStepForState(activeState, visibleStep); throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${authLoginStep}。`); } diff --git a/flows/openai/background/steps/fetch-login-code.js b/flows/openai/background/steps/fetch-login-code.js index 3208846..1fc5082 100644 --- a/flows/openai/background/steps/fetch-login-code.js +++ b/flows/openai/background/steps/fetch-login-code.js @@ -36,6 +36,7 @@ STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS, STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS, throwIfStopped, + getStepIdByKeyForState = null, } = deps; let activeFetchLoginCodeStep = null; let activeFetchLoginCodeStepKey = 'fetch-login-code'; @@ -93,7 +94,17 @@ } function getAuthLoginStepForVisibleStep(visibleStep) { - return visibleStep >= 11 ? 10 : 7; + return visibleStep >= 11 ? Math.max(1, visibleStep - 1) : 7; + } + + function getAuthLoginStepForState(state = {}, visibleStep = 8) { + const authStep = typeof getStepIdByKeyForState === 'function' + ? Number(getStepIdByKeyForState('oauth-login', state)) + : 0; + if (Number.isInteger(authStep) && authStep > 0) { + return authStep; + } + return getAuthLoginStepForVisibleStep(visibleStep); } async function getStep8ReadyTimeoutMs(actionLabel, expectedOauthUrl = '', visibleStep = 8) { @@ -331,7 +342,7 @@ } async function recoverStep8PollingFailure(currentState, visibleStep) { - const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep); + const authLoginStep = getAuthLoginStepForState(currentState, visibleStep); try { const pageState = await ensureStep8VerificationPageReady({ visibleStep, @@ -771,7 +782,7 @@ await chrome.tabs.update(authTabId, { active: true }); } else { if (!state.oauthUrl) { - throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}。`); + throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForState(state, visibleStep)}。`); } await reuseOrCreateTab('openai-auth', state.oauthUrl); } @@ -779,7 +790,7 @@ throwIfStopped(); let pageState = await ensureStep8VerificationPageReady({ visibleStep, - authLoginStep: getAuthLoginStepForVisibleStep(visibleStep), + authLoginStep: getAuthLoginStepForState(state, visibleStep), allowPhoneVerificationPage: true, allowAddEmailPage: true, timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep), @@ -848,7 +859,7 @@ return; } catch (err) { const visibleStep = getVisibleStep(currentState, 8); - const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep); + const authLoginStep = getAuthLoginStepForState(currentState, visibleStep); let currentError = err; let retryWithoutStep7 = false; diff --git a/flows/openai/content/openai-auth.js b/flows/openai/content/openai-auth.js index f62fa29..013b242 100644 --- a/flows/openai/content/openai-auth.js +++ b/flows/openai/content/openai-auth.js @@ -2961,6 +2961,27 @@ function isLikelyLoggedInChatgptHomeUrl(rawUrl = location.href) { } } +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 @@ -6693,43 +6714,13 @@ function getStep5PostSubmitSuccessState() { return null; } - if (isLikelyLoggedInChatgptHomeUrl()) { + if (isStep5CompletionChatgptUrl()) { return { state: 'logged_in_home', url: location.href, }; } - if (typeof isOAuthConsentPage === 'function' && isOAuthConsentPage()) { - return { - state: 'oauth_consent', - url: location.href, - }; - } - - if (typeof isAddPhonePageReady === 'function' && isAddPhonePageReady()) { - return { - state: 'add_phone', - url: location.href, - }; - } - - if (!isStep5ProfileStillVisible()) { - try { - const parsed = new URL(String(location.href || '').trim()); - const host = String(parsed.hostname || '').toLowerCase(); - if (['auth.openai.com', 'auth0.openai.com', 'accounts.openai.com'].includes(host)) { - return null; - } - } catch { - // Fall through to the generic "left_profile" success state. - } - return { - state: 'left_profile', - url: location.href, - }; - } - return null; } @@ -6764,6 +6755,29 @@ function getStep5SubmitState() { }; } +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, @@ -6780,14 +6794,21 @@ function installStep5NavigationCompletionReporter(completeOnce) { if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') { return () => {}; } + 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 = () => { - completeOnce({ - navigationStarted: true, - outcome: { - state: 'navigation_started', - url: location.href, - }, + const onNavigationStarted = (event) => { + const eventType = String(event?.type || 'navigation').trim() || 'navigation'; + debugLog(`检测到页面开始导航(event=${eventType})。`, { + level: 'warn', }); }; @@ -6801,6 +6822,16 @@ function installStep5NavigationCompletionReporter(completeOnce) { } 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, @@ -6828,6 +6859,9 @@ async function waitForStep5SubmitOutcome(options = {}) { throw new Error(`步骤 5:资料提交后连续进入认证重试页 ${maxAuthRetryRecoveries} 次,页面仍未恢复。URL: ${location.href}`); } authRetryRecoveryCount += 1; + debugLog(`检测到资料提交后的认证重试页,准备执行恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries})。`, { + level: 'warn', + }); log(`步骤 5:资料提交后进入认证重试页,正在自动恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries})...`, 'warn'); await recoverCurrentAuthRetryPage({ flow: 'signup', @@ -6837,12 +6871,18 @@ async function waitForStep5SubmitOutcome(options = {}) { step: 5, timeoutMs: 12000, }); + debugLog('认证重试页恢复动作已完成,准备继续等待最终结果。', { + level: 'info', + }); lastSubmitClickAt = Date.now(); continue; } const successState = getStep5PostSubmitSuccessState(); if (successState) { + debugLog(`检测到资料提交成功状态:${successState.state || 'unknown'}`, { + level: 'ok', + }); return successState; } @@ -7146,8 +7186,23 @@ async function step5_fillNameBirthday(payload) { } 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; } @@ -7156,6 +7211,9 @@ async function step5_fillNameBirthday(payload) { navigationStarted: Boolean(extra.navigationStarted), outcome: extra.outcome || null, }); + debugLog(`准备发送完成信号(reason=${completionReason},isAgeMode=${isAgeMode})。`, { + level: extra?.navigationStarted ? 'warn' : 'info', + }); reportedCompletionPayload = completionPayload; reportComplete(5, completionPayload); return completionPayload; diff --git a/flows/openai/workflow.js b/flows/openai/workflow.js index 59d0ad1..ddd27e8 100644 --- a/flows/openai/workflow.js +++ b/flows/openai/workflow.js @@ -5,12 +5,14 @@ const SIGNUP_METHOD_PHONE = 'phone'; const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted'; + const PLUS_PAYMENT_METHOD_NONE = 'none'; const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth'; const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session'; const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session'; const PLUS_PAYMENT_STEP_KEY = 'paypal-approve'; + const PLUS_REGISTRATION_WAIT_STEP_KEY = 'wait-registration-success'; function freezeDeep(entry) { if (!entry || typeof entry !== 'object' || Object.isFrozen(entry)) { @@ -3014,12 +3016,87 @@ ] }); + const PLUS_PAYMENT_CHAIN_STEP_KEYS = Object.freeze([ + 'plus-checkout-create', + 'plus-checkout-billing', + 'paypal-approve', + 'plus-checkout-return', + 'paypal-hosted-email', + 'paypal-hosted-card', + 'paypal-hosted-create-account', + 'paypal-hosted-review', + 'gopay-subscription-confirm', + ]); + + function omitPlusPaymentChainSteps(steps = []) { + return steps.filter((step) => !PLUS_PAYMENT_CHAIN_STEP_KEYS.includes(String(step?.key || '').trim())); + } + + function reindexModeStepDefinitions(steps = []) { + return (Array.isArray(steps) ? steps : []).map((step, index) => ({ + ...step, + id: index + 1, + order: (index + 1) * 10, + })); + } + + function getPlusRegistrationWaitStep() { + const sourceStep = STEP_VARIANTS.normal.find((step) => step.key === PLUS_REGISTRATION_WAIT_STEP_KEY); + return { + ...(sourceStep || { + key: PLUS_REGISTRATION_WAIT_STEP_KEY, + title: '等待注册成功', + sourceId: 'chatgpt', + driverId: null, + command: PLUS_REGISTRATION_WAIT_STEP_KEY, + flowId: 'openai', + }), + id: 6, + order: 60, + }; + } + + function shiftPlusStepAfterRegistrationWait(step = {}) { + const nextStep = { ...step }; + const id = Number(step.id); + const order = Number(step.order); + if (Number.isFinite(id)) { + nextStep.id = id + 1; + } + if (Number.isFinite(order)) { + nextStep.order = order + 10; + } + return nextStep; + } + + function insertPlusRegistrationWaitStep(steps = []) { + if (!Array.isArray(steps) || steps.some((step) => step.key === PLUS_REGISTRATION_WAIT_STEP_KEY)) { + return steps; + } + const fillProfileIndex = steps.findIndex((step) => step.key === 'fill-profile'); + if (fillProfileIndex < 0) { + return steps; + } + return steps.flatMap((step, index) => { + if (index < fillProfileIndex) { + return [step]; + } + if (index === fillProfileIndex) { + return [step, getPlusRegistrationWaitStep()]; + } + return [shiftPlusStepAfterRegistrationWait(step)]; + }); + } + function isPlusModeEnabled(options = {}) { return Boolean(options?.plusModeEnabled || options?.plusMode); } function normalizePlusPaymentMethod(value = '') { const normalized = String(value || '').trim().toLowerCase(); + if (normalized === PLUS_PAYMENT_METHOD_NONE || normalized === 'no-payment' || normalized === 'skip-payment') { + return PLUS_PAYMENT_METHOD_NONE; + } if (normalized === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED || normalized === 'paypal_direct' || normalized === 'paypal-direct') { return PLUS_PAYMENT_METHOD_PAYPAL_HOSTED; } @@ -3118,13 +3195,27 @@ } function getModeStepDefinitions(options = {}) { - return getVariantStepDefinitions(resolveVariantKey(options)); + const isPlusMode = isPlusModeEnabled(options); + let steps = getVariantStepDefinitions(resolveVariantKey(options)); + if (isPlusMode) { + steps = insertPlusRegistrationWaitStep(steps); + } + if ( + isPlusMode + && normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod) === PLUS_PAYMENT_METHOD_NONE + ) { + steps = omitPlusPaymentChainSteps(steps); + } + return reindexModeStepDefinitions(steps); } function getAllSteps() { const keyed = new Map(); - Object.values(STEP_VARIANTS).forEach((steps) => { - (Array.isArray(steps) ? steps : []).forEach((step) => { + Object.entries(STEP_VARIANTS).forEach(([variantKey, steps]) => { + const variantSteps = String(variantKey || '').startsWith('plus') + ? insertPlusRegistrationWaitStep(steps) + : steps; + reindexModeStepDefinitions(variantSteps).forEach((step) => { keyed.set(`${step.id}:${step.key}`, step); }); }); diff --git a/gopay-utils.js b/gopay-utils.js index 1f110b4..3622067 100644 --- a/gopay-utils.js +++ b/gopay-utils.js @@ -3,6 +3,7 @@ })(typeof self !== 'undefined' ? self : globalThis, function createGoPayUtils() { const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted'; + const PLUS_PAYMENT_METHOD_NONE = 'none'; const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top'; @@ -12,6 +13,9 @@ function normalizePlusPaymentMethod(value = '') { const normalized = String(value || '').trim().toLowerCase(); + if (normalized === PLUS_PAYMENT_METHOD_NONE || normalized === 'no-payment' || normalized === 'skip-payment') { + return PLUS_PAYMENT_METHOD_NONE; + } if (normalized === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED || normalized === 'paypal_direct' || normalized === 'paypal-direct') { return PLUS_PAYMENT_METHOD_PAYPAL_HOSTED; } @@ -419,6 +423,7 @@ GPC_HELPER_PHONE_MODE_MANUAL, PLUS_PAYMENT_METHOD_GPC_HELPER, PLUS_PAYMENT_METHOD_GOPAY, + PLUS_PAYMENT_METHOD_NONE, PLUS_PAYMENT_METHOD_PAYPAL, PLUS_PAYMENT_METHOD_PAYPAL_HOSTED, buildGpcCardBalanceUrl, diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 892e196..ccc80e7 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -326,6 +326,7 @@ Plus 支付