Python 67%
TypeScript 18.2%
CSS 14.4%
1# -----------------------------------------------------------------------------2# Immo-Ka — Agrégateur de propriétés à vendre (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# hubfav.py : favoris unifiés « Mon univers Ka » — Immo·Ka n'a AUCUN stockage5# local de favoris : le hub Groupe KA (groupe-ka.com) est le magasin central.6# Chaque ♥ est poussé au hub (POST signé, SYNCHRONE : c'est l'action7# utilisateur) et la liste est relue du hub (GET signé, cache mémoire 30 s8# par ka_id, [] sur toute erreur). sig = HMAC-SHA256(KA_SSO_SECRET,9# "immo-ka.<ka_id>.<ts>") hex — même secret que le SSO (ts ±5 min).10# Config .env : KA_SSO_SECRET, KA_HUB_URL (optionnel).11# -----------------------------------------------------------------------------12from __future__ import annotations1314import hashlib15import hmac16import os17import threading18import time1920import requests2122CLIENT_ID = "house-ka"23KA_HUB_URL = os.environ.get("KA_HUB_URL", "https://www.groupe-ka.com").rstrip("/")24LIST_TTL = 30 # secondes — cache mémoire de hub_list par ka_id2526_cache: dict[str, tuple[float, list]] = {}27_lock = threading.Lock()282930def _sig(ka_id: str, ts: int) -> str | None:31 secret = os.environ.get("KA_SSO_SECRET")32 if not secret:33 return None34 return hmac.new(secret.encode(),35 f"{CLIENT_ID}.{ka_id}.{ts}".encode(),36 hashlib.sha256).hexdigest()373839def hub_toggle(ka_id: str, action: str, item: dict) -> bool:40 """Pousse un ♥ au hub (action « add » ou « remove ») — SYNCHRONE.4142 C'est l'action utilisateur : on attend la réponse du hub (timeout 6 s)43 pour que le GET qui suit reflète l'état réel. True si le hub a accepté.44 """45 if not ka_id or not str(ka_id).startswith("ka-"):46 return False # compte legacy non relié au hub47 ts = int(time.time())48 sig = _sig(ka_id, ts)49 if not sig:50 return False51 try:52 r = requests.post(f"{KA_HUB_URL}/api/sso/favorites", timeout=6, json={53 "client_id": CLIENT_ID, "ka_id": ka_id,54 "ts": str(ts), "sig": sig,55 "action": action, "item": item,56 })57 return r.status_code == 20058 except Exception:59 return False606162def hub_list(ka_id: str) -> list:63 """Favoris immo-ka du membre, lus du hub (GET signé, timeout 5 s).6465 Cache mémoire 30 s par ka_id ; [] sur toute erreur (réseau, 401, 5xx…)66 — l'erreur n'est PAS mise en cache pour réessayer au prochain appel.67 """68 if not ka_id:69 return []70 now = time.time()71 with _lock:72 hit = _cache.get(ka_id)73 if hit and now - hit[0] < LIST_TTL:74 return hit[1]75 ts = int(now)76 sig = _sig(ka_id, ts)77 if not sig:78 return []79 try:80 r = requests.get(81 f"{KA_HUB_URL}/api/sso/favorites",82 params={"client_id": CLIENT_ID, "ka_id": ka_id,83 "ts": ts, "sig": sig},84 timeout=5)85 if r.status_code != 200:86 return []87 favs = r.json().get("favorites")88 if not isinstance(favs, list):89 return []90 except Exception:91 return []92 with _lock:93 _cache[ka_id] = (now, favs)94 return favs959697def invalidate(ka_id: str) -> None:98 """Invalide le cache de hub_list après un toggle (état frais au prochain GET)."""99 with _lock:100 _cache.pop(ka_id, None)101