Files
openswarm/backend/apps/agents/browser/detect_captcha.py
T

62 lines
3.0 KiB
Python

import re
from typing import Optional
from typeguard import typechecked
# A bot-detection challenge in the page's own words. We NEVER solve one of these: defeating
# bot-detection is off-limits, and an agent that tries produces exactly the confident nonsense this
# codebase exists to prevent (it cannot hear an audio clip, and "press and hold" against a
# behavioural detector is a coin flip it will report as a success either way).
#
# Named separately from the login wall because the remedy is different. A login wall is fixed by
# signing in once and it stays fixed; a challenge is a one-shot the human has to clear right now,
# in this card, before anything else can proceed.
P_CAPTCHA_RE = re.compile(
r"enter (?:the )?(?:code|characters) you hear|play the audio|"
r"press (?:and|&) hold|click and hold|hold to (?:verify|continue)|"
r"drag (?:the )?(?:slider|puzzle|piece|handle)|slide to (?:verify|complete)|"
r"i'?m not a robot|verify you are (?:a )?human|are you a human|"
r"security check|complete the (?:security )?(?:check|challenge)|"
r"recaptcha|hcaptcha|are you a robot|unusual traffic|"
r"select all (?:images|squares) (?:with|containing)",
re.I,
)
# Vendor iframes and containers, for challenges that render no readable copy at all.
P_CAPTCHA_DOM_RE = re.compile(
r"recaptcha|hcaptcha|turnstile|arkose|funcaptcha|px-captcha|geetest|captcha-?(?:container|frame)",
re.I,
)
@typechecked
def captcha_kind(state_text: str) -> Optional[str]:
"""Which challenge is on the page, or None.
The KIND is the whole point of returning a string: "solve the captcha" tells a user nothing,
while "it wants you to play an audio clip and type what you hear" tells them what they are
about to do. Measured on tiktok 2026-08-04, where the challenge was an audio one and the run
burned its whole budget clicking at a page it could never get past.
"""
text = state_text or ""
if re.search(r"enter (?:the )?(?:code|characters) you hear|play the audio", text, re.I):
return "an audio challenge (play the clip and type what you hear)"
if re.search(r"press (?:and|&) hold|click and hold|hold to (?:verify|continue)", text, re.I):
return "a press-and-hold challenge"
if re.search(r"drag (?:the )?(?:slider|puzzle|piece|handle)|slide to (?:verify|complete)", text, re.I):
return "a slider/puzzle challenge"
if re.search(r"select all (?:images|squares) (?:with|containing)", text, re.I):
return "an image-selection challenge"
if P_CAPTCHA_RE.search(text) or P_CAPTCHA_DOM_RE.search(text):
return "a bot-detection challenge"
return None
@typechecked
def prompt_copy(kind: str, host: str) -> tuple[str, str]:
"""(problem, instruction) for the human handoff. Says plainly that we will not attempt it."""
return (
f"{host or 'This site'} is showing {kind}.",
"I can't solve these, and guessing at one would be worse than stopping. Please complete it "
"in the browser card, then tell me to continue.",
)