Spaces:
Sleeping
Sleeping
| """ | |
| Group 1: Physical Hands - Stealth Browser Module | |
| ================================================= | |
| This module provides a "Digital Human" browser instance with: | |
| 1. Stealth Layer (playwright-stealth) | |
| 2. Natural Mouse Movement (Bezier curves) | |
| 3. Mobile Emulation | |
| 4. Captcha Detection Hooks | |
| """ | |
| import asyncio | |
| import random | |
| from typing import Optional, Tuple | |
| from playwright.async_api import async_playwright, Page, Browser, BrowserContext | |
| from playwright_stealth.stealth import Stealth | |
| # ============================================================================ | |
| # CONSTANTS | |
| # ============================================================================ | |
| MOBILE_DEVICE = { | |
| "viewport": {"width": 390, "height": 844}, | |
| "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1", | |
| "device_scale_factor": 3, | |
| "is_mobile": True, | |
| "has_touch": True, | |
| } | |
| DESKTOP_DEVICE = { | |
| "viewport": {"width": 1920, "height": 1080}, | |
| "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", | |
| "device_scale_factor": 1, | |
| "is_mobile": False, | |
| "has_touch": False, | |
| } | |
| # ============================================================================ | |
| # NATURAL MOUSE MOVEMENT (Bezier Curve Simulation) | |
| # ============================================================================ | |
| def bezier_curve(t: float, p0: tuple, p1: tuple, p2: tuple, p3: tuple) -> tuple: | |
| """Calculate point on a cubic Bezier curve at parameter t (0 to 1).""" | |
| x = (1-t)**3 * p0[0] + 3*(1-t)**2 * t * p1[0] + 3*(1-t) * t**2 * p2[0] + t**3 * p3[0] | |
| y = (1-t)**3 * p0[1] + 3*(1-t)**2 * t * p1[1] + 3*(1-t) * t**2 * p2[1] + t**3 * p3[1] | |
| return (x, y) | |
| def generate_human_path(start: tuple, end: tuple, steps: int = 25) -> list: | |
| """ | |
| Generate a human-like mouse path using Bezier curves with randomness. | |
| Includes slight overshoot and correction. | |
| """ | |
| # Random control points for natural curve | |
| dx = end[0] - start[0] | |
| dy = end[1] - start[1] | |
| # Control points with randomness | |
| cp1 = ( | |
| start[0] + dx * random.uniform(0.2, 0.4) + random.randint(-50, 50), | |
| start[1] + dy * random.uniform(0.2, 0.4) + random.randint(-50, 50) | |
| ) | |
| cp2 = ( | |
| start[0] + dx * random.uniform(0.6, 0.8) + random.randint(-50, 50), | |
| start[1] + dy * random.uniform(0.6, 0.8) + random.randint(-50, 50) | |
| ) | |
| # Generate path points | |
| path = [] | |
| for i in range(steps): | |
| t = i / (steps - 1) | |
| # Add micro-jitter | |
| point = bezier_curve(t, start, cp1, cp2, end) | |
| jitter_x = random.uniform(-2, 2) | |
| jitter_y = random.uniform(-2, 2) | |
| path.append((point[0] + jitter_x, point[1] + jitter_y)) | |
| # Add overshoot and correction (human behavior) | |
| overshoot = ( | |
| end[0] + random.randint(5, 15) * (1 if random.random() > 0.5 else -1), | |
| end[1] + random.randint(5, 15) * (1 if random.random() > 0.5 else -1) | |
| ) | |
| path.append(overshoot) | |
| path.append(end) # Correction back to target | |
| return path | |
| async def human_move_to(page: Page, x: int, y: int): | |
| """Move mouse to coordinates with human-like behavior.""" | |
| # Get current mouse position (approximate from viewport center if unknown) | |
| current_x = page.viewport_size["width"] // 2 | |
| current_y = page.viewport_size["height"] // 2 | |
| path = generate_human_path((current_x, current_y), (x, y)) | |
| for point in path: | |
| await page.mouse.move(point[0], point[1]) | |
| # Variable delay between movements (faster in middle, slower at ends) | |
| await asyncio.sleep(random.uniform(0.01, 0.05)) | |
| async def human_click(page: Page, selector: str): | |
| """Click an element with human-like mouse movement and timing.""" | |
| # Get element bounding box | |
| element = await page.query_selector(selector) | |
| if not element: | |
| raise Exception(f"Element not found: {selector}") | |
| box = await element.bounding_box() | |
| if not box: | |
| raise Exception(f"Element has no bounding box: {selector}") | |
| # Click at random position within element (not dead center) | |
| target_x = box["x"] + box["width"] * random.uniform(0.3, 0.7) | |
| target_y = box["y"] + box["height"] * random.uniform(0.3, 0.7) | |
| # Move to target with human path | |
| await human_move_to(page, int(target_x), int(target_y)) | |
| # Pre-click delay (human hesitation) | |
| await asyncio.sleep(random.uniform(0.1, 0.3)) | |
| # Click | |
| await page.mouse.click(target_x, target_y) | |
| # Post-click delay | |
| await asyncio.sleep(random.uniform(0.1, 0.2)) | |
| async def human_type(page: Page, selector: str, text: str): | |
| """Type text with human-like delays between keystrokes.""" | |
| await human_click(page, selector) | |
| for char in text: | |
| await page.keyboard.type(char) | |
| # Variable typing speed | |
| await asyncio.sleep(random.uniform(0.05, 0.15)) | |
| # ============================================================================ | |
| # STEALTH BROWSER FACTORY | |
| # ============================================================================ | |
| class StealthBrowser: | |
| """Factory for creating stealth browser instances.""" | |
| def __init__(self, proxy: dict = None, mobile: bool = False, headless: bool = True): | |
| self.proxy = proxy | |
| self.mobile = mobile | |
| self.headless = headless | |
| self.playwright = None | |
| self.browser: Browser = None | |
| self.context: BrowserContext = None | |
| self.page: Page = None | |
| async def start(self) -> Page: | |
| """Initialize and return a stealth browser page.""" | |
| self.playwright = await async_playwright().start() | |
| # Browser launch arguments for maximum stealth | |
| launch_args = [ | |
| "--disable-blink-features=AutomationControlled", | |
| "--disable-infobars", | |
| "--disable-dev-shm-usage", | |
| "--no-sandbox", | |
| "--disable-setuid-sandbox", | |
| "--disable-gpu", | |
| "--disable-extensions", | |
| ] | |
| # Launch browser | |
| self.browser = await self.playwright.chromium.launch( | |
| headless=self.headless, | |
| args=launch_args | |
| ) | |
| # Select device profile | |
| device = MOBILE_DEVICE if self.mobile else DESKTOP_DEVICE | |
| # Context options | |
| context_options = { | |
| "viewport": device["viewport"], | |
| "user_agent": device["user_agent"], | |
| "device_scale_factor": device["device_scale_factor"], | |
| "is_mobile": device["is_mobile"], | |
| "has_touch": device["has_touch"], | |
| "locale": "en-US", | |
| "timezone_id": "America/New_York", | |
| } | |
| # Add proxy if provided | |
| if self.proxy: | |
| context_options["proxy"] = self.proxy | |
| # Create context | |
| self.context = await self.browser.new_context(**context_options) | |
| # Create page | |
| self.page = await self.context.new_page() | |
| # Apply stealth patches | |
| stealth = Stealth() | |
| await stealth.apply_stealth_async(self.page) | |
| # Inject additional anti-detection scripts | |
| await self._inject_anti_detection() | |
| return self.page | |
| async def _inject_anti_detection(self): | |
| """Inject JavaScript to further hide automation indicators.""" | |
| await self.page.add_init_script(""" | |
| // Override navigator.webdriver | |
| Object.defineProperty(navigator, 'webdriver', { | |
| get: () => undefined | |
| }); | |
| // Override permissions | |
| const originalQuery = window.navigator.permissions.query; | |
| window.navigator.permissions.query = (parameters) => ( | |
| parameters.name === 'notifications' ? | |
| Promise.resolve({ state: Notification.permission }) : | |
| originalQuery(parameters) | |
| ); | |
| // Override plugins | |
| Object.defineProperty(navigator, 'plugins', { | |
| get: () => [1, 2, 3, 4, 5] | |
| }); | |
| // Override languages | |
| Object.defineProperty(navigator, 'languages', { | |
| get: () => ['en-US', 'en'] | |
| }); | |
| """) | |
| async def goto_with_stealth(self, url: str, wait_until: str = "domcontentloaded"): | |
| """Navigate to URL with human-like behavior.""" | |
| # Random delay before navigation (human thinking time) | |
| await asyncio.sleep(random.uniform(0.5, 1.5)) | |
| await self.page.goto(url, wait_until=wait_until) | |
| # Random scroll after page load (human behavior) | |
| await asyncio.sleep(random.uniform(0.5, 1.0)) | |
| await self.page.evaluate("window.scrollBy(0, Math.random() * 100)") | |
| async def close(self): | |
| """Clean up browser resources.""" | |
| if self.context: | |
| await self.context.close() | |
| if self.browser: | |
| await self.browser.close() | |
| if self.playwright: | |
| await self.playwright.stop() | |
| # ============================================================================ | |
| # CAPTCHA DETECTION | |
| # ============================================================================ | |
| async def detect_captcha(page: Page) -> Optional[str]: | |
| """ | |
| Detect if a captcha is present on the page. | |
| Returns captcha type or None. | |
| """ | |
| # Check for reCAPTCHA | |
| recaptcha = await page.query_selector('iframe[src*="recaptcha"]') | |
| if recaptcha: | |
| return "recaptcha" | |
| # Check for Cloudflare Turnstile | |
| turnstile = await page.query_selector('iframe[src*="challenges.cloudflare.com"]') | |
| if turnstile: | |
| return "turnstile" | |
| # Check for hCaptcha | |
| hcaptcha = await page.query_selector('iframe[src*="hcaptcha.com"]') | |
| if hcaptcha: | |
| return "hcaptcha" | |
| # Check for Cloudflare challenge page | |
| cf_challenge = await page.query_selector('#cf-challenge-running') | |
| if cf_challenge: | |
| return "cloudflare_challenge" | |
| return None | |
| # ============================================================================ | |
| # CONVENIENCE EXPORTS | |
| # ============================================================================ | |
| async def create_stealth_page(proxy: dict = None, mobile: bool = False, headless: bool = True) -> Tuple[Page, 'StealthBrowser']: | |
| """ | |
| Convenience function to create a stealth browser page. | |
| Returns (page, browser_instance) tuple. | |
| """ | |
| browser = StealthBrowser(proxy=proxy, mobile=mobile, headless=headless) | |
| page = await browser.start() | |
| return page, browser | |
| # ============================================================================ | |
| # EXAMPLE USAGE (for testing) | |
| # ============================================================================ | |
| if __name__ == "__main__": | |
| async def test(): | |
| page, browser = await create_stealth_page(mobile=False, headless=False) | |
| try: | |
| await browser.goto_with_stealth("https://bot.sannysoft.com/") | |
| await asyncio.sleep(5) # Let user see the result | |
| # Check for captcha | |
| captcha = await detect_captcha(page) | |
| print(f"Captcha detected: {captcha}") | |
| finally: | |
| await browser.close() | |
| asyncio.run(test()) | |