From 67304bb64cf3f0c7aea58c2ecd4150e1d9b854d3 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Sun, 19 Apr 2026 03:43:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E6=9C=80=E5=A4=9A=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E7=82=B9=E5=87=BB=205=20=E6=AC=A1=E4=BB=A5=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E9=A1=B5=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- content/auth-page-recovery.js | 33 +++++++++++++++++++++++++++++--- content/signup-page.js | 29 +++++++++++++++++++++++++--- tests/auth-page-recovery.test.js | 26 +++++++++++++++++++++++++ 项目完整链路说明.md | 8 ++++---- 5 files changed, 90 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c831e89..fd1d808 100644 --- a/README.md +++ b/README.md @@ -491,7 +491,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 - 使用第 2 步已经确定好的邮箱 - 使用自定义密码或自动生成密码 - 在密码页填写密码并提交注册表单 -- 后台会在真正把 Step 3 记为完成前,再确认页面是否已经推进;如果此时出现认证页 `重试` 页面,会先自动点击 `重试` 恢复,再继续后续链路 +- 后台会在真正把 Step 3 记为完成前,再确认页面是否已经推进;如果此时出现认证页 `重试` 页面,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续后续链路 实际使用的密码会写入会话状态,并同步到侧边栏显示。 @@ -499,7 +499,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 根据 `Mail` 配置,轮询邮箱并提取 6 位验证码。 -进入邮箱轮询前,脚本会先确认认证页是否已经进入验证码页面;如果密码页出现 `糟糕,出错了 / 操作超时(Operation timed out)` 并带有 `重试` 按钮,会先通过共享恢复逻辑自动点击 `重试`、回到密码页重新提交,再继续等待验证码页面。 +进入邮箱轮询前,脚本会先确认认证页是否已经进入验证码页面;如果密码页出现 `糟糕,出错了 / 操作超时(Operation timed out)` 并带有 `重试` 按钮,会先通过共享恢复逻辑最多自动点击 5 次 `重试`,回到密码页重新提交,再继续等待验证码页面。 在 `Auto` 模式下,如果 Step 4 当前轮失败,后台不会立刻丢弃这轮邮箱;而是沿用当前邮箱回到 Step 1 重新开始当前轮,避免刚拿到的邮箱被直接换掉。 @@ -545,7 +545,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。 - 已刷新到最新 OAuth 链接 - 认证页已经真正进入登录验证码页面 -- 如遇登录超时报错,会先尝试自动点击认证页上的 `重试` 恢复当前页面,再按既有逻辑重跑整个 Step 7 +- 如遇登录超时报错,会先尝试通过共享恢复逻辑最多自动点击 5 次认证页上的 `重试` 恢复当前页面;若仍未恢复,再按既有逻辑重跑整个 Step 7 - 如遇登录页长时间停滞,会由后台刷新 OAuth 后重跑整个 Step 7 - 如果重试页内容中出现 `max_check_attempts`,会立刻完全停止流程,并在侧边栏复用现有确认弹窗提示这是 Cloudflare 风控拦截,确认按钮显示为“我知道了” @@ -583,7 +583,7 @@ Step 8 默认要求当前认证页已经处于登录验证码页。 - 等待按钮可点击 - 获取按钮坐标 - 通过 Chrome `debugger` 的输入事件点击该按钮 -- 点击后会持续检查页面是否真正离开当前状态;如果出现认证页 `重试` 页面,会先自动点击 `重试` 恢复,再重新执行当前轮的“继续”点击 +- 点击后会持续检查页面是否真正离开当前状态;如果出现认证页 `重试` 页面,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再重新执行当前轮的“继续”点击 - 同时监听 `chrome.webNavigation.onBeforeNavigate` - 一旦捕获本地回调地址,就把结果保存到 `Callback` diff --git a/content/auth-page-recovery.js b/content/auth-page-recovery.js index 818919b..e046396 100644 --- a/content/auth-page-recovery.js +++ b/content/auth-page-recovery.js @@ -115,16 +115,20 @@ async function recoverAuthRetryPage(options = {}) { const { logLabel = '', + maxClickAttempts = 5, pathPatterns = [], pollIntervalMs = 250, step = null, timeoutMs = 12000, waitAfterClickMs = 3000, } = options; - const start = Date.now(); + const maxIdlePolls = timeoutMs > 0 + ? Math.max(1, Math.ceil(timeoutMs / Math.max(1, pollIntervalMs))) + : Number.POSITIVE_INFINITY; let clickCount = 0; + let idlePollCount = 0; - while (Date.now() - start < timeoutMs) { + while (clickCount < maxClickAttempts) { if (typeof throwIfStopped === 'function') { throwIfStopped(); } @@ -145,6 +149,7 @@ } if (retryState.retryButton && retryState.retryEnabled) { + idlePollCount = 0; clickCount += 1; if (typeof log === 'function') { const prefix = logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`; @@ -169,11 +174,33 @@ continue; } + idlePollCount += 1; + if (idlePollCount >= maxIdlePolls) { + throw new Error( + `${logLabel || `步骤 ${step || '?'}:重试页恢复`}超时:重试按钮长时间不可点击。URL: ${location.href}` + ); + } + await sleep(pollIntervalMs); } + const finalRetryState = getAuthTimeoutErrorPageState({ pathPatterns }); + if (!finalRetryState) { + return { + recovered: clickCount > 0, + clickCount, + url: location.href, + }; + } + + if (finalRetryState.maxCheckAttemptsBlocked) { + throw new Error( + 'CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器' + ); + } + throw new Error( - `${logLabel || `步骤 ${step || '?'}:重试页恢复`}超时。URL: ${location.href}` + `${logLabel || `步骤 ${step || '?'}:重试页恢复`}失败:已连续点击“重试” ${maxClickAttempts} 次,页面仍未恢复。URL: ${location.href}` ); } diff --git a/content/signup-page.js b/content/signup-page.js index b36e8df..8632bf9 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -974,6 +974,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) { const { flow = 'auth', logLabel = '', + maxClickAttempts = 5, step = null, timeoutMs = 12000, waitAfterClickMs = 3000, @@ -982,6 +983,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) { if (authPageRecovery?.recoverAuthRetryPage) { return authPageRecovery.recoverAuthRetryPage({ logLabel, + maxClickAttempts, pathPatterns, step, timeoutMs, @@ -989,9 +991,12 @@ async function recoverCurrentAuthRetryPage(payload = {}) { }); } - const start = Date.now(); + const maxIdlePolls = timeoutMs > 0 + ? Math.max(1, Math.ceil(timeoutMs / Math.max(1, 250))) + : Number.POSITIVE_INFINITY; let clickCount = 0; - while (Date.now() - start < timeoutMs) { + let idlePollCount = 0; + while (clickCount < maxClickAttempts) { throwIfStopped(); const retryState = getCurrentAuthRetryPageState(flow); if (!retryState) { @@ -1006,6 +1011,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) { throw new Error('CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器'); } if (retryState.retryButton && retryState.retryEnabled) { + idlePollCount = 0; clickCount += 1; log(`${logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`}(第 ${clickCount} 次)...`, 'warn'); await humanPause(300, 800); @@ -1025,10 +1031,27 @@ async function recoverCurrentAuthRetryPage(payload = {}) { continue; } + idlePollCount += 1; + if (idlePollCount >= maxIdlePolls) { + throw new Error(`${logLabel || `步骤 ${step || '?'}:重试页恢复`}超时:重试按钮长时间不可点击。URL: ${location.href}`); + } + await sleep(250); } - throw new Error(`${logLabel || `步骤 ${step || '?'}:重试页恢复`}超时。URL: ${location.href}`); + const finalRetryState = getCurrentAuthRetryPageState(flow); + if (!finalRetryState) { + return { + recovered: clickCount > 0, + clickCount, + url: location.href, + }; + } + if (finalRetryState.maxCheckAttemptsBlocked) { + throw new Error('CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器'); + } + + throw new Error(`${logLabel || `步骤 ${step || '?'}:重试页恢复`}失败:已连续点击“重试” ${maxClickAttempts} 次,页面仍未恢复。URL: ${location.href}`); } function getSignupPasswordTimeoutErrorPageState() { diff --git a/tests/auth-page-recovery.test.js b/tests/auth-page-recovery.test.js index 90fd7f7..b807e43 100644 --- a/tests/auth-page-recovery.test.js +++ b/tests/auth-page-recovery.test.js @@ -137,6 +137,32 @@ test('auth page recovery can click retry twice before page recovers', async () = assert.equal(state.retryVisible, false); }); +test('auth page recovery stops after five retry clicks when page does not recover', async () => { + const state = { + clickCount: 0, + pageText: 'Something went wrong. Please try again.', + retryVisible: true, + onClick() {}, + }; + const api = createRecoveryApi(state); + + await assert.rejects( + () => api.recoverAuthRetryPage({ + logLabel: '步骤 8:检测到重试页,正在点击“重试”恢复', + maxClickAttempts: 5, + pathPatterns: [/\/log-in(?:[/?#]|$)/i], + step: 8, + timeoutMs: 1000, + waitAfterClickMs: 10, + pollIntervalMs: 1, + }), + /已连续点击“重试” 5 次/ + ); + + assert.equal(state.clickCount, 5); + assert.equal(state.retryVisible, true); +}); + test('auth page recovery throws cloudflare security blocked error on max_check_attempts page', async () => { const state = { clickCount: 0, diff --git a/项目完整链路说明.md b/项目完整链路说明.md index 595acf9..cf33226 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -257,7 +257,7 @@ 5. 内容脚本先上报 Step 3 完成信号 6. 上报完成后再异步点击提交,避免页面跳转打断响应通道 7. 延迟提交真正触发前会再次检查 Stop 状态,避免用户已停止时页面仍继续自动提交 -8. 后台在真正确认 Step 3 完成前,会额外检查提交后是否切换页面;如果出现认证页 `Try again / 重试` 页面,会先自动点击 `重试` 恢复,再继续后续链路 +8. 后台在真正确认 Step 3 完成前,会额外检查提交后是否切换页面;如果出现认证页 `Try again / 重试` 页面,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续后续链路 ### Step 4 / Step 8 @@ -546,8 +546,8 @@ ## 2026-04 链路补充:认证页共享恢复 - 新增共享恢复层:`content/auth-page-recovery.js`。 -- Step 4 在等待注册验证码页时,如果命中认证页 `Try again / 重试` 页,会先自动点击 `重试` 恢复,再继续回到密码页重提和验证码页确认流程。 -- Step 7 在识别到登录超时报错页时,会先尝试自动点击 `重试` 恢复当前页面;若仍未恢复,则按原有可恢复失败逻辑重跑 Step 7。 +- Step 4 在等待注册验证码页时,如果命中认证页 `Try again / 重试` 页,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续回到密码页重提和验证码页确认流程。 +- Step 7 在识别到登录超时报错页时,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复当前页面;若仍未恢复,则按原有可恢复失败逻辑重跑 Step 7。 - Step 8 如果发现认证页已经进入登录超时报错/重试页,会直接报错并回到 Step 7 重新开始,而不是在 Step 8 内部点击 `重试`。 - 任意认证页重试页如果正文中出现 `max_check_attempts`,会被视为 Cloudflare 风控触发:后台立刻完全停止流程,侧边栏会复用现有确认弹窗提示等待 15~30 分钟后再试,避免继续刷新或反复重试加重风控,确认按钮显示为“我知道了”。 -- Step 9 在点击 OAuth 同意页 `继续` 后,会额外检查是否进入认证页重试页;若命中则先自动点击 `重试` 恢复,再重新执行当前轮的 `继续` 点击。 +- Step 9 在点击 OAuth 同意页 `继续` 后,会额外检查是否进入认证页重试页;若命中则先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再重新执行当前轮的 `继续` 点击。