SPB Git forge

spb/sorti-ka

Public

Toutes les sorties et tous les événements du Québec, un seul endroit — 7 connecteurs, fiches SSR, design Groupe KA.

58commits 1branches 0releases
13.7 MBsize
maindefault branch
17 days agolast push
HTML 82.9% Python 15.2% TypeScript 0.9% JavaScript 0.7%
3.6 KB · 92 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/troisrivieres.py : connecteur Ville de Trois-Rivières — activités5#   Source    : https://www.v3r.net/activites-et-loisirs/calendrier-des-activites6#               (l'API REST WP /wp-json est verrouillée en 401 — consigné7#               2026-08-18 — mais le calendrier AngularJS interroge un8#               endpoint AJAX public du thème).9#   Extraction: POST JSON /ajax/wp-queries/archive-activites.php (même appel10#               que le frontend, découvert dans ActivitesController.js) —11#               1 requête : title, startDate/endDate « dd/mm/yyyy »,12#               description (= NOM DU LIEU, vérifié sur les 17 fiches live13#               2026-08-25), imageUrl, url de la fiche. Heure non publiée14#               par l'endpoint → jamais inventée.15#   Accès     : endpoint public du site municipal (aucune auth) ; robots.txt16#               n'interdit pas /ajax/. GET/POST directs, identification17#               honnête, 1 requête par sync.18#   Prix      : non publié par l'endpoint → jamais inventé.19# -----------------------------------------------------------------------------20from __future__ import annotations2122import re23import time2425from ..normalize import clean_text26from ..schema import Event27from .base import BaseConnector2829API = "https://www.v3r.net/ajax/wp-queries/archive-activites.php"30QUERY = {31    "postType": "activites",32    "orderBy": "meta_value date",33    "order": "DESC",34    "filters": {"category": "", "startDate": "", "endDate": ""},35}36_DDMMYYYY_RE = re.compile(r"^(\d{1,2})/(\d{1,2})/(\d{4})$")373839def _iso(raw: str | None) -> str | None:40    """« 25/08/2026 » (format de l'endpoint) → « 2026-08-25 »."""41    m = _DDMMYYYY_RE.match(str(raw or "").strip())42    if not m:43        return None44    d, mo, y = int(m.group(1)), int(m.group(2)), int(m.group(3))45    if not (1 <= d <= 31 and 1 <= mo <= 12 and 1990 <= y <= 2100):46        return None47    return f"{y:04d}-{mo:02d}-{d:02d}"484950class TroisRivieresConnector(BaseConnector):51    source_id = "troisrivieres"52    request_delay = 0.85354    def _post_json(self) -> dict:55        self._throttle()56        resp = self.session.post(API, json={"query": QUERY},57                                 timeout=self.timeout)58        self._last_request = time.time()59        resp.raise_for_status()60        return resp.json()6162    def fetch(self) -> list[Event]:63        payload = self._post_json()64        events: list[Event] = []65        seen: set[str] = set()66        for rec in payload.get("data") or []:67            title = clean_text(rec.get("title") or "")68            url = rec.get("url") or ""69            start = _iso(rec.get("startDate"))70            if not title or not url or not start:71                continue72            slug = url.rstrip("/").rsplit("/", 1)[-1]73            ext = f"{slug}-{start}"74            if ext in seen:75                continue76            seen.add(ext)77            events.append(Event(78                source=self.source_id,79                external_id=ext,80                url=url,81                title=title,82                raw_categories=[title],83                venue=clean_text(rec.get("description") or ""),84                city="Trois-Rivières",85                region="Mauricie",86                start_date=start,87                end_date=_iso(rec.get("endDate")) or start,88                organizer="Ville de Trois-Rivières",89                image=rec.get("imageUrl") or "",90            ))91        return events92