# ----------------------------------------------------------------------------- # Sorti-Ka — Agrégateur de sorties & événements (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/lhjmq.py : LHJMQ — matchs des 12 équipes québécoises (Phase 3) # Source : API HockeyTech « modulekit » (lscluster.hockeytech.com) — le # flux JSON public qui motorise le site officiel theqmjhl.ca # (clé publique embarquée dans les sites de la ligue, largement # documentée). 2 GET/sync : saisons + calendrier de la saison # courante (~580 matchs, dont ~384 à domicile au Québec, # vérifié 2026-08-19 pour 2026-27). # Extraction: date + heure locale exactes, aréna (venue_name), équipes, # statut. Seuls les matchs À DOMICILE au Québec sont publiés # (Chicoutimi, Baie-Comeau, Victoriaville, Blainville-Boisbriand, # Sherbrooke, Gatineau, Québec, Rouyn-Noranda, Rimouski, # Val-d'Or, Shawinigan, Drummondville). Prix non publié par la # ligue → jamais inventé. Lien = fiche de match officielle. # Accès : usage index + lien vers theqmjhl.ca ; GET throttlés, # identification honnête. Couverture régionale forte # (Abitibi, Côte-Nord, Bas-Saint-Laurent, Saguenay…). # ----------------------------------------------------------------------------- from __future__ import annotations from datetime import date from ..normalize import clean_text, parse_time from ..schema import Event from .base import BaseConnector FEED = ("https://lscluster.hockeytech.com/feed/?feed=modulekit&view={view}" "&key=f322673b6bcae299&fmt=json&client_code=lhjmq&lang=fr{extra}") GAME_URL = "https://theqmjhl.ca/gamecentre/{gid}" # villes des équipes QC → la ville affichée sur la fiche Sorti-Ka QC_TEAM_CITIES = { "Chicoutimi": "Saguenay", "Baie-Comeau": "Baie-Comeau", "Victoriaville": "Victoriaville", "Blainville-Boisbriand": "Boisbriand", "Sherbrooke": "Sherbrooke", "Gatineau": "Gatineau", "Québec": "Québec", "Rouyn-Noranda": "Rouyn-Noranda", "Rimouski": "Rimouski", "Val-d'Or": "Val-d'Or", "Shawinigan": "Shawinigan", "Drummondville": "Drummondville", } def _label(team: dict, side: str) -> str: """« Remparts de Québec » à partir de city/nickname ; repli sur le nom brut.""" nick = clean_text(team.get(f"{side}_team_nickname") or "") city = clean_text(team.get(f"{side}_team_city") or "") if nick and city: return f"{nick} ({city})" return clean_text(team.get(f"{side}_team_name") or "") class LhjmqConnector(BaseConnector): source_id = "lhjmq" request_delay = 1.0 def _current_seasons(self) -> list[str]: """Saisons non terminées (régulière + séries), les plus récentes.""" data = self.get_json(FEED.format(view="seasons", extra="")) seasons = (data.get("SiteKit") or {}).get("Seasons") or [] today = date.today().isoformat() keep = [s["season_id"] for s in seasons if (s.get("end_date") or "") >= today] return sorted(keep, key=int)[-2:] # au plus 2 (régulière + séries) def fetch(self) -> list[Event]: events: list[Event] = [] seen: set[str] = set() for sid in self._current_seasons(): data = self.get_json(FEED.format( view="schedule", extra=f"&season_id={sid}")) for g in (data.get("SiteKit") or {}).get("Schedule") or []: home_city = clean_text(g.get("home_team_city") or "") if home_city not in QC_TEAM_CITIES: continue gid = str(g.get("game_id") or "") d = g.get("date_played") or "" if not gid or not d or gid in seen: continue seen.add(gid) home, vis = _label(g, "home"), _label(g, "visiting") heure = parse_time(g.get("schedule_time") or "") venue = clean_text(g.get("venue_name") or "") status = "" if str(g.get("game_status") or "").lower().startswith( ("cancel", "annul")): status = "cancelled" events.append(Event( source=self.source_id, external_id=gid, url=GAME_URL.format(gid=gid), title=f"LHJMQ : {vis} c. {home}", description=(f"Match de la Ligue de hockey junior " f"Maritimes Québec (LHJMQ) — {vis} en " f"visite chez {home}, {venue}."), raw_categories=["hockey", "sport"], venue=venue, city=QC_TEAM_CITIES[home_city], start_date=d, start_time=heure, status=status, organizer="LHJMQ", )) return events