feat: 更新重试逻辑,最多自动点击 5 次以恢复认证页状态

This commit is contained in:
QLHazyCoder
2026-04-19 03:43:00 +08:00
parent afef124856
commit 67304bb64c
5 changed files with 90 additions and 14 deletions
+4 -4
View File
@@ -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`
+30 -3
View File
@@ -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}`
);
}
+26 -3
View File
@@ -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() {
+26
View File
@@ -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,
+4 -4
View File
@@ -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 次 `重试` 尝试恢复,再重新执行当前轮的 `继续` 点击。