"""Registra/actualiza un perfil de Chromium (y opcionalmente sus cuentas) en osint_db. Wrapper cliente del service local `osint_db` (FastAPI + DuckDB single-writer) que mantiene el catalogo de perfiles del navegador usados para investigaciones multicuenta OSINT. En una sola llamada hace: 1. POST /api/browser-profile con la metadata del perfil (upsert idempotente). 2. Un POST /api/browser-profile/account por cada cuenta de la lista `accounts`. Funcion impura: hace red (HTTP al service). No lanza; devuelve un dict de estado. El service responde SIEMPRE HTTP 200 con body `{"status":...}` (se parsea el body). """ from browser._osint_db_client import post_json def browser_profile_register( profile_dir: str, label: str = "", persona: str = "", purpose: str = "", note_path: str = "", tags: list | None = None, notes: str = "", user_data_dir: str = "", status: str = "active", accounts: list | None = None, base_url: str = "http://127.0.0.1:8771", ) -> dict: """Registra o actualiza un perfil Chromium y sus cuentas en el catalogo osint_db. Args: profile_dir: nombre del directorio real del perfil Chromium (ej. "Profile 1", "Default", "osint_01"). Es la PK del perfil; el upsert es idempotente sobre el. label: etiqueta humana del perfil (ej. "Persona Maria - OSINT"). "" para omitir. persona: identidad/alias ficticio asociado al perfil. "" para omitir. purpose: proposito de la investigacion (ej. "rastreo cuentas falsas"). "" para omitir. note_path: ruta (rel al vault) de la nota OSINT ligada al perfil. "" para omitir. tags: lista de strings de etiquetas (ej. ["osint", "sock-puppet"]). None -> []. notes: notas libres sobre el perfil. "" para omitir. user_data_dir: directorio user-data-dir de Chromium si NO es el default del wrapper. "" -> el perfil hereda el default chromium-cdp al abrirlo. status: estado del perfil (active|archived|burned...). Default "active". accounts: lista de dicts de cuentas a registrar, cada uno {service, identity, secret_ref?, role?, status?, notes?}. None -> sin cuentas. `secret_ref` es una REFERENCIA al secreto (ej. "pass show osint/p1/gmail"), NUNCA el password en claro. base_url: base del service osint_db. Default http://127.0.0.1:8771. Returns: Caso ok: {"status":"ok", "profile_dir": str, "accounts": int (cuentas registradas con exito), "account_errors": list (errores por cuenta, vacia si todo OK)}. Caso error (fallo del POST del perfil): {"status":"error", "error": str}. """ try: profile_payload: dict = {"profile_dir": profile_dir, "status": status} if label: profile_payload["label"] = label if persona: profile_payload["persona"] = persona if purpose: profile_payload["purpose"] = purpose if note_path: profile_payload["note_path"] = note_path if tags: profile_payload["tags"] = list(tags) if notes: profile_payload["notes"] = notes if user_data_dir: profile_payload["user_data_dir"] = user_data_dir resp = post_json(base_url, "/api/browser-profile", profile_payload) if resp.get("status") != "ok": return { "status": "error", "error": resp.get("error", f"el service rechazo el perfil: {resp}"), } registered_accounts = 0 account_errors: list = [] for acc in accounts or []: if not isinstance(acc, dict) or not acc.get("service") or not acc.get("identity"): account_errors.append( {"account": acc, "error": "cuenta requiere al menos {service, identity}"} ) continue acc_payload = {"profile_dir": profile_dir} for key in ("service", "identity", "secret_ref", "role", "status", "notes"): if acc.get(key): acc_payload[key] = acc[key] acc_resp = post_json(base_url, "/api/browser-profile/account", acc_payload) if acc_resp.get("status") == "ok": registered_accounts += 1 else: account_errors.append( { "account": {"service": acc.get("service"), "identity": acc.get("identity")}, "error": acc_resp.get("error", str(acc_resp)), } ) return { "status": "ok", "profile_dir": profile_dir, "accounts": registered_accounts, "account_errors": account_errors, } except Exception as e: # noqa: BLE001 - contrato: nunca lanzar return {"status": "error", "error": f"{type(e).__name__}: {e}"} if __name__ == "__main__": # Smoke contra un puerto muerto: ejercita la degradacion graceful (service inaccesible). res = browser_profile_register( "Profile 1", label="Persona Maria - OSINT", persona="maria_ficticia", purpose="rastreo cuentas falsas", tags=["osint", "sock-puppet"], accounts=[{"service": "gmail", "identity": "maria@example.com", "secret_ref": "pass show osint/p1/gmail"}], base_url="http://127.0.0.1:1", ) assert res["status"] == "error", res print("browser_profile_register smoke OK (service caido -> status error)") print(f" {res}")