# ----------------------------------------------------------------------------- # Immo-Ka — Agrégateur de maisons à vendre (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/via_capitale_agences.py : Via Capitale par SOUS-AGENCES (robuste) # # Le portail central viacapitalevendu.com est derrière Cloudflare (scrape # fragile, 1 page). Les sous-agences Via Capitale (Élite, Sélect, du Mont-Royal, # Accès…) tournent chacune sur la plateforme source.immo (plugin WordPress), # dont l'API publique (api-v1.source.immo) est stable et paginée — même # mécanique que Vendirect (voir sourceimmo.py). On génère une classe par # sous-agence depuis data/via_capitale_agencies.json ; le n° Centris # (ref_number) sert de clé de déduplication (voir web.py, sources « %_ag_% »). # # La config publique de chaque site est à # /wp-content/uploads/_sourceimmo/_configs.json (account_id/api_key/app_id + # default_view). Le registre est régénérable par scripts/refresh_via_capitale. # ----------------------------------------------------------------------------- from __future__ import annotations import json import re from pathlib import Path from .sourceimmo import SourceImmoConnector from ..schema import PropertyListing REGISTRY = Path(__file__).resolve().parent.parent.parent / "data" / "via_capitale_agencies.json" class ViaCapitaleAgenceConnector(SourceImmoConnector): """Sous-agence Via Capitale sur source.immo. Les sous-classes fournissent account_id / api_key / app_id / view_id / site_url / agency_name.""" agency_name = "Via Capitale" def fetch(self) -> list[PropertyListing]: # auto-guérison : recharger les identifiants depuis la config publique # du site (résilient si source.immo fait tourner ses UUID). try: cfg = self.get( f"{self.site_url.rstrip('/')}/wp-content/uploads/_sourceimmo/_configs.json" ).json() self.account_id = cfg.get("account_id", self.account_id) self.api_key = cfg.get("api_key", self.api_key) self.app_id = cfg.get("app_id", self.app_id) self.view_id = cfg.get("default_view", self.view_id) except Exception: pass # on garde les valeurs du registre return super().fetch() def _to_listing(self, it: dict) -> PropertyListing | None: lst = super()._to_listing(it) if lst is not None: lst.broker_name = self.agency_name # URL lisible vers la fiche de la sous-agence quand le slug est dispo ref = it.get("ref_number") or it.get("id") if ref and self.site_url: lst.url = f"{self.site_url.rstrip('/')}/propriete/{ref}" return lst def _load() -> list[dict]: try: return json.loads(REGISTRY.read_text(encoding="utf-8")) except Exception: return [] def _slug(banner: str) -> str: return "via_ag_" + re.sub(r"[^a-z0-9]+", "_", banner.lower()).strip("_") for _ag in _load(): if not all(_ag.get(k) for k in ("domain", "account", "api", "app", "view")): continue _sid = _slug(_ag["banner"]) _name = "Via Capitale " + _ag["banner"].replace("-", " ").title() globals()[_sid.upper()] = type( "".join(p.title() for p in _sid.split("_")), (ViaCapitaleAgenceConnector,), { "source_id": _sid, "account_id": _ag["account"], "api_key": _ag["api"], "app_id": _ag["app"], "view_id": _ag["view"], "site_url": f"https://{_ag['domain']}", "agency_name": _name, }, )