Replace CF path with zip register solver; keep Gmail verification

Use grok-register.zip getTurnstileToken/fill_profile and pure MV2
turnstilePatch. Browser options match ReinerBRO headed flags.
Local gmail worker + IMAP + defaultDomains catch-all stay intact.
This commit is contained in:
chaos committed 2026-07-12 07:09:45 +08:00
1 parent 60e24b89c1
commit 9e0f45ac3b
3 files changed
+480 -685

No files matched your search

+134 -661
View File
@@ -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,275 +4132,21 @@ 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 ''; }
"""
)
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) {}
"""
page.run_js(
"try { if (window.turnstile && typeof turnstile.reset === 'function') turnstile.reset(); } 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)."""
rounds = max(1, int(max_rounds or 20))
for _ in range(0, rounds):
raise_if_cancelled(cancel_callback)
try:
token = page.run_js(
"""
@@ -4445,130 +4160,17 @@ try {
} 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,58 +4188,15 @@ 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
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:
else:
# 兜底:尝试触发页面上可见的 Turnstile 容器
page.run_js(
"""
const nodes = Array.from(document.querySelectorAll('div,span,iframe')).filter((n) => {
@@ -4647,18 +4206,12 @@ const nodes = Array.from(document.querySelectorAll('div,span,iframe')).filter((n
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)
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"
# 卡住后自动二次复用 Turnstile 组件
if now - wait_cf_since >= 12 and now - last_cf_retry_at >= 8:
if log_callback:
log_callback("[Debug] Cloudflare 验证卡住,开始二次复用 Turnstile...")
try:
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,
)
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:
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...")
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 now - wait_cf_since >= 12 and now - last_cf_retry_at >= 8:
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:
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
+345
View File
@@ -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()
+1 -24
View File
@@ -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 {