feat: implement multi-step background process for user registration

- Add step 1: Open ChatGPT homepage and prepare for signup.
- Add step 2: Handle email submission and ensure the signup entry page is ready.
- Add step 3: Fill in the password using either a custom or generated password.
- Add step 4: Confirm the registration verification page and handle email verification.
- Add step 5: Generate random user details and handle onboarding redirection.
- Add step 6: Manage OAuth login process and handle login verification.
- Add step 7: Retrieve verification code from email and manage retries.
- Add step 8: Handle consent page interactions and manage callback URLs.
- Add step 9: Submit callback information to CPA or SUB2API based on user settings.
- Implement tests to ensure all steps are correctly imported and functional.
This commit is contained in:
QLHazyCoder
2026-04-16 23:38:43 +08:00
parent f0c3dcda62
commit 4f12d67d9f
12 changed files with 1212 additions and 659 deletions
+174
View File
@@ -0,0 +1,174 @@
(function attachBackgroundStep8(root, factory) {
root.MultiPageBackgroundStep8 = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() {
function createStep8Executor(deps = {}) {
const {
addLog,
chrome,
cleanupStep8NavigationListeners,
clickWithDebugger,
completeStepFromBackground,
ensureStep8SignupPageReady,
getStep8CallbackUrlFromNavigation,
getStep8CallbackUrlFromTabUpdate,
getStep8EffectLabel,
getTabId,
isTabAlive,
prepareStep8DebuggerClick,
reloadStep8ConsentPage,
reuseOrCreateTab,
sleepWithStop,
STEP8_CLICK_RETRY_DELAY_MS,
STEP8_MAX_ROUNDS,
STEP8_READY_WAIT_TIMEOUT_MS,
STEP8_STRATEGIES,
throwIfStep8SettledOrStopped,
triggerStep8ContentStrategy,
waitForStep8ClickEffect,
waitForStep8Ready,
setWebNavListener,
setWebNavCommittedListener,
setStep8PendingReject,
setStep8TabUpdatedListener,
} = deps;
async function executeStep8(state) {
if (!state.oauthUrl) {
throw new Error('缺少登录用 OAuth 链接,请先完成步骤 6。');
}
await addLog('步骤 8:正在监听 localhost 回调地址...');
return new Promise((resolve, reject) => {
let resolved = false;
let signupTabId = null;
const cleanupListener = () => {
cleanupStep8NavigationListeners();
setStep8PendingReject(null);
};
const rejectStep8 = (error) => {
if (resolved) return;
resolved = true;
clearTimeout(timeout);
cleanupListener();
reject(error);
};
const finalizeStep8Callback = (callbackUrl) => {
if (resolved || !callbackUrl) return;
resolved = true;
cleanupListener();
clearTimeout(timeout);
addLog(`步骤 8:已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
return completeStepFromBackground(8, { localhostUrl: callbackUrl });
}).then(() => {
resolve();
}).catch((err) => {
reject(err);
});
};
const timeout = setTimeout(() => {
rejectStep8(new Error('120 秒内未捕获到 localhost 回调跳转,步骤 8 的点击可能被拦截了。'));
}, 120000);
setStep8PendingReject((error) => {
rejectStep8(error);
});
setWebNavListener((details) => {
const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId);
finalizeStep8Callback(callbackUrl);
});
setWebNavCommittedListener((details) => {
const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId);
finalizeStep8Callback(callbackUrl);
});
setStep8TabUpdatedListener((tabId, changeInfo, tab) => {
const callbackUrl = getStep8CallbackUrlFromTabUpdate(tabId, changeInfo, tab, signupTabId);
finalizeStep8Callback(callbackUrl);
});
(async () => {
try {
throwIfStep8SettledOrStopped(resolved);
signupTabId = await getTabId('signup-page');
throwIfStep8SettledOrStopped(resolved);
if (signupTabId && await isTabAlive('signup-page')) {
await chrome.tabs.update(signupTabId, { active: true });
await addLog('步骤 8:已切回认证页,正在准备调试器点击...');
} else {
signupTabId = await reuseOrCreateTab('signup-page', state.oauthUrl);
await addLog('步骤 8:已重新打开认证页,正在准备调试器点击...');
}
throwIfStep8SettledOrStopped(resolved);
chrome.webNavigation.onBeforeNavigate.addListener(deps.getWebNavListener());
chrome.webNavigation.onCommitted.addListener(deps.getWebNavCommittedListener());
chrome.tabs.onUpdated.addListener(deps.getStep8TabUpdatedListener());
await ensureStep8SignupPageReady(signupTabId, {
timeoutMs: 15000,
logMessage: '步骤 8:认证页内容脚本尚未就绪,正在等待页面恢复...',
});
for (let round = 1; round <= STEP8_MAX_ROUNDS && !resolved; round++) {
throwIfStep8SettledOrStopped(resolved);
const pageState = await waitForStep8Ready(signupTabId, STEP8_READY_WAIT_TIMEOUT_MS);
if (!pageState?.consentReady) {
await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
continue;
}
const strategy = STEP8_STRATEGIES[Math.min(round - 1, STEP8_STRATEGIES.length - 1)];
await addLog(`步骤 8:第 ${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label}...`);
if (strategy.mode === 'debugger') {
const clickTarget = await prepareStep8DebuggerClick(signupTabId);
throwIfStep8SettledOrStopped(resolved);
await clickWithDebugger(signupTabId, clickTarget?.rect);
} else {
await triggerStep8ContentStrategy(signupTabId, strategy.strategy);
}
if (resolved) {
return;
}
const effect = await waitForStep8ClickEffect(signupTabId, pageState.url);
if (resolved) {
return;
}
if (effect.progressed) {
await addLog(`步骤 8:检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info');
break;
}
if (round >= STEP8_MAX_ROUNDS) {
throw new Error(`步骤 8:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`);
}
await addLog(`步骤 8${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS}...`, 'warn');
await reloadStep8ConsentPage(signupTabId);
await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
}
} catch (err) {
rejectStep8(err);
}
})();
});
}
return { executeStep8 };
}
return { createStep8Executor };
});