Files
UHD_Media_Tool/docs/design/cloudflare-handling.md
T

48 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CF-1 Cloudflare 验证处理(调研)
> 状态:CF-2 已按本文 §3 推荐方案 A→B 级联落地,另加出口统一(站点请求明确直连 / 站点代理)。运维与字段见
> `docs/runbook/sites.md`「出口」「Cloudflare(CF-2)」、`docs/runbook/browser.md` §13。与本文的差异:同步 cf_clearance 单独存
> `site_account.user_info.cloudflare`(值在 secret),普通浏览器回写仍剔除;视觉子流程最多 2 次;`site.config.cloudflare` 多了
> `lastAttemptAt / lastSolvedAt / pending / sessionId / lastResult / lastRule / lastError`。
## 1. 复现(2026-09-30,HTTP 5 次、浏览器 5 次,间隔 ≥15s)
| 请求 | 结果 |
| --- | --- |
| HTTP(Deno,Firefox UA)观众 / 憨憨搜索页,无 Cookie、仅 cf_clearance、补齐浏览器请求头 | 全部 302 → login.php(`server: cloudflare`,无 `cf-mitigated`):**没被挑战** |
| 浏览器,观众账号 profile(已有 cf_clearance)搜索页 | 200,直接出结果 |
| 浏览器,全新 profile:憨憨 | 无挑战、无 CF Cookie |
| 浏览器,全新 profile:观众 ×2 | 首次导航 **403(挑战)**,1–5 秒内**不点即过**,拿到 `cf_clearance`(Cookie 期限 1 年;真实有效期由站点 Challenge Passage 决定,不可见) |
结论:观众有 CF 挑战(非交互 managed/JS 类,挑战页类型未抓到),固定 Windows 身份的 camofox 能自动通过;憨憨未见。
**关键发现:两端出口不一致**。`cdn-cgi/trace`:浏览器(站点未开代理 → 直连)出口 HKG;app 的 HTTP 端走 `globalThis.fetch`,而 Deno 会读容器的 `HTTPS_PROXY`(override 里的 mihomo 中继)→ 出口 DFW。BR-4 §11.5 的"按站点统一出口"在 HTTP 侧没生效;代理换节点也是"随机出 CF"的可能来源。
**分类缺陷**:HTTP 遇挑战(403)在 spider `fetchPage` 被归为 `auth_invalid` → 账号标失效、开"登录失效"事项、触发重登;503 挑战被归为维护。
## 2. 试验:浏览器 cf_clearance + 同 UA 给 HTTP
HTTP 本身未被挑战,带不带 cf_clearance 都是 302,**本次无法判定**。另因出口不同(HKG vs DFW),即使被挑战也不满足"同 IP"前提。MP 的 HTTP 端是 requests/httpx(非浏览器 TLS),照样复用浏览器取得的 cookie+UA,说明多数 PT 站实际只绑 UA+IP。把"首次复用重试"本身作为在线验证,按站点记结果。
## 3. 方案对比
| | 成功率 | 请求成本 | 礼貌 | 改动 | 与 BR-4 |
| --- | --- | --- | --- | --- | --- |
| A 同步 cf_clearance+UA,HTTP 重试 1 次 | 取决于站点是否绑 TLS | 最低 | 好 | 小 | 需改"仅浏览器"规则 |
| B 该请求改由浏览器执行 + N 小时走浏览器标记 | 高 | 中(整页加载) | 好 | 中 | 一致 |
| C 站点长期全走浏览器 | 高 | 高 | 一般 | 中 | 一致 |
| D FlareSolverr | 与 camofox 相当 | 多一容器 | — | 大 | 不需要(用户否决) |
**推荐 A→B 级联**(照 MP,camofox 即仿真浏览器):
1. **触发**:`http.ts` 识别 `cf-mitigated: challenge` 或 403/503 + `underChallenge(html)` → 新错误 `SiteError("challenge")`;不再算凭据错误。
2. **过挑战**:用该账号 profile 开浏览器会话打开同一 URL。① 等自动通过(≤15s,轮询标题 / cf_clearance);② 未过 → `browser.solve_challenge` 视觉子流程,只允许"我是人类"复选框 / Turnstile 单步点击,maxSteps 小、最多 1–2 次;③ 图片选择 / 输码类 → 会话转 `waiting(challenge_unsolved)`,生成待处理事项"观众需要你在原站视图里过一次验证",用户在桌面(同一会话)过完,交还即继续。约束:只处理用户已登录账号的站点;同站单次触发只处理一次,30 分钟节流;不接打码服务。
3. **同步**:读回 cf_clearance + `navigator.userAgent`,前提是账号 UA 来源 = browser 且两端出口相同;HTTP 重试 1 次。
4. **回退**:仍被挑战 → 本次请求改为浏览器导航取 `outerHTML` 交给现有解析器;站点记 `browserMode` 6 小时(续期 / 到期再试 HTTP);记 `cfReuse = fails`,以后跳过第 3 步。
5. **展示**:站点页显示"Cloudflare:浏览器代理中,至 hh:mm / 需人工验证 / 复用有效"。
## 4. 实现清单
- `packages/adapters/src/sites/{http.ts,types.ts,spider/adapter.ts}`:`challenge` 错误类,修 403 误判。
- `sources-identity/runtime.ts`:非代理站点显式 `Deno.createHttpClient({proxy:null})` 直连(或浏览器也按 env 代理),两端同出口;`siteFetch` 挂挑战钩子(重试 / 浏览器执行)。
- `service/credentials.ts`、`browser/cookies.ts`:`splitBrowserOnlyCookies` 改为"同 UA 同出口时 cf_clearance 进 HTTP Cookie",`__cf_bm` 仍不带。
- `site.config.cloudflare = {browserUntil, cfReuse, lastChallengeAt, lastSolvedBy}`;`snapshot.ts` 不再对 challenge 调 `onAuthError`。
- `browser/service.ts`:新增 `fetchViaBrowser(siteId, url)`(profile 锁、限流令牌、`site_disabled` 检查);`enterWaiting` / 待处理事项、桌面接管(WT-1)、BR-3 重连沿用。
- `agent/challenge.ts`:无父 Agent 时由系统发起子流程(预算来自设置);Turnstile 在跨域 iframe,`click_at` 需插件提供真实鼠标坐标点击。
- 测试:挑战分类、级联状态机、出口一致。