Fix Cloudflare signup verification recovery

This commit is contained in:
dyhhhhhqwer
2026-04-21 19:46:28 +08:00
parent 5c75d2e3aa
commit da0ca04153
5 changed files with 419 additions and 11 deletions
+119 -10
View File
@@ -1622,15 +1622,27 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
async function waitForVerificationSubmitOutcome(step, timeout) {
const resolvedTimeout = timeout ?? (step === 8 ? 30000 : 12000);
const start = Date.now();
let recoveryCount = 0;
const maxRecoveryCount = 2;
while (Date.now() - start < resolvedTimeout) {
throwIfStopped();
if (step === 4) {
const signupRetryState = getCurrentAuthRetryPageState('signup');
if (signupRetryState?.userAlreadyExistsBlocked) {
throw createSignupUserAlreadyExistsError();
}
const retryFlow = step === 4 ? 'signup' : 'login';
const retryState = getCurrentAuthRetryPageState(retryFlow);
if (retryState?.userAlreadyExistsBlocked) {
throw createSignupUserAlreadyExistsError();
}
if (retryState && recoveryCount < maxRecoveryCount) {
recoveryCount += 1;
log(`步骤 ${step}:验证码提交后进入认证重试页,正在自动恢复(${recoveryCount}/${maxRecoveryCount}...`, 'warn');
await recoverCurrentAuthRetryPage({
flow: retryFlow,
logLabel: `步骤 ${step}:验证码提交后检测到认证重试页,正在点击“重试”恢复`,
step,
timeoutMs: 12000,
});
continue;
}
const errorText = getVerificationErrorText();
@@ -1670,6 +1682,76 @@ async function waitForVerificationSubmitOutcome(step, timeout) {
return { success: true, assumed: true };
}
function getVerificationSubmitButtonForTarget(codeInput, options = {}) {
const { allowDisabled = false } = options;
const form = codeInput?.form || codeInput?.closest?.('form') || null;
const isUsableAction = (element) => {
if (!element || !isVisibleElement(element)) return false;
return allowDisabled || isActionEnabled(element);
};
const findSubmitInRoot = (root) => {
if (!root?.querySelectorAll) return null;
const directCandidates = root.querySelectorAll('button[type="submit"], input[type="submit"]');
for (const element of directCandidates) {
if (isUsableAction(element)) {
return element;
}
}
const textCandidates = root.querySelectorAll('button, [role="button"], input[type="button"], input[type="submit"]');
return Array.from(textCandidates).find((element) => {
if (!isUsableAction(element)) return false;
const text = getActionText(element);
return /verify|confirm|submit|continue|确认|验证|继续/i.test(text);
}) || null;
};
return findSubmitInRoot(form) || findSubmitInRoot(document);
}
async function waitForVerificationSubmitButton(codeInput, timeout = 5000) {
const start = Date.now();
while (Date.now() - start < timeout) {
throwIfStopped();
if (is405MethodNotAllowedPage()) {
throw new Error('当前页面处于 405 错误恢复流程中,暂时无法定位验证码提交按钮。');
}
const button = getVerificationSubmitButtonForTarget(codeInput, { allowDisabled: false });
if (button) {
return button;
}
await sleep(150);
}
return null;
}
async function waitForSplitVerificationInputsFilled(inputs, code, timeout = 2500) {
const expected = String(code || '').slice(0, 6);
const start = Date.now();
while (Date.now() - start < timeout) {
throwIfStopped();
const current = Array.from(inputs || [])
.slice(0, expected.length)
.map((input) => String(input?.value || '').trim())
.join('');
if (current === expected) {
return true;
}
await sleep(100);
}
return false;
}
async function fillVerificationCode(step, payload) {
const { code } = payload;
if (!code) throw new Error('未提供验证码。');
@@ -1704,9 +1786,37 @@ async function fillVerificationCode(step, payload) {
if (singleInputs.length >= 6) {
log(`步骤 ${step}:发现分开的单字符验证码输入框,正在逐个填写...`);
for (let i = 0; i < 6 && i < singleInputs.length; i++) {
const targetInput = singleInputs[i];
try {
targetInput.focus?.();
} catch {}
fillInput(singleInputs[i], code[i]);
try {
targetInput.dispatchEvent(new KeyboardEvent('keyup', { key: code[i], bubbles: true }));
} catch {}
await sleep(100);
}
const filled = await waitForSplitVerificationInputsFilled(singleInputs, code, 2500);
if (!filled) {
const current = Array.from(singleInputs)
.slice(0, 6)
.map((input) => String(input?.value || '').trim() || '_')
.join('');
log(`步骤 ${step}:分格验证码输入框未稳定呈现目标值,当前页面值为 ${current},准备继续观察提交流程。`, 'warn');
} else {
log(`步骤 ${step}:分格验证码输入框已稳定显示 ${code}`, 'info');
}
await sleep(800);
const splitSubmitBtn = await waitForVerificationSubmitButton(singleInputs[0], 2000).catch(() => null);
if (splitSubmitBtn) {
await humanPause(450, 1200);
simulateClick(splitSubmitBtn);
log(`步骤 ${step}:分格验证码已提交`);
} else {
log(`步骤 ${step}:分格验证码页面未找到可点击提交按钮,继续等待页面自动推进。`, 'info');
}
const outcome = await waitForVerificationSubmitOutcome(step);
if (outcome.invalidCode) {
log(`步骤 ${step}:验证码被拒绝:${outcome.errorText}`, 'warn');
@@ -1736,17 +1846,16 @@ async function fillVerificationCode(step, payload) {
fillInput(codeInput, code);
log(`步骤 ${step}:验证码已填写`);
// Report complete BEFORE submit (page may navigate away)
// Submit
await sleep(500);
const submitBtn = document.querySelector('button[type="submit"]')
|| await waitForElementByText('button', /verify|confirm|submit|continue|确认|验证/i, 5000).catch(() => null);
await sleep(800);
const submitBtn = await waitForVerificationSubmitButton(codeInput, 5000).catch(() => null);
if (submitBtn) {
await humanPause(450, 1200);
simulateClick(submitBtn);
log(`步骤 ${step}:验证码已提交`);
} else {
log(`步骤 ${step}:未找到可提交的验证码按钮,先等待页面自动推进或反馈结果。`, 'warn');
}
const outcome = await waitForVerificationSubmitOutcome(step);