Apply same link/content fixes to the Python variant
Mirrors the fixes made to the PHP feeds: correct link format (/actualites|agenda/<slug>?u=<UID> instead of the wrong ?news=/?event=), and full rich-text body + attachment links instead of the short teaser only. Keeps both implementations in sync even though only the PHP one is currently deployed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+44
-13
@@ -14,6 +14,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import unicodedata
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from email.utils import format_datetime
|
from email.utils import format_datetime
|
||||||
from xml.sax.saxutils import escape
|
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"
|
NEWS_API = f"{SITE}/actualites/@results"
|
||||||
EVENTS_API = f"{SITE}/agenda/@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:
|
def fetch_json(url: str) -> dict:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
@@ -72,6 +70,42 @@ def strip_html(text: str | None) -> str:
|
|||||||
return re.sub(r"\s+", " ", text).strip()
|
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'<p><a href="{html.escape(url)}">{html.escape(label)}</a></p>')
|
||||||
|
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"<p>{html.escape(summary)}</p>" if summary else ""
|
||||||
|
return body + attachments_html(item)
|
||||||
|
|
||||||
|
|
||||||
def parse_dt(value: str | None) -> datetime | None:
|
def parse_dt(value: str | None) -> datetime | None:
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
@@ -91,13 +125,11 @@ def build_news_items() -> list[str]:
|
|||||||
entries = []
|
entries = []
|
||||||
for item in fetch_all_items(NEWS_API):
|
for item in fetch_all_items(NEWS_API):
|
||||||
item_id = item.get("id", "")
|
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)"
|
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
|
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(
|
entries.append(
|
||||||
" <item>\n"
|
" <item>\n"
|
||||||
f" <title>{escape(title)}</title>\n"
|
f" <title>{escape(title)}</title>\n"
|
||||||
@@ -132,13 +164,12 @@ def build_event_items() -> list[str]:
|
|||||||
entries = []
|
entries = []
|
||||||
for item in fetch_all_items(EVENTS_API):
|
for item in fetch_all_items(EVENTS_API):
|
||||||
event_id = item.get("@id", "").rstrip("/").split("/")[-1]
|
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)"
|
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
|
guid = item.get("UID") or event_id
|
||||||
|
link = f"{SITE}/agenda/{slugify(title)}?u={guid}"
|
||||||
|
meta = format_event_meta(item)
|
||||||
|
description = (f"<p>{html.escape(meta)}</p>" if meta else "") + full_body_html(item)
|
||||||
|
pub_dt = parse_dt(item.get("created") or item.get("start"))
|
||||||
entries.append(
|
entries.append(
|
||||||
" <item>\n"
|
" <item>\n"
|
||||||
f" <title>{escape(title)}</title>\n"
|
f" <title>{escape(title)}</title>\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user