diff --git a/grok_register_ttk.py b/grok_register_ttk.py index 1b567ab..761d46b 100644 --- a/grok_register_ttk.py +++ b/grok_register_ttk.py @@ -1001,20 +1001,10 @@ def _first_existing_browser(candidates) -> str | None: def resolve_browser_path(): - """Pick Chromium/Chrome for registration. + """Zip style: only return path if config/env override exists. - Zip style (grok-register / protocol_cpa): do NOT set browser_path unless - config/env explicitly points at a real binary — let DrissionPage use system Chrome. - - Priority: - 1. config browser_path (absolute, must exist) - 2. env BROWSER_PATH / CHROME_PATH / CHROMIUM_PATH - 3. browser_prefer=chromium|chrome → auto-detect candidates - 4. browser_prefer=auto/system → None (system default, zip style) - - Never returns /usr/bin/snap (common snap symlink collapse bug). + browser_prefer=auto/system → None (DrissionPage system Chrome). """ - # Optional override from config.json "browser_path" override = str((config.get("browser_path") if isinstance(config, dict) else "") or "").strip() if override: norm = _normalize_browser_path(override) @@ -1022,7 +1012,7 @@ def resolve_browser_path(): return norm print(f" [browser] ignore invalid browser_path={override!r}", flush=True) - for env_key in ("BROWSER_PATH", "CHROMIUM_PATH", "CHROME_PATH"): + for env_key in ("BROWSER_PATH", "CHROME_PATH", "CHROMIUM_PATH"): env_path = (os.environ.get(env_key) or "").strip() if not env_path: continue @@ -1038,20 +1028,16 @@ def resolve_browser_path(): if env_pref: prefer = env_pref - # Zip style: leave path empty so Chromium() uses OS default Chrome. if prefer in ("auto", "system"): return None - if prefer in ("chrome", "google-chrome", "google"): order = list(CHROME_CANDIDATES) + list(CHROMIUM_CANDIDATES) elif prefer in ("chromium", "chromeium"): order = list(CHROMIUM_CANDIDATES) + list(CHROME_CANDIDATES) else: - order = list(CHROME_CANDIDATES) + list(CHROMIUM_CANDIDATES) - + return None return _first_existing_browser(order) - def apply_stealth_patches(page=None, log_callback=None) -> bool: """Minimal anti-automation patch via CDP (Chrome cannot load unpacked extensions). @@ -1100,69 +1086,63 @@ def apply_stealth_patches(page=None, log_callback=None) -> bool: def create_browser_options(headless=None): - """Build ChromiumOptions — aligned with grok-register.zip / protocol_cpa zip. + """Zip-style Chromium options (grok-register / ReinerBRO). - Reference (both zips): - options.auto_port() - --no-first-run / --no-default-browser-check - always options.add_extension(turnstilePatch) - no browser_path override (system Chrome/Chromium) - no --disable-images (breaks Turnstile) - - headless: - None → resolve from config/env/auto (no DISPLAY → headless) - True → force headless - False → prefer headed (still headless if no DISPLAY) + Critical for CF: + - always load turnstilePatch + - no --disable-images + - no browser_path unless config/env set + - no AutomationControlled / heavy stealth flags on headed """ options = ChromiumOptions() options.auto_port() options.set_timeouts(base=1) - - use_hl = should_use_headless(headless, log_hint=True) - - # grok-register.zip headed flags (CF-friendly) - for flag in CHROMIUM_BASE_FLAGS: + # ReinerBRO headed flags + for flag in ( + "--no-first-run", + "--no-default-browser-check", + "--disable-background-networking", + "--disable-component-update", + "--disable-background-timer-throttling", + "--disable-backgrounding-occluded-windows", + "--disable-renderer-backgrounding", + ): options.set_argument(flag) - # Linux / headless-only extras (Windows headed stays minimal like the zip) - if use_hl or sys.platform != "win32": - for flag in CHROMIUM_HEADLESS_EXTRA_FLAGS: - if flag in ("--disable-gpu", "--disable-software-rasterizer") and not use_hl: - # headed Xvfb/desktop: keep software rasterizer so CF iframe paints - continue + use_headless = should_use_headless(headless, log_hint=True) + if use_headless: + apply_headless_to_options(options, True) + for flag in ( + "--no-sandbox", + "--disable-dev-shm-usage", + "--disable-gpu", + "--mute-audio", + ): options.set_argument(flag) + print(" [browser] headless=true", flush=True) + else: + print(" [browser] headless=false (zip headed)", flush=True) - try: - options.set_pref("credentials_enable_service", False) - except Exception: - pass - - # Optional UA override (zip uses Chrome/138). Empty config → browser default. - ua = (get_user_agent() or "").strip() - if ua: - try: - options.set_user_agent(ua) - except Exception: - try: - options.set_argument(f"--user-agent={ua}") - except Exception: - pass - print(f" [browser] user-agent={ua[:72]}...", flush=True) - - # Only set browser_path when config/env points to a real existing binary. - # Zip never hardcodes path; empty browser_path → system default Chrome. + # system default unless override browser_path = resolve_browser_path() if browser_path: try: options.set_browser_path(browser_path) print(f" [browser] path={browser_path}", flush=True) - except Exception: - pass + except Exception as exc: + print(f" [browser] set path failed: {exc}", flush=True) else: print(" [browser] path=system-default (zip style)", flush=True) - # BOTH zips always load turnstilePatch — do not skip for Google Chrome. - # DrissionPage add_extension works with the temp profile even on Chrome. + # Force UA from config (Chrome/138 etc.) + try: + ua = get_user_agent() + if ua: + options.set_user_agent(ua) + print(f" [browser] ua={ua[:60]}...", flush=True) + except Exception: + pass + if os.path.exists(EXTENSION_PATH): try: options.add_extension(EXTENSION_PATH) @@ -1172,8 +1152,7 @@ def create_browser_options(headless=None): else: print(f" [ext] turnstilePatch missing: {EXTENSION_PATH}", flush=True) - # Apply config.json "proxy" to Chromium. - proxy = (config.get("proxy") or "").strip() + proxy = (config.get("proxy") or "").strip() if isinstance(config, dict) else "" if proxy: try: from urllib.parse import urlparse @@ -1184,21 +1163,11 @@ def create_browser_options(headless=None): port = u.port or (443 if (u.scheme or "http") == "https" else 80) scheme = u.scheme or "http" options.set_argument(f"--proxy-server={scheme}://{host}:{port}") + print(f" [proxy] browser --proxy-server={scheme}://{host}:{port}", flush=True) except Exception as e: - print(f" [proxy] set browser proxy failed: {e}") - - apply_headless_to_options(options, use_hl) - if use_hl: - print(" [browser] headless=True", flush=True) - else: - print( - f" [browser] headed (zip-style minimal flags) " - f"DISPLAY={os.environ.get('DISPLAY', '')!r}", - flush=True, - ) + print(f" [proxy] set browser proxy failed: {e}", flush=True) return options - def _build_request_kwargs(**kwargs): request_kwargs = dict(kwargs) proxies = request_kwargs.pop("proxies", None) @@ -4163,278 +4132,24 @@ return 'clicked'; raise Exception("验证码已获取,但自动填写/提交失败") -def _read_turnstile_token(page): - """Read cf-turnstile-response / turnstile.getResponse if present. +def getTurnstileToken(log_callback=None, cancel_callback=None, max_rounds: int = 20): + page = _get_page() + if page is None: + raise Exception("页面未就绪,无法执行 Turnstile") - Prefer DrissionPage native attr read (sees closed-shadow-ish values better), - then JS walk of open shadow roots + turnstile.getResponse(). - """ - # Path 1: native element value (historical success tokens were length ~773) - for loc in ( - "@name=cf-turnstile-response", - "css:input[name='cf-turnstile-response']", - "css:textarea[name='cf-turnstile-response']", - "tag:input@@name=cf-turnstile-response", - ): - try: - el = page.ele(loc, timeout=0.15) - except Exception: - el = None - if el is None: - continue - try: - val = el.attr("value") - except Exception: - val = None - if not val: - try: - val = el.value - except Exception: - val = None - if not val: - try: - val = page.run_js("return arguments[0] ? String(arguments[0].value||'') : '';", el) - except Exception: - val = None - val = str(val or "").strip() - if len(val) >= 80: - return val - - # Path 2: JS light DOM + open shadow + API try: - token = page.run_js( - """ -try { - function walk(root, acc, depth) { - if (!root || depth > 6) return acc; - try { - const nodes = root.querySelectorAll - ? root.querySelectorAll('input[name="cf-turnstile-response"], textarea[name="cf-turnstile-response"]') - : []; - for (const n of nodes) acc.push(n); - } catch (e) {} - let children = []; - try { children = root.querySelectorAll ? root.querySelectorAll('*') : []; } catch (e) { children = []; } - for (const el of children) { - try { - if (el.shadowRoot) walk(el.shadowRoot, acc, depth + 1); - } catch (e) {} - } - return acc; - } - const inputs = walk(document, [], 0); - for (const input of inputs) { - const v = String((input && input.value) || '').trim(); - if (v) return v; - } - if (window.turnstile && typeof turnstile.getResponse === 'function') { - try { - const r = String(turnstile.getResponse() || '').trim(); - if (r) return r; - } catch (e) {} - try { - const r2 = String(turnstile.getResponse('') || '').trim(); - if (r2) return r2; - } catch (e) {} - } - return ''; -} catch(e) { return ''; } - """ + page.run_js( + "try { if (window.turnstile && typeof turnstile.reset === 'function') turnstile.reset(); } catch(e) {}" ) - return str(token or "").strip() - except Exception: - return "" - - -def _turnstile_diag(page) -> dict: - """Lightweight CF widget diagnostics for logs (no secrets). - - Searches light DOM + open shadow roots. Detects managed-mode "Verifying..." - spinner text so callers can avoid reset/click storms. - """ - try: - info = page.run_js( - """ -try { - function walk(root, acc, depth) { - if (!root || depth > 6) return acc; - try { - const q = root.querySelectorAll - ? root.querySelectorAll( - 'input[name="cf-turnstile-response"], textarea[name="cf-turnstile-response"], ' + - 'iframe[src*="challenges.cloudflare.com"], iframe[src*="turnstile"], ' + - 'div.cf-turnstile, [data-sitekey], iframe[title*="Cloudflare"], iframe[title*="cloudflare"], iframe[title*="Widget"]' - ) - : []; - for (const n of q) acc.push(n); - } catch (e) {} - let children = []; - try { children = root.querySelectorAll ? root.querySelectorAll('*') : []; } catch (e) { children = []; } - for (const el of children) { - try { if (el.shadowRoot) walk(el.shadowRoot, acc, depth + 1); } catch (e) {} - } - return acc; - } - const all = walk(document, [], 0); - const inputs = all.filter((n) => n && (n.tagName === 'INPUT' || n.tagName === 'TEXTAREA')); - const iframes = all.filter((n) => n && n.tagName === 'IFRAME'); - const widgets = all.filter((n) => n && n.tagName !== 'IFRAME' && n.tagName !== 'INPUT' && n.tagName !== 'TEXTAREA'); - const input = inputs[0] || document.querySelector('input[name="cf-turnstile-response"], textarea[name="cf-turnstile-response"]'); - const srcs = iframes.slice(0, 3).map((f) => { - try { return String(f.src || f.getAttribute('src') || '').slice(0, 120); } catch(e) { return ''; } - }); - const bodyText = ((document.body && document.body.innerText) || '').slice(0, 4000); - const verifying = /正在验证|verifying|checking|just a moment|请完成|人机验证/i.test(bodyText); - // success checkmark / aria - const successish = /成功|success|verified/i.test(bodyText) && !!input && String(input.value||'').trim().length >= 80; - let sitekey = ''; - try { - const w = widgets[0] || document.querySelector('[data-sitekey], div.cf-turnstile'); - if (w) sitekey = String(w.getAttribute('data-sitekey') || '').slice(0, 16); - } catch (e) {} - // iframe box size (0x0 means not painted) - let iframeBox = ''; - try { - if (iframes[0]) { - const r = iframes[0].getBoundingClientRect(); - iframeBox = Math.round(r.width) + 'x' + Math.round(r.height); - } - } catch (e) {} - return { - hasInput: !!input, - tokenLen: input ? String(input.value || '').trim().length : 0, - iframeCount: iframes.length, - widgetCount: widgets.length, - sitekey: sitekey, - iframeSrc0: srcs[0] || '', - iframeBox: iframeBox, - turnstileApi: !!(window.turnstile && typeof turnstile.getResponse === 'function'), - webdriver: !!navigator.webdriver, - verifying: verifying, - successish: successish, - }; -} catch(e) { - return { err: String(e && e.message || e) }; -} - """ - ) - return info if isinstance(info, dict) else {"raw": str(info)[:200]} - except Exception as exc: - return {"err": str(exc)[:160]} - - -def _click_turnstile_widget(page, log_callback=None) -> str: - """Best-effort click path for interactive Turnstile checkbox. - - Ported from ReinerBRO/grok-register + Git-creat7/grokRegister-cpa: - input[name=cf-turnstile-response] → parent shadow → iframe - → patch screenX/Y inside iframe → body.shadow_root → input.click() - - Returns a short tag describing which path succeeded (or 'none'). - """ - # Path A: classic ReinerBRO shadow-root checkbox - try: - challenge_input = page.ele("@name=cf-turnstile-response", timeout=0.6) - except Exception: - challenge_input = None - if challenge_input: - iframe = None - try: - wrapper = challenge_input.parent() - iframe = wrapper.shadow_root.ele("tag:iframe") - except Exception: - iframe = None - if iframe is None: - try: - iframe = page.ele( - 'css:iframe[src*="challenges.cloudflare.com"], iframe[src*="turnstile"]', - timeout=0.6, - ) - except Exception: - iframe = None - if iframe: - # Match ReinerBRO exactly: value: not get: (CF reads own props). - try: - iframe.run_js( - """ -window.dtp = 1; -function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; -} -let screenX = getRandomInt(800, 1200); -let screenY = getRandomInt(400, 600); -Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX }); -Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY }); -try { - if (typeof PointerEvent !== 'undefined' && PointerEvent.prototype) { - Object.defineProperty(PointerEvent.prototype, 'screenX', { value: screenX }); - Object.defineProperty(PointerEvent.prototype, 'screenY', { value: screenY }); - } -} catch (e) {} - """ - ) - except Exception: - pass - # A1: classic ReinerBRO path — body.shadow_root → tag:input → click - try: - body_sr = iframe.ele("tag:body").shadow_root - btn = body_sr.ele("tag:input") or body_sr.ele("css:.mark") or body_sr.ele("css:label") - if btn: - try: - btn.click() - except Exception: - try: - btn.click(by_js=True) - except Exception: - page.actions.click(btn) - return "shadow-checkbox" - except Exception: - pass - # A2: click iframe element itself (center) - try: - iframe.click() - return "iframe-click" - except Exception: - pass - try: - page.actions.click(iframe) - return "iframe-actions" - except Exception: - pass - - # Path B: DOM query + synthetic pointer events (Git-creat7 fallback) - try: - clicked = page.run_js( - """ -const nodes = Array.from(document.querySelectorAll( - 'iframe[src*="challenges.cloudflare.com"], iframe[src*="turnstile"], div.cf-turnstile, [data-sitekey]' -)).filter((n) => { - const txt = (n.className || '') + ' ' + (n.id || '') + ' ' + (n.getAttribute?.('src') || ''); - return String(txt).toLowerCase().includes('turnstile') - || String(n.getAttribute?.('src') || '').includes('challenges.cloudflare.com') - || n.hasAttribute?.('data-sitekey'); -}); -let ok = false; -for (const n of nodes) { - try { - if (typeof n.click === 'function') { n.click(); ok = true; } - } catch (e) {} -} -return ok; - """ - ) - if clicked: - return "js-click" except Exception: pass - return "none" - -def _read_turnstile_token_simple(page) -> str: - """Light token read used by solver (input + turnstile.getResponse).""" - try: - token = page.run_js( - """ + rounds = max(1, int(max_rounds or 20)) + for _ in range(0, rounds): + raise_if_cancelled(cancel_callback) + try: + token = page.run_js( + """ try { const byInput = String((document.querySelector('input[name="cf-turnstile-response"]') || {}).value || '').trim(); if (byInput) return byInput; @@ -4443,132 +4158,19 @@ try { } return ''; } catch(e) { return ''; } - """ - ) - return str(token or "").strip() - except Exception: - return "" - - -def _turnstile_ui_state(page) -> dict: - """Detect managed / interactive / failed Turnstile UI (best-effort).""" - try: - info = page.run_js( - """ -try { - const body = ((document.body && document.body.innerText) || '').slice(0, 6000); - const lower = body.toLowerCase(); - const input = document.querySelector('input[name="cf-turnstile-response"]'); - const tokenLen = input ? String(input.value || '').trim().length : 0; - let iframeCount = 0; - try { - iframeCount = document.querySelectorAll( - 'iframe[src*="challenges.cloudflare.com"], iframe[src*="turnstile"]' - ).length; - } catch (e) {} - // open shadow iframes - try { - const all = document.querySelectorAll('*'); - for (const el of all) { - if (el.shadowRoot) { - iframeCount += el.shadowRoot.querySelectorAll( - 'iframe[src*="challenges.cloudflare.com"], iframe[src*="turnstile"]' - ).length; - } - } - } catch (e) {} - const interactive = /确认您是真人|verify you are human|确认您是人类|i am human|请完成安全检查/i.test(body); - const verifying = /正在验证|verifying|checking your browser|just a moment/i.test(body); - const failed = /验证失败|verification failed|try again|请重试|失败,请/i.test(body); - return { - tokenLen: tokenLen, - iframeCount: iframeCount, - interactive: interactive, - verifying: verifying, - failed: failed, - }; -} catch (e) { - return { err: String(e && e.message || e) }; -} - """ - ) - return info if isinstance(info, dict) else {} - except Exception as exc: - return {"err": str(exc)[:120]} - - -def getTurnstileToken(log_callback=None, cancel_callback=None, max_rounds: int = 20): - """Turnstile solver (zip base + interactive-safe). - - Critical fixes vs naive zip loop: - - Do NOT reset when interactive checkbox already shown (reset kills progress → 验证失败) - - Click at most once every few seconds; after click WAIT for token - - Prefer real mouse actions over pure CDP ele.click spam - """ - page = _get_page() - if page is None: - raise Exception("页面未就绪,无法执行 Turnstile") - - # Only reset when widget is missing / hard-failed — never while interactive is up. - ui0 = _turnstile_ui_state(page) - should_reset = False - if ui0.get("failed"): - should_reset = True - elif not ui0.get("iframeCount") and not ui0.get("tokenLen"): - should_reset = True - if should_reset: - try: - page.run_js( - "try { if (window.turnstile && typeof turnstile.reset === 'function') turnstile.reset(); } catch(e) {}" + """ ) - if log_callback: - log_callback("[Debug] Turnstile reset (widget missing/failed)") - except Exception: - pass - elif log_callback and (ui0.get("interactive") or ui0.get("verifying")): - log_callback( - f"[Debug] Turnstile 已有 UI interactive={ui0.get('interactive')} " - f"verifying={ui0.get('verifying')} — 跳过 reset,避免清掉真人勾选" - ) - - rounds = max(1, int(max_rounds or 20)) - last_click_at = 0.0 - click_count = 0 - for round_i in range(0, rounds): - raise_if_cancelled(cancel_callback) - try: - token = _read_turnstile_token_simple(page) + token = str(token or "").strip() if len(token) >= 80: if log_callback: - log_callback(f"[*] Turnstile 已通过,token长度={len(token)}") + log_callback(f"[Debug] Turnstile 已通过,token长度={len(token)}") return token - ui = _turnstile_ui_state(page) - # After a click: prefer waiting over re-clicking (CF interactive fails on spam) - now = time.time() - min_click_gap = 4.5 if ui.get("interactive") else 2.8 - if ui.get("verifying") and not ui.get("failed"): - if log_callback and round_i % 3 == 0: - log_callback("[Debug] Turnstile verifying… 等待 token,不点击") - cf_sleep(1.2, cancel_callback, floor=1.0) - continue - - # Only click when cool-down elapsed - if now - last_click_at < min_click_gap and click_count > 0: - cf_sleep(0.8, cancel_callback, floor=0.7) - continue - - # Zip path: input → parent.shadow_root → iframe → patch screenX → body.shadow_root → input - challenge_input = None - try: - challenge_input = page.ele("@name=cf-turnstile-response", timeout=0.5) - except Exception: - challenge_input = None - clicked = False + challenge_input = page.ele("@name=cf-turnstile-response") if challenge_input: + wrapper = challenge_input.parent() iframe = None try: - wrapper = challenge_input.parent() iframe = wrapper.shadow_root.ele("tag:iframe") except Exception: iframe = None @@ -4586,79 +4188,30 @@ Object.defineProperty(MouseEvent.prototype, 'screenY', { value: sy }); ) except Exception: pass - # Prefer real mouse path (less likely to trip interactive fail than ele.click spam) try: body_sr = iframe.ele("tag:body").shadow_root - btn = ( - body_sr.ele("tag:input") - or body_sr.ele("css:.mark") - or body_sr.ele("css:label") - or body_sr.ele("css:[role='checkbox']") - ) + btn = body_sr.ele("tag:input") if btn: - try: - page.actions.move_to(btn).click() - clicked = True - path = "actions-checkbox" - except Exception: - try: - btn.click() - clicked = True - path = "shadow-checkbox" - except Exception: - path = "none" - if clicked: - click_count += 1 - last_click_at = time.time() - if log_callback: - log_callback( - f"[Debug] Turnstile click path={path} " - f"round={round_i+1}/{rounds} clicks={click_count}" - ) - # After interactive click, wait longer for token / fail UI - cf_sleep(3.5 if ui.get("interactive") else 2.2, cancel_callback, floor=2.0) - continue + btn.click() except Exception: pass - # iframe center click as last resort - try: - page.actions.move_to(iframe).click() - clicked = True - click_count += 1 - last_click_at = time.time() - if log_callback: - log_callback( - f"[Debug] Turnstile click path=iframe-actions " - f"round={round_i+1}/{rounds}" - ) - cf_sleep(3.0, cancel_callback, floor=2.0) - continue - except Exception: - pass - if not clicked: - # Soft fallback: one JS click, not every round - if click_count == 0 or (now - last_click_at) >= 5.0: - page.run_js( - """ + else: + # 兜底:尝试触发页面上可见的 Turnstile 容器 + page.run_js( + """ const nodes = Array.from(document.querySelectorAll('div,span,iframe')).filter((n) => { const txt = (n.className || '') + ' ' + (n.id || '') + ' ' + (n.getAttribute?.('src') || ''); return String(txt).toLowerCase().includes('turnstile'); }); if (nodes.length && typeof nodes[0].click === 'function') nodes[0].click(); - """ - ) - click_count += 1 - last_click_at = time.time() - if log_callback and (round_i == 0 or round_i % 5 == 0): - log_callback(f"[Debug] Turnstile click path=js-fallback round={round_i+1}/{rounds}") + """ + ) except Exception: pass - cf_sleep(1.0, cancel_callback, floor=0.9) + sleep_with_cancel(1, cancel_callback) raise Exception("Turnstile 获取 token 失败") - - def build_profile(): given_name_pool = [ "Neo", "Ethan", "Liam", "Noah", "Lucas", "Mason", "Ryan", "Leo", @@ -4685,96 +4238,18 @@ def build_profile(): def fill_profile_and_submit(timeout=120, log_callback=None, cancel_callback=None): - """Fill profile form and submit after Turnstile is solved. - - CF wait / retry loop follows Git-creat7/grokRegister-cpa: - fill → if wait-cloudflare → after ~12s call getTurnstileToken (reset+click) - → re-fill token into hidden input → submit. - """ page = _get_page() - # zip 方案:靠 turnstilePatch 扩展,不额外 CDP stealth(stealth_mode=off) - apply_stealth_patches(page, log_callback=log_callback) check_timeout(time.time()) - dismiss_cookie_banner(page, log_callback=log_callback) dump_state(page, "profile-form") take_screenshot(page, "profile-form") given_name, family_name, password = build_profile() - # zip: 预热 Turnstile — 等 2 秒让 iframe 初始化,插件会自动点击 checkbox if log_callback: log_callback("[*] 预热 Turnstile...") - try: - human_sleep(2, cancel_callback) - except Exception: - cf_sleep(2.0, cancel_callback, floor=2.0) + human_sleep(2, cancel_callback) deadline = time.time() + timeout form_filled_once = False wait_cf_since = None last_cf_retry_at = 0.0 - last_cf_log_at = 0.0 - cf_token_fail_streak = 0 - try: - # Git-creat7 风格:卡住就二次复用;允许多轮,别太早放弃 - max_cf_token_fails = max(3, int(config.get("turnstile_fast_fail_count", 3) or 3)) - except Exception: - max_cf_token_fails = 3 - try: - # 参考实现:等 12s 后开始 getTurnstileToken,间隔 8s - cf_retry_after = float(config.get("turnstile_retry_after_sec", 45) or 45) - except Exception: - cf_retry_after = 12.0 - try: - cf_retry_gap = float(config.get("turnstile_retry_gap_sec", 8) or 8) - except Exception: - cf_retry_gap = 8.0 - try: - # 参考 15-20 轮 reset+click;默认抬到 16 - turnstile_rounds = max(12, int(config.get("turnstile_max_rounds", 16) or 16)) - except Exception: - turnstile_rounds = 16 - - def _sync_cf_token(token: str): - return page.run_js( - """ -const token = String(arguments[0] || '').trim(); -const cfInput = document.querySelector('input[name="cf-turnstile-response"]'); -if (!cfInput || !token) return false; -const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set; -if (nativeSetter) nativeSetter.call(cfInput, token); -else cfInput.value = token; -cfInput.dispatchEvent(new Event('input', { bubbles: true })); -cfInput.dispatchEvent(new Event('change', { bubbles: true })); -return String(cfInput.value || '').trim().length; - """, - token, - ) - - def _retry_turnstile(reason: str) -> bool: - nonlocal cf_token_fail_streak, last_cf_retry_at - if log_callback: - log_callback(f"[*] {reason}") - try: - token = getTurnstileToken( - log_callback=log_callback, - cancel_callback=cancel_callback, - max_rounds=turnstile_rounds, - ) - if token: - cf_token_fail_streak = 0 - synced = _sync_cf_token(token) - if log_callback: - log_callback(f"[*] Turnstile 二次复用完成,回填长度={synced}") - last_cf_retry_at = time.time() - return True - except Exception as cf_exc: - cf_token_fail_streak += 1 - if log_callback: - log_callback( - f"[Debug] Turnstile 二次复用失败({cf_token_fail_streak}/{max_cf_token_fails}): {cf_exc}" - ) - if cf_token_fail_streak >= max_cf_token_fails: - raise Exception(f"Turnstile 连续失败,快速放弃资料页: {cf_exc}") - last_cf_retry_at = time.time() - return False while time.time() < deadline: raise_if_cancelled(cancel_callback) @@ -4832,7 +4307,7 @@ const buttons = Array.from(document.querySelectorAll('button[type="submit"], but }); const submitBtn = buttons.find((node) => { const t = (node.innerText || node.textContent || '').replace(/\\s+/g, '').toLowerCase(); - return t.includes('完成注册') || t.includes('创建账户') || t.includes('signup') || t.includes('createaccount') || t.includes('completesignup'); + return t.includes('完成注册') || t.includes('创建账户') || t.includes('signup') || t.includes('createaccount'); }); // 必须等待 Cloudflare 校验通过后再提交 @@ -4857,39 +4332,44 @@ return 'filled-no-submit'; if isinstance(filled, str) and filled.startswith("wait-cloudflare"): form_filled_once = True + if log_callback: + token_len = filled.split(":", 1)[1] if ":" in filled else "0" + log_callback(f"[Debug] 资料已填写,等待 Cloudflare 人机验证通过... 当前token长度={token_len}") + if token_len == "0": + pause_seconds = random.uniform(1, 3) + if log_callback: + log_callback(f"[Debug] Cloudflare token 为空,暂停 {pause_seconds:.1f}s 后继续检测") + sleep_with_cancel(pause_seconds, cancel_callback) now = time.time() if wait_cf_since is None: wait_cf_since = now - token_len = filled.split(":", 1)[1] if ":" in filled else "0" - if log_callback and (last_cf_log_at <= 0 or now - last_cf_log_at >= 5): - waited = now - wait_cf_since if wait_cf_since else 0 - log_callback( - f"[*] 资料已填写,等待 Cloudflare 人机验证通过... " - f"当前token长度={token_len} waited={waited:.0f}s" - ) - last_cf_log_at = now - # Git-creat7: token 为空时多等 1-3s - if token_len == "0": - pause_seconds = random.uniform(1.0, 3.0) - if log_callback and (now - last_cf_log_at >= 4): - log_callback(f"[*] Cloudflare token 为空,暂停 {pause_seconds:.1f}s 后继续检测") - last_cf_log_at = now - sleep_with_cancel(pause_seconds, cancel_callback) - # Cookie overlay can reappear mid-wait and keep iframe=0 - if wait_cf_since and (now - wait_cf_since) >= 2.0: + # 卡住后自动二次复用 Turnstile 组件 + if now - wait_cf_since >= 12 and now - last_cf_retry_at >= 8: + if log_callback: + log_callback("[Debug] Cloudflare 验证卡住,开始二次复用 Turnstile...") try: - dismiss_cookie_banner(page, log_callback=None) - except Exception: - pass - # 轻点一次(参考会在 getTurnstileToken 内反复点;外层也补一次) - if now - wait_cf_since >= 3.0 and now - last_cf_retry_at >= 3.0: - try: - _click_turnstile_widget(page) - except Exception: - pass - # 卡住后二次复用 Turnstile 组件(reset + shadow click) - if now - wait_cf_since >= cf_retry_after and now - last_cf_retry_at >= cf_retry_gap: - _retry_turnstile("Cloudflare 验证卡住,开始二次复用 Turnstile...") + token = getTurnstileToken(log_callback=log_callback, cancel_callback=cancel_callback) + if token: + synced = page.run_js( + """ +const token = String(arguments[0] || '').trim(); +const cfInput = document.querySelector('input[name="cf-turnstile-response"]'); +if (!cfInput || !token) return false; +const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set; +if (nativeSetter) nativeSetter.call(cfInput, token); +else cfInput.value = token; +cfInput.dispatchEvent(new Event('input', { bubbles: true })); +cfInput.dispatchEvent(new Event('change', { bubbles: true })); +return String(cfInput.value || '').trim().length; + """, + token, + ) + if log_callback: + log_callback(f"[Debug] Turnstile 二次复用完成,回填长度={synced}") + except Exception as cf_exc: + if log_callback: + log_callback(f"[Debug] Turnstile 二次复用失败: {cf_exc}") + last_cf_retry_at = now sleep_with_cancel(0.8, cancel_callback) continue @@ -4897,10 +4377,10 @@ return 'filled-no-submit'; form_filled_once = True elif filled == "fill-failed" and log_callback: log_callback("[Debug] 资料输入失败,重试中...") - human_sleep(0.5, cancel_callback) + sleep_with_cancel(0.5, cancel_callback) continue elif filled == "not-ready": - human_sleep(0.5, cancel_callback) + sleep_with_cancel(0.5, cancel_callback) continue submit_state = page.run_js( @@ -4936,7 +4416,7 @@ const buttons = Array.from(document.querySelectorAll('button[type="submit"], but }); const submitBtn = buttons.find((node) => { const t = buttonText(node).replace(/\s+/g, '').toLowerCase(); - return t.includes('完成注册') || t.includes('创建账户') || t.includes('signup') || t.includes('createaccount') || t.includes('completesignup'); + return t.includes('完成注册') || t.includes('创建账户') || t.includes('signup') || t.includes('createaccount'); }); if (!submitBtn) { const visibleTexts = buttons.map(buttonText).filter(Boolean).slice(0, 8).join(' | '); @@ -4949,62 +4429,55 @@ return 'submitted'; ) if isinstance(submit_state, str) and submit_state.startswith("wait-cloudflare"): + if log_callback: + token_len = submit_state.split(":", 1)[1] if ":" in submit_state else "0" + log_callback(f"[Debug] 等待 Cloudflare 人机验证通过后再提交... 当前token长度={token_len}") now = time.time() if wait_cf_since is None: wait_cf_since = now - ui = _turnstile_ui_state(page) - if log_callback and (last_cf_log_at <= 0 or now - last_cf_log_at >= 5): - token_len = submit_state.split(":", 1)[1] if ":" in submit_state else "0" - waited = now - wait_cf_since if wait_cf_since else 0 - log_callback( - f"[*] 等待 Cloudflare 人机验证通过后再提交... " - f"当前token长度={token_len} waited={waited:.0f}s " - f"interactive={ui.get('interactive')} verifying={ui.get('verifying')} failed={ui.get('failed')}" - ) - last_cf_log_at = now - # Interactive checkbox 已出现:不要每 3s 狂点,也不要立刻 reset 复用 - if ui.get("verifying") and not ui.get("failed"): - sleep_with_cancel(1.2, cancel_callback) - continue - if ui.get("interactive") and not ui.get("failed"): - # 轻点一次后耐心等 token;间隔拉长 - if wait_cf_since and now - last_cf_retry_at >= 6.0: - try: - _click_turnstile_widget(page) - last_cf_retry_at = now - if log_callback: - log_callback("[Debug] interactive 模式:轻点一次 checkbox,等待通过…") - except Exception: - pass - sleep_with_cancel(1.5, cancel_callback) - continue - # managed / 无 UI:沿用 zip 二次复用(内部已避免误 reset) - if wait_cf_since and now - wait_cf_since >= 5.0 and now - last_cf_retry_at >= 5.0: + if now - wait_cf_since >= 12 and now - last_cf_retry_at >= 8: + if log_callback: + log_callback("[Debug] 提交前仍卡住,自动再次复用 Turnstile...") try: - _click_turnstile_widget(page) - except Exception: - pass - if now - wait_cf_since >= cf_retry_after and now - last_cf_retry_at >= cf_retry_gap: - _retry_turnstile("提交前仍卡住,自动再次复用 Turnstile...") + token = getTurnstileToken(log_callback=log_callback, cancel_callback=cancel_callback) + if token: + synced = page.run_js( + """ +const token = String(arguments[0] || '').trim(); +const cfInput = document.querySelector('input[name="cf-turnstile-response"]'); +if (!cfInput || !token) return false; +const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set; +if (nativeSetter) nativeSetter.call(cfInput, token); +else cfInput.value = token; +cfInput.dispatchEvent(new Event('input', { bubbles: true })); +cfInput.dispatchEvent(new Event('change', { bubbles: true })); +return String(cfInput.value || '').trim().length; + """, + token, + ) + if log_callback: + log_callback(f"[Debug] Turnstile 二次复用完成,回填长度={synced}") + except Exception as cf_exc: + if log_callback: + log_callback(f"[Debug] Turnstile 二次复用失败: {cf_exc}") + last_cf_retry_at = now sleep_with_cancel(0.8, cancel_callback) continue if submit_state == "submitted": if log_callback: - log_callback(f"[*] 已填写注册资料并提交: {given_name} {family_name}") + log_callback(f"[Debug] 已填写注册资料并提交: {given_name} {family_name}") return {"given_name": given_name, "family_name": family_name, "password": password} wait_cf_since = None if isinstance(submit_state, str) and submit_state.startswith("no-submit-button") and log_callback: - log_callback(f"[Debug] 未找到提交按钮,继续等待页面稳定... {submit_state[17:80]}") + visible_buttons = submit_state.split(":", 1)[1] if ":" in submit_state else "" + suffix = f" 可见按钮: {visible_buttons}" if visible_buttons else "" + log_callback(f"[Debug] 未找到提交按钮,继续等待页面稳定...{suffix}") - human_sleep(0.5, cancel_callback) + sleep_with_cancel(0.5, cancel_callback) raise Exception("最终注册页资料填写失败") - -# ── NSFW 自动开启(从 AaronL725 移植) ── - - def generate_random_birthdate(): """生成随机生日(20-40 岁)。""" import datetime as dt diff --git a/scripts/port_zip_cf.py b/scripts/port_zip_cf.py new file mode 100644 index 0000000..7d8766c --- /dev/null +++ b/scripts/port_zip_cf.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python3 +"""Replace local Turnstile/browser CF path with pure zip implementations. + +Keeps local Gmail / hotmail / register_cli / cpa_xai intact. +Source of truth: grok-register.zip (ReinerBRO) for CF + browser options. +""" +from __future__ import annotations + +import os +import re +import shutil +from pathlib import Path + +ROOT = Path(r"D:/Projects/AI/grok-keygen") +# Git Bash /tmp maps to %TEMP% on Windows +_TEMP = Path(os.environ.get("TEMP") or os.environ.get("TMP") or r"C:/Users/Chaos/AppData/Local/Temp") +ZIP_REG = _TEMP / "zip_full/grok-register/grok_register_ttk.py" +ZIP_CPA = _TEMP / "zip_full/grok_reg-protocol_cpa/grok_register_ttk.py" +ZIP_PATCH = _TEMP / "zip_full/grok-register/turnstilePatch" +LOCAL = ROOT / "grok_register_ttk.py" +LOCAL_PATCH = ROOT / "turnstilePatch" + + +def extract_func(text: str, name: str) -> str: + lines = text.splitlines(keepends=True) + starts = {} + for i, line in enumerate(lines): + m = re.match(r"^def (\w+)\(", line) + if m: + starts[m.group(1)] = i + m = re.match(r"^([A-Z_][A-Z0-9_]*)\s*=", line) + if m: + starts[m.group(1)] = i + if name not in starts: + raise KeyError(f"missing {name}") + s = starts[name] + e = len(lines) + for j in range(s + 1, len(lines)): + if re.match(r"^(def |class |[A-Z_][A-Z0-9_]*\s*=)", lines[j]): + e = j + break + return "".join(lines[s:e]).rstrip() + "\n\n\n" + + +def replace_func(src: str, name: str, new_body: str) -> str: + """Replace a top-level def/assignment block by name.""" + lines = src.splitlines(keepends=True) + start = None + for i, line in enumerate(lines): + if re.match(rf"^def {re.escape(name)}\(", line) or re.match( + rf"^{re.escape(name)}\s*=", line + ): + start = i + break + if start is None: + raise KeyError(f"local missing {name}") + end = len(lines) + for j in range(start + 1, len(lines)): + if re.match(r"^(def |class |[A-Z_][A-Z0-9_]*\s*=)", lines[j]): + end = j + break + # preserve trailing blank style: ensure exactly two newlines before next + body = new_body + if not body.endswith("\n"): + body += "\n" + # normalize to end with two blank lines if next block exists + while body.endswith("\n\n\n"): + body = body[:-1] + if not body.endswith("\n\n"): + body = body.rstrip("\n") + "\n\n" + return "".join(lines[:start]) + body + "".join(lines[end:]) + + +def remove_func(src: str, name: str) -> str: + lines = src.splitlines(keepends=True) + start = None + for i, line in enumerate(lines): + if re.match(rf"^def {re.escape(name)}\(", line) or re.match( + rf"^{re.escape(name)}\s*=", line + ): + start = i + break + if start is None: + return src + end = len(lines) + for j in range(start + 1, len(lines)): + if re.match(r"^(def |class |[A-Z_][A-Z0-9_]*\s*=)", lines[j]): + end = j + break + return "".join(lines[:start]) + "".join(lines[end:]) + + +def adapt_reg_get_turnstile(zip_fn: str) -> str: + """grok-register uses session.page; local uses _get_page().""" + text = zip_fn + text = text.replace( + "def getTurnstileToken(session, log_callback=None, cancel_callback=None):\n page = session.page\n", + "def getTurnstileToken(log_callback=None, cancel_callback=None, max_rounds: int = 20):\n page = _get_page()\n", + ) + # support max_rounds + text = text.replace( + "for _ in range(0, 20):", + "rounds = max(1, int(max_rounds or 20))\n for _ in range(0, rounds):", + ) + # local uses human_sleep/cf_sleep; zip uses sleep_with_cancel — keep sleep_with_cancel (exists) + return text + + +def adapt_reg_fill_profile(zip_fn: str) -> str: + """Adapt fill_profile from grok-register to local globals.""" + text = zip_fn + text = text.replace( + "def fill_profile_and_submit(session, timeout=120, log_callback=None, cancel_callback=None):\n page = session.page\n", + "def fill_profile_and_submit(timeout=120, log_callback=None, cancel_callback=None):\n page = _get_page()\n check_timeout(time.time())\n dump_state(page, \"profile-form\")\n take_screenshot(page, \"profile-form\")\n", + ) + # fix getTurnstileToken(session, ...) -> getTurnstileToken(...) + text = text.replace( + "token = getTurnstileToken(session, log_callback=log_callback, cancel_callback=cancel_callback)", + "token = getTurnstileToken(log_callback=log_callback, cancel_callback=cancel_callback)", + ) + # add zip warmup like protocol_cpa + if "预热 Turnstile" not in text: + text = text.replace( + "given_name, family_name, password = build_profile()\n deadline = time.time() + timeout\n", + "given_name, family_name, password = build_profile()\n" + " if log_callback:\n" + " log_callback(\"[*] 预热 Turnstile...\")\n" + " human_sleep(2, cancel_callback)\n" + " deadline = time.time() + timeout\n", + ) + return text + + +def adapt_create_browser_options(zip_fn: str) -> str: + """Use zip minimal options; keep local headless/proxy helpers lightly.""" + # Prefer ReinerBRO pure options (no disable-images). Keep proxy + headless glue. + return '''def create_browser_options(headless=None): + """Zip-style Chromium options (grok-register / ReinerBRO). + + Critical for CF: + - always load turnstilePatch + - no --disable-images + - no browser_path unless config/env set + - no AutomationControlled / heavy stealth flags on headed + """ + options = ChromiumOptions() + options.auto_port() + options.set_timeouts(base=1) + # ReinerBRO headed flags + for flag in ( + "--no-first-run", + "--no-default-browser-check", + "--disable-background-networking", + "--disable-component-update", + "--disable-background-timer-throttling", + "--disable-backgrounding-occluded-windows", + "--disable-renderer-backgrounding", + ): + options.set_argument(flag) + + use_headless = should_use_headless(headless, log_hint=True) + if use_headless: + apply_headless_to_options(options, True) + for flag in ( + "--no-sandbox", + "--disable-dev-shm-usage", + "--disable-gpu", + "--mute-audio", + ): + options.set_argument(flag) + print(" [browser] headless=true", flush=True) + else: + print(" [browser] headless=false (zip headed)", flush=True) + + # system default unless override + browser_path = resolve_browser_path() + if browser_path: + try: + options.set_browser_path(browser_path) + print(f" [browser] path={browser_path}", flush=True) + except Exception as exc: + print(f" [browser] set path failed: {exc}", flush=True) + else: + print(" [browser] path=system-default (zip style)", flush=True) + + # Force UA from config (Chrome/138 etc.) + try: + ua = get_user_agent() + if ua: + options.set_user_agent(ua) + print(f" [browser] ua={ua[:60]}...", flush=True) + except Exception: + pass + + if os.path.exists(EXTENSION_PATH): + try: + options.add_extension(EXTENSION_PATH) + print(f" [ext] turnstilePatch loaded: {EXTENSION_PATH}", flush=True) + except Exception as ext_exc: + print(f" [ext] turnstilePatch load failed: {ext_exc}", flush=True) + else: + print(f" [ext] turnstilePatch missing: {EXTENSION_PATH}", flush=True) + + proxy = (config.get("proxy") or "").strip() if isinstance(config, dict) else "" + if proxy: + try: + from urllib.parse import urlparse + + u = urlparse(proxy if "://" in proxy else f"http://{proxy}") + host = u.hostname or "" + if host: + port = u.port or (443 if (u.scheme or "http") == "https" else 80) + scheme = u.scheme or "http" + options.set_argument(f"--proxy-server={scheme}://{host}:{port}") + print(f" [proxy] browser --proxy-server={scheme}://{host}:{port}", flush=True) + except Exception as e: + print(f" [proxy] set browser proxy failed: {e}", flush=True) + return options + + +''' + + +def main() -> None: + if not ZIP_REG.is_file(): + raise SystemExit(f"missing {ZIP_REG}") + reg_text = ZIP_REG.read_text(encoding="utf-8", errors="replace") + local = LOCAL.read_text(encoding="utf-8", errors="replace") + + # backup + bak = ROOT / "grok_register_ttk.py.bak_pre_zip_port" + shutil.copy2(LOCAL, bak) + print(f"backup -> {bak}") + + # restore pure zip turnstilePatch + if ZIP_PATCH.is_dir(): + LOCAL_PATCH.mkdir(exist_ok=True) + for name in ("content.js", "manifest.json"): + src = ZIP_PATCH / name + if src.is_file(): + shutil.copy2(src, LOCAL_PATCH / name) + print(f"copied {name}") + # remove MV3 leftovers + for stale in ("script.js",): + p = LOCAL_PATCH / stale + if p.exists(): + p.unlink() + print(f"removed {stale}") + + get_ts = adapt_reg_get_turnstile(extract_func(reg_text, "getTurnstileToken")) + fill = adapt_reg_fill_profile(extract_func(reg_text, "fill_profile_and_submit")) + create_opts = adapt_create_browser_options("") + + # Remove interactive-safe helpers that zip doesn't need (keep if referenced elsewhere carefully) + for helper in ( + "_click_turnstile_widget", + "_read_turnstile_token_simple", + "_turnstile_ui_state", + "_read_turnstile_token", + "_turnstile_diag", + ): + before = local + local = remove_func(local, helper) + if local != before: + print(f"removed helper {helper}") + + local = replace_func(local, "getTurnstileToken", get_ts) + print("replaced getTurnstileToken") + local = replace_func(local, "fill_profile_and_submit", fill) + print("replaced fill_profile_and_submit") + local = replace_func(local, "create_browser_options", create_opts) + print("replaced create_browser_options") + + # Simplify resolve_browser_path to zip style (auto -> None) + resolve_browser = '''def resolve_browser_path(): + """Zip style: only return path if config/env override exists. + + browser_prefer=auto/system → None (DrissionPage system Chrome). + """ + override = str((config.get("browser_path") if isinstance(config, dict) else "") or "").strip() + if override: + norm = _normalize_browser_path(override) + if norm: + return norm + print(f" [browser] ignore invalid browser_path={override!r}", flush=True) + + for env_key in ("BROWSER_PATH", "CHROME_PATH", "CHROMIUM_PATH"): + env_path = (os.environ.get(env_key) or "").strip() + if not env_path: + continue + norm = _normalize_browser_path(env_path) + if norm: + return norm + print(f" [browser] ignore invalid {env_key}={env_path!r}", flush=True) + + prefer = str((config.get("browser_prefer") if isinstance(config, dict) else "") or "auto").strip().lower() + if prefer in ("", "default", "detect", "system"): + prefer = "auto" + env_pref = (os.environ.get("BROWSER_PREFER") or "").strip().lower() + if env_pref: + prefer = env_pref + + if prefer in ("auto", "system"): + return None + if prefer in ("chrome", "google-chrome", "google"): + order = list(CHROME_CANDIDATES) + list(CHROMIUM_CANDIDATES) + elif prefer in ("chromium", "chromeium"): + order = list(CHROMIUM_CANDIDATES) + list(CHROME_CANDIDATES) + else: + return None + return _first_existing_browser(order) + + +''' + local = replace_func(local, "resolve_browser_path", resolve_browser) + print("replaced resolve_browser_path") + + # Force stealth off by default in apply_stealth_patches + local = re.sub( + r'mode = str\(\(config\.get\("stealth_mode"\).*?or "\w+"\)\.strip\(\)\.lower\(\)', + 'mode = str((config.get("stealth_mode") if isinstance(config, dict) else "") or "off").strip().lower()', + local, + count=1, + ) + + # Ensure fill_profile / other call sites don't reference removed helpers + # getTurnstileToken(..., max_rounds=) still ok + if "_turnstile_ui_state" in local or "_click_turnstile_widget" in local: + print("WARN: leftover helper references:") + for i, line in enumerate(local.splitlines(), 1): + if "_turnstile_ui_state" in line or "_click_turnstile_widget" in line or "_read_turnstile_token" in line: + print(f" L{i}: {line.strip()[:120]}") + + LOCAL.write_text(local, encoding="utf-8") + print(f"wrote {LOCAL} ({len(local.splitlines())} lines)") + + # syntax check + import py_compile + + py_compile.compile(str(LOCAL), doraise=True) + print("syntax OK") + + +if __name__ == "__main__": + main() diff --git a/turnstilePatch/content.js b/turnstilePatch/content.js index 2e9b993..6af4eee 100644 --- a/turnstilePatch/content.js +++ b/turnstilePatch/content.js @@ -1,6 +1,5 @@ // Turnstile Patch - 隐藏自动化标识,加速 Turnstile 验证 // 在 document_start 阶段执行,确保在页面脚本之前生效 -// Source: grok-register / grok_reg-protocol_cpa (zip reference) (function () { "use strict"; @@ -55,29 +54,7 @@ }); } catch (e) {} - // 6. CDP MouseEvent.screenX/screenY 与 client 相同会被 CF 检测(Chromium 40280325) - // 两个 zip 的 getTurnstileToken 也会在 iframe 内再补一次;扩展层提前全局打补丁更稳 - try { - function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - } - var _sx = getRandomInt(800, 1200); - var _sy = getRandomInt(400, 600); - Object.defineProperty(MouseEvent.prototype, "screenX", { - configurable: true, - get: function () { - return _sx + (this.clientX || 0); - }, - }); - Object.defineProperty(MouseEvent.prototype, "screenY", { - configurable: true, - get: function () { - return _sy + (this.clientY || 0); - }, - }); - } catch (e) {} - - // 7. 页面加载完成后,自动监控并点击 Turnstile 复选框 + // 6. 页面加载完成后,自动监控并点击 Turnstile 复选框 if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", autoClickTurnstile); } else {