Toutes les sorties et tous les événements du Québec, un seul endroit — 7 connecteurs, fiches SSR, design Groupe KA.
HTML 82.9%
Python 15.2%
TypeScript 0.9%
JavaScript 0.7%
1# -----------------------------------------------------------------------------2# Sorti-Ka — Agrégateur de sorties & événements (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/gatineau.py : connecteur Ville de Gatineau — calendrier officiel5# Source : https://calendrier.gatineau.cloud — application calendrier6# dédiée de la Ville (gatineau.ca/calendrier y redirige).7# Extraction: API JSON interne POST /calendrier/json/evenements.aspx8# (même appel que le frontend, découvert dans scripts.js) —9# ~160 événements / ~250 occurrences en 1 requête : titre,10# description, image, lien officiel, occurrences horodatées11# (UTC → heure locale via utc_to_local), emplacements12# (nom, adresse, secteur). Vérifié 2026-08-25.13# Accès : application municipale publique, robots.txt « robots: all »14# sur la page, GET/POST directs, identification honnête.15# Prix : non publié par l'API → jamais inventé (les liens « Acheter16# des billets » sont conservés dans website).17# -----------------------------------------------------------------------------18from __future__ import annotations1920import os21import time2223from ..normalize import clean_text, utc_to_local24from ..schema import Event25from .base import BaseConnector2627BASE = "https://calendrier.gatineau.cloud"28API = BASE + "/calendrier/json/evenements.aspx"29FICHE = BASE + "/calendrier/evenement.aspx?langue=fr-ca&id_evenement={id}"30LIGNES = int(os.environ.get("GATINEAU_LIGNES", "500")) # plafond de lignes313233class GatineauConnector(BaseConnector):34 source_id = "gatineau"35 request_delay = 0.83637 def _post_json(self, data: dict):38 self._throttle()39 resp = self.session.post(API, data=data, timeout=self.timeout)40 self._last_request = time.time()41 resp.raise_for_status()42 return resp.json()4344 def fetch(self) -> list[Event]:45 records = self._post_json({"langue": "fr-ca", "lignes": str(LIGNES)})46 events: list[Event] = []47 seen: set[str] = set()48 for rec in records:49 ev_id = str(rec.get("id") or "")50 title = clean_text(rec.get("evenement") or "")51 if not ev_id or not title:52 continue53 description = clean_text(rec.get("description") or "")54 image = rec.get("image") or ""55 website = rec.get("url") or ""56 # billets éventuels (« Acheter des billets ») → site officiel57 if not website:58 for act in rec.get("actions") or []:59 if act.get("url"):60 website = act["url"]61 break62 cats = [clean_text(e.get("etiquette") or str(e))63 for e in rec.get("etiquettes") or [] if e]64 for occ in rec.get("occurrences") or []:65 ext = f"{ev_id}-{occ.get('id')}"66 if ext in seen:67 continue68 seen.add(ext)69 # horodatages publiés en UTC (« ...Z ») → date + heure locales70 start, start_time = utc_to_local(occ.get("date_heure_debut"))71 end, end_time = utc_to_local(occ.get("date_heure_fin"))72 if not start:73 continue74 venue = address = ""75 emp = (occ.get("emplacements") or [{}])[0]76 venue = clean_text(emp.get("emplacement_primaire")77 or emp.get("emplacement") or "")78 address = clean_text(emp.get("adresse") or "")79 desc = description or clean_text(occ.get("description") or "")80 mention = clean_text(occ.get("mention_speciale") or "")81 if mention:82 desc = f"{desc}\n{mention}".strip()83 events.append(Event(84 source=self.source_id,85 external_id=ext,86 url=FICHE.format(id=ev_id),87 title=title,88 description=desc,89 raw_categories=[c for c in cats if c] or [title],90 venue=venue,91 address=address,92 city="Gatineau",93 region="Outaouais",94 start_date=start,95 start_time=start_time,96 end_date=end or start,97 end_time=end_time,98 organizer="Ville de Gatineau",99 website=website,100 image=image,101 ))102 return events103