mirror of
https://github.com/ThePhaseless/Byparr.git
synced 2026-09-24 06:10:14 +01:00
debug: try each click strategy against the turnstile checkbox
The widget is reachable but the click leaves it unchecked, so compare locator.click, force, check, label, and a humanized page.mouse press on fresh pages and report which ticks the box. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8aa5f921e0
commit
57e7f26be4
@@ -0,0 +1,157 @@
|
||||
"""Find a click Cloudflare's checkbox actually accepts.
|
||||
|
||||
The widget is reachable now, but `Checkbox clicked successfully` leaves it
|
||||
`checked=False`, so the click is not registering as a user gesture. This tries
|
||||
each candidate in turn against a fresh page and reports which one ticks the box
|
||||
and which one clears the challenge.
|
||||
|
||||
docker run --rm -e PYTHONPATH=/app -e TRACE_URL=https://extratorrent.st/ \\
|
||||
-v "$PWD/out:/out" byparr-test uv run python debug/click_modes.py
|
||||
|
||||
MODES defaults to every strategy; set MODES=force,label to narrow it.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import pathlib
|
||||
import time
|
||||
|
||||
from src.utils import get_browser
|
||||
|
||||
URL = os.environ.get("TRACE_URL", "https://extratorrent.st/")
|
||||
MODES = os.environ.get(
|
||||
"MODES", "locator,force,check,label,mouse,widget_centre"
|
||||
).split(",")
|
||||
SETTLE = int(os.environ.get("SETTLE", "45"))
|
||||
OUT = pathlib.Path(os.environ.get("DIAG_OUT", "/out"))
|
||||
|
||||
|
||||
def log(*a: object) -> None:
|
||||
"""Print immediately."""
|
||||
print(*a, flush=True)
|
||||
|
||||
|
||||
def cf_frame(page):
|
||||
"""The turnstile widget frame."""
|
||||
for frame in page.frames:
|
||||
if "challenges.cloudflare.com" in frame.url and not frame.is_detached():
|
||||
return frame
|
||||
return None
|
||||
|
||||
|
||||
async def wait_for_widget(page, seconds: int = 30):
|
||||
"""Wait until the checkbox is visible, returning (frame, locator)."""
|
||||
for _ in range(seconds * 2):
|
||||
frame = cf_frame(page)
|
||||
if frame is not None:
|
||||
try:
|
||||
box = frame.locator('input[type="checkbox"]')
|
||||
if await box.count() and await box.first.is_visible():
|
||||
return frame, box.first
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
await asyncio.sleep(0.5)
|
||||
return None, None
|
||||
|
||||
|
||||
async def do_click(page, frame, box, mode: str) -> str:
|
||||
"""Perform one click strategy."""
|
||||
if mode == "locator":
|
||||
await box.click(timeout=10_000)
|
||||
return "locator.click()"
|
||||
if mode == "force":
|
||||
await box.click(timeout=10_000, force=True)
|
||||
return "locator.click(force=True)"
|
||||
if mode == "check":
|
||||
await box.check(timeout=10_000)
|
||||
return "locator.check()"
|
||||
if mode == "label":
|
||||
label = frame.locator("label")
|
||||
if await label.count():
|
||||
await label.first.click(timeout=10_000)
|
||||
return "label.click()"
|
||||
return "no label present"
|
||||
if mode in {"mouse", "widget_centre"}:
|
||||
rect = await box.bounding_box()
|
||||
if rect is None:
|
||||
return "no bounding box"
|
||||
x = rect["x"] + rect["width"] / 2
|
||||
y = rect["y"] + rect["height"] / 2
|
||||
# Approach first: a cursor that teleports is itself a signal.
|
||||
await page.mouse.move(x - 180, y - 120)
|
||||
await asyncio.sleep(0.3)
|
||||
await page.mouse.move(x - 40, y - 20, steps=18)
|
||||
await asyncio.sleep(0.2)
|
||||
await page.mouse.move(x, y, steps=10)
|
||||
await asyncio.sleep(0.35)
|
||||
await page.mouse.down()
|
||||
await asyncio.sleep(0.07)
|
||||
await page.mouse.up()
|
||||
return f"page.mouse at ({x:.0f}, {y:.0f})"
|
||||
return f"unknown mode {mode}"
|
||||
|
||||
|
||||
async def try_mode(mode: str) -> None:
|
||||
"""One fresh browser, one strategy, one verdict."""
|
||||
log(f"\n===== mode={mode} =====")
|
||||
async for dep in get_browser():
|
||||
page = dep.page
|
||||
await page.goto(URL, timeout=60_000)
|
||||
await page.wait_for_load_state("domcontentloaded", timeout=30_000)
|
||||
|
||||
# Is our shadow-root patch even surviving the page's CSP?
|
||||
try:
|
||||
src = await page.evaluate("() => Element.prototype.attachShadow.toString()")
|
||||
flag = await page.evaluate("() => '_shadowRootPatched' in window")
|
||||
log(f" on challenge page: native={'[native code]' in src} flag={flag}")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log(f" tamper probe blocked: {str(exc)[:70]}")
|
||||
|
||||
frame, box = await wait_for_widget(page)
|
||||
if box is None:
|
||||
log(" checkbox never became visible")
|
||||
return
|
||||
|
||||
log(f" before: checked={await box.is_checked()}")
|
||||
try:
|
||||
what = await do_click(page, frame, box, mode)
|
||||
log(f" clicked via {what}")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log(f" click raised: {str(exc)[:120]}")
|
||||
return
|
||||
|
||||
start = time.perf_counter()
|
||||
for _ in range(SETTLE // 3):
|
||||
await asyncio.sleep(3)
|
||||
elapsed = time.perf_counter() - start
|
||||
title = await page.title()
|
||||
checked = None
|
||||
frame_now = cf_frame(page)
|
||||
if frame_now is not None:
|
||||
try:
|
||||
b = frame_now.locator('input[type="checkbox"]')
|
||||
checked = await b.first.is_checked() if await b.count() else None
|
||||
except Exception: # noqa: BLE001
|
||||
checked = "unreadable"
|
||||
log(f" +{elapsed:3.0f}s checked={checked} title={title!r}")
|
||||
if "oment" not in title and "ierpliwo" not in title:
|
||||
log(f" >>> CLEARED by {mode}")
|
||||
await page.screenshot(path=str(OUT / f"cleared-{mode}.png"))
|
||||
return
|
||||
await page.screenshot(path=str(OUT / f"stuck-{mode}.png"))
|
||||
log(f" {mode}: still challenged")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Try each strategy on its own fresh browser."""
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
log(f"### {URL} modes={MODES}")
|
||||
for mode in MODES:
|
||||
try:
|
||||
await try_mode(mode.strip())
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log(f" mode {mode} blew up: {str(exc)[:150]}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user