Merge branch 'Plus模式开发' into dev

This commit is contained in:
QLHazyCoder
2026-04-26 02:06:26 +08:00
27 changed files with 3153 additions and 209 deletions
+8 -5
View File
@@ -311,7 +311,7 @@
let successfulRuns = roundSummaries.filter((item) => item.status === 'success').length;
const initialState = await getState();
const initialPhase = continueCurrentOnFirstAttempt && getRunningSteps(initialState.stepStatuses).length
const initialPhase = continueCurrentOnFirstAttempt && getRunningSteps(initialState.stepStatuses, initialState).length
? 'waiting_step'
: 'running';
const showResumePosition = continueCurrentOnFirstAttempt || resumeCurrentRun > 1 || resumeAttemptRun > 1;
@@ -351,18 +351,18 @@
if (reuseExistingProgress) {
let currentState = await getState();
if (getRunningSteps(currentState.stepStatuses).length) {
if (getRunningSteps(currentState.stepStatuses, currentState).length) {
currentState = await waitForRunningStepsToFinish({
currentRun: targetRun,
totalRuns,
attemptRun,
});
}
const resumeStep = getFirstUnfinishedStep(currentState.stepStatuses);
if (resumeStep && hasSavedProgress(currentState.stepStatuses)) {
const resumeStep = getFirstUnfinishedStep(currentState.stepStatuses, currentState);
if (resumeStep && hasSavedProgress(currentState.stepStatuses, currentState)) {
startStep = resumeStep;
useExistingProgress = true;
} else if (hasSavedProgress(currentState.stepStatuses)) {
} else if (hasSavedProgress(currentState.stepStatuses, currentState)) {
await addLog('检测到当前流程已处理完成,本轮将改为从步骤 1 重新开始。', 'info');
}
}
@@ -373,6 +373,9 @@
vpsUrl: prevState.vpsUrl,
vpsPassword: prevState.vpsPassword,
customPassword: prevState.customPassword,
plusModeEnabled: prevState.plusModeEnabled,
paypalEmail: prevState.paypalEmail,
paypalPassword: prevState.paypalPassword,
autoRunSkipFailures: prevState.autoRunSkipFailures,
autoRunFallbackThreadIntervalMinutes: prevState.autoRunFallbackThreadIntervalMinutes,
autoRunDelayEnabled: prevState.autoRunDelayEnabled,
+109 -62
View File
@@ -40,6 +40,9 @@
getPendingAutoRunTimerPlan,
getSourceLabel,
getState,
getStepDefinitionForState,
getStepIdsForState,
getLastStepIdForState,
getTabId,
getStopRequested,
handleAutoRunLoopUnhandledError,
@@ -127,7 +130,86 @@
}
}
function getStepKeyForState(step, state = {}) {
if (typeof getStepDefinitionForState === 'function') {
return String(getStepDefinitionForState(step, state)?.key || '').trim();
}
return '';
}
async function handlePlatformVerifyStepData(payload) {
if (payload.localhostUrl) {
await closeLocalhostCallbackTabs(payload.localhostUrl);
}
const latestState = await getState();
if (latestState.currentHotmailAccountId && isHotmailProvider(latestState)) {
await patchHotmailAccount(latestState.currentHotmailAccountId, {
used: true,
lastUsedAt: Date.now(),
});
await addLog('当前 Hotmail 账号已自动标记为已用。', 'ok');
}
if (String(latestState.mailProvider || '').trim().toLowerCase() === '2925' && latestState.currentMail2925AccountId) {
await patchMail2925Account(latestState.currentMail2925AccountId, {
lastUsedAt: Date.now(),
lastError: '',
});
await addLog('当前 2925 账号已记录最近使用时间。', 'ok');
}
if (isLuckmailProvider(latestState)) {
const currentPurchase = getCurrentLuckmailPurchase(latestState);
if (currentPurchase?.id) {
await setLuckmailPurchaseUsedState(currentPurchase.id, true);
await addLog(`当前 LuckMail 邮箱 ${currentPurchase.email_address} 已在本地标记为已用。`, 'ok');
}
await clearLuckmailRuntimeState({ clearEmail: true });
await addLog('当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。', 'ok');
}
const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl);
if (localhostPrefix) {
await closeTabsByUrlPrefix(localhostPrefix, {
excludeUrls: [payload.localhostUrl],
excludeLocalhostCallbacks: true,
});
}
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
}
async function handleStepData(step, payload) {
const stateForStep = await getState();
const stepKey = getStepKeyForState(step, stateForStep);
if (stepKey === 'oauth-login') {
if (payload.loginVerificationRequestedAt) {
await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
}
return;
}
if (stepKey === 'fetch-login-code') {
await setState({
lastEmailTimestamp: payload.emailTimestamp || null,
loginVerificationRequestedAt: null,
});
return;
}
if (stepKey === 'confirm-oauth') {
if (payload.localhostUrl) {
if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) {
throw new Error(`步骤 ${step} 返回了无效的 localhost OAuth 回调地址。`);
}
await setState({ localhostUrl: payload.localhostUrl });
broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
}
return;
}
if (stepKey === 'platform-verify') {
await handlePlatformVerifyStepData(payload);
return;
}
switch (step) {
case 1: {
const updates = {};
@@ -181,11 +263,6 @@
await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
}
break;
case 7:
if (payload.loginVerificationRequestedAt) {
await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
}
break;
case 4:
await setState({
lastEmailTimestamp: payload.emailTimestamp || null,
@@ -200,59 +277,6 @@
}
}
break;
case 8:
await setState({
lastEmailTimestamp: payload.emailTimestamp || null,
loginVerificationRequestedAt: null,
});
break;
case 9:
if (payload.localhostUrl) {
if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) {
throw new Error('步骤 9 返回了无效的 localhost OAuth 回调地址。');
}
await setState({ localhostUrl: payload.localhostUrl });
broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
}
break;
case 10: {
if (payload.localhostUrl) {
await closeLocalhostCallbackTabs(payload.localhostUrl);
}
const latestState = await getState();
if (latestState.currentHotmailAccountId && isHotmailProvider(latestState)) {
await patchHotmailAccount(latestState.currentHotmailAccountId, {
used: true,
lastUsedAt: Date.now(),
});
await addLog('当前 Hotmail 账号已自动标记为已用。', 'ok');
}
if (String(latestState.mailProvider || '').trim().toLowerCase() === '2925' && latestState.currentMail2925AccountId) {
await patchMail2925Account(latestState.currentMail2925AccountId, {
lastUsedAt: Date.now(),
lastError: '',
});
await addLog('当前 2925 账号已记录最近使用时间。', 'ok');
}
if (isLuckmailProvider(latestState)) {
const currentPurchase = getCurrentLuckmailPurchase(latestState);
if (currentPurchase?.id) {
await setLuckmailPurchaseUsedState(currentPurchase.id, true);
await addLog(`当前 LuckMail 邮箱 ${currentPurchase.email_address} 已在本地标记为已用。`, 'ok');
}
await clearLuckmailRuntimeState({ clearEmail: true });
await addLog('当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。', 'ok');
}
const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl);
if (localhostPrefix) {
await closeTabsByUrlPrefix(localhostPrefix, {
excludeUrls: [payload.localhostUrl],
excludeLocalhostCallbacks: true,
});
}
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
break;
}
default:
break;
}
@@ -303,11 +327,15 @@
return { ok: true, error: errorMessage };
}
const completionState = message.step === 10 ? await getState() : null;
const completionStateCandidate = await getState();
const lastStepId = typeof getLastStepIdForState === 'function'
? getLastStepIdForState(completionStateCandidate)
: 10;
const completionState = message.step === lastStepId ? completionStateCandidate : null;
await setStepStatus(message.step, 'completed');
await addLog(`步骤 ${message.step} 已完成`, 'ok');
await handleStepData(message.step, message.payload);
if (message.step === 10 && typeof appendAccountRunRecord === 'function') {
if (message.step === lastStepId && typeof appendAccountRunRecord === 'function') {
await appendAccountRunRecord('success', completionState);
}
notifyStepComplete(message.step, message.payload);
@@ -561,13 +589,32 @@
}
case 'SAVE_SETTING': {
const currentState = await getState();
const updates = buildPersistentSettingsPayload(message.payload || {});
const sessionUpdates = buildLuckmailSessionSettingsPayload(message.payload || {});
const modeChanged = Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled')
&& Boolean(currentState?.plusModeEnabled) !== Boolean(updates.plusModeEnabled);
await setPersistentSettings(updates);
await setState({
const stateUpdates = {
...updates,
...sessionUpdates,
});
};
if (modeChanged && typeof getStepIdsForState === 'function') {
const nextStateForSteps = { ...currentState, ...stateUpdates };
stateUpdates.stepStatuses = Object.fromEntries(
getStepIdsForState(nextStateForSteps).map((stepId) => [stepId, 'pending'])
);
stateUpdates.currentStep = 0;
}
await setState(stateUpdates);
if (modeChanged) {
await addLog(
Boolean(updates.plusModeEnabled)
? 'Plus 模式已开启,已切换为 Plus Checkout + PayPal 步骤。'
: 'Plus 模式已关闭,已恢复普通注册授权步骤。',
'info'
);
}
return { ok: true, state: await getState() };
}
+26 -19
View File
@@ -33,16 +33,23 @@
setStep8TabUpdatedListener,
} = deps;
function getVisibleStep(state, fallback = 9) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep > 0 ? visibleStep : fallback;
}
async function executeStep9(state) {
const visibleStep = getVisibleStep(state, 9);
if (!state.oauthUrl) {
throw new Error('缺少登录用 OAuth 链接,请先完成步骤 7。');
const authLoginStep = visibleStep === 11 ? 10 : 7;
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${authLoginStep}`);
}
await addLog('步骤 9:正在监听 localhost 回调地址...');
await addLog(`步骤 ${visibleStep}:正在监听 localhost 回调地址...`);
const callbackTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(240000, {
step: 9,
step: visibleStep,
actionLabel: 'OAuth localhost 回调',
})
: 240000;
@@ -71,8 +78,8 @@
cleanupListener();
clearTimeout(timeout);
addLog(`步骤 9:已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
return completeStepFromBackground(9, { localhostUrl: callbackUrl });
addLog(`步骤 ${visibleStep}:已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
return completeStepFromBackground(visibleStep, { localhostUrl: callbackUrl });
}).then(() => {
resolve();
}).catch((err) => {
@@ -81,7 +88,7 @@
};
const timeout = setTimeout(() => {
rejectStep9(new Error('120 秒内未捕获到 localhost 回调跳转,步骤 9 的点击可能被拦截了。'));
rejectStep9(new Error(`${Math.round(callbackTimeoutMs / 1000)} 秒内未捕获到 localhost 回调跳转,步骤 ${visibleStep} 的点击可能被拦截了。`));
}, callbackTimeoutMs);
setStep8PendingReject((error) => {
@@ -111,10 +118,10 @@
if (signupTabId && await isTabAlive('signup-page')) {
await chrome.tabs.update(signupTabId, { active: true });
await addLog('步骤 9:已切回认证页,正在准备调试器点击...');
await addLog(`步骤 ${visibleStep}:已切回认证页,正在准备调试器点击...`);
} else {
signupTabId = await reuseOrCreateTab('signup-page', state.oauthUrl);
await addLog('步骤 9:已重新打开认证页,正在准备调试器点击...');
await addLog(`步骤 ${visibleStep}:已重新打开认证页,正在准备调试器点击...`);
}
throwIfStep8SettledOrStopped(resolved);
@@ -124,11 +131,11 @@
await ensureStep8SignupPageReady(signupTabId, {
timeoutMs: typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(15000, {
step: 9,
step: visibleStep,
actionLabel: '等待 OAuth 同意页内容脚本就绪',
})
: 15000,
logMessage: '步骤 9:认证页内容脚本尚未就绪,正在等待页面恢复...',
logMessage: `步骤 ${visibleStep}:认证页内容脚本尚未就绪,正在等待页面恢复...`,
});
for (let round = 1; round <= STEP8_MAX_ROUNDS && !resolved; round++) {
@@ -137,7 +144,7 @@
signupTabId,
typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(STEP8_READY_WAIT_TIMEOUT_MS, {
step: 9,
step: visibleStep,
actionLabel: '等待 OAuth 同意页出现',
})
: STEP8_READY_WAIT_TIMEOUT_MS
@@ -149,12 +156,12 @@
const strategy = STEP8_STRATEGIES[Math.min(round - 1, STEP8_STRATEGIES.length - 1)];
await addLog(`步骤 9:第 ${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label}...`);
await addLog(`步骤 ${visibleStep}:第 ${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label}...`);
if (strategy.mode === 'debugger') {
const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(15000, {
step: 9,
step: visibleStep,
actionLabel: '定位 OAuth 同意页继续按钮',
})
: 15000;
@@ -167,7 +174,7 @@
} else {
const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(15000, {
step: 9,
step: visibleStep,
actionLabel: '点击 OAuth 同意页继续按钮',
})
: 15000;
@@ -186,7 +193,7 @@
pageState.url,
typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(15000, {
step: 9,
step: visibleStep,
actionLabel: '等待 OAuth 同意页点击生效',
})
: 15000
@@ -196,20 +203,20 @@
}
if (effect.progressed) {
await addLog(`步骤 9:检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info');
await addLog(`步骤 ${visibleStep}:检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info');
break;
}
if (round >= STEP8_MAX_ROUNDS) {
throw new Error(`步骤 9:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`);
throw new Error(`步骤 ${visibleStep}:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`);
}
await addLog(`步骤 9${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS}...`, 'warn');
await addLog(`步骤 ${visibleStep}${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS}...`, 'warn');
await reloadStep8ConsentPage(
signupTabId,
typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(30000, {
step: 9,
step: visibleStep,
actionLabel: '刷新 OAuth 同意页',
})
: 30000
+81
View File
@@ -0,0 +1,81 @@
(function attachBackgroundPlusCheckoutCreate(root, factory) {
root.MultiPageBackgroundPlusCheckoutCreate = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPlusCheckoutCreateModule() {
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
const PLUS_CHECKOUT_ENTRY_URL = 'https://chatgpt.com/';
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/plus-checkout.js'];
function createPlusCheckoutCreateExecutor(deps = {}) {
const {
addLog,
chrome,
completeStepFromBackground,
ensureContentScriptReadyOnTabUntilStopped,
reuseOrCreateTab,
sendTabMessageUntilStopped,
setState,
sleepWithStop,
waitForTabCompleteUntilStopped,
} = deps;
async function executePlusCheckoutCreate() {
await addLog('步骤 6:正在打开 ChatGPT 会话页,准备创建 Plus Checkout...', 'info');
const tabId = await reuseOrCreateTab(PLUS_CHECKOUT_SOURCE, PLUS_CHECKOUT_ENTRY_URL, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
reloadIfSameUrl: false,
});
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
logMessage: '步骤 6ChatGPT 页面仍在加载,等待 Plus Checkout 脚本就绪...',
});
const result = await sendTabMessageUntilStopped(tabId, PLUS_CHECKOUT_SOURCE, {
type: 'CREATE_PLUS_CHECKOUT',
source: 'background',
payload: {},
});
if (result?.error) {
throw new Error(result.error);
}
if (!result?.checkoutUrl) {
throw new Error('步骤 6Plus Checkout 创建后未返回支付链接。');
}
await addLog('步骤 6Plus Checkout 已创建,正在打开支付页面...', 'ok');
await chrome.tabs.update(tabId, { url: result.checkoutUrl, active: true });
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
logMessage: '步骤 6Checkout 页面仍在加载,等待页面脚本就绪...',
});
await setState({
plusCheckoutTabId: tabId,
plusCheckoutUrl: result.checkoutUrl,
plusCheckoutCountry: result.country || 'DE',
plusCheckoutCurrency: result.currency || 'EUR',
});
await completeStepFromBackground(6, {
plusCheckoutCountry: result.country || 'DE',
plusCheckoutCurrency: result.currency || 'EUR',
});
}
return {
executePlusCheckoutCreate,
};
}
return {
createPlusCheckoutCreateExecutor,
};
});
+442
View File
@@ -0,0 +1,442 @@
(function attachBackgroundPlusCheckoutBilling(root, factory) {
root.MultiPageBackgroundPlusCheckoutBilling = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPlusCheckoutBillingModule() {
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/plus-checkout.js'];
const PLUS_CHECKOUT_URL_PATTERN = /^https:\/\/chatgpt\.com\/checkout(?:\/|$)/i;
const PLUS_CHECKOUT_FRAME_READY_DELAY_MS = 500;
function createPlusCheckoutBillingExecutor(deps = {}) {
const {
addLog,
chrome,
completeStepFromBackground,
ensureContentScriptReadyOnTabUntilStopped,
generateRandomName,
getAddressSeedForCountry,
getTabId,
isTabAlive,
setState,
sleepWithStop,
waitForTabCompleteUntilStopped,
waitForTabUrlMatchUntilStopped,
} = deps;
function isPlusCheckoutUrl(url = '') {
return PLUS_CHECKOUT_URL_PATTERN.test(String(url || ''));
}
async function getAlivePlusCheckoutTabId(tabId) {
if (!Number.isInteger(tabId) || tabId <= 0) {
return null;
}
if (!chrome?.tabs?.get) {
return tabId;
}
const tab = await chrome.tabs.get(tabId).catch(() => null);
return tab && isPlusCheckoutUrl(tab.url) ? tabId : null;
}
async function getCurrentPlusCheckoutTabId() {
if (!chrome?.tabs?.query) {
return null;
}
const activeTabs = await chrome.tabs.query({ active: true, currentWindow: true }).catch(() => []);
const activeCheckoutTab = activeTabs.find((tab) => Number.isInteger(tab?.id) && isPlusCheckoutUrl(tab.url));
if (activeCheckoutTab) {
return activeCheckoutTab.id;
}
const checkoutTabs = await chrome.tabs.query({ url: 'https://chatgpt.com/checkout/*' }).catch(() => []);
const checkoutTab = checkoutTabs.find((tab) => Number.isInteger(tab?.id) && isPlusCheckoutUrl(tab.url));
return checkoutTab?.id || null;
}
async function getCheckoutFrames(tabId) {
if (!chrome?.webNavigation?.getAllFrames) {
return [{ frameId: 0, url: '' }];
}
const frames = await chrome.webNavigation.getAllFrames({ tabId }).catch(() => null);
if (!Array.isArray(frames) || !frames.length) {
return [{ frameId: 0, url: '' }];
}
return frames
.filter((frame) => Number.isInteger(frame?.frameId))
.sort((left, right) => Number(left.frameId) - Number(right.frameId));
}
async function pingCheckoutFrame(tabId, frameId) {
try {
const pong = await chrome.tabs.sendMessage(tabId, {
type: 'PING',
source: 'background',
payload: {},
}, {
frameId: Number.isInteger(frameId) ? frameId : 0,
});
return Boolean(pong?.ok && (!pong.source || pong.source === PLUS_CHECKOUT_SOURCE));
} catch {
return false;
}
}
async function ensurePlusCheckoutFrameReady(tabId, frameId) {
if (await pingCheckoutFrame(tabId, frameId)) {
return true;
}
if (!chrome?.scripting?.executeScript) {
return false;
}
try {
await chrome.scripting.executeScript({
target: { tabId, frameIds: [frameId] },
func: (injectedSource) => {
window.__MULTIPAGE_SOURCE = injectedSource;
},
args: [PLUS_CHECKOUT_SOURCE],
});
await chrome.scripting.executeScript({
target: { tabId, frameIds: [frameId] },
files: PLUS_CHECKOUT_INJECT_FILES,
});
} catch {
// If the frame was already injected or navigated mid-injection, ping once more below.
}
await sleepWithStop(PLUS_CHECKOUT_FRAME_READY_DELAY_MS);
return await pingCheckoutFrame(tabId, frameId);
}
async function ensurePlusCheckoutFramesReady(tabId, frames) {
const checkedFrames = [];
for (const frame of frames) {
const ready = await ensurePlusCheckoutFrameReady(tabId, frame.frameId);
checkedFrames.push({ ...frame, ready });
}
return checkedFrames;
}
async function sendFrameMessage(tabId, frameId, message) {
return chrome.tabs.sendMessage(tabId, message, {
frameId: Number.isInteger(frameId) ? frameId : 0,
});
}
async function inspectCheckoutFrame(tabId, frame) {
if (frame.ready === false) {
return { frame, error: 'content-script-not-ready' };
}
try {
const result = await sendFrameMessage(tabId, frame.frameId, {
type: 'PLUS_CHECKOUT_GET_STATE',
source: 'background',
payload: {},
});
if (result?.error) {
return { frame, error: result.error };
}
return { frame, result: result || {} };
} catch (error) {
return { frame, error: error?.message || String(error || '') };
}
}
function isPaymentFrameUrl(url = '') {
return /elements-inner-payment|componentName=payment/i.test(String(url || ''));
}
function isAddressFrameUrl(url = '') {
return /elements-inner-address|componentName=address/i.test(String(url || ''));
}
function isAutocompleteFrameUrl(url = '') {
return /elements-inner-autocompl/i.test(String(url || ''));
}
function buildFrameSummary(inspections) {
return inspections
.map((item) => {
const flags = [];
if (item.result?.hasPayPal) flags.push('paypal');
if (item.result?.billingFieldsVisible) flags.push('billing');
if (item.result?.hasSubscribeButton) flags.push('subscribe');
if (!flags.length && item.error) flags.push(item.error);
if (!flags.length) flags.push('no-match');
return `${item.frame.frameId}:${item.frame.url || 'about:blank'}:${flags.join(',')}`;
})
.slice(0, 8)
.join(' | ');
}
async function inspectCheckoutFrames(tabId, frames) {
const inspections = [];
for (const frame of frames) {
const inspection = await inspectCheckoutFrame(tabId, frame);
inspections.push(inspection);
}
return inspections;
}
function pickPaymentFrame(inspections) {
return inspections.find((item) => item.result?.hasPayPal || item.result?.paypalCandidates?.length)
|| inspections.find((item) => isPaymentFrameUrl(item.frame.url))
|| null;
}
function pickBillingFrame(inspections) {
return inspections.find((item) => item.result?.billingFieldsVisible)
|| inspections.find((item) => isAddressFrameUrl(item.frame.url))
|| null;
}
function pickSubscribeFrame(inspections) {
return inspections.find((item) => item.result?.hasSubscribeButton)
|| inspections.find((item) => item.frame.frameId === 0)
|| null;
}
async function getReadyCheckoutFrames(tabId) {
return ensurePlusCheckoutFramesReady(tabId, await getCheckoutFrames(tabId));
}
async function resolveOptionalFrameByUrl(tabId, predicate) {
const frames = await getCheckoutFrames(tabId);
const frame = frames.find((item) => predicate(item.url));
if (!frame) {
return null;
}
const ready = await ensurePlusCheckoutFrameReady(tabId, frame.frameId);
return {
frame,
ready,
};
}
async function resolvePaymentFrame(tabId, frames) {
const inspections = await inspectCheckoutFrames(tabId, frames);
const picked = pickPaymentFrame(inspections);
if (picked) {
return {
frameId: picked.frame.frameId,
frameUrl: picked.frame.url || '',
ready: picked.frame.ready !== false,
inspections,
};
}
return {
frameId: null,
frameUrl: '',
inspections,
};
}
async function waitForBillingFrame(tabId) {
while (true) {
const frames = await getReadyCheckoutFrames(tabId);
const inspections = await inspectCheckoutFrames(tabId, frames);
const picked = pickBillingFrame(inspections);
if (picked) {
return {
frameId: picked.frame.frameId,
frameUrl: picked.frame.url || '',
ready: picked.frame.ready !== false,
inspections,
};
}
await sleepWithStop(250);
}
}
async function waitForSubscribeFrame(tabId, candidateFrames) {
const frames = candidateFrames.length ? candidateFrames : [{ frameId: 0, url: '' }];
while (true) {
const readyFrames = await ensurePlusCheckoutFramesReady(tabId, frames);
const inspections = await inspectCheckoutFrames(tabId, readyFrames);
const picked = pickSubscribeFrame(inspections);
if (picked) {
return picked.frame;
}
await sleepWithStop(250);
}
}
async function getCheckoutTabId(state = {}) {
const registeredTabId = await getTabId(PLUS_CHECKOUT_SOURCE);
if (registeredTabId && await isTabAlive(PLUS_CHECKOUT_SOURCE)) {
const aliveRegisteredTabId = await getAlivePlusCheckoutTabId(registeredTabId);
if (aliveRegisteredTabId) {
return aliveRegisteredTabId;
}
}
const storedTabId = Number(state.plusCheckoutTabId) || 0;
if (storedTabId) {
const aliveStoredTabId = await getAlivePlusCheckoutTabId(storedTabId);
if (aliveStoredTabId) {
return aliveStoredTabId;
}
}
const currentCheckoutTabId = await getCurrentPlusCheckoutTabId();
if (currentCheckoutTabId) {
await addLog('步骤 7:检测到当前已在 Plus Checkout 页面,直接接管当前标签页。', 'info');
return currentCheckoutTabId;
}
throw new Error('步骤 7:未找到 Plus Checkout 标签页。请先打开 Plus Checkout 页面,或完成步骤 6。');
}
async function executePlusCheckoutBilling(state = {}) {
const tabId = await getCheckoutTabId(state);
await addLog('步骤 7:正在等待 Plus Checkout 页面加载完成...', 'info');
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PLUS_CHECKOUT_SOURCE, tabId, {
inject: PLUS_CHECKOUT_INJECT_FILES,
injectSource: PLUS_CHECKOUT_SOURCE,
logMessage: '步骤 7Checkout 页面仍在加载,等待账单填写脚本就绪...',
});
const readyFrames = await getReadyCheckoutFrames(tabId);
const paymentFrame = await resolvePaymentFrame(tabId, readyFrames);
if (paymentFrame.frameId === null) {
const frameSummary = buildFrameSummary(paymentFrame.inspections);
throw new Error(`步骤 7:未在主页面或 iframe 中发现 PayPal DOM,无法自动切换付款方式。frame 摘要:${frameSummary}`);
}
if (!paymentFrame.ready) {
throw new Error(`步骤 7:已定位到 PayPal 所在 iframeframeId=${paymentFrame.frameId}),但账单脚本无法注入该 iframe。请提供该 iframe 的控制台结构或截图。`);
}
if (paymentFrame.frameId !== 0) {
await addLog(`步骤 7PayPal 位于 checkout iframeframeId=${paymentFrame.frameId}),将改为在该 frame 内操作。`, 'info');
}
const randomName = generateRandomName();
const fullName = [randomName.firstName, randomName.lastName].filter(Boolean).join(' ');
const addressSeed = getAddressSeedForCountry(state.plusCheckoutCountry || 'DE', {
fallbackCountry: 'DE',
});
if (!addressSeed) {
throw new Error('步骤 7:未找到可用的本地账单地址种子。');
}
await addLog('步骤 7:正在切换 PayPal 付款方式...', 'info');
const paymentResult = await sendFrameMessage(tabId, paymentFrame.frameId, {
type: 'PLUS_CHECKOUT_SELECT_PAYPAL',
source: 'background',
payload: {},
});
if (paymentResult?.error) {
throw new Error(paymentResult.error);
}
const billingFrame = await waitForBillingFrame(tabId);
if (!billingFrame.ready) {
throw new Error(`步骤 7:已定位到账单地址 iframeframeId=${billingFrame.frameId}),但账单脚本无法注入该 iframe。请提供该 iframe 的控制台结构或截图。`);
}
if (billingFrame.frameId !== paymentFrame.frameId) {
await addLog(`步骤 7:账单地址位于 checkout iframeframeId=${billingFrame.frameId}),将改为在该 frame 内填写。`, 'info');
}
await addLog(`步骤 7:正在填写账单地址(${addressSeed.countryCode} / ${addressSeed.query}...`, 'info');
const autocompleteFrame = await resolveOptionalFrameByUrl(tabId, isAutocompleteFrameUrl);
let result = null;
if (autocompleteFrame?.frame && autocompleteFrame.frame.frameId !== billingFrame.frameId) {
if (!autocompleteFrame.ready) {
throw new Error('步骤 7:发现 Google 地址推荐 iframe,但无法注入账单脚本。请提供该 iframe 的控制台结构。');
}
await addLog(`步骤 7:Google 地址推荐位于独立 iframeframeId=${autocompleteFrame.frame.frameId}),将拆分输入与选择动作。`, 'info');
const queryResult = await sendFrameMessage(tabId, billingFrame.frameId, {
type: 'PLUS_CHECKOUT_FILL_ADDRESS_QUERY',
source: 'background',
payload: {
fullName,
addressSeed,
},
});
if (queryResult?.error) {
throw new Error(queryResult.error);
}
const suggestionResult = await sendFrameMessage(tabId, autocompleteFrame.frame.frameId, {
type: 'PLUS_CHECKOUT_SELECT_ADDRESS_SUGGESTION',
source: 'background',
payload: {
addressSeed,
},
});
if (suggestionResult?.error) {
throw new Error(suggestionResult.error);
}
const structuredResult = await sendFrameMessage(tabId, billingFrame.frameId, {
type: 'PLUS_CHECKOUT_ENSURE_BILLING_ADDRESS',
source: 'background',
payload: {
addressSeed,
},
});
if (structuredResult?.error) {
throw new Error(structuredResult.error);
}
result = {
...structuredResult,
selectedAddressText: suggestionResult?.selectedAddressText || '',
};
} else {
result = await sendFrameMessage(tabId, billingFrame.frameId, {
type: 'PLUS_CHECKOUT_FILL_BILLING_ADDRESS',
source: 'background',
payload: {
fullName,
addressSeed,
},
});
if (result?.error) {
throw new Error(result.error);
}
}
await addLog('步骤 7:账单地址已填写完成,正在定位订阅按钮...', 'info');
const subscribeFrame = await waitForSubscribeFrame(tabId, [
{ frameId: 0, url: '' },
{ frameId: paymentFrame.frameId, url: paymentFrame.frameUrl || '' },
{ frameId: billingFrame.frameId, url: billingFrame.frameUrl || '' },
]);
const subscribeResult = await sendFrameMessage(tabId, subscribeFrame.frameId, {
type: 'PLUS_CHECKOUT_CLICK_SUBSCRIBE',
source: 'background',
payload: {},
});
if (subscribeResult?.error) {
throw new Error(subscribeResult.error);
}
await setState({
plusCheckoutTabId: tabId,
plusBillingCountryText: result?.countryText || '',
plusBillingAddress: result?.structuredAddress || null,
});
await addLog('步骤 7:账单地址已提交,正在等待跳转到 PayPal...', 'info');
await waitForTabUrlMatchUntilStopped(tabId, (url) => /paypal\./i.test(url));
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await completeStepFromBackground(7, {
plusBillingCountryText: result?.countryText || '',
});
}
return {
executePlusCheckoutBilling,
};
}
return {
createPlusCheckoutBillingExecutor,
};
});
+16 -9
View File
@@ -40,7 +40,13 @@
return /缺少|未配置|请输入|无效|错误|失败|401|认证失败|未授权|unauthorized|invalid/i.test(message);
}
function getVisibleStep(state, fallback = 7) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep > 0 ? visibleStep : fallback;
}
async function executeStep7(state) {
const visibleStep = getVisibleStep(state, 7);
if (!state.email) {
throw new Error('缺少邮箱地址,请先完成步骤 3。');
}
@@ -56,20 +62,20 @@
const password = currentState.password || currentState.customPassword || '';
const oauthUrl = await refreshOAuthUrlBeforeStep6(currentState);
if (typeof startOAuthFlowTimeoutWindow === 'function') {
await startOAuthFlowTimeoutWindow({ step: 7, oauthUrl });
await startOAuthFlowTimeoutWindow({ step: visibleStep, oauthUrl });
}
const loginTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
? await getOAuthFlowStepTimeoutMs(180000, {
step: 7,
step: visibleStep,
actionLabel: 'OAuth 登录并进入验证码页',
oauthUrl,
})
: 180000;
if (attempt === 1) {
await addLog('步骤 7:正在打开最新 OAuth 链接并登录...');
await addLog(`步骤 ${visibleStep}:正在打开最新 OAuth 链接并登录...`);
} else {
await addLog(`步骤 7:上一轮失败后,正在进行第 ${attempt} 次尝试(最多 ${STEP6_MAX_ATTEMPTS} 次)...`, 'warn');
await addLog(`步骤 ${visibleStep}:上一轮失败后,正在进行第 ${attempt} 次尝试(最多 ${STEP6_MAX_ATTEMPTS} 次)...`, 'warn');
}
await reuseOrCreateTab('signup-page', oauthUrl);
@@ -83,13 +89,14 @@
payload: {
email: currentState.email,
password,
visibleStep,
},
},
{
timeoutMs: loginTimeoutMs,
responseTimeoutMs: loginTimeoutMs,
retryDelayMs: 700,
logMessage: '步骤 7:认证页正在切换,等待页面重新就绪后继续登录...',
logMessage: `步骤 ${visibleStep}:认证页正在切换,等待页面重新就绪后继续登录...`,
}
);
@@ -98,7 +105,7 @@
}
if (isStep6SuccessResult(result)) {
await completeStepFromBackground(7, {
await completeStepFromBackground(visibleStep, {
loginVerificationRequestedAt: result.loginVerificationRequestedAt || null,
});
return;
@@ -118,7 +125,7 @@
}
if (isManagementSecretConfigError(err)) {
await addLog(
`步骤 7:检测到来源后台管理密钥缺失或错误,不再重试,当前流程停止。原因:${getErrorMessage(err)}`,
`步骤 ${visibleStep}:检测到来源后台管理密钥缺失或错误,不再重试,当前流程停止。原因:${getErrorMessage(err)}`,
'error'
);
throw err;
@@ -128,11 +135,11 @@
break;
}
await addLog(`步骤 7:第 ${attempt} 次尝试失败,原因:${getErrorMessage(err)};准备重试...`, 'warn');
await addLog(`步骤 ${visibleStep}:第 ${attempt} 次尝试失败,原因:${getErrorMessage(err)};准备重试...`, 'warn');
}
}
throw new Error(`步骤 7:判断失败后已重试 ${STEP6_MAX_ATTEMPTS - 1} 次,仍未成功。最后原因:${getErrorMessage(lastError)}`);
throw new Error(`步骤 ${visibleStep}:判断失败后已重试 ${STEP6_MAX_ATTEMPTS - 1} 次,仍未成功。最后原因:${getErrorMessage(lastError)}`);
}
return { executeStep7 };
+169
View File
@@ -0,0 +1,169 @@
(function attachBackgroundPayPalApprove(root, factory) {
root.MultiPageBackgroundPayPalApprove = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPayPalApproveModule() {
const PAYPAL_SOURCE = 'paypal-flow';
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
const PAYPAL_INJECT_FILES = ['content/utils.js', 'content/paypal-flow.js'];
function createPayPalApproveExecutor(deps = {}) {
const {
addLog,
chrome,
completeStepFromBackground,
ensureContentScriptReadyOnTabUntilStopped,
getTabId,
isTabAlive,
sendTabMessageUntilStopped,
setState,
sleepWithStop,
waitForTabCompleteUntilStopped,
waitForTabUrlMatchUntilStopped,
} = deps;
async function resolvePayPalTabId(state = {}) {
const paypalTabId = await getTabId(PAYPAL_SOURCE);
if (paypalTabId && await isTabAlive(PAYPAL_SOURCE)) {
return paypalTabId;
}
const checkoutTabId = await getTabId(PLUS_CHECKOUT_SOURCE);
if (checkoutTabId) {
return checkoutTabId;
}
const storedTabId = Number(state.plusCheckoutTabId) || 0;
if (storedTabId) {
return storedTabId;
}
throw new Error('步骤 8:未找到 PayPal 标签页,请先完成步骤 7。');
}
async function ensurePayPalReady(tabId, logMessage = '') {
await waitForTabUrlMatchUntilStopped(tabId, (url) => /paypal\./i.test(url));
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await ensureContentScriptReadyOnTabUntilStopped(PAYPAL_SOURCE, tabId, {
inject: PAYPAL_INJECT_FILES,
injectSource: PAYPAL_SOURCE,
logMessage: logMessage || '步骤 8:PayPal 页面仍在加载,等待脚本就绪...',
});
}
async function getPayPalState(tabId) {
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_GET_STATE',
source: 'background',
payload: {},
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
async function dismissPrompts(tabId) {
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_DISMISS_PROMPTS',
source: 'background',
payload: {},
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
async function submitLogin(tabId, state = {}) {
if (!state.paypalPassword) {
throw new Error('步骤 8:未配置 PayPal 密码,请先在侧边栏填写。');
}
await addLog('步骤 8:正在填写 PayPal 登录信息并提交...', 'info');
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_SUBMIT_LOGIN',
source: 'background',
payload: {
email: state.paypalEmail || '',
password: state.paypalPassword || '',
},
});
if (result?.error) {
throw new Error(result.error);
}
}
async function clickApprove(tabId) {
const result = await sendTabMessageUntilStopped(tabId, PAYPAL_SOURCE, {
type: 'PAYPAL_CLICK_APPROVE',
source: 'background',
payload: {},
});
if (result?.error) {
throw new Error(result.error);
}
return Boolean(result?.clicked);
}
async function executePayPalApprove(state = {}) {
const tabId = await resolvePayPalTabId(state);
await ensurePayPalReady(tabId);
await setState({ plusCheckoutTabId: tabId });
let loggedWaiting = false;
while (true) {
const currentUrl = (await chrome.tabs.get(tabId).catch(() => null))?.url || '';
if (currentUrl && !/paypal\./i.test(currentUrl)) {
await addLog('步骤 8:PayPal 已跳转离开授权页,准备进入回跳确认。', 'ok');
break;
}
await ensurePayPalReady(tabId, '步骤 8:PayPal 页面正在切换,等待脚本重新就绪...');
const pageState = await getPayPalState(tabId);
if (pageState.needsLogin) {
await submitLogin(tabId, state);
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
continue;
}
if (pageState.hasPasskeyPrompt) {
await addLog('步骤 8:检测到 PayPal 通行密钥提示,正在关闭...', 'info');
await dismissPrompts(tabId);
await sleepWithStop(1000);
continue;
}
const dismissed = await dismissPrompts(tabId).catch(() => ({ clicked: 0 }));
if (dismissed.clicked) {
await sleepWithStop(1000);
continue;
}
if (pageState.approveReady) {
await addLog('步骤 8:正在点击 PayPal“同意并继续”...', 'info');
const clicked = await clickApprove(tabId);
if (clicked) {
await setState({ plusPaypalApprovedAt: Date.now() });
break;
}
}
if (!loggedWaiting) {
loggedWaiting = true;
await addLog('步骤 8:等待 PayPal 授权按钮或下一步页面出现...', 'info');
}
await sleepWithStop(500);
}
await completeStepFromBackground(8, {
plusPaypalApprovedAt: Date.now(),
});
}
return {
executePayPalApprove,
};
}
return {
createPayPalApproveExecutor,
};
});
+37 -21
View File
@@ -26,6 +26,15 @@
return String(value || '').trim();
}
function getVisibleStep(state, fallback = 10) {
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
return visibleStep > 0 ? visibleStep : fallback;
}
function getConfirmStepForVisibleStep(visibleStep) {
return visibleStep === 12 ? 11 : 9;
}
function parseLocalhostCallback(rawUrl) {
let parsed;
try {
@@ -109,26 +118,28 @@
}
async function executeCpaStep10(state) {
const visibleStep = getVisibleStep(state, 10);
const confirmStep = getConfirmStepForVisibleStep(visibleStep);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
throw new Error(`步骤 ${confirmStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmStep}`);
}
if (!state.localhostUrl) {
throw new Error('缺少 localhost 回调地址,请先完成步骤 9。');
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmStep}`);
}
if (!state.vpsUrl) {
throw new Error('尚未填写 CPA 地址,请先在侧边栏输入。');
}
if (shouldBypassStep9ForLocalCpa(state)) {
await addLog('步骤 10:检测到本地 CPA,且当前策略为“跳过第10步”,本轮不再重复提交回调地址。', 'info');
await completeStepFromBackground(10, {
await addLog(`步骤 ${visibleStep}:检测到本地 CPA,且当前策略为“跳过第10步”,本轮不再重复提交回调地址。`, 'info');
await completeStepFromBackground(visibleStep, {
localhostUrl: state.localhostUrl,
verifiedStatus: 'local-auto',
});
return;
}
await addLog('步骤 10:正在打开 CPA 面板...');
await addLog(`步骤 ${visibleStep}:正在打开 CPA 面板...`);
const injectFiles = ['content/activation-utils.js', 'content/utils.js', 'content/vps-panel.js'];
let tabId = await getTabId('vps-panel');
@@ -149,20 +160,20 @@
inject: injectFiles,
timeoutMs: 45000,
retryDelayMs: 900,
logMessage: '步骤 10:CPA 面板仍在加载,正在重试连接...',
logMessage: `步骤 ${visibleStep}:CPA 面板仍在加载,正在重试连接...`,
});
await addLog('步骤 10:正在填写回调地址...');
await addLog(`步骤 ${visibleStep}:正在填写回调地址...`);
const result = await sendToContentScriptResilient('vps-panel', {
type: 'EXECUTE_STEP',
step: 10,
step: visibleStep,
source: 'background',
payload: { localhostUrl: state.localhostUrl, vpsPassword: state.vpsPassword },
payload: { localhostUrl: state.localhostUrl, vpsPassword: state.vpsPassword, visibleStep },
}, {
timeoutMs: 125000,
responseTimeoutMs: 125000,
retryDelayMs: 700,
logMessage: '步骤 10:CPA 面板通信未就绪,正在等待页面恢复...',
logMessage: `步骤 ${visibleStep}:CPA 面板通信未就绪,正在等待页面恢复...`,
});
if (result?.error) {
@@ -171,14 +182,16 @@
}
async function executeCodex2ApiStep10(state) {
const visibleStep = getVisibleStep(state, 10);
const confirmStep = getConfirmStepForVisibleStep(visibleStep);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
throw new Error(`步骤 ${confirmStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmStep}`);
}
if (!state.localhostUrl) {
throw new Error('缺少 localhost 回调地址,请先完成步骤 9。');
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmStep}`);
}
if (!state.codex2apiSessionId) {
throw new Error('缺少 Codex2API 会话信息,请重新执行步骤 7。');
throw new Error(`缺少 Codex2API 会话信息,请重新执行步骤 ${visibleStep === 12 ? 10 : 7}`);
}
if (!normalizeString(state.codex2apiAdminKey)) {
throw new Error('尚未配置 Codex2API 管理密钥,请先在侧边栏填写。');
@@ -193,7 +206,7 @@
const codex2apiUrl = normalizeCodex2ApiUrl(state.codex2apiUrl);
const origin = new URL(codex2apiUrl).origin;
await addLog('步骤 10:正在向 Codex2API 提交回调并创建账号...');
await addLog(`步骤 ${visibleStep}:正在向 Codex2API 提交回调并创建账号...`);
const result = await fetchCodex2ApiJson(origin, '/api/admin/oauth/exchange-code', {
adminKey: state.codex2apiAdminKey,
method: 'POST',
@@ -205,19 +218,21 @@
});
const verifiedStatus = normalizeString(result?.message) || 'Codex2API OAuth 账号添加成功';
await addLog(`步骤 10${verifiedStatus}`, 'ok');
await completeStepFromBackground(10, {
await addLog(`步骤 ${visibleStep}${verifiedStatus}`, 'ok');
await completeStepFromBackground(visibleStep, {
localhostUrl: callback.url,
verifiedStatus,
});
}
async function executeSub2ApiStep10(state) {
const visibleStep = getVisibleStep(state, 10);
const confirmStep = getConfirmStepForVisibleStep(visibleStep);
if (state.localhostUrl && !isLocalhostOAuthCallbackUrl(state.localhostUrl)) {
throw new Error('步骤 9 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 9。');
throw new Error(`步骤 ${confirmStep} 捕获到的 localhost OAuth 回调地址无效,请重新执行步骤 ${confirmStep}`);
}
if (!state.localhostUrl) {
throw new Error('缺少 localhost 回调地址,请先完成步骤 9。');
throw new Error(`缺少 localhost 回调地址,请先完成步骤 ${confirmStep}`);
}
if (!state.sub2apiSessionId) {
throw new Error('缺少 SUB2API 会话信息,请重新执行步骤 1。');
@@ -232,7 +247,7 @@
const sub2apiUrl = normalizeSub2ApiUrl(state.sub2apiUrl);
const injectFiles = ['content/utils.js', 'content/sub2api-panel.js'];
await addLog('步骤 10:正在打开 SUB2API 后台...');
await addLog(`步骤 ${visibleStep}:正在打开 SUB2API 后台...`);
let tabId = await getTabId('sub2api-panel');
const alive = tabId && await isTabAlive('sub2api-panel');
@@ -254,12 +269,13 @@
injectSource: 'sub2api-panel',
});
await addLog('步骤 10:正在向 SUB2API 提交回调并创建账号...');
await addLog(`步骤 ${visibleStep}:正在向 SUB2API 提交回调并创建账号...`);
const result = await sendToContentScript('sub2api-panel', {
type: 'EXECUTE_STEP',
step: 10,
step: visibleStep,
source: 'background',
payload: {
visibleStep,
localhostUrl: state.localhostUrl,
sub2apiUrl,
sub2apiEmail: state.sub2apiEmail,
+64
View File
@@ -0,0 +1,64 @@
(function attachBackgroundPlusReturnConfirm(root, factory) {
root.MultiPageBackgroundPlusReturnConfirm = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPlusReturnConfirmModule() {
const PAYPAL_SOURCE = 'paypal-flow';
const PLUS_CHECKOUT_SOURCE = 'plus-checkout';
function createPlusReturnConfirmExecutor(deps = {}) {
const {
addLog,
completeStepFromBackground,
getTabId,
isTabAlive,
setState,
sleepWithStop,
waitForTabCompleteUntilStopped,
waitForTabUrlMatchUntilStopped,
} = deps;
async function resolveReturnTabId(state = {}) {
const paypalTabId = await getTabId(PAYPAL_SOURCE);
if (paypalTabId && await isTabAlive(PAYPAL_SOURCE)) {
return paypalTabId;
}
const checkoutTabId = await getTabId(PLUS_CHECKOUT_SOURCE);
if (checkoutTabId) {
return checkoutTabId;
}
const storedTabId = Number(state.plusCheckoutTabId) || 0;
if (storedTabId) {
return storedTabId;
}
throw new Error('步骤 9:未找到 Plus / PayPal 标签页,无法确认订阅回跳。');
}
function isReturnUrl(url = '') {
return /https:\/\/(?:chatgpt\.com|chat\.openai\.com|openai\.com)\//i.test(String(url || ''))
&& !/paypal\./i.test(String(url || ''));
}
async function executePlusReturnConfirm(state = {}) {
const tabId = await resolveReturnTabId(state);
await addLog('步骤 9:正在等待 PayPal 授权后回跳到 ChatGPT / OpenAI 页面...', 'info');
const tab = await waitForTabUrlMatchUntilStopped(tabId, isReturnUrl);
await waitForTabCompleteUntilStopped(tabId);
await sleepWithStop(1000);
await setState({
plusCheckoutTabId: tabId,
plusReturnUrl: tab?.url || '',
});
await completeStepFromBackground(9, {
plusReturnUrl: tab?.url || '',
});
}
return {
executePlusReturnConfirm,
};
}
return {
createPlusReturnConfirmExecutor,
};
});