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/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