// ==UserScript== // @name Cloudflare 盾牌助手 // @namespace https://github.com/ // @version 1.0.0 // @description Cloudflare 人机验证半自动处理:纯 JS 验证页自动等待通过并防卡死;复选框验证自动定位+高亮,只需手动点一次。 // @author ARIES // @match *://*/* // @run-at document-start // @grant none // @noframes // @license MIT // ==/UserScript== /* * 工作原理与能力边界(必读): * 1. 纯 JS 验证页("Checking your browser / Just a moment"):Cloudflare 会自己完成验证, * 脚本只需等待,卡住时自动刷新(带防循环计数)。 * 2. 复选框验证("Verify you are human"):勾选框在跨域 iframe 里,浏览器安全模型禁止脚本 * 代点(程序化点击 isTrusted=false 会被直接无视)。脚本能做的是:自动滚动定位、高亮闪烁、 * 浮窗提醒,你手动点一次,脚本检测到通过后自动收尾。 * 3. 被 Cloudflare 封 IP(Error 1015/1020/Attention Required):脚本解决不了,会明确提示换 IP。 */ (function () { 'use strict'; const 选择器 = { 验证框: 'iframe[src*="challenges.cloudflare.com"]', 质询表单: '#challenge-form', }; const 最大刷新次数 = 3; const 刷新计数键 = 'cf_helper_reloads'; const 状态 = { toast: null, pill: null, pill文本: '', 已处理复选框: null, 看门狗: null, 已计划刷新: false, 见过盾: false, 全局观察器: null, 通过时间: 0 }; /* ================= 样式 ================= */ function 注入样式() { if (document.querySelector('#cf-helper-style')) return; const s = document.createElement('style'); s.id = 'cf-helper-style'; s.textContent = ` .cf-helper-highlight { outline: 3px solid #f59e0b !important; outline-offset: 5px !important; border-radius: 8px !important; animation: cfHelperPulse 1.2s ease-in-out infinite !important; } @keyframes cfHelperPulse { 0%, 100% { outline-color: #f59e0b; } 50% { outline-color: #ef4444; } } .cf-helper-toast { position: fixed; right: 20px; bottom: 20px; z-index: 2147483647; max-width: 320px; padding: 14px 16px; background: rgba(17, 24, 39, .94); color: #f9fafb; border: 1px solid rgba(245, 158, 11, .5); border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,.4); font: 13px/1.6 -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; } .cf-helper-toast b { color: #fbbf24; } .cf-helper-toast .cf-helper-close { position: absolute; top: 6px; right: 10px; cursor: pointer; color: #9ca3af; font-size: 16px; line-height: 1; } .cf-helper-toast .cf-helper-close:hover { color: #fff; } .cf-helper-pill { position: fixed; top: 14px; left: 50%; transform: translateX(-50%); z-index: 2147483647; padding: 8px 18px; background: rgba(17, 24, 39, .9); color: #fbbf24; border: 1px solid rgba(245, 158, 11, .4); border-radius: 999px; font: 13px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; white-space: nowrap; } .cf-helper-pill.ok { color: #34d399; border-color: rgba(52, 211, 153, .4); } `; (document.head || document.documentElement).appendChild(s); } /* ================= UI(样式按需注入,无盾页面零侵入) ================= */ function 显示浮窗(标题, 内容) { 注入样式(); 隐藏浮窗(); const el = document.createElement('div'); el.className = 'cf-helper-toast'; const 关闭 = document.createElement('span'); 关闭.className = 'cf-helper-close'; 关闭.textContent = '×'; 关闭.onclick = 隐藏浮窗; const 标 = document.createElement('div'); 标.innerHTML = '' + 标题 + ''; const 文 = document.createElement('div'); 文.textContent = 内容; el.appendChild(关闭); el.appendChild(标); el.appendChild(文); document.body.appendChild(el); 状态.toast = el; } function 隐藏浮窗() { if (状态.toast) { 状态.toast.remove(); 状态.toast = null; } } function 显示状态条(文本, 成功) { if (状态.pill && 状态.pill文本 === 文本) return; // 幂等,避免反复重建闪烁 注入样式(); 隐藏状态条(); const el = document.createElement('div'); el.className = 'cf-helper-pill' + (成功 ? ' ok' : ''); el.textContent = 文本; document.body.appendChild(el); 状态.pill = el; 状态.pill文本 = 文本; } function 隐藏状态条() { if (状态.pill) { 状态.pill.remove(); 状态.pill = null; 状态.pill文本 = ''; } } function 隐藏全部UI() { 隐藏浮窗(); 隐藏状态条(); } /* ============ 检测(无嫌疑时只做廉价检查,不读 innerText,避免触发重排) ============ */ function 检测页面() { const 标题 = (document.title || '').toLowerCase(); const 验证框 = document.querySelector(选择器.验证框); // 注意:不用 offsetParent 判断可见性——fixed 定位元素的 offsetParent 为 null,会误判 let 验证框可见 = false; if (验证框) { try { const r = 验证框.getBoundingClientRect(); 验证框可见 = r.width > 10 && r.height > 10; } catch (e) {} } const 是盾标题 = /just a moment|attention required|请稍候|正在验证|security verification/i.test(标题); // innerText 会触发重排,只在有嫌疑(看到验证框 / 盾标题)时才读正文 let 有复选框文案 = false, 被拦截 = false, 验证失败 = false; if (验证框可见 || 是盾标题) { let 正文 = ''; try { 正文 = (document.body ? document.body.innerText : '').slice(0, 6000); } catch (e) {} 有复选框文案 = /verify you are human|verifying you are human|确认您是真人|请验证您是人类/i.test(正文); 被拦截 = /attention required/i.test(标题) && (/error\s*10(15|20|16)/i.test(正文) || /access denied/i.test(正文)); 验证失败 = /verification failed|验证失败|try again|请重试/i.test(正文); } return { 验证框, 有验证框: 验证框可见, 是盾标题, 有复选框文案, 被拦截, 验证失败 }; } function 刷新计数() { try { return parseInt(sessionStorage.getItem(刷新计数键) || '0', 10) || 0; } catch (e) { return 0; } } function 计数加一() { try { sessionStorage.setItem(刷新计数键, String(刷新计数() + 1)); } catch (e) {} } function 清除计数() { try { sessionStorage.removeItem(刷新计数键); } catch (e) {} } /* ================= 处理分支 ================= */ // 分支 1:IP 被封 / 限流 —— 脚本解决不了,明确提示 function 处理被拦截() { 显示浮窗('🛡️ Cloudflare 已拦截', '当前 IP 被 Cloudflare 限制或封禁(Error 1015/1020),脚本无法绕过,请更换 IP 或稍后再试。'); } // 分支 2:复选框验证 —— 定位 + 高亮 + 提醒手动点一次 function 处理复选框(验证框) { if (状态.已处理复选框 === 验证框) return; // 同一个框只处理一次 状态.已处理复选框 = 验证框; 状态.见过盾 = true; 注入样式(); try { 验证框.scrollIntoView({ block: 'center', behavior: 'smooth' }); } catch (e) {} 验证框.classList.add('cf-helper-highlight'); 显示浮窗('🛡️ 检测到 Cloudflare 人机验证', '已自动定位到验证框(橙色闪烁处),请手动勾选一次复选框,通过后本提示自动消失。\n注:浏览器安全限制,脚本无法代替你点击。'); // 通过检测:验证框从页面消失(被移除或隐藏)即算通过 const 观察 = new MutationObserver(() => { let 还在 = 验证框.isConnected; if (还在) { try { 还在 = 验证框.getBoundingClientRect().width > 10; } catch (e) { 还在 = false; } } if (!还在) { 观察.disconnect(); 状态.已处理复选框 = null; 状态.通过时间 = Date.now(); // 宽限期开始:等页面跳转,标题残留不算数 清除计数(); 显示状态条('✅ 验证通过', true); setTimeout(隐藏状态条, 2500); 隐藏浮窗(); } }); 观察.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); } // 分支 3:纯 JS 自动验证页 —— 等待 + 卡死自动刷新(防循环) function 处理自动验证() { 状态.见过盾 = true; 显示状态条('🛡️ Cloudflare 自动验证中…'); if (状态.看门狗) return; 状态.看门狗 = setTimeout(() => { 状态.看门狗 = null; const st = 检测页面(); if (!st.是盾标题 && !st.有复选框文案) return; // 已经过了 if (刷新计数() >= 最大刷新次数) { 显示浮窗('🛡️ 验证卡住了', '自动刷新 ' + 最大刷新次数 + ' 次仍未通过,请手动刷新页面或检查网络/IP 是否被限制。'); return; } 计数加一(); location.reload(); }, 25000); } // 分支 4:普通页面内嵌的 Turnstile(如登录/评论表单) function 处理内嵌验证(验证框) { if (状态.已处理复选框 === 验证框) return; 状态.已处理复选框 = 验证框; 状态.见过盾 = true; 注入样式(); try { 验证框.scrollIntoView({ block: 'center', behavior: 'smooth' }); } catch (e) {} 验证框.classList.add('cf-helper-highlight'); 显示浮窗('🛡️ 页面需要人机验证', '请完成页面中橙色闪烁处的验证,完成后高亮自动消失。'); const 观察 = new MutationObserver(() => { const 还在 = 验证框.isConnected && 验证框.getBoundingClientRect().width > 10; if (!还在) { 观察.disconnect(); 状态.已处理复选框 = null; 隐藏浮窗(); } }); 观察.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] }); } /* ================= 主循环 ================= */ let 防抖 = null; function 主循环() { if (!document.body) return; const st = 检测页面(); if (st.被拦截) { 处理被拦截(); return; } if (st.有验证框) { // 区分:盾牌验证页 vs 普通页内嵌的 Turnstile if (st.是盾标题 || st.有复选框文案) 处理复选框(st.验证框); else 处理内嵌验证(st.验证框); return; } if (st.是盾标题 || st.有复选框文案) { // 刚检测到通过:宽限 8 秒等页面跳转,标题残留不算数(但新验证框出现则立即处理) if (!st.有验证框 && 状态.通过时间 && Date.now() - 状态.通过时间 < 8000) return; // 有盾的文案但暂时没看到验证框:自动验证页,或验证框还在加载 if (st.验证失败) { if (刷新计数() < 最大刷新次数 && !状态.已计划刷新) { 状态.已计划刷新 = true; 计数加一(); setTimeout(() => location.reload(), 3000); } 显示状态条('🛡️ 验证失败,3 秒后重试…'); return; } 处理自动验证(); return; } // 无盾:清理 隐藏全部UI(); 清除计数(); 状态.已处理复选框 = null; 状态.通过时间 = 0; if (状态.看门狗) { clearTimeout(状态.看门狗); 状态.看门狗 = null; } } function 调度主循环() { clearTimeout(防抖); 防抖 = setTimeout(主循环, 300); } /* ================= 启动 ================= */ function 启动() { // 监听迟到的验证框(Cloudflare 的验证框经常是 JS 后加载的) 状态.全局观察器 = new MutationObserver(调度主循环); 状态.全局观察器.observe(document.documentElement, { childList: true, subtree: true }); // 页面加载完成后 10 秒都没见过盾:断开全局观察器,普通页面零常驻开销 // (盾牌页都是整页加载,不会 10 秒后才出现;已见过盾则保持监听以检测"通过") window.addEventListener('load', () => { setTimeout(() => { if (!状态.见过盾 && 状态.全局观察器) { 状态.全局观察器.disconnect(); 状态.全局观察器 = null; } }, 10000); }); 调度主循环(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', 启动, { once: true }); } else { 启动(); } })();