# ----------------------------------------------------------------------------- # Lou-Ka — Location court terme # connectors/rvmt.py : Rendez-vous Mont-Tremblant (rvmt.com) — agence de # gestion locative de la station Mont-Tremblant, ~108 condos, maisons et # maisons de ville (Plateau, Algonquin, Étoile du Matin, etc.). # # Méthode : le site (Nuxt 3) renvoie systématiquement 429 en direct → # UN SEUL appel Scrapfly (sans render_js) sur une page de complexe # (/fr/algonquin — la page liste n'embarque PAS les taxonomies, la page # complexe si). Tout l'inventaire (les 108 unités) vit dans le # ', h) if not m: return [] arr = json.loads(m.group(1)) # racine = le dict qui contient unités ET taxonomies (une page liste # n'aurait que units-rvmt : on garde le nœud le plus complet) root = None site_cfg = None fallback_i = None for i, v in enumerate(arr): if not isinstance(v, dict): continue if root is None and "units-rvmt" in v: if "features-rvmt" in v: root = self._resolve(arr, i) elif fallback_i is None: fallback_i = i elif site_cfg is None and {"pages", "nav", "slug"} <= set(v): site_cfg = self._resolve(arr, i) if root is not None and site_cfg is not None: break if root is None and fallback_i is not None: root = self._resolve(arr, fallback_i) if not root: return [] units = root.get("units-rvmt") or [] tax = {name: {t.get("value"): _fr(t) for t in (root.get(f"{name}-rvmt") or []) if isinstance(t, dict)} for name in ("features", "beds", "rooms", "areas", "amenities")} # id de complexe → slug d'URL (pages du site-config avec data={id}) complexes: dict[str, str] = {} for p in (site_cfg or {}).get("pages") or []: data = p.get("data") if isinstance(p, dict) else None if isinstance(data, dict) and set(data) == {"id"} and p.get("url"): complexes[str(data["id"])] = str(p["url"]).strip("/") listings: list[StListing] = [] vus: set[str] = set() for u in units: if not isinstance(u, dict): continue code = str(u.get("code") or "").strip() name = _html.unescape(str(u.get("name") or "")).strip() if not code or code in vus or not name: continue vus.add(code) comp = complexes.get(str(u.get("complex") or "")) url = (f"https://www.rvmt.com/fr/{comp}/{_slugify(name)}" if comp else LIST_URL) addr = u.get("address") or {} tags = u.get("tags") or [] ptype = next((_TYPES[t] for t in tags if t in _TYPES), "Condo") area = tax["areas"].get(u.get("area")) or "" # lits : somme des quantités des pièces déclarées beds = 0 for room in u.get("rooms") or []: for b in (room or {}).get("beds") or []: try: beds += int(b.get("quantity") or 0) except (TypeError, ValueError): pass amen: list[str] = [] for f in u.get("features") or []: t = tax["features"].get(f) if t and t not in amen: amen.append(t) for a in u.get("amenities") or []: t = tax["amenities"].get((a or {}).get("amenity")) if t and t not in amen: amen.append(t) desc = u.get("short_description") or {} description = _html.unescape(str(desc.get("fr") or desc.get("en") or "")).strip() price = u.get("min_price") price = float(price) if isinstance(price, (int, float)) \ and 20 <= price <= 20000 else None price_label = f"À partir de {price:g} $ / nuit" if price else "" gallery = [g for g in (u.get("gallery") or []) if isinstance(g, str) and g.startswith("https://")] details = {k: v for k, v in { "area": area, "complex": comp or "", "max_price_night": u.get("max_price"), "wheelchair_accessible": bool(u.get("wheelchair_accessible")), }.items() if v} line1 = str(addr.get("line_1") or "").strip() apt = str(addr.get("apt") or "").strip() listings.append(StListing( source=self.source_id, external_id=code, url=url, title=name, property_type=ptype, address=f"{line1}, app. {apt}" if line1 and apt else line1, city=str(addr.get("city") or "Mont-Tremblant").strip(), region="Laurentides", price_night=price, price_label=price_label, capacity=float(u["occupancy"]) if u.get("occupancy") else None, bedrooms=float(u["bedrooms"]) if u.get("bedrooms") is not None else None, beds=float(beds) if beds else None, bathrooms=float(u["bathrooms"]) if u.get("bathrooms") else None, pets="oui" if u.get("pets_allowed") else "non", citq=str(u.get("rental_license") or "").strip(), description=description[:5000], amenities=amen, details=details, images=gallery[:20], lat=addr.get("latitude"), lng=addr.get("longitude"), )) if limit and len(listings) >= limit: break return listings