aef8791151
- Added Appshell component with responsive navbar and main content area - Integrated ColorSchemeToggle for light/dark mode switching - Created Welcome component with styled title and introductory text - Developed ChatPage for LLM interaction with WebSocket support - Implemented Biblioteca for managing notes with rich text editor - Added LoginPage for user authentication with error handling - Introduced MessageList and MessageBubble components for chat messages - Styled components with CSS modules for consistent design
194 lines
7.2 KiB
Python
194 lines
7.2 KiB
Python
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.")
|