Créa·Ka — annuaire public cross-plateforme des créateurs de contenu québécois (crea-ka.com)
Python 73.6%
HTML 13.2%
TypeScript 6%
JavaScript 4.5%
CSS 1.7%
Dockerfile 0.6%
1# ==============================================================================2# Author: Simon-Pierre Boucher <contact@spboucher.ai>3# File: creaka/connectors/balados_itunes.py4# Desc: Connecteur DÉCOUVERTE — balados québécois via l'API de recherche5# iTunes/Apple Podcasts (OFFICIELLE, gratuite, sans clé). Palier 2-36# (§9). Filtre conservateur : marqueur québécois requis dans le nom du7# balado ou de l'artiste.8# ==============================================================================9"""Découverte des podcasteurs québécois via l'API iTunes Search.1011`itunes.apple.com/search?media=podcast` retourne pour chaque balado : nom,12artiste, pochette (artworkUrl600), genres, nb d'épisodes, flux RSS et page13Apple Podcasts. Le balado est le compte SOURCE de la fiche → profil_source.14"""15from __future__ import annotations1617from datetime import datetime, timedelta, timezone1819from ..identity import account20from ..normalize import slugify21from ..schema import Creator22from .base import BaseConnector23from .youtube_recherche import is_quebec2425SEARCH_URL = "https://itunes.apple.com/search"2627# au-delà de 12 mois sans épisode : balado dormant (flag public dans le doc ;28# la fiche reste dans l'annuaire — §13, on ne supprime jamais brutalement)29DORMANT_AFTER_DAYS = 365303132def _dormant(release_date: str | None) -> bool:33 """Vrai si le dernier épisode (releaseDate iTunes) date de > 12 mois."""34 if not release_date:35 return False36 try:37 last = datetime.fromisoformat(release_date.replace("Z", "+00:00"))38 except ValueError:39 return False40 return datetime.now(timezone.utc) - last > timedelta(days=DORMANT_AFTER_DAYS)4142QUERIES = [43 "québécois", "québécoise", "balado québec", "podcast québec", "montréal",44 "humour québécois", "balado montréal", "saguenay", "gatineau", "sherbrooke",45 "trois-rivières", "ville de québec balado", "balado gaspésie", "mauricie",46 "balado estrie", "rimouski", "abitibi", "lévis balado", "laval balado",47 "balado", "podcast montréal", "balado hockey", "canadiens de montréal",48 "histoire du québec", "crime québec", "balado finance québec",49 "balado maman québec", "balado couple québec", "balado santé québec",50 "balado sport québec", "balado politique québec", "balado culture québec",51 "balado musique québec", "balado cinéma québec", "balado jeux vidéo québec",52 "balado plein air québec", "balado immobilier québec", "balado entrepreneur québec",53 "outaouais", "lanaudière", "laurentides balado", "montérégie",54 "côte-nord balado", "charlevoix balado", "beauce balado",55 "drummondville", "victoriaville balado", "granby balado", "chicoutimi",56 # longue traîne : villes + créneaux (nano/micro podcasteurs locaux)57 "longueuil", "terrebonne balado", "repentigny balado", "saint-jérôme",58 "saint-hyacinthe", "shawinigan", "rouyn-noranda", "val-d'or",59 "sept-îles", "baie-comeau", "alma balado", "matane", "rivière-du-loup",60 "sorel-tracy", "magog balado", "joliette balado", "châteauguay",61 "balado chasse québec", "balado pêche québec", "balado agriculture québec",62 "balado construction québec", "balado véhicule québec", "balado techno québec",63 "balado ados québec", "balado église québec", "balado histoire vraie québec",64 "balado humour absurde québec", "balado société québec", "balado femmes québec",65 "balado papa québec", "balado éducation québec", "balado science québec",66 "balado littérature québec", "balado poésie québec", "balado bd québec",67 "balado jeux de société québec", "balado vin québec", "balado bière québec",68 # vague 4 : villes restantes + créneaux supplémentaires69 "mascouche", "blainville balado", "brossard", "mirabel", "boucherville",70 "chambly", "varennes balado", "sainte-julie", "beloeil", "mont-tremblant",71 "sainte-adèle", "thetford mines", "la tuque", "gaspé", "roberval",72 "dolbeau", "amqui", "montmagny", "saint-georges beauce", "cowansville",73 "lachute", "salaberry-de-valleyfield", "sainte-thérèse", "l'assomption",74 "rawdon", "baie-saint-paul", "la malbaie", "chibougamau",75 "balado ia québec", "balado crypto québec", "balado marketing québec",76 "balado droit québec", "balado médecine québec", "balado psycho québec",77 "balado philosophie québec", "balado environnement québec",78 "balado autochtone", "balado voyage québec", "balado spiritualité québec",79 "balado true crime québec", "balado paranormal québec",80 "balado nostalgie québec", "balado télé québec", "balado wrestling québec",81 "balado lutte québec", "balado course québec", "balado vélo québec",82 "balado golf québec", "balado pêche à la mouche québec",83 "balado gestion québec", "balado rh québec", "balado photo québec",84 "balado musique émergente québec", "balado métal québec",85 "balado country québec", "balado horreur québec", "balado cinéma d'horreur",86 "balado animation québec", "balado improvisation québec",87]8889_GENRE_NICHE = {90 "Comedy": "humour", "Food": "bouffe-resto", "Sports": "sport-fitness",91 "Music": "musique", "Business": "finance-affaires", "Investing": "finance-affaires",92 "Health & Fitness": "sante-mieux-etre", "Education": "education",93 "True Crime": "actualite-opinion", "News": "actualite-opinion",94 "Politics": "actualite-opinion", "Society & Culture": "actualite-opinion",95 "TV & Film": "actualite-opinion", "Leisure": "gaming",96 "Video Games": "gaming", "Technology": "tech", "Arts": "arts",97 "Parenting": "famille-parentalite", "Kids & Family": "famille-parentalite",98 "Fiction": "arts", "Religion & Spirituality": "sante-mieux-etre",99 "Science": "education", "History": "education", "Wilderness": "plein-air",100}101102103class BaladosItunesConnector(BaseConnector):104 source_id = "balados-itunes"105 kind = "discovery"106 request_delay = 1.5107108 def fetch(self) -> list[Creator]:109 creators: list[Creator] = []110 seen: set[int] = set()111 for query in QUERIES:112 try:113 resp = self.get(SEARCH_URL, params={114 "media": "podcast", "term": query, "country": "CA",115 "limit": 200})116 results = resp.json().get("results") or []117 except Exception:118 continue119 for r in results:120 cid = r.get("collectionId")121 name = (r.get("collectionName") or "").strip()122 artist = (r.get("artistName") or "").strip()123 if not cid or cid in seen or not name:124 continue125 # filtre conservateur : marqueur QC dans le nom du balado/artiste126 if not is_quebec(f"{name} {artist}"):127 continue128 seen.add(cid)129 feed = r.get("feedUrl") or ""130 url = r.get("collectionViewUrl") or feed131 if not url:132 continue133 acc = account("podcast", str(cid), "profil_source", url=url)134 dormant = _dormant(r.get("releaseDate"))135 acc.metrics = {k: v for k, v in {136 "episodes": r.get("trackCount"),137 "genre": r.get("primaryGenreName"),138 "feed_url": feed, # site du balado (RSS)139 "artist_id": r.get("artistId"),140 "last_episode": r.get("releaseDate"),141 "dormant": dormant or None, # >12 mois sans épisode142 }.items() if v}143 # genres[] complets → niches §6.1 (le genre principal inclus)144 genres = [g for g in (r.get("genres") or []) if isinstance(g, str)]145 niches = sorted({_GENRE_NICHE[g] for g in146 [r.get("primaryGenreName"), *genres]147 if g in _GENRE_NICHE}) or ["actualite-opinion"]148 creators.append(Creator(149 source=self.source_id,150 external_id=str(cid),151 display_name=name,152 bio=f"Balado de {artist}." if artist and artist != name else "",153 niches=niches,154 creator_type="podcasteur",155 avatar_url=r.get("artworkUrl600") or r.get("artworkUrl100"),156 # §13 : plus de publication depuis 12 mois → inactive157 # (réversible : redevient actif dès un nouvel épisode)158 status="inactive" if dormant else "active",159 platforms=[acc],160 notes="Référencé sur Apple Podcasts (API de recherche officielle).",161 source_ids=[f"{self.source_id}:{cid}"],162 ))163 return creators164