spb/auto-ka Public
Python 81.9%
TypeScript 12.4%
CSS 5.5%
1# -----------------------------------------------------------------------------2# Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/clubautozone.py : Club Auto Zone (Longueuil) — WordPress sur la5# plateforme Convertus (thème « achilles » + plugin convertus-vms).6#7# Stratégie : la page inventaire est une app Vue qui charge les véhicules8# depuis l'API VMS (vms.prod.convertus.rocks) VIA le proxy PHP du site :9# /wp-content/plugins/convertus-vms/include/php/ajax-vehicles.php10# ?endpoint=<URL VMS encodée>&action=vms_data11# (l'API VMS directe répond 403 ; le proxy du site est la voie officielle).12# Réponse JSON riche : prix, km, VIN, moteur, couleurs, URL VDP… 30/page.13# -----------------------------------------------------------------------------14from __future__ import annotations1516import urllib.parse1718from ..schema import Vehicle19from .base import BaseConnector2021_VMS_API = "https://vms.prod.convertus.rocks/api/"22_PROXY = "/wp-content/plugins/convertus-vms/include/php/ajax-vehicles.php"232425def _num(value) -> float | None:26 try:27 v = float(value)28 return v if v > 0 else None29 except (TypeError, ValueError):30 return None313233class ClubAutoZone(BaseConnector):34 source_id = "clubautozone"35 base_url = "https://www.clubautozone.ca"36 dealer_name = "Club Auto Zone"37 city = "Longueuil"38 inventory_id = 2292 # globalVars.inventoryId du site (cp=…)39 request_delay: float = 1.04041 def _page(self, pg: int) -> dict:42 endpoint = (f"{_VMS_API}filtering/?cp={self.inventory_id}"43 f"&ln=fr&sc=used&pg={pg}&pc=100")44 url = (f"{self.base_url}{_PROXY}"45 f"?endpoint={urllib.parse.quote(endpoint, safe='')}"46 f"&action=vms_data")47 return self.get(url).json()4849 def fetch(self) -> list[Vehicle]:50 vehicles: list[Vehicle] = []51 seen: set[str] = set()52 pg = 153 while pg <= 40:54 data = self._page(pg)55 results = data.get("results") or []56 new = 057 for d in results:58 veh = self._to_vehicle(d)59 if veh is not None and veh.external_id not in seen:60 seen.add(veh.external_id)61 vehicles.append(veh)62 new += 163 if not results or new == 0: # page vide ou déjà tout vu64 break65 pg += 166 return vehicles6768 def _to_vehicle(self, d: dict) -> Vehicle | None:69 ext_id = str(d.get("vehicle_id") or d.get("ad_id") or "")70 if not ext_id:71 return None72 sale_class = str(d.get("sale_class") or "")73 if sale_class and "occasion" not in sale_class.lower() \74 and "used" not in sale_class.lower():75 return None7677 year = d.get("year") if isinstance(d.get("year"), int) else None78 title = " ".join(str(x) for x in79 (year, d.get("make"), d.get("model"),80 d.get("search_trim") or d.get("trim")) if x)8182 price = (_num(d.get("final_price")) or _num(d.get("internet_price"))83 or _num(d.get("asking_price")))84 km = _num(d.get("odometer"))8586 image = d.get("image") or {}87 images = []88 if isinstance(image, dict) and image.get("image_original") \89 and not d.get("placeholder_image"):90 images = [image["image_original"]]9192 try:93 doors = int(d.get("doors")) or None94 except (TypeError, ValueError):95 doors = None96 try:97 seats = int(d.get("passenger")) or None98 except (TypeError, ValueError):99 seats = None100101 details = {k: d[k] for k in ("days_on_lot", "certified", "cylinder",102 "engine_displacement", "vehicle_class")103 if d.get(k) not in (None, "", 0)}104105 return Vehicle(106 source=self.source_id,107 external_id=ext_id,108 url=str(d.get("vdp_url") or f"{self.base_url}/vehicles/used/"),109 title=title,110 make=str(d.get("make") or ""),111 model=str(d.get("model") or ""),112 trim=str(d.get("search_trim") or d.get("trim") or ""),113 year=year,114 price=price,115 price_label=(f"{price:,.0f} $".replace(",", " ") if price else ""),116 mileage_km=km,117 mileage_label=(f"{km:,.0f} km".replace(",", " ") if km else ""),118 transmission=str(d.get("transmission") or ""),119 fuel=str(d.get("fuel_type") or ""),120 drivetrain=str(d.get("drive_train") or ""),121 body_type=str(d.get("body_style") or ""),122 exterior_color=str(d.get("exterior_color") or ""),123 interior_color=str(d.get("interior_color") or ""),124 engine=str(d.get("engine") or ""),125 doors=doors,126 seats=seats,127 vin=str(d.get("vin") or ""),128 stock_number=str(d.get("stock_number") or ""),129 dealer_name=self.dealer_name,130 city=self.city,131 details=details,132 images=images,133 )134