134 lines
4.3 KiB
Python
134 lines
4.3 KiB
Python
"""
|
|
Browser automation controller for CAPTCHA solving workflows.
|
|
|
|
Supports Playwright and Selenium when installed; otherwise operates in manual mode.
|
|
"""
|
|
import logging
|
|
from dataclasses import dataclass
|
|
from typing import Any, Optional, Tuple
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class BrowserConfig:
|
|
engine: str = "Playwright"
|
|
headless: bool = False
|
|
viewport: Tuple[int, int] = (1280, 720)
|
|
mouse_delay_ms: int = 50
|
|
retry_limit: int = 3
|
|
|
|
|
|
class BrowserController:
|
|
"""Launches and controls a browser session for CAPTCHA automation."""
|
|
|
|
def __init__(self, config: Optional[BrowserConfig] = None):
|
|
self.config = config or BrowserConfig()
|
|
self._driver: Any = None
|
|
self._playwright: Any = None
|
|
self._browser: Any = None
|
|
self._page: Any = None
|
|
|
|
@property
|
|
def is_running(self) -> bool:
|
|
return self._driver is not None or self._page is not None
|
|
|
|
@property
|
|
def active_driver(self) -> Any:
|
|
"""Return the active Playwright page or Selenium WebDriver."""
|
|
return self._page or self._driver
|
|
|
|
def launch(self, url: Optional[str] = None) -> bool:
|
|
engine = (self.config.engine or "None (Manual)").lower()
|
|
|
|
if "playwright" in engine:
|
|
return self._launch_playwright(url)
|
|
if "selenium" in engine:
|
|
return self._launch_selenium(url)
|
|
|
|
logger.info("Browser automation disabled — manual mode")
|
|
return False
|
|
|
|
def _launch_playwright(self, url: Optional[str]) -> bool:
|
|
try:
|
|
from playwright.sync_api import sync_playwright
|
|
except ImportError:
|
|
logger.warning("Playwright not installed. Install with: pip install playwright")
|
|
return False
|
|
|
|
try:
|
|
self._playwright = sync_playwright().start()
|
|
self._browser = self._playwright.chromium.launch(headless=self.config.headless)
|
|
context = self._browser.new_context(
|
|
viewport={
|
|
"width": self.config.viewport[0],
|
|
"height": self.config.viewport[1],
|
|
}
|
|
)
|
|
self._page = context.new_page()
|
|
if url:
|
|
self._page.goto(url, wait_until="domcontentloaded")
|
|
logger.info("Playwright browser launched")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to launch Playwright: {e}")
|
|
self.close()
|
|
return False
|
|
|
|
def _launch_selenium(self, url: Optional[str]) -> bool:
|
|
try:
|
|
from selenium import webdriver
|
|
from selenium.webdriver.chrome.options import Options
|
|
except ImportError:
|
|
logger.warning("Selenium not installed. Install with: pip install selenium")
|
|
return False
|
|
|
|
try:
|
|
options = Options()
|
|
if self.config.headless:
|
|
options.add_argument("--headless=new")
|
|
options.add_argument(
|
|
f"--window-size={self.config.viewport[0]},{self.config.viewport[1]}"
|
|
)
|
|
self._driver = webdriver.Chrome(options=options)
|
|
if url:
|
|
self._driver.get(url)
|
|
logger.info("Selenium browser launched")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Failed to launch Selenium: {e}")
|
|
self.close()
|
|
return False
|
|
|
|
def navigate(self, url: str) -> bool:
|
|
driver = self.active_driver
|
|
if not driver:
|
|
return False
|
|
try:
|
|
if self._page is not None:
|
|
self._page.goto(url, wait_until="domcontentloaded")
|
|
else:
|
|
driver.get(url)
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Navigation failed: {e}")
|
|
return False
|
|
|
|
def close(self):
|
|
try:
|
|
if self._page is not None:
|
|
self._page.close()
|
|
if self._browser is not None:
|
|
self._browser.close()
|
|
if self._playwright is not None:
|
|
self._playwright.stop()
|
|
if self._driver is not None:
|
|
self._driver.quit()
|
|
except Exception as e:
|
|
logger.warning(f"Browser shutdown warning: {e}")
|
|
finally:
|
|
self._driver = None
|
|
self._page = None
|
|
self._browser = None
|
|
self._playwright = None
|