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.9 KB · 96 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/rimouski.py : connecteur Ville de Rimouski — événements officiels5#   Source    : https://rimouski.ca/loisirs-culture/activites/evenements6#   Extraction: flux RSS ÉTENDU du calendrier (découvert sur la page) :7#               /rss/loisirs-culture/activites/evenements/ — ~240 items avec8#               balises maison <startDate> (ISO), <endDate>, <eventTime>9#               (« 13 h 30 »), <category>, <organizer>, <location>10#               (« Salle (adresse civique) »), <image>, <description>.11#               1 seule requête. Vérifié 2026-08-25.12#   Accès     : flux RSS public du site municipal, robots.txt permissif13#               (sitemap seulement). GET direct, identification honnête.14#   Prix      : non publié par le flux → jamais inventé.15# -----------------------------------------------------------------------------16from __future__ import annotations1718import html as _html19import re2021from ..normalize import clean_text, parse_date_iso, parse_time22from ..schema import Event23from .base import BaseConnector2425FEED = "https://rimouski.ca/rss/loisirs-culture/activites/evenements/"2627_ITEM_RE = re.compile(r"<item>(.*?)</item>", re.S)28# « Bibliothèque Lisette-Morin (110, rue de l'Évêché Est) » → salle + adresse29_LOC_RE = re.compile(r"^(.*?)\s*(?:\(([^)]+)\))?\s*$", re.S)303132def _tag(block: str, name: str) -> str:33    m = re.search(rf"<{name}>(.*?)</{name}>", block, re.S)34    return _html.unescape(m.group(1)).strip() if m else ""353637class RimouskiConnector(BaseConnector):38    source_id = "rimouski"3940    def _parse_feed(self, xml: str) -> list[dict]:41        rows = []42        for block in _ITEM_RE.findall(xml):43            link = _tag(block, "link")44            title = clean_text(_tag(block, "title"))45            start = parse_date_iso(_tag(block, "startDate"))46            if not link or not title or not start:47                continue48            venue = address = ""49            m = _LOC_RE.match(_tag(block, "location"))50            if m:51                venue = clean_text(m.group(1))52                address = clean_text(m.group(2) or "")53            rows.append({54                "link": link,55                "title": title,56                "start": start,57                "end": parse_date_iso(_tag(block, "endDate")),58                "time": parse_time(_tag(block, "eventTime")),59                "description": clean_text(_tag(block, "description")),60                "category": clean_text(_tag(block, "category")),61                "organizer": clean_text(_tag(block, "organizer")),62                "venue": venue,63                "address": address,64                "image": _tag(block, "image"),65            })66        return rows6768    def fetch(self) -> list[Event]:69        rows = self._parse_feed(self.get(FEED).text)70        events: list[Event] = []71        seen: set[str] = set()72        for r in rows:73            slug = r["link"].rstrip("/").rsplit("/", 1)[-1]74            ext = f"{slug}-{r['start']}"75            if ext in seen:76                continue77            seen.add(ext)78            events.append(Event(79                source=self.source_id,80                external_id=ext,81                url=r["link"],82                title=r["title"],83                description=r["description"],84                raw_categories=[r["category"]] if r["category"] else [r["title"]],85                venue=r["venue"],86                address=r["address"],87                city="Rimouski",88                region="Bas-Saint-Laurent",89                start_date=r["start"],90                start_time=r["time"],91                end_date=r["end"] or r["start"],92                organizer=r["organizer"],93                image=r["image"],94            ))95        return events96