feat: 增强注册流程,自动处理密码页超时错误并恢复到验证码页面
This commit is contained in:
@@ -197,6 +197,8 @@ Step 3 使用的注册邮箱。
|
||||
|
||||
根据 `Mail` 配置,轮询邮箱并提取 6 位验证码。
|
||||
|
||||
进入邮箱轮询前,脚本会先确认认证页是否已经进入验证码页面;如果密码页出现 `糟糕,出错了 / Operation timed out` 并带有 `重试` 按钮,会先自动点击 `重试`、回到密码页重新提交,再继续等待验证码页面。
|
||||
|
||||
支持:
|
||||
|
||||
- `content/qq-mail.js`
|
||||
|
||||
@@ -1583,6 +1583,28 @@ async function resolveVerificationStep(step, state, mail) {
|
||||
async function executeStep4(state) {
|
||||
const mail = getMailConfig(state);
|
||||
if (mail.error) throw new Error(mail.error);
|
||||
const signupTabId = await getTabId('signup-page');
|
||||
if (!signupTabId) {
|
||||
throw new Error('认证页面标签页已关闭,无法继续步骤 4。');
|
||||
}
|
||||
|
||||
await chrome.tabs.update(signupTabId, { active: true });
|
||||
await addLog('步骤 4:正在确认注册验证码页面是否就绪,必要时自动恢复密码页超时报错...');
|
||||
const prepareResult = await sendToContentScript('signup-page', {
|
||||
type: 'PREPARE_SIGNUP_VERIFICATION',
|
||||
step: 4,
|
||||
source: 'background',
|
||||
payload: { password: state.password || state.customPassword || '' },
|
||||
});
|
||||
|
||||
if (prepareResult && prepareResult.error) {
|
||||
throw new Error(prepareResult.error);
|
||||
}
|
||||
if (prepareResult?.alreadyVerified) {
|
||||
await completeStepFromBackground(4, {});
|
||||
return;
|
||||
}
|
||||
|
||||
await addLog(`步骤 4:正在打开${mail.label}...`);
|
||||
|
||||
// For mail tabs, only create if not alive — don't navigate (preserves login session)
|
||||
|
||||
@@ -10,6 +10,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
|| message.type === 'FILL_CODE'
|
||||
|| message.type === 'STEP8_FIND_AND_CLICK'
|
||||
|| message.type === 'PREPARE_LOGIN_CODE'
|
||||
|| message.type === 'PREPARE_SIGNUP_VERIFICATION'
|
||||
|| message.type === 'RESEND_VERIFICATION_CODE'
|
||||
) {
|
||||
resetStopState();
|
||||
@@ -49,6 +50,8 @@ async function handleCommand(message) {
|
||||
case 'FILL_CODE':
|
||||
// Step 4 = signup code, Step 7 = login code (same handler)
|
||||
return await fillVerificationCode(message.step, message.payload);
|
||||
case 'PREPARE_SIGNUP_VERIFICATION':
|
||||
return await prepareSignupVerificationFlow(message.payload);
|
||||
case 'PREPARE_LOGIN_CODE':
|
||||
return await prepareLoginCodeFlow();
|
||||
case 'RESEND_VERIFICATION_CODE':
|
||||
@@ -353,6 +356,8 @@ const INVALID_VERIFICATION_CODE_PATTERN = /代码不正确|验证码不正确|
|
||||
const VERIFICATION_PAGE_PATTERN = /检查您的收件箱|输入我们刚刚向|重新发送电子邮件|重新发送验证码|验证码|代码不正确|email\s+verification/i;
|
||||
const OAUTH_CONSENT_PAGE_PATTERN = /使用\s*ChatGPT\s*登录到\s*Codex|login\s+to\s+codex|log\s+in\s+to\s+codex|authorize|授权/i;
|
||||
const ADD_PHONE_PAGE_PATTERN = /add[\s-]*phone|添加手机号|手机号码|手机号|phone\s+number|telephone/i;
|
||||
const SIGNUP_PASSWORD_ERROR_TITLE_PATTERN = /糟糕,出错了|something\s+went\s+wrong|oops/i;
|
||||
const SIGNUP_PASSWORD_ERROR_DETAIL_PATTERN = /operation\s+timed\s+out|timed\s+out|请求超时|操作超时/i;
|
||||
|
||||
function getVerificationErrorText() {
|
||||
const messages = [];
|
||||
@@ -443,6 +448,119 @@ function isStep8Ready() {
|
||||
return OAUTH_CONSENT_PAGE_PATTERN.test(getPageTextSnapshot());
|
||||
}
|
||||
|
||||
function isSignupPasswordPage() {
|
||||
return /\/create-account\/password(?:[/?#]|$)/i.test(location.pathname || '');
|
||||
}
|
||||
|
||||
function getSignupPasswordInput() {
|
||||
const input = document.querySelector('input[type="password"]');
|
||||
return input && isVisibleElement(input) ? input : null;
|
||||
}
|
||||
|
||||
function getSignupPasswordSubmitButton() {
|
||||
const direct = document.querySelector('button[type="submit"]');
|
||||
if (direct && isVisibleElement(direct) && isActionEnabled(direct)) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
const candidates = document.querySelectorAll('button, [role="button"]');
|
||||
return Array.from(candidates).find((el) => {
|
||||
if (!isVisibleElement(el) || !isActionEnabled(el)) return false;
|
||||
const text = getActionText(el);
|
||||
return /继续|continue|submit|创建|create/i.test(text);
|
||||
}) || null;
|
||||
}
|
||||
|
||||
function getSignupRetryButton() {
|
||||
const direct = document.querySelector('button[data-dd-action-name="Try again"]');
|
||||
if (direct && isVisibleElement(direct) && isActionEnabled(direct)) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
const candidates = document.querySelectorAll('button, [role="button"]');
|
||||
return Array.from(candidates).find((el) => {
|
||||
if (!isVisibleElement(el) || !isActionEnabled(el)) return false;
|
||||
const text = getActionText(el);
|
||||
return /重试|try\s+again/i.test(text);
|
||||
}) || null;
|
||||
}
|
||||
|
||||
function isSignupPasswordErrorPage() {
|
||||
if (!isSignupPasswordPage()) return false;
|
||||
const text = getPageTextSnapshot();
|
||||
return Boolean(
|
||||
getSignupRetryButton()
|
||||
&& (SIGNUP_PASSWORD_ERROR_TITLE_PATTERN.test(text)
|
||||
|| SIGNUP_PASSWORD_ERROR_DETAIL_PATTERN.test(text)
|
||||
|| SIGNUP_PASSWORD_ERROR_TITLE_PATTERN.test(document.title || ''))
|
||||
);
|
||||
}
|
||||
|
||||
async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
|
||||
const { password } = payload;
|
||||
const start = Date.now();
|
||||
let retried = 0;
|
||||
let lastSubmitAt = 0;
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
throwIfStopped();
|
||||
|
||||
if (isStep5Ready()) {
|
||||
log('步骤 4:页面已进入验证码后的下一阶段,本步骤按已完成处理。', 'ok');
|
||||
return { ready: true, alreadyVerified: true, retried };
|
||||
}
|
||||
|
||||
if (isVerificationPageStillVisible()) {
|
||||
log(`步骤 4:验证码页面已就绪${retried ? `(期间自动重试 ${retried} 次)` : ''}。`, 'ok');
|
||||
return { ready: true, retried };
|
||||
}
|
||||
|
||||
if (isSignupPasswordErrorPage()) {
|
||||
const retryBtn = getSignupRetryButton();
|
||||
if (!retryBtn) {
|
||||
throw new Error('检测到密码页超时报错,但未找到可点击的“重试”按钮。URL: ' + location.href);
|
||||
}
|
||||
retried += 1;
|
||||
log(`步骤 4:检测到密码页超时报错,正在点击“重试”(第 ${retried} 次)...`, 'warn');
|
||||
await humanPause(350, 900);
|
||||
simulateClick(retryBtn);
|
||||
await sleep(1500);
|
||||
continue;
|
||||
}
|
||||
|
||||
const passwordInput = getSignupPasswordInput();
|
||||
if (passwordInput) {
|
||||
if (!password) {
|
||||
throw new Error('当前回到了密码页,但没有可用密码,无法自动重新提交。');
|
||||
}
|
||||
|
||||
if ((passwordInput.value || '') !== password) {
|
||||
log('步骤 4:已回到密码页,正在重新填写密码...', 'warn');
|
||||
await humanPause(450, 1100);
|
||||
fillInput(passwordInput, password);
|
||||
}
|
||||
|
||||
const submitBtn = getSignupPasswordSubmitButton();
|
||||
if (!submitBtn) {
|
||||
throw new Error('密码页存在,但未找到“继续”提交按钮。URL: ' + location.href);
|
||||
}
|
||||
|
||||
if (Date.now() - lastSubmitAt > 1800) {
|
||||
log('步骤 4:正在重新提交密码,等待验证码页面...', 'warn');
|
||||
lastSubmitAt = Date.now();
|
||||
await humanPause(350, 900);
|
||||
simulateClick(submitBtn);
|
||||
await sleep(1800);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
throw new Error('等待注册验证码页面就绪超时。URL: ' + location.href);
|
||||
}
|
||||
|
||||
|
||||
async function waitForVerificationSubmitOutcome(step, timeout) {
|
||||
const resolvedTimeout = timeout ?? (step === 7 ? 30000 : 12000);
|
||||
|
||||
Reference in New Issue
Block a user