diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index 62331d4..c92de65 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -92,6 +92,7 @@ const PHONE_RESTART_STEP7_ERROR_PREFIX = 'PHONE_RESTART_STEP7::'; const PHONE_RESEND_THROTTLED_ERROR_PREFIX = 'PHONE_RESEND_THROTTLED::'; const PHONE_RESEND_BANNED_NUMBER_ERROR_PREFIX = 'PHONE_RESEND_BANNED_NUMBER::'; + const PHONE_RESEND_SERVER_ERROR_PREFIX = 'PHONE_RESEND_SERVER_ERROR::'; const PHONE_ROUTE_405_RECOVERY_FAILED_ERROR_PREFIX = 'PHONE_ROUTE_405_RECOVERY_FAILED::'; const PHONE_MANUAL_FREE_REUSE_ERROR_PREFIX = 'PHONE_MANUAL_FREE_REUSE::'; const PHONE_AUTO_FREE_REUSE_PREPARE_ERROR_PREFIX = 'PHONE_AUTO_FREE_REUSE_PREPARE::'; @@ -1331,6 +1332,25 @@ return /无法向此电话号码发送短信|无法向此手机号发送短信|无法发送短信到此电话号码|无法发送短信到此手机号|can(?:not|'t)\s+send\s+(?:an?\s+)?(?:sms|text(?:\s+message)?)\s+to\s+(?:this|that)\s+(?:phone\s+)?number|unable\s+to\s+send\s+(?:an?\s+)?(?:sms|text(?:\s+message)?)\s+to\s+(?:this|that)\s+(?:phone\s+)?number/i.test(message); } + function isPhoneResendServerError(error) { + const message = String(error?.message || error || '').trim(); + if (!message) { + return false; + } + if (message.startsWith(PHONE_RESEND_SERVER_ERROR_PREFIX)) { + return true; + } + return /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.test(message); + } + + function buildPhoneResendServerError(error) { + const message = String(error?.message || error || '').trim(); + if (message.startsWith(PHONE_RESEND_SERVER_ERROR_PREFIX)) { + return new Error(message); + } + return new Error(`${PHONE_RESEND_SERVER_ERROR_PREFIX}${message || 'OpenAI contact-verification page returned HTTP ERROR 500 after resend.'}`); + } + function shouldTreatResendThrottledAsBanned(state = {}) { return Boolean(state?.phoneResendThrottledAsBannedEnabled); } @@ -4220,6 +4240,13 @@ message: error.message, }; } + if (isPhoneResendServerError(error)) { + return { + hasError: true, + reason: 'resend_server_error', + message: error.message, + }; + } if (isPhoneMaxUsageExceededFlowError(error)) { return { hasError: true, @@ -4891,6 +4918,9 @@ if (pageError?.reason === 'phone_max_usage_exceeded') { throw buildPhoneMaxUsageExceededError(pageError.message); } + if (pageError?.reason === 'resend_server_error') { + throw buildPhoneResendServerError(pageError.message); + } if (pageError?.reason === 'resend_throttled') { if (shouldTreatResendThrottledAsBanned(state)) { throw buildHighRiskResendThrottledError(pageError.message); @@ -4948,6 +4978,18 @@ reason: 'phone_max_usage_exceeded', }; } + if (isPhoneResendServerError(error)) { + await addLog( + `步骤 9:重发短信后进入 contact-verification 500 页面,立即更换号码。${error.message}`, + 'warn' + ); + await clearPhoneRuntimeCountdown(); + return { + code: '', + replaceNumber: true, + reason: 'resend_server_error', + }; + } if (isPhoneResendThrottledError(error)) { if (shouldTreatResendThrottledAsBanned(state)) { await addLog( @@ -5037,6 +5079,18 @@ : 'resend_throttled', }; } + if (isPhoneResendServerError(resendError)) { + await addLog( + `步骤 9:重发短信后进入 contact-verification 500 页面,立即更换号码。${resendError.message}`, + 'warn' + ); + await clearPhoneRuntimeCountdown(); + return { + code: '', + replaceNumber: true, + reason: 'resend_server_error', + }; + } await addLog(`步骤 9:点击手机验证码页面重发按钮失败。${resendError.message}`, 'warn'); } continue; @@ -5242,6 +5296,9 @@ if (isStopRequestedError(resendError)) { throw resendError; } + if (isPhoneResendServerError(resendError)) { + throw buildPhoneResendServerError(resendError); + } await addLog(`步骤 4:注册手机验证码页面重发失败,将继续轮询短信。${resendError.message}`, 'warn', { step: 4, stepKey: 'fetch-signup-code', @@ -5277,6 +5334,9 @@ if (isStopRequestedError(resendError)) { throw resendError; } + if (isPhoneResendServerError(resendError)) { + throw buildPhoneResendServerError(resendError); + } await addLog(`步骤 4:验证码被拒后点击重发失败。${resendError.message}`, 'warn', { step: 4, stepKey: 'fetch-signup-code', @@ -5479,6 +5539,9 @@ if (isStopRequestedError(resendError)) { throw resendError; } + if (isPhoneResendServerError(resendError)) { + throw buildPhoneResendServerError(resendError); + } await addLog(`步骤 ${visibleStep}:登录手机验证码页面重发失败,将继续轮询短信。${resendError.message}`, 'warn', { step: visibleStep, stepKey: 'fetch-login-code', @@ -5514,6 +5577,9 @@ if (isStopRequestedError(resendError)) { throw resendError; } + if (isPhoneResendServerError(resendError)) { + throw buildPhoneResendServerError(resendError); + } await addLog(`步骤 ${visibleStep}:登录手机验证码被拒后点击重发失败。${resendError.message}`, 'warn', { step: visibleStep, stepKey: 'fetch-login-code', diff --git a/background/steps/oauth-login.js b/background/steps/oauth-login.js index 7c8e0bd..eac0fa2 100644 --- a/background/steps/oauth-login.js +++ b/background/steps/oauth-login.js @@ -56,7 +56,30 @@ && !Boolean(state?.contributionMode); } + function hasStep7PhoneSignupIdentity(state = {}) { + return Boolean( + String(state?.signupPhoneNumber || '').trim() + || String(state?.signupPhoneCompletedActivation?.phoneNumber || '').trim() + || String(state?.signupPhoneActivation?.phoneNumber || '').trim() + || ( + normalizeStep7IdentifierType(state?.accountIdentifierType) === 'phone' + && String(state?.accountIdentifier || '').trim() + ) + ); + } + + function shouldPreferStep7PhoneSignupIdentity(state = {}) { + const frozenSignupMethod = normalizeStep7IdentifierType(state?.resolvedSignupMethod); + return canUseConfiguredPhoneSignup(state) + && frozenSignupMethod !== 'email' + && hasStep7PhoneSignupIdentity(state); + } + function resolveStep7LoginIdentifierType(state = {}, fallbackType = '') { + if (shouldPreferStep7PhoneSignupIdentity(state)) { + return 'phone'; + } + const explicitIdentifierType = normalizeStep7IdentifierType(state?.accountIdentifierType); if (explicitIdentifierType) { return explicitIdentifierType; diff --git a/content/phone-auth.js b/content/phone-auth.js index be79514..1643983 100644 --- a/content/phone-auth.js +++ b/content/phone-auth.js @@ -20,6 +20,7 @@ } = deps; const PHONE_RESEND_THROTTLED_ERROR_PREFIX = 'PHONE_RESEND_THROTTLED::'; const PHONE_RESEND_BANNED_NUMBER_ERROR_PREFIX = 'PHONE_RESEND_BANNED_NUMBER::'; + const PHONE_RESEND_SERVER_ERROR_PREFIX = 'PHONE_RESEND_SERVER_ERROR::'; const PHONE_MAX_USAGE_EXCEEDED_PATTERN = /phone_max_usage_exceeded/i; const PHONE_ROUTE_405_RECOVERY_FAILED_ERROR_PREFIX = 'PHONE_ROUTE_405_RECOVERY_FAILED::'; const PHONE_ROUTE_405_RECOVERY_COOLDOWN_MS = 6000; @@ -27,6 +28,7 @@ const PHONE_RESEND_ROUTE_405_MAX_RECOVERY_TOTAL_MS = 12000; const PHONE_RESEND_THROTTLED_PATTERN = /tried\s+to\s+resend\s+too\s+many\s+times|please\s+try\s+again\s+later|too\s+many\s+resend|resend\s+too\s+many|发送.*过于频繁|稍后再试|重试次数过多/i; const PHONE_RESEND_BANNED_NUMBER_PATTERN = /无法向此电话号码发送短信|无法向此手机号发送短信|无法发送短信到此电话号码|无法发送短信到此手机号|can(?:not|'t)\s+send\s+(?:an?\s+)?(?:sms|text(?:\s+message)?)\s+to\s+(?:this|that)\s+(?:phone\s+)?number|unable\s+to\s+send\s+(?:an?\s+)?(?:sms|text(?:\s+message)?)\s+to\s+(?:this|that)\s+(?:phone\s+)?number/i; + const PHONE_RESEND_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; const PHONE_ROUTE_405_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+["']?\/phone-verification/i; const PHONE_ROUTE_405_MAX_RECOVERY_CLICKS = 3; const rootScope = typeof self !== 'undefined' ? self : globalThis; @@ -512,6 +514,20 @@ return ''; } + function getPhoneResendServerErrorText() { + const path = String(location?.pathname || ''); + if (!/\/contact-verification(?:[/?#]|$)/i.test(path)) { + return ''; + } + const text = String(getPageTextSnapshot?.() || '').replace(/\s+/g, ' ').trim(); + const title = String(document?.title || '').replace(/\s+/g, ' ').trim(); + const combined = `${title} ${text}`.trim(); + if (!PHONE_RESEND_SERVER_ERROR_PATTERN.test(combined)) { + return ''; + } + return combined || 'OpenAI contact-verification page returned HTTP ERROR 500 after resend.'; + } + function checkPhoneResendError() { const maxUsageText = getAddPhoneErrorText(); if (maxUsageText && PHONE_MAX_USAGE_EXCEEDED_PATTERN.test(maxUsageText)) { @@ -545,6 +561,17 @@ }; } + const serverErrorText = getPhoneResendServerErrorText(); + if (serverErrorText) { + return { + hasError: true, + reason: 'resend_server_error', + prefix: PHONE_RESEND_SERVER_ERROR_PREFIX, + message: serverErrorText, + url: location.href, + }; + } + return { hasError: false, reason: '', @@ -847,6 +874,10 @@ if (throttledText) { throw new Error(`${PHONE_RESEND_THROTTLED_ERROR_PREFIX}${throttledText}`); } + const serverErrorText = getPhoneResendServerErrorText(); + if (serverErrorText) { + throw new Error(`${PHONE_RESEND_SERVER_ERROR_PREFIX}${serverErrorText}`); + } const resendButton = getPhoneVerificationResendButton({ allowDisabled: true }); if (resendButton && isActionEnabled(resendButton)) { await humanPause(250, 700); @@ -864,6 +895,10 @@ if (afterClickThrottleText) { throw new Error(`${PHONE_RESEND_THROTTLED_ERROR_PREFIX}${afterClickThrottleText}`); } + const afterClickServerErrorText = getPhoneResendServerErrorText(); + if (afterClickServerErrorText) { + throw new Error(`${PHONE_RESEND_SERVER_ERROR_PREFIX}${afterClickServerErrorText}`); + } return { resent: true, url: location.href, @@ -882,6 +917,11 @@ throw new Error(`${PHONE_RESEND_THROTTLED_ERROR_PREFIX}${timeoutThrottleText}`); } + const timeoutServerErrorText = getPhoneResendServerErrorText(); + if (timeoutServerErrorText) { + throw new Error(`${PHONE_RESEND_SERVER_ERROR_PREFIX}${timeoutServerErrorText}`); + } + throw new Error('Timed out waiting for the phone verification resend button.'); })().finally(() => { activePhoneResendPromise = null; diff --git a/content/signup-page.js b/content/signup-page.js index b0000f3..a5dbd64 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -147,6 +147,8 @@ const LOGIN_EXTERNAL_IDP_PATTERN = /google|microsoft|apple|sso|single\s+sign[-\s 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 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; @@ -250,6 +252,27 @@ 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 throwIfContactVerificationServerError() { + const serverErrorText = getContactVerificationServerErrorText(); + if (serverErrorText) { + throw new Error(`${PHONE_RESEND_SERVER_ERROR_PREFIX}${serverErrorText}`); + } +} + async function resendVerificationCode(step, timeout = 45000) { if (step === 8) { await waitForLoginVerificationPageReady(10000, step); @@ -261,6 +284,7 @@ async function resendVerificationCode(step, timeout = 45000) { while (Date.now() - start < timeout) { throwIfStopped(); + throwIfContactVerificationServerError(); // Check for 405 error page and recover by clicking "Try again" if (is405MethodNotAllowedPage()) { @@ -285,6 +309,7 @@ async function resendVerificationCode(step, timeout = 45000) { loggedWaiting = false; continue; } + throwIfContactVerificationServerError(); return { resent: true, @@ -300,6 +325,7 @@ async function resendVerificationCode(step, timeout = 45000) { await sleep(250); } + throwIfContactVerificationServerError(); throw new Error('无法点击重新发送验证码按钮。URL: ' + location.href); } diff --git a/tests/background-step6-retry-limit.test.js b/tests/background-step6-retry-limit.test.js index 579977b..9ded179 100644 --- a/tests/background-step6-retry-limit.test.js +++ b/tests/background-step6-retry-limit.test.js @@ -412,6 +412,62 @@ test('step 7 keeps Plus email login even when phone sms runtime exists', async ( assert.equal(events.payloads[0].accountIdentifier, 'plus.user@example.com'); }); +test('step 7 keeps phone login after step 8 stores an unbound email for phone signup', async () => { + const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8'); + const globalScope = {}; + const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope); + + const events = { + payloads: [], + }; + + const phoneSignupState = { + phoneVerificationEnabled: true, + signupMethod: 'phone', + resolvedSignupMethod: 'phone', + email: 'bound.step8@example.com', + accountIdentifierType: 'email', + accountIdentifier: 'bound.step8@example.com', + signupPhoneNumber: '66959916439', + signupPhoneCompletedActivation: { + activationId: 'signup-done', + phoneNumber: '66959916439', + countryId: 52, + countryLabel: 'Thailand', + }, + password: 'secret', + }; + + const executor = api.createStep7Executor({ + addLog: async () => {}, + completeStepFromBackground: async () => {}, + getErrorMessage: (error) => error?.message || String(error || ''), + getLoginAuthStateLabel: (state) => state || 'unknown', + getState: async () => ({ ...phoneSignupState }), + isStep6RecoverableResult: (result) => result?.step6Outcome === 'recoverable', + isStep6SuccessResult: (result) => result?.step6Outcome === 'success', + refreshOAuthUrlBeforeStep6: async () => 'https://oauth.example/latest', + reuseOrCreateTab: async () => {}, + sendToContentScriptResilient: async (_sourceName, message) => { + events.payloads.push(message.payload); + return { + step6Outcome: 'success', + state: 'phone_verification_page', + loginVerificationRequestedAt: 123456, + }; + }, + STEP6_MAX_ATTEMPTS: 3, + throwIfStopped: () => {}, + }); + + await executor.executeStep7(phoneSignupState); + + assert.equal(events.payloads[0].loginIdentifierType, 'phone'); + assert.equal(events.payloads[0].phoneNumber, '66959916439'); + assert.equal(events.payloads[0].email, ''); + assert.equal(events.payloads[0].accountIdentifier, '66959916439'); +}); + test('step 7 can infer phone login from an available phone signup configuration before step 2 finishes', async () => { const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8'); const globalScope = {}; diff --git a/tests/phone-verification-flow.test.js b/tests/phone-verification-flow.test.js index 89d853b..11609c7 100644 --- a/tests/phone-verification-flow.test.js +++ b/tests/phone-verification-flow.test.js @@ -4677,6 +4677,69 @@ test('phone verification helper replaces number immediately when phone-verificat assert.equal(messages.includes('RETURN_TO_ADD_PHONE'), true); }); +test('signup phone verification cancels activation when resend lands on contact-verification HTTP 500 page', async () => { + const requests = []; + let currentState = { + heroSmsApiKey: 'demo-key', + heroSmsCountryId: 52, + heroSmsCountryLabel: 'Thailand', + verificationResendCount: 0, + phoneCodeWaitSeconds: 60, + phoneCodeTimeoutWindows: 2, + phoneCodePollIntervalSeconds: 1, + phoneCodePollMaxRounds: 1, + signupPhoneActivation: { + activationId: '920001', + phoneNumber: '66953330001', + provider: 'hero-sms', + countryId: 52, + countryLabel: 'Thailand', + }, + }; + + const helpers = api.createPhoneVerificationHelpers({ + addLog: async () => {}, + fetchImpl: async (url) => { + const parsedUrl = new URL(url); + requests.push(parsedUrl); + const action = parsedUrl.searchParams.get('action'); + const id = parsedUrl.searchParams.get('id'); + if (action === 'getStatus') { + return { ok: true, text: async () => 'STATUS_WAIT_CODE' }; + } + if (action === 'setStatus') { + return { ok: true, text: async () => `STATUS_UPDATED:${id}` }; + } + throw new Error(`Unexpected HeroSMS action: ${action}`); + }, + getState: async () => ({ ...currentState }), + sendToContentScriptResilient: async (_source, message) => { + if (message.type === 'RESEND_VERIFICATION_CODE') { + throw new Error( + 'PHONE_RESEND_SERVER_ERROR::This page isn\'t working auth.openai.com is currently unable to handle this request. HTTP ERROR 500' + ); + } + throw new Error(`Unexpected content-script message: ${message.type}`); + }, + setState: async (updates) => { + currentState = { ...currentState, ...updates }; + }, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + }); + + await assert.rejects( + () => helpers.completeSignupPhoneVerificationFlow(1, { state: currentState }), + (error) => { + assert.match(error.message, /^PHONE_RESEND_SERVER_ERROR::This page isn't working/); + assert.equal(error.message.includes('PHONE_RESEND_SERVER_ERROR::PHONE_RESEND_SERVER_ERROR::'), false); + return true; + } + ); + + assert.equal(currentState.signupPhoneActivation, null); +}); + test('phone verification helper skips page resend for 5sim timeouts and rotates number directly', async () => { const requests = []; const messages = []; diff --git a/tests/step4-split-code-submit.test.js b/tests/step4-split-code-submit.test.js index db4500e..2af6c07 100644 --- a/tests/step4-split-code-submit.test.js +++ b/tests/step4-split-code-submit.test.js @@ -166,6 +166,59 @@ return { assert.deepStrictEqual(snapshot.clicks, ['Continue']); }); +test('resendVerificationCode reports contact-verification HTTP 500 after resend click', async () => { + const api = new Function(` +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; +const logs = []; +const location = { + href: 'https://auth.openai.com/email-verification', + pathname: '/email-verification', +}; +const document = { + title: 'Verify your email', + body: { textContent: 'Enter the verification code.' }, +}; +function throwIfStopped() {} +function log(message, level = 'info') { logs.push({ message, level }); } +function findResendVerificationCodeTrigger() { return { textContent: 'Resend code' }; } +function isActionEnabled() { return true; } +async function humanPause() {} +function simulateClick() { + location.href = 'https://auth.openai.com/contact-verification'; + location.pathname = '/contact-verification'; + document.title = "This page isn't working"; + document.body.textContent = 'auth.openai.com is currently unable to handle this request. HTTP ERROR 500'; +} +async function sleep() {} +function is405MethodNotAllowedPage() { return false; } +async function handle405ResendError() {} +function getActionText(element) { return element?.textContent || ''; } +function getPageTextSnapshot() { return document.body.textContent; } + +${extractFunction('getContactVerificationServerErrorText')} +${extractFunction('throwIfContactVerificationServerError')} +${extractFunction('resendVerificationCode')} + +return { + async run() { + try { + await resendVerificationCode(4, 1000); + return { ok: true }; + } catch (error) { + return { ok: false, message: String(error?.message || error), logs }; + } + }, +}; +`)(); + + const result = await api.run(); + + assert.equal(result.ok, false); + assert.match(result.message, /^PHONE_RESEND_SERVER_ERROR::This page isn't working/); + assert.equal(result.message.includes('PHONE_RESEND_SERVER_ERROR::PHONE_RESEND_SERVER_ERROR::'), false); +}); + test('fillVerificationCode does not short-circuit on mixed email-verification profile page before verification exits', async () => { const api = new Function(` const logs = []; diff --git a/项目完整链路说明.md b/项目完整链路说明.md index 30d0d68..3818dca 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -488,6 +488,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 - 对 `custom` provider 而言,Step 8 仍使用手动验证码确认弹窗;弹窗当前额外提供“出现手机号验证”按钮,点击后会直接抛出与真实 `add-phone` 页面一致的 fatal 错误,供 Auto 按既有 add-phone 分支继续下一邮箱。 - 当登录验证码提交后进入 `phone-verification` 页时,内容脚本会显式把该页面识别为“手机验证码页”,避免与邮箱验证码页混淆。 - 手机号注册身份本身不会让 Step 8 自动走短信;只有真实页面状态是 `phone_verification_page` 时才调用短信 helper。手机号注册登录后出现 `add-email` 时,Step 8 走“按需生成邮箱 -> 绑定邮箱 -> 邮箱验证码”链路。 +- 如果手机号注册账号在 Step 8 的 `add-email` / 邮箱验证码阶段失败并回退到 Step 7,Step 7 会优先使用当前轮保留的注册手机号身份重新登录,避免误用尚未绑定成功的邮箱登录。 - `email_in_use` 会在 Step 8 当前步骤内清理当前邮箱并重新打开 `add-email` 获取新邮箱;`max_check_attempts` 不继续点击认证页 `重试`,只恢复当前 Step 8。 ### Step 9 @@ -514,6 +515,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 - 号码当前最多复用 3 次成功注册;超过上限后会清空可复用激活记录,下次重新申请新号码。 - 5sim 的国家/地区使用字符串 slug(当前开放 `indonesia / thailand / vietnam`),HeroSMS 仍使用数字国家 ID(当前开放印度尼西亚/泰国/越南)。 - 如果同一个号码在重发短信后仍收不到验证码,后台会按当前平台取消或 ban 激活并换号,而不是把当前号码无限重试下去。 +- 如果注册手机号验证码页在点击 `resend` 后跳到 `contact-verification` 的 `This page isn’t working / HTTP ERROR 500`,后台会把当前号码视为不可继续使用并结束当前激活,避免继续停留在坏页面上空轮询。 ### Step 10