Merge remote-tracking branch 'origin/dev' into pr-112

This commit is contained in:
Ryan Liu
2026-04-25 11:50:26 +08:00
29 changed files with 1468 additions and 179 deletions
+4 -1
View File
@@ -283,7 +283,10 @@
return fetchManagedAliasEmail(mergedState, options);
}
if (generator === 'icloud') {
return fetchIcloudHideMyEmail();
const stateFetchMode = String(mergedState.icloudFetchMode || '').trim().toLowerCase();
return fetchIcloudHideMyEmail({
generateNew: Boolean(options.generateNew) || stateFetchMode === 'always_new',
});
}
if (generator === 'cloudflare') {
return fetchCloudflareEmail(mergedState, options);
+20
View File
@@ -151,6 +151,18 @@
if (payload.email) {
await setEmailState(payload.email);
}
if (payload.skipRegistrationFlow) {
const latestState = await getState();
for (const skipStep of [3, 4, 5]) {
const status = latestState.stepStatuses?.[skipStep];
if (status === 'running' || status === 'completed' || status === 'manual_completed') {
continue;
}
await setStepStatus(skipStep, 'skipped');
}
await addLog('步骤 2:检测到当前已登录会话,已自动跳过步骤 3/4/5,流程将直接进入步骤 6。', 'warn');
break;
}
if (payload.skippedPasswordStep) {
const latestState = await getState();
const step3Status = latestState.stepStatuses?.[3];
@@ -179,6 +191,14 @@
lastEmailTimestamp: payload.emailTimestamp || null,
signupVerificationRequestedAt: null,
});
if (payload.skipProfileStep) {
const latestState = await getState();
const step5Status = latestState.stepStatuses?.[5];
if (step5Status !== 'running' && step5Status !== 'completed' && step5Status !== 'manual_completed') {
await setStepStatus(5, 'skipped');
await addLog('步骤 4:检测到账号已直接进入已登录态,已自动跳过步骤 5。', 'warn');
}
}
break;
case 8:
await setState({
+10
View File
@@ -10,6 +10,7 @@
CLOUDFLARE_TEMP_EMAIL_PROVIDER,
confirmCustomVerificationStepBypass,
ensureMail2925MailboxSession,
ensureIcloudMailSession,
ensureStep8VerificationPageReady,
getOAuthFlowRemainingMs,
getOAuthFlowStepTimeoutMs,
@@ -140,6 +141,15 @@
return;
}
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
await addLog('步骤 8:正在确认 iCloud 邮箱登录态...', 'info');
await ensureIcloudMailSession({
state,
step: 8,
actionLabel: '步骤 8:确认 iCloud 邮箱登录态',
});
}
throwIfStopped();
if (
mail.provider === HOTMAIL_PROVIDER
+10
View File
@@ -10,6 +10,7 @@
completeStepFromBackground,
confirmCustomVerificationStepBypass,
ensureMail2925MailboxSession,
ensureIcloudMailSession,
getMailConfig,
getTabId,
HOTMAIL_PROVIDER,
@@ -111,6 +112,15 @@
return;
}
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
await addLog('步骤 4:正在确认 iCloud 邮箱登录态...', 'info');
await ensureIcloudMailSession({
state,
step: 4,
actionLabel: '步骤 4:确认 iCloud 邮箱登录态',
});
}
throwIfStopped();
if (
mail.provider === HOTMAIL_PROVIDER
+128
View File
@@ -1,14 +1,142 @@
(function attachBackgroundStep1(root, factory) {
root.MultiPageBackgroundStep1 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep1Module() {
const STEP1_COOKIE_CLEAR_DOMAINS = [
'chatgpt.com',
'chat.openai.com',
'openai.com',
'auth.openai.com',
'auth0.openai.com',
'accounts.openai.com',
];
const STEP1_COOKIE_CLEAR_ORIGINS = [
'https://chatgpt.com',
'https://chat.openai.com',
'https://auth.openai.com',
'https://auth0.openai.com',
'https://accounts.openai.com',
'https://openai.com',
];
function normalizeCookieDomainForStep1(domain) {
return String(domain || '').trim().replace(/^\.+/, '').toLowerCase();
}
function shouldClearStep1Cookie(cookie) {
const domain = normalizeCookieDomainForStep1(cookie?.domain);
if (!domain) return false;
return STEP1_COOKIE_CLEAR_DOMAINS.some((target) => (
domain === target || domain.endsWith(`.${target}`)
));
}
function buildStep1CookieRemovalUrl(cookie) {
const host = normalizeCookieDomainForStep1(cookie?.domain);
const rawPath = String(cookie?.path || '/');
const path = rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
return `https://${host}${path}`;
}
function getStep1ErrorMessage(error) {
return error?.message || String(error || '未知错误');
}
async function collectStep1Cookies(chromeApi) {
if (!chromeApi.cookies?.getAll) {
return [];
}
const stores = chromeApi.cookies.getAllCookieStores
? await chromeApi.cookies.getAllCookieStores()
: [{ id: undefined }];
const cookies = [];
const seen = new Set();
for (const store of stores) {
const storeId = store?.id;
const batch = await chromeApi.cookies.getAll(storeId ? { storeId } : {});
for (const cookie of batch || []) {
if (!shouldClearStep1Cookie(cookie)) continue;
const key = [
cookie.storeId || storeId || '',
cookie.domain || '',
cookie.path || '',
cookie.name || '',
cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
].join('|');
if (seen.has(key)) continue;
seen.add(key);
cookies.push(cookie);
}
}
return cookies;
}
async function removeStep1Cookie(chromeApi, cookie) {
const details = {
url: buildStep1CookieRemovalUrl(cookie),
name: cookie.name,
};
if (cookie.storeId) {
details.storeId = cookie.storeId;
}
if (cookie.partitionKey) {
details.partitionKey = cookie.partitionKey;
}
try {
const result = await chromeApi.cookies.remove(details);
return Boolean(result);
} catch (error) {
console.warn('[MultiPage:step1] remove cookie failed', {
domain: cookie?.domain,
name: cookie?.name,
message: getStep1ErrorMessage(error),
});
return false;
}
}
function createStep1Executor(deps = {}) {
const {
addLog,
chrome: chromeApi = globalThis.chrome,
completeStepFromBackground,
openSignupEntryTab,
} = deps;
async function clearOpenAiCookiesBeforeStep1() {
if (!chromeApi?.cookies?.getAll || !chromeApi.cookies?.remove) {
await addLog('步骤 1:当前浏览器不支持 cookies API,跳过打开官网前 cookie 清理。', 'warn');
return;
}
await addLog('步骤 1:打开 ChatGPT 官网前清理 ChatGPT / OpenAI cookies...', 'info');
const cookies = await collectStep1Cookies(chromeApi);
let removedCount = 0;
for (const cookie of cookies) {
if (await removeStep1Cookie(chromeApi, cookie)) {
removedCount += 1;
}
}
if (chromeApi.browsingData?.removeCookies) {
try {
await chromeApi.browsingData.removeCookies({
since: 0,
origins: STEP1_COOKIE_CLEAR_ORIGINS,
});
} catch (error) {
await addLog(`步骤 1browsingData 补扫 cookies 失败:${getStep1ErrorMessage(error)}`, 'warn');
}
}
await addLog(`步骤 1:已清理 ${removedCount} 个 ChatGPT / OpenAI cookies。`, 'ok');
}
async function executeStep1() {
await clearOpenAiCookiesBeforeStep1();
await addLog('步骤 1:正在打开 ChatGPT 官网...');
await openSignupEntryTab(1);
await completeStepFromBackground(1, {});
+164 -8
View File
@@ -7,6 +7,7 @@
chrome,
completeStepFromBackground,
ensureContentScriptReadyOnTab,
ensureSignupAuthEntryPageReady,
ensureSignupEntryPageReady,
ensureSignupPostEmailPageReadyInTab,
getTabId,
@@ -16,6 +17,107 @@
SIGNUP_PAGE_INJECT_FILES,
} = deps;
function getErrorMessage(error) {
return String(typeof error === 'string' ? error : error?.message || '');
}
function isSignupEntryUnavailableErrorMessage(errorLike) {
const message = getErrorMessage(errorLike);
return /未找到可用的邮箱输入入口|当前页面没有可用的注册入口,也不在邮箱\/密码页/.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);
}
function isLikelyLoggedInChatgptHomeUrl(rawUrl) {
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'].includes(host)) {
return false;
}
const path = String(parsed.pathname || '');
if (/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path)) {
return false;
}
return true;
} catch {
return false;
}
}
async function shouldForceAuthEntryRetry(tabId) {
if (!Number.isInteger(tabId) || typeof chrome?.tabs?.get !== 'function') {
return false;
}
try {
const tab = await chrome.tabs.get(tabId);
const currentUrl = String(tab?.url || '');
return isLikelyLoggedInChatgptHomeUrl(currentUrl);
} catch {
return false;
}
}
async function getTabUrl(tabId) {
if (!Number.isInteger(tabId) || typeof chrome?.tabs?.get !== 'function') {
return '';
}
try {
const tab = await chrome.tabs.get(tabId);
return String(tab?.url || '');
} catch {
return '';
}
}
async function completeStep2AsLoggedInSession(tabId, resolvedEmail, reasonMessage = '') {
const currentUrl = await getTabUrl(tabId);
if (!isLikelyLoggedInChatgptHomeUrl(currentUrl)) {
return false;
}
const reasonText = getErrorMessage(reasonMessage);
const reasonSuffix = reasonText ? `(触发原因:${reasonText}` : '';
await addLog(`步骤 2:检测到当前会话已登录 ChatGPT,已跳过注册链路(步骤 3/4/5),将直接进入步骤 6。${reasonSuffix}`, 'warn');
await completeStepFromBackground(2, {
email: resolvedEmail,
nextSignupState: 'already_logged_in_home',
nextSignupUrl: currentUrl || 'https://chatgpt.com/',
skippedPasswordStep: true,
skipRegistrationFlow: true,
});
return true;
}
async function submitSignupEmail(resolvedEmail, options = {}) {
const {
timeoutMs = 35000,
retryDelayMs = 700,
logMessage = '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...',
} = options;
try {
return await sendToContentScriptResilient('signup-page', {
type: 'EXECUTE_STEP',
step: 2,
source: 'background',
payload: { email: resolvedEmail },
}, {
timeoutMs,
retryDelayMs,
logMessage,
});
} catch (error) {
return { error: getErrorMessage(error) };
}
}
async function executeStep2(state) {
const resolvedEmail = await resolveSignupEmailForFlow(state);
@@ -34,19 +136,73 @@
});
}
const step2Result = await sendToContentScriptResilient('signup-page', {
type: 'EXECUTE_STEP',
step: 2,
source: 'background',
payload: { email: resolvedEmail },
}, {
timeoutMs: 20000,
if (await shouldForceAuthEntryRetry(signupTabId)) {
await addLog('步骤 2:检测到当前位于已登录 ChatGPT 首页,先切换认证入口页再提交邮箱。', 'warn');
try {
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
} catch (entryError) {
const entryErrorMessage = getErrorMessage(entryError);
if (await completeStep2AsLoggedInSession(signupTabId, resolvedEmail, entryErrorMessage)) {
return;
}
await addLog('步骤 2:切换认证入口失败,正在重新打开官网入口并重试提交邮箱...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
}
}
let step2Result = await submitSignupEmail(resolvedEmail, {
timeoutMs: 35000,
retryDelayMs: 700,
logMessage: '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...',
});
if (step2Result?.error) {
throw new Error(step2Result.error);
const errorMessage = getErrorMessage(step2Result.error);
if (isSignupEntryUnavailableErrorMessage(errorMessage)) {
await addLog('步骤 2:未找到邮箱输入入口,正在切换认证入口页后重试一次...', 'warn');
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
step2Result = await submitSignupEmail(resolvedEmail, {
timeoutMs: 35000,
retryDelayMs: 700,
logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...',
});
if (step2Result?.error) {
const retryErrorMessage = getErrorMessage(step2Result.error);
if (isSignupEntryUnavailableErrorMessage(retryErrorMessage)) {
if (await completeStep2AsLoggedInSession(signupTabId, resolvedEmail, retryErrorMessage)) {
return;
}
await addLog('步骤 2:认证入口仍不可用,正在重新进入官网注册入口再重试一次...', 'warn');
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
step2Result = await submitSignupEmail(resolvedEmail, {
timeoutMs: 35000,
retryDelayMs: 700,
logMessage: '步骤 2:重试官网注册入口后正在重新提交邮箱...',
});
}
}
} else if (isRetryableStep2TransportErrorMessage(errorMessage)) {
await addLog('步骤 2:注册入口页通信超时,正在切换认证入口页并重试提交邮箱...', 'warn');
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
step2Result = await submitSignupEmail(resolvedEmail, {
timeoutMs: 45000,
retryDelayMs: 700,
logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...',
});
}
}
if (step2Result?.error) {
const finalErrorMessage = getErrorMessage(step2Result.error);
if (
(isSignupEntryUnavailableErrorMessage(finalErrorMessage)
|| isRetryableStep2TransportErrorMessage(finalErrorMessage))
&& await completeStep2AsLoggedInSession(signupTabId, resolvedEmail, finalErrorMessage)
) {
return;
}
throw new Error(finalErrorMessage);
}
if (!step2Result?.alreadyOnPasswordPage) {
+147 -9
View File
@@ -23,6 +23,7 @@
pollHotmailVerificationCode,
pollLuckmailVerificationCode,
sendToContentScript,
sendToContentScriptResilient,
sendToMailContentScriptResilient,
setState,
sleepWithStop,
@@ -30,6 +31,12 @@
VERIFICATION_POLL_MAX_ROUNDS,
} = deps;
const isRetryableVerificationTransportError = typeof deps.isRetryableContentScriptTransportError === 'function'
? deps.isRetryableContentScriptTransportError
: ((error) => /back\/forward cache|message channel is closed|Receiving end does not exist|port closed before a response was received|A listener indicated an asynchronous response|did not respond in \d+s/i.test(
String(typeof error === 'string' ? error : error?.message || '')
));
function getVerificationCodeStateKey(step) {
return step === 4 ? 'lastSignupCode' : 'lastLoginCode';
}
@@ -38,6 +45,89 @@
return step === 4 ? '注册' : '登录';
}
function isLikelyLoggedInChatgptHomeUrl(rawUrl) {
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'].includes(host)) {
return false;
}
const path = String(parsed.pathname || '');
if (/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path)) {
return false;
}
return true;
} catch {
return false;
}
}
function isSignupProfilePageUrl(rawUrl) {
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(?:[/?#]|$)/i.test(String(parsed.pathname || ''));
} catch {
return false;
}
}
async function detectStep4PostSubmitFallback(tabId, options = {}) {
const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 8000);
const pollIntervalMs = Math.max(100, Number(options.pollIntervalMs) || 250);
const startedAt = Date.now();
let lastUrl = '';
while (Date.now() - startedAt < timeoutMs) {
throwIfStopped();
try {
const tab = await chrome.tabs.get(tabId);
const currentUrl = String(tab?.url || '').trim();
if (currentUrl) {
lastUrl = currentUrl;
}
if (isLikelyLoggedInChatgptHomeUrl(currentUrl)) {
return {
success: true,
reason: 'chatgpt_home',
skipProfileStep: true,
url: currentUrl,
};
}
if (isSignupProfilePageUrl(currentUrl)) {
return {
success: true,
reason: 'signup_profile',
skipProfileStep: false,
url: currentUrl,
};
}
} catch {
// Keep polling until timeout; tab may be mid-navigation.
}
await sleepWithStop(pollIntervalMs);
}
return {
success: false,
reason: 'unknown',
skipProfileStep: false,
url: lastUrl,
};
}
function getVerificationResendStateKey() {
return 'verificationResendCount';
}
@@ -448,6 +538,17 @@
`步骤 ${step}:距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,继续刷新邮箱(第 ${round}/${maxRounds} 轮)...`,
'info'
);
const configuredIntervalMs = Math.max(
1,
Number(payloadOverrides.intervalMs)
|| Number(pollOverrides.intervalMs)
|| 3000
);
const cooldownSleepMs = Math.min(
remainingBeforeResendMs,
Math.max(1000, Math.min(configuredIntervalMs, 3000))
);
await sleepWithStop(cooldownSleepMs);
continue;
}
@@ -597,19 +698,54 @@
}
await chrome.tabs.update(signupTabId, { active: true });
const result = await sendToContentScript('signup-page', {
const baseResponseTimeoutMs = await getResponseTimeoutMsForStep(
step,
options,
step === 7 ? 45000 : 30000,
`填写${getVerificationCodeLabel(step)}验证码`
);
const message = {
type: 'FILL_CODE',
step,
source: 'background',
payload: { code },
}, {
responseTimeoutMs: await getResponseTimeoutMsForStep(
step,
options,
step === 7 ? 45000 : 30000,
`填写${getVerificationCodeLabel(step)}验证码`
),
});
};
let result;
if (typeof sendToContentScriptResilient === 'function') {
try {
result = await sendToContentScriptResilient('signup-page', message, {
timeoutMs: Math.max(baseResponseTimeoutMs + 15000, 30000),
retryDelayMs: 700,
responseTimeoutMs: baseResponseTimeoutMs,
logMessage: `步骤 ${step}:认证页正在切换,等待页面重新就绪后继续确认验证码提交结果...`,
});
} catch (err) {
if (step === 4 && isRetryableVerificationTransportError(err)) {
const fallback = await detectStep4PostSubmitFallback(signupTabId, {
timeoutMs: 9000,
pollIntervalMs: 300,
});
if (fallback.success) {
const fallbackLabel = fallback.reason === 'chatgpt_home'
? 'ChatGPT 已登录首页'
: '注册资料页';
await addLog(`步骤 4:验证码提交后页面已切换到${fallbackLabel},按提交成功继续。`, 'warn');
return {
success: true,
assumed: true,
transportRecovered: true,
skipProfileStep: Boolean(fallback.skipProfileStep),
url: fallback.url,
};
}
}
throw err;
}
} else {
result = await sendToContentScript('signup-page', message, {
responseTimeoutMs: baseResponseTimeoutMs,
});
}
if (result && result.error) {
throw new Error(result.error);
@@ -735,6 +871,7 @@
`步骤 ${step}:提交失败后距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,先继续刷新邮箱(${attempt + 1}/${maxSubmitAttempts}...`,
'warn'
);
await sleepWithStop(Math.min(remainingBeforeResendMs, 2000));
continue;
}
@@ -763,6 +900,7 @@
await completeStepFromBackground(step, {
emailTimestamp: result.emailTimestamp,
code: result.code,
...(step === 4 && submitResult?.skipProfileStep ? { skipProfileStep: true } : {}),
});
triggerPostSuccessMailboxCleanup(step, mail);
return;