import asyncio import os import signal import subprocess import json from typing import Optional import aiohttp class Navegador: def __init__(self, chrome_path: str, user_data_dir: str, id: Optional[int] = None, download_dir: Optional[str] = None, debugging_port: int = 9222, headless: bool = False, user_agent: Optional[str] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"): self.chrome_path = chrome_path self.user_data_dir = user_data_dir self.id = id self.download_dir = download_dir or os.path.join(self.user_data_dir, "downloads") self.debugging_port = debugging_port self.headless = headless self.user_agent = user_agent self.chrome_process: Optional[subprocess.Popen] = None async def _esperar_debugger(self, timeout=10): url = f"http://127.0.0.1:{self.debugging_port}/json" for _ in range(timeout * 10): # 10 intentos por segundo try: async with aiohttp.ClientSession() as session: async with session.get(url) as resp: if resp.status == 200: print("✅ Chrome listo para debugging.") return except Exception: pass await asyncio.sleep(0.1) raise RuntimeError("❌ Chrome no respondió en el puerto de debugging.") def _preconfigurar_preferencias(self): prefs_path = os.path.join(self.user_data_dir, "Default", "Preferences") os.makedirs(os.path.dirname(prefs_path), exist_ok=True) os.makedirs(self.download_dir, exist_ok=True) prefs = { "profile": { "exit_type": "Normal", "exited_cleanly": True }, "browser": { "has_seen_welcome_page": True }, "distribution": { "skip_first_run_ui": True }, "download": { "default_directory": self.download_dir, "prompt_for_download": False, "directory_upgrade": True, "extensions_to_open": "" }, "savefile": { "default_directory": self.download_dir } } if os.path.exists(prefs_path): try: with open(prefs_path, "r", encoding="utf-8") as f: existing = json.load(f) existing.update(prefs) prefs = existing except Exception: pass with open(prefs_path, "w", encoding="utf-8") as f: json.dump(prefs, f, indent=2) def _build_args(self): os.makedirs(self.user_data_dir, exist_ok=True) self._preconfigurar_preferencias() args = [ f"--remote-debugging-port={self.debugging_port}", f"--user-data-dir={self.user_data_dir}", "--disable-blink-features=AutomationControlled", "--no-sandbox", # "--disable-web-security", # "--disable-extensions", # "--disable-dev-shm-usage", "--disable-infobars", "--disable-popup-blocking", "--disable-default-apps", "--mute-audio", "--window-size=1024,1024", "--no-first-run", "--no-default-browser-check", "--disable-features=DefaultBrowserPrompt", "--disable-component-update", "--disable-background-networking", "--disable-sync", "--disable-translate", "--disable-background-timer-throttling", "--disable-client-side-phishing-detection", "--disable-component-extensions-with-background-pages", "--metrics-recording-only", "--safebrowsing-disable-auto-update", ] if self.headless: args.append("--headless=new") if self.user_agent: args.append(f"--user-agent={self.user_agent}") return args async def inyectar_spoof_chrome(self): script = """ window.chrome = { app: { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' } }, runtime: { PlatformOs: { MAC: 'mac', WIN: 'win', ANDROID: 'android', CROS: 'cros', LINUX: 'linux', OPENBSD: 'openbsd' }, PlatformArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64' }, PlatformNaclArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64' }, RequestUpdateCheckStatus: { THROTTLED: 'throttled', NO_UPDATE: 'no_update', UPDATE_AVAILABLE: 'update_available' }, OnInstalledReason: { INSTALL: 'install', UPDATE: 'update', CHROME_UPDATE: 'chrome_update', SHARED_MODULE_UPDATE: 'shared_module_update' }, OnRestartRequiredReason: { APP_UPDATE: 'app_update', OS_UPDATE: 'os_update', PERIODIC: 'periodic' } } }; """ url = f"http://127.0.0.1:{self.debugging_port}/json" async with aiohttp.ClientSession() as session: async with session.get(url) as resp: targets = await resp.json() for target in targets: if "webSocketDebuggerUrl" not in target: continue target_id = target["id"] async with session.post( f"http://127.0.0.1:{self.debugging_port}/json/protocol", json={"targetId": target_id} ): pass # CDP protocol fetch optional async with session.post( f"http://127.0.0.1:{self.debugging_port}/json/send", json={ "id": 1, "method": "Page.addScriptToEvaluateOnNewDocument", "params": {"source": script} } ) as inject_resp: if inject_resp.status == 200: print("✅ chrome.* spoof inyectado.") async def iniciar(self): args = self._build_args() self.chrome_process = subprocess.Popen([self.chrome_path] + args) print(f"Chrome iniciado (headless={self.headless}). Esperando disponibilidad del debugger...") await self._esperar_debugger() await self.inyectar_spoof_chrome() async def cerrar(self): if self.chrome_process and self.chrome_process.poll() is None: self.chrome_process.terminate() try: await asyncio.wait_for(asyncio.to_thread(self.chrome_process.wait), timeout=5) except asyncio.TimeoutError: self.chrome_process.kill() print("🛑 Chrome cerrado correctamente.")