# ----------------------------------------------------------------------------- # Sorti-Ka — Agrégateur de sorties & événements (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/granby.py : connecteur Ville de Granby — calendrier des événements # Source : https://www.granby.ca/fr/calendrier-evenements (Drupal). # Extraction: la Ville ne publie PAS de calendrier structuré (jsonapi # désactivé, page « actualités/événements » vide côté serveur, # vérifié 2026-08-25) — la page calendrier est une liste # ÉDITORIALE rendue serveur : blocs riches
de dates FR (« Les 22 et 23 mai # 2026 », « Du 28 au 30 mai 2026 ») et d'un lien « Découvrez # l'événement » (souvent externe, vers l'organisateur). # Parsing tolérant : un bloc sans date parseable est ignoré. # Volume faible (~15-25 événements saisonniers), 1 requête. # Accès : site municipal public, GET direct throttlé, identification # honnête ; robots.txt permissif. # Prix : non publié par la source → jamais inventé. Lieu/heure non # publiés dans la liste → laissés vides. # ----------------------------------------------------------------------------- from __future__ import annotations import html as _html import re from ..normalize import _FR_MONTHS, clean_text, strip_accents # type: ignore from ..schema import Event from .base import BaseConnector LIST_URL = "https://www.granby.ca/fr/calendrier-evenements" _BLOCK_RE = re.compile( r"
avec une date parseable = la plage de l'événement start = end = None for p in re.findall(r"
]*>(.*?)
", body, re.S): start, end = _range(_html.unescape(re.sub(r"<[^>]+>", " ", p))) if start: break if not start: continue link = _LINK_RE.search(body) website = (link.group(1) or link.group(2)).strip() if link else "" rows.append({"title": title, "start": start, "end": end, "website": website}) return rows def fetch(self) -> list[Event]: rows = self._parse_page(self.get(LIST_URL).text) events: list[Event] = [] seen: set[str] = set() for r in rows: slug = re.sub(r"[^a-z0-9]+", "-", strip_accents(r["title"]).lower()).strip("-") ext = f"{slug}-{r['start']}" if ext in seen: continue seen.add(ext) events.append(Event( source=self.source_id, external_id=ext, url=r["website"] or LIST_URL, title=r["title"], raw_categories=[r["title"]], city="Granby", region="Estrie", start_date=r["start"], end_date=r["end"] or r["start"], organizer="Ville de Granby", website=r["website"], )) return events