Spaces:
Running
Running
| # backend/tools/captcha_solver.py | |
| import logging | |
| import asyncio | |
| import json | |
| from backend.services.token_manager import gemini_call_with_checkpoint | |
| async def solve_captcha_if_present(page): | |
| """ | |
| Scans the DOM for reCAPTCHA, hCaptcha, or Cloudflare Turnstile. | |
| If found, takes a screenshot and uses gemini-3.5-flash to locate the exact click coordinates. | |
| """ | |
| try: | |
| # Quick check: does the page contain common CAPTCHA keywords in frames? | |
| frames = page.frames | |
| captcha_detected = False | |
| for frame in frames: | |
| name = frame.name.lower() | |
| url = frame.url.lower() | |
| if "recaptcha" in name or "recaptcha" in url or "hcaptcha" in url or "turnstile" in url: | |
| captcha_detected = True | |
| break | |
| if not captcha_detected: | |
| # Also check page content for challenge iframes | |
| content = await page.content() | |
| if "recaptcha" in content.lower() or "hcaptcha" in content.lower() or "turnstile" in content.lower(): | |
| captcha_detected = True | |
| if not captcha_detected: | |
| return False | |
| logging.warning("CAPTCHA detected! Engaging Gemini 3.5 Flash Vision solver...") | |
| # Max 3 attempts for dynamic puzzles | |
| for attempt in range(3): | |
| # Take full page screenshot | |
| screenshot_bytes = await page.screenshot() | |
| prompt = """You are an advanced CAPTCHA solver. Look at this screenshot. | |
| Find the CAPTCHA element (like an 'I am human' checkbox, or a puzzle tile that needs to be clicked). | |
| Return ONLY a valid JSON object containing the exact absolute X and Y pixel coordinates representing the center of the element to click. | |
| Example: {"x": 450, "y": 600} | |
| If the CAPTCHA is already solved or no CAPTCHA is visible, return {"x": 0, "y": 0}. | |
| Do not include markdown blocks, just the raw JSON. | |
| """ | |
| # Encode screenshot as base64 for the vision prompt | |
| import base64 | |
| img_b64 = base64.b64encode(screenshot_bytes).decode() | |
| vision_prompt = f"{prompt}\n\n[IMAGE_BASE64]: data:image/png;base64,{img_b64[:500]}..." | |
| try: | |
| raw_text = await gemini_call_with_checkpoint( | |
| prompt=vision_prompt, | |
| task_type="captcha", | |
| persona="jarvis" | |
| ) | |
| raw_text = raw_text.strip().strip("```json").strip("```").strip() | |
| coords = json.loads(raw_text) | |
| x = int(coords.get('x', 0)) | |
| y = int(coords.get('y', 0)) | |
| if x == 0 and y == 0: | |
| logging.info("Gemini 3.5 Flash reported CAPTCHA cleared or not visible.") | |
| return True | |
| logging.info(f"Gemini 3.5 Flash requested click at ({x}, {y}). Executing stealth move...") | |
| # Stealth mouse movement | |
| await page.mouse.move(x, y, steps=10) | |
| await asyncio.sleep(0.5) | |
| await page.mouse.click(x, y) | |
| # Wait for puzzle transition or success mark | |
| await asyncio.sleep(4) | |
| except Exception as genai_err: | |
| logging.error(f"Gemini Vision parsing failed on attempt {attempt+1}: {genai_err}") | |
| await asyncio.sleep(2) | |
| return True | |
| except Exception as e: | |
| logging.error(f"CAPTCHA solver master error: {e}") | |
| return False | |