"""Attache Playwright à TON Chrome (via CDP) au lieu de lancer un navigateur piloté. Pourquoi : un navigateur lancé par Playwright porte des drapeaux d'automatisation que HelloFresh peut rejeter au login. Ici, c'est TOI qui lances Chrome (session normale, le login marche), et on s'y attache en lecture pour : - capturer le trafic gateway (dont l'appel d'écriture de sélection de recettes) ; - exporter ta session (cookies) -> .session/storage_state.json, réutilisable en headless. Prérequis — lance Chrome avec le port de debug et un PROFIL DÉDIÉ (Chrome 149 interdit le debug sur le profil par défaut). Ta fenêtre Chrome habituelle peut rester ouverte : "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \\ --remote-debugging-port=9222 --user-data-dir="$HOME/.hf-chrome-debug" \\ https://www.hellofresh.fr/my-account/deliveries/menu Puis, dans cette fenêtre : connecte-toi (email + mot de passe) et change une recette. Usage : python tools/attach_capture.py # attend ~5 min puis sauvegarde ANTICOCO_DISCOVER_WAIT=600 python tools/attach_capture.py """ from __future__ import annotations import json import os import sys import time from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) from playwright.sync_api import sync_playwright # noqa: E402 from hellofresh import auth # noqa: E402 CDP_URL = os.environ.get("ANTICOCO_CDP_URL", "http://localhost:9222") LOG_PATH = auth.SESSION_DIR / "discovery_log_cdp.json" STATE_PATH = auth.SESSION_DIR / "storage_state.json" DISCOVERED = ROOT / "config" / "endpoints_discovered.json" def _connect(pw, timeout_s: int = 60): """Connexion CDP avec attente que Chrome (port debug) soit disponible.""" deadline = time.time() + timeout_s last = None while time.time() < deadline: try: return pw.chromium.connect_over_cdp(CDP_URL) except Exception as e: # Chrome pas encore prêt last = e print(f" ...en attente de Chrome sur {CDP_URL}") time.sleep(3) raise RuntimeError(f"Impossible de se connecter à {CDP_URL} : {last}") def main() -> None: auth.SESSION_DIR.mkdir(parents=True, exist_ok=True) requests_seen: list[dict] = [] def on_request(req): if not auth._is_gateway_request(req.url): return entry = {"method": req.method, "url": req.url, "has_auth": bool(req.headers.get("authorization"))} if req.method in ("POST", "PUT", "PATCH"): try: entry["post_data"] = req.post_data except Exception: entry["post_data"] = None requests_seen.append(entry) print(f" [{req.method}] {req.url}") with sync_playwright() as pw: print(f"Connexion à Chrome via {CDP_URL} ...") browser = _connect(pw) contexts = browser.contexts print(f"Connecté. {len(contexts)} contexte(s).") def wire_page(page): try: page.on("request", on_request) except Exception: pass for ctx in contexts: for p in ctx.pages: wire_page(p) ctx.on("page", wire_page) wait_s = int(os.environ.get("ANTICOCO_DISCOVER_WAIT", "300")) print(f">>> Connecte-toi et change une recette. Capture {wait_s}s max " "(Ctrl-C pour finir plus tôt).") try: waited = 0 while waited < wait_s and browser.is_connected(): time.sleep(2) waited += 2 except KeyboardInterrupt: pass # Export de la session (cookies) tant que la connexion tient. try: if browser.contexts: browser.contexts[0].storage_state(path=str(STATE_PATH)) print(f"Session exportée -> {STATE_PATH}") except Exception as e: print("Export storage_state impossible:", e) _save_report(requests_seen) def _save_report(requests_seen: list[dict]) -> None: LOG_PATH.write_text(json.dumps(requests_seen, indent=2, ensure_ascii=False), encoding="utf-8") print(f"\n{len(requests_seen)} requêtes gateway -> {LOG_PATH}") noise = ("otlp/traces", "/login", "auth/email", "translations", "/bot/") writes = [r for r in requests_seen if r["method"] in ("POST", "PUT", "PATCH") and not any(n in r["url"] for n in noise)] skeleton = { "_comment": "Brouillon issu de attach_capture (ne remplace pas config/endpoints.json).", "set_selection_candidates": [f"[{r['method']}] {r['url']}" for r in writes], } DISCOVERED.write_text(json.dumps(skeleton, indent=2, ensure_ascii=False), encoding="utf-8") print(f"Candidats d'écriture ({len(writes)}) -> {DISCOVERED}") for r in writes: print(f" [{r['method']}] {r['url']}") if __name__ == "__main__": main()