- arte_api.py : GENRE_PAGES devient une liste de (nom, url), chaque concert reçoit un champ "categories" avec ses genres d'appartenance - main.py : endpoint /api/categories + param ?category= sur /api/concerts - index.html : barre de pills catégories (Tout + 10 genres) - style.css : styles .cat-bar / .cat-pill avec pill active en or - app.js : chargement dynamique des pills, filtre catégorie dans le state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+42
-26
@@ -16,21 +16,25 @@ PLAYER_API = "https://api.arte.tv/api/player/v2/config/fr/{pid}"
|
||||
SEARCH_URL = "https://www.arte.tv/fr/search/?q={q}"
|
||||
|
||||
GENRE_PAGES = [
|
||||
"https://www.arte.tv/fr/p/pop-rock/",
|
||||
"https://www.arte.tv/fr/p/classique/",
|
||||
"https://www.arte.tv/fr/p/musiques-electroniques/",
|
||||
"https://www.arte.tv/fr/p/jazz",
|
||||
"https://www.arte.tv/fr/p/arts-de-la-scene",
|
||||
"https://www.arte.tv/fr/p/hip-hop",
|
||||
"https://www.arte.tv/fr/p/metal",
|
||||
"https://www.arte.tv/fr/p/opera",
|
||||
"https://www.arte.tv/fr/p/world",
|
||||
"https://www.arte.tv/fr/p/musique-baroque/",
|
||||
# arte-concert pages pour l'agenda et contenu exclusif
|
||||
("Pop & Rock", "https://www.arte.tv/fr/p/pop-rock/"),
|
||||
("Classique", "https://www.arte.tv/fr/p/classique/"),
|
||||
("Electro", "https://www.arte.tv/fr/p/musiques-electroniques/"),
|
||||
("Jazz", "https://www.arte.tv/fr/p/jazz"),
|
||||
("Arts de la scène", "https://www.arte.tv/fr/p/arts-de-la-scene"),
|
||||
("Hip-hop", "https://www.arte.tv/fr/p/hip-hop"),
|
||||
("Metal", "https://www.arte.tv/fr/p/metal"),
|
||||
("Opéra", "https://www.arte.tv/fr/p/opera"),
|
||||
("World", "https://www.arte.tv/fr/p/world"),
|
||||
("Baroque", "https://www.arte.tv/fr/p/musique-baroque/"),
|
||||
]
|
||||
|
||||
EXTRA_PAGES = [
|
||||
"https://www.arte.tv/fr/arte-concert/agenda/",
|
||||
"https://www.arte.tv/fr/arte-concert/",
|
||||
]
|
||||
|
||||
CATEGORIES = [name for name, _ in GENRE_PAGES]
|
||||
|
||||
_HEADERS = {
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
|
||||
@@ -88,14 +92,23 @@ def _metadata_for_pid(pid: str) -> dict | None:
|
||||
|
||||
|
||||
def _fetch_all_sync() -> list[dict]:
|
||||
all_ids: set[str] = set()
|
||||
for url in GENRE_PAGES:
|
||||
id_cats: dict[str, list[str]] = {}
|
||||
for name, url in GENRE_PAGES:
|
||||
ids = _prog_ids_from_page(url)
|
||||
logger.info(" %s → %d IDs", name, len(ids))
|
||||
for pid in ids:
|
||||
id_cats.setdefault(pid, []).append(name)
|
||||
|
||||
all_ids: set[str] = set(id_cats)
|
||||
for url in EXTRA_PAGES:
|
||||
ids = _prog_ids_from_page(url)
|
||||
logger.info(" %s → %d IDs", url.split("/fr/")[1], len(ids))
|
||||
all_ids |= ids
|
||||
logger.info("Total unique programme IDs: %d", len(all_ids))
|
||||
|
||||
concerts = _resolve_ids(all_ids)
|
||||
for c in concerts:
|
||||
c["categories"] = id_cats.get(c["id"], [])
|
||||
concerts.sort(key=lambda c: c.get("expiry") or "", reverse=True)
|
||||
return concerts
|
||||
|
||||
@@ -131,30 +144,33 @@ async def get_all_concerts() -> list[dict]:
|
||||
return _cache["data"]
|
||||
|
||||
|
||||
async def fetch_concerts(page: int = 1, search: str = "", page_size: int = 24) -> dict:
|
||||
async def fetch_concerts(page: int = 1, search: str = "", page_size: int = 24, category: str = "") -> dict:
|
||||
all_c = await get_all_concerts()
|
||||
|
||||
if category:
|
||||
all_c = [c for c in all_c if category in (c.get("categories") or [])]
|
||||
|
||||
cached_ids = {c["id"] for c in all_c}
|
||||
|
||||
if search:
|
||||
q = search.lower()
|
||||
# local filter
|
||||
local = [
|
||||
c for c in all_c
|
||||
if q in (c.get("title") or "").lower()
|
||||
or q in (c.get("subtitle") or "").lower()
|
||||
or q in (c.get("description") or "").lower()
|
||||
]
|
||||
# Arte search for IDs not in cache
|
||||
loop = asyncio.get_event_loop()
|
||||
remote_ids = await loop.run_in_executor(None, _search_sync, search)
|
||||
new_ids = remote_ids - cached_ids
|
||||
if new_ids:
|
||||
extra = await loop.run_in_executor(None, _resolve_ids, new_ids, None)
|
||||
# merge: local results first, then extras not already present
|
||||
local_ids = {c["id"] for c in local}
|
||||
for c in extra:
|
||||
if c["id"] not in local_ids:
|
||||
local.append(c)
|
||||
# Remote search only when no category filter (results have no category info)
|
||||
if not category:
|
||||
loop = asyncio.get_event_loop()
|
||||
remote_ids = await loop.run_in_executor(None, _search_sync, search)
|
||||
new_ids = remote_ids - cached_ids
|
||||
if new_ids:
|
||||
extra = await loop.run_in_executor(None, _resolve_ids, new_ids, None)
|
||||
local_ids = {c["id"] for c in local}
|
||||
for c in extra:
|
||||
if c["id"] not in local_ids:
|
||||
local.append(c)
|
||||
filtered = local
|
||||
else:
|
||||
filtered = all_c
|
||||
|
||||
Reference in New Issue
Block a user