AntiCoco: serveur MCP HelloFresh sans noix de coco
- Auth Playwright (login local, session persistee, capture du bearer token) - Client httpx vers l'API interne (endpoints via discover_api.py) - Filtre d'exclusion insensible aux accents (coco & co) - Serveur FastMCP (streamable-http) + outils hf_* - Docker + compose pour deploiement homelab
This commit is contained in:
106
tools/discover_api.py
Normal file
106
tools/discover_api.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""ÉTAPE 0 — Découverte de l'API interne HelloFresh.
|
||||
|
||||
Lance un navigateur visible, te laisse te connecter et naviguer (menu de la semaine,
|
||||
sélection de recettes), puis enregistre TOUTES les requêtes vers le gateway pour en
|
||||
déduire les 3 endpoints utiles :
|
||||
1. abonnement + semaines éditables
|
||||
2. menu d'une semaine (recettes / ingrédients / allergènes)
|
||||
3. enregistrement de la sélection de recettes
|
||||
|
||||
Usage :
|
||||
ANTICOCO_HEADLESS=0 python tools/discover_api.py
|
||||
|
||||
Pendant que la fenêtre est ouverte :
|
||||
- connecte-toi,
|
||||
- ouvre le menu de la semaine,
|
||||
- (optionnel) change une recette pour capturer l'appel d'écriture,
|
||||
puis reviens dans le terminal et appuie sur Entrée pour écrire le rapport.
|
||||
|
||||
Sortie :
|
||||
- .session/discovery_log.json : toutes les requêtes gateway observées (debug complet)
|
||||
- config/endpoints.json : squelette pré-rempli à compléter/valider à la main
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
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
|
||||
|
||||
LOG_PATH = auth.SESSION_DIR / "discovery_log.json"
|
||||
ENDPOINTS_PATH = ROOT / "config" / "endpoints.json"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
auth.SESSION_DIR.mkdir(parents=True, exist_ok=True)
|
||||
requests_seen: list[dict] = []
|
||||
|
||||
with sync_playwright() as pw:
|
||||
ctx = pw.chromium.launch_persistent_context(
|
||||
user_data_dir=str(auth.PROFILE_DIR),
|
||||
headless=False, # toujours visible : c'est une étape interactive
|
||||
locale="fr-FR",
|
||||
viewport={"width": 1280, "height": 900},
|
||||
)
|
||||
page = ctx.pages[0] if ctx.pages else ctx.new_page()
|
||||
|
||||
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}")
|
||||
|
||||
page.on("request", on_request)
|
||||
|
||||
print("Ouvre le menu de la semaine, change une recette si tu veux capturer l'écriture.")
|
||||
page.goto(auth.BASE_URL + "/my-account", wait_until="domcontentloaded", timeout=30000)
|
||||
|
||||
try:
|
||||
input("\n>>> Quand tu as fini de naviguer, appuie sur Entrée pour générer le rapport...\n")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
pass
|
||||
|
||||
ctx.close()
|
||||
|
||||
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 enregistrées dans {LOG_PATH}")
|
||||
|
||||
# Heuristiques pour pré-remplir le squelette d'endpoints.
|
||||
def find(method: str, *needles: str) -> str:
|
||||
for r in requests_seen:
|
||||
if r["method"] != method:
|
||||
continue
|
||||
if all(n in r["url"].lower() for n in needles):
|
||||
return r["url"]
|
||||
return ""
|
||||
|
||||
skeleton = {
|
||||
"_comment": "Endpoints HelloFresh confirmés via discovery. Compléter/valider à la main. Utiliser {week} comme placeholder pour le handle de semaine.",
|
||||
"base": "",
|
||||
"weeks": find("GET", "subscription") or find("GET", "deliveries") or "",
|
||||
"menu": find("GET", "menu") or find("GET", "courses") or "",
|
||||
"set_selection": find("PUT", "menu") or find("POST", "menu") or "",
|
||||
}
|
||||
ENDPOINTS_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
ENDPOINTS_PATH.write_text(json.dumps(skeleton, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"Squelette d'endpoints écrit dans {ENDPOINTS_PATH} — à vérifier avant usage.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user