diff --git a/silly_rss.py b/silly_rss.py index 09e0996..0339a4f 100644 --- a/silly_rss.py +++ b/silly_rss.py @@ -14,6 +14,7 @@ import json import re import subprocess import sys +import unicodedata from datetime import datetime, timezone from email.utils import format_datetime from xml.sax.saxutils import escape @@ -36,9 +37,6 @@ MAX_PAGES = 2 # jusqu'a 40 items par flux NEWS_API = f"{SITE}/actualites/@results" EVENTS_API = f"{SITE}/agenda/@results" -NEWS_LINK_TMPL = f"{SITE}/actualites?news={{id}}" -EVENT_LINK_TMPL = f"{SITE}/agenda?event={{id}}" - def fetch_json(url: str) -> dict: result = subprocess.run( @@ -72,6 +70,42 @@ def strip_html(text: str | None) -> str: return re.sub(r"\s+", " ", text).strip() +def slugify(text: str) -> str: + """ + Reproduit le slug d'URL genere par le site (cosmetique : le routeur JS + de silly.be lit en realite le parametre `u` pour savoir quel item + afficher, pas ce segment de chemin - mais autant coller a l'URL + "officielle" telle qu'on la voit dans la barre d'adresse). + """ + ascii_text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode("ascii") + ascii_text = ascii_text.lower() + ascii_text = re.sub(r"[^a-z0-9]+", "-", ascii_text) + return ascii_text.strip("-") + + +def attachments_html(item: dict) -> str: + """HTML des liens vers les pieces jointes (PDF, etc.) d'un item.""" + parts = [] + for sub in item.get("items") or []: + file_info = sub.get("file") or {} + url = file_info.get("download") or sub.get("targetUrl") + if not url: + continue + label = sub.get("title") or url.rsplit("/", 1)[-1] + parts.append(f'

{html.escape(label)}

') + return "".join(parts) + + +def full_body_html(item: dict) -> str: + """Corps HTML complet (texte riche + pieces jointes), repli sur le resume court.""" + text_fr = item.get("text_fr") + body = (text_fr.get("data") or "").strip() if isinstance(text_fr, dict) else "" + if not body: + summary = strip_html(item.get("description_fr") or item.get("description")) + body = f"

{html.escape(summary)}

" if summary else "" + return body + attachments_html(item) + + def parse_dt(value: str | None) -> datetime | None: if not value: return None @@ -91,13 +125,11 @@ def build_news_items() -> list[str]: entries = [] for item in fetch_all_items(NEWS_API): item_id = item.get("id", "") - link = NEWS_LINK_TMPL.format(id=item_id) title = item.get("title_fr") or item.get("title") or "(sans titre)" - summary = strip_html(item.get("description_fr") or item.get("description")) - body = strip_html(item.get("text_fr", {}).get("data") if isinstance(item.get("text_fr"), dict) else None) - description = summary or body - pub_dt = parse_dt(item.get("effective") or item.get("created")) guid = item.get("UID") or item_id + link = f"{SITE}/actualites/{slugify(title)}?u={guid}" + description = full_body_html(item) + pub_dt = parse_dt(item.get("effective") or item.get("created")) entries.append( " \n" f" {escape(title)}\n" @@ -132,13 +164,12 @@ def build_event_items() -> list[str]: entries = [] for item in fetch_all_items(EVENTS_API): event_id = item.get("@id", "").rstrip("/").split("/")[-1] - link = EVENT_LINK_TMPL.format(id=event_id) title = item.get("title_fr") or item.get("title") or "(sans titre)" - summary = strip_html(item.get("description_fr") or item.get("description")) - meta = format_event_meta(item) - description = " | ".join(p for p in (meta, summary) if p) - pub_dt = parse_dt(item.get("created") or item.get("start")) guid = item.get("UID") or event_id + link = f"{SITE}/agenda/{slugify(title)}?u={guid}" + meta = format_event_meta(item) + description = (f"

{html.escape(meta)}

" if meta else "") + full_body_html(item) + pub_dt = parse_dt(item.get("created") or item.get("start")) entries.append( " \n" f" {escape(title)}\n"