feat: 增强手机号注册流程,确保手机号输入页就绪后再申请号码并提交

This commit is contained in:
QLHazyCoder
2026-05-04 15:41:09 +08:00
parent 31a68e5a6b
commit 21b156291d
8 changed files with 512 additions and 24 deletions
+10 -10
View File
@@ -99,6 +99,10 @@
return step > 0 ? step : null;
}
function getActivePhoneVerificationVisibleStep(fallback = 9) {
return normalizeLogStep(activePhoneVerificationLogStep) || fallback;
}
function normalizePhoneVerificationLogMessage(message) {
return String(message || '')
.replace(/^Step\s+9\s+diagnostics\s*:\s*/i, 'diagnostics: ')
@@ -2122,7 +2126,7 @@
? config.countryCandidates
: [];
if (!allCountryCandidates.length) {
throw new Error('Step 9: 5sim countries are empty. Please select at least one country in 接码设置。');
throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: 5sim countries are empty. Please select at least one country in 接码设置。`);
}
const blockedCountryIds = new Set(
(Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : [])
@@ -2610,7 +2614,7 @@
? config.countryCandidates
: resolveNexSmsCountryCandidates(state);
if (!allCountryCandidates.length) {
throw new Error('Step 9: NexSMS countries are empty. Please select at least one country in 接码设置。');
throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: NexSMS countries are empty. Please select at least one country in 接码设置。`);
}
const blockedCountryIds = new Set(
(Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : [])
@@ -2872,7 +2876,7 @@
? config.countryCandidates
: resolveCountryCandidates(state);
if (!allCountryCandidates.length) {
throw new Error('Step 9: HeroSMS countries are empty. Please select at least one country in 接码设置。');
throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: HeroSMS countries are empty. Please select at least one country in 接码设置。`);
}
const blockedCountryIds = new Set(
(Array.isArray(options?.blockedCountryIds) ? options.blockedCountryIds : [])
@@ -2916,10 +2920,6 @@
`步骤 9:HeroSMS 正在获取手机号(第 ${round}/${maxAcquireRounds} 轮)...`,
'info'
);
await addLog(
`步骤 9:HeroSMS 正在获取手机号(第 ${round}/${maxAcquireRounds} 轮)...`,
'info'
);
}
const countryAttempts = countryCandidates.map((countryConfig, index) => ({
@@ -3868,7 +3868,7 @@
(provider === PHONE_SMS_PROVIDER_5SIM || provider === PHONE_SMS_PROVIDER_NEXSMS)
&& !countryCandidates.length
) {
throw new Error(`Step 9: ${provider === PHONE_SMS_PROVIDER_5SIM ? '5sim' : 'NexSMS'} countries are empty. Please select at least one country in 接码设置。`);
throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: ${provider === PHONE_SMS_PROVIDER_5SIM ? '5sim' : 'NexSMS'} countries are empty. Please select at least one country in 接码设置。`);
}
const normalizeCountryKey = (value) => (
provider === PHONE_SMS_PROVIDER_5SIM
@@ -4064,9 +4064,9 @@
const skippedSuffix = skippedFallbackProviders.length
? ` | skipped fallback providers: ${skippedFallbackProviders.join('; ')}`
: '';
throw new Error(`Step 9: all provider candidates failed to acquire number. ${providerErrors.join(' | ')}${skippedSuffix}`);
throw new Error(`Step ${getActivePhoneVerificationVisibleStep()}: all provider candidates failed to acquire number. ${providerErrors.join(' | ')}${skippedSuffix}`);
}
throw lastProviderError || new Error('Step 9: failed to acquire phone activation.');
throw lastProviderError || new Error(`Step ${getActivePhoneVerificationVisibleStep()}: failed to acquire phone activation.`);
}
async function prepareSignupPhoneActivation(state = {}, options = {}) {
+103 -11
View File
@@ -29,6 +29,11 @@
return /未找到可用的邮箱输入入口|当前页面没有可用的注册入口,也不在邮箱\/密码页/i.test(message);
}
function isSignupPhoneEntryUnavailableErrorMessage(errorLike) {
const message = getErrorMessage(errorLike);
return /未找到可用的手机号输入入口|当前页面没有可用的手机号注册入口,也不在密码页/i.test(message);
}
function isRetryableStep2TransportErrorMessage(errorLike) {
const message = getErrorMessage(errorLike);
return /Content script on signup-page did not respond in \d+s|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message);
@@ -58,17 +63,58 @@
}
}
async function shouldForceAuthEntryRetry(tabId) {
if (!Number.isInteger(tabId) || typeof chrome?.tabs?.get !== 'function') {
return false;
function isReadySignupEntryState(state = '') {
const normalized = String(state || '').trim().toLowerCase();
return normalized === 'entry_home'
|| normalized === 'email_entry'
|| normalized === 'phone_entry'
|| normalized === 'password_page';
}
async function getSignupEntryReadyState(tabId) {
if (!Number.isInteger(tabId) || typeof sendToContentScriptResilient !== 'function') {
return '';
}
try {
const tab = await chrome.tabs.get(tabId);
return isLikelyLoggedInChatgptHomeUrl(tab?.url);
const result = await sendToContentScriptResilient('signup-page', {
type: 'ENSURE_SIGNUP_ENTRY_READY',
step: 2,
source: 'background',
payload: {},
}, {
timeoutMs: 12000,
retryDelayMs: 500,
logMessage: '步骤 2:正在检查官网注册入口状态...',
});
if (result?.error) {
return '';
}
return String(result?.state || '').trim().toLowerCase();
} catch {
return '';
}
}
async function isLikelyLoggedInChatgptHomeTab(tabId) {
if (typeof chrome?.tabs?.get !== 'function') {
return false;
}
const readyState = await getSignupEntryReadyState(tabId);
if (isReadySignupEntryState(readyState)) {
return false;
}
const currentUrl = await getTabUrl(tabId);
return isLikelyLoggedInChatgptHomeUrl(currentUrl);
}
async function shouldForceAuthEntryRetry(tabId) {
if (!Number.isInteger(tabId)) {
return false;
}
return isLikelyLoggedInChatgptHomeTab(tabId);
}
async function getTabUrl(tabId) {
@@ -85,8 +131,7 @@
}
async function failStep2OnLoggedInSession(tabId, reasonMessage = '') {
const currentUrl = await getTabUrl(tabId);
if (!isLikelyLoggedInChatgptHomeUrl(currentUrl)) {
if (!(await isLikelyLoggedInChatgptHomeTab(tabId))) {
return false;
}
@@ -120,6 +165,28 @@
}
}
async function ensureSignupPhoneEntryReady(tabId) {
if (!Number.isInteger(tabId)) {
throw new Error('步骤 2:未找到可用的注册页标签,无法切换到手机号注册入口。');
}
const result = await sendToContentScriptResilient('signup-page', {
type: 'ENSURE_SIGNUP_PHONE_ENTRY_READY',
step: 2,
source: 'background',
payload: {},
}, {
timeoutMs: 30000,
retryDelayMs: 700,
logMessage: '步骤 2:正在打开官网注册入口并切换到手机号注册...',
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
async function submitSignupEmail(resolvedEmail, options = {}) {
return sendSignupIdentity({ email: resolvedEmail }, options);
}
@@ -174,6 +241,26 @@
}
}
try {
await ensureSignupPhoneEntryReady(signupTabId);
} catch (entryError) {
const entryErrorMessage = getErrorMessage(entryError);
if (await failStep2OnLoggedInSession(signupTabId, entryErrorMessage)) {
return;
}
if (
isSignupPhoneEntryUnavailableErrorMessage(entryErrorMessage)
|| isSignupEntryUnavailableErrorMessage(entryErrorMessage)
|| isRetryableStep2TransportErrorMessage(entryErrorMessage)
) {
await addLog('步骤 2:手机号注册入口尚未就绪,正在重新打开官网入口后重试一次...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
await ensureSignupPhoneEntryReady(signupTabId);
} else {
throw entryError;
}
}
const activation = await phoneVerificationHelpers.prepareSignupPhoneActivation(state);
let step2Result = await submitSignupPhone(activation.phoneNumber, activation, {
timeoutMs: 45000,
@@ -183,13 +270,18 @@
if (step2Result?.error) {
const errorMessage = getErrorMessage(step2Result.error);
if (isSignupEntryUnavailableErrorMessage(errorMessage) || isRetryableStep2TransportErrorMessage(errorMessage)) {
await addLog('步骤 2:手机号注册入口不可用或通信超时,正在切换认证入口页后重试一次...', 'warn');
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
if (
isSignupPhoneEntryUnavailableErrorMessage(errorMessage)
|| isSignupEntryUnavailableErrorMessage(errorMessage)
|| isRetryableStep2TransportErrorMessage(errorMessage)
) {
await addLog('步骤 2:手机号注册入口不可用或通信超时,正在重新准备手机号注册入口后重试一次...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
await ensureSignupPhoneEntryReady(signupTabId);
step2Result = await submitSignupPhone(activation.phoneNumber, activation, {
timeoutMs: 45000,
retryDelayMs: 700,
logMessage: '步骤 2认证入口页已打开,正在重新提交手机号...',
logMessage: '步骤 2手机号注册入口已就绪,正在重新提交手机号...',
});
}
}