From 13e5c94ee76adde071fa9d8728f0e12387695338 Mon Sep 17 00:00:00 2001 From: para <157126561+mdegallaix@users.noreply.github.com> Date: Fri, 25 Sep 2026 23:49:31 +0200 Subject: [PATCH] Fix W3C validator errors: duplicate guids and missing atom:link self - Recurring events share one UID across all occurrences (confirmed via the API: same UID, different start dates), which produced duplicate values the RSS spec forbids. Suffix the guid with the occurrence's start date to make it unique per occurrence. - Add atom:link rel="self" to the channel, per the validator's recommendation, in both the PHP and Python implementations. Co-Authored-By: Claude Sonnet 5 --- php/actualites.php | 3 ++- php/agenda.php | 9 +++++++-- php/common.php | 7 +++++-- silly_rss.py | 18 +++++++++++++++--- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/php/actualites.php b/php/actualites.php index ab8b87d..cab6391 100644 --- a/php/actualites.php +++ b/php/actualites.php @@ -44,7 +44,8 @@ function build_news_feed(): string 'Silly.be - Actualités', SITE . '/actualites', 'Actualités de la commune de Silly (Belgique)', - $items + $items, + 'https://sillycone-village.be/silly-rss/actualites.php' ); } diff --git a/php/agenda.php b/php/agenda.php index f2006bb..adc792a 100644 --- a/php/agenda.php +++ b/php/agenda.php @@ -56,10 +56,14 @@ function build_events_feed(): string $meta = format_event_meta($item); $description = ($meta !== '' ? '

' . htmlspecialchars($meta, ENT_QUOTES, 'UTF-8') . '

' : '') . full_body_html($item); + // Un evenement recurrent partage le meme UID sur toutes ses + // occurrences (memes UID, dates differentes) : on suffixe le guid + // avec la date de debut pour rester unique par occurrence, comme + // l'exige la spec RSS. $items[] = [ 'title' => $title, 'link' => SITE . '/agenda/' . slugify($title) . '?u=' . rawurlencode($uid), - 'guid' => $uid, + 'guid' => $uid . '-' . ($item['start'] ?? ''), 'pubDate' => $item['created'] ?? $item['start'] ?? null, 'description' => $description, ]; @@ -68,7 +72,8 @@ function build_events_feed(): string 'Silly.be - Agenda', SITE . '/agenda', "Agenda / événements de la commune de Silly (Belgique)", - $items + $items, + 'https://sillycone-village.be/silly-rss/agenda.php' ); } diff --git a/php/common.php b/php/common.php index f4a3113..6688553 100644 --- a/php/common.php +++ b/php/common.php @@ -166,14 +166,17 @@ function xml_escape(string $text): string /** * Construit un flux RSS 2.0 complet a partir d'une liste d'items deja * formates (chacun un tableau title/link/guid/pubDate/description). + * $selfUrl est l'URL du flux lui-meme (atom:link rel="self", recommande + * par le validateur W3C). */ -function build_rss(string $title, string $link, string $description, array $items): string +function build_rss(string $title, string $link, string $description, array $items, string $selfUrl): string { $now = rfc822(null); $xml = '' . "\n"; - $xml .= '' . "\n \n"; + $xml .= '' . "\n \n"; $xml .= ' ' . xml_escape($title) . "\n"; $xml .= ' ' . xml_escape($link) . "\n"; + $xml .= ' ' . "\n"; $xml .= ' ' . xml_escape($description) . "\n"; $xml .= " fr-be\n"; $xml .= " $now\n"; diff --git a/silly_rss.py b/silly_rss.py index 0339a4f..0fbec1a 100644 --- a/silly_rss.py +++ b/silly_rss.py @@ -37,6 +37,11 @@ MAX_PAGES = 2 # jusqu'a 40 items par flux NEWS_API = f"{SITE}/actualites/@results" EVENTS_API = f"{SITE}/agenda/@results" +# Base publique ou ce script publie ses flux une fois deploye (voir +# README.md, variante Yunohost/cron) - sert a l'atom:link rel="self". +# A adapter si vous deployez ailleurs. +SELF_BASE_URL = "https://akbar.nohost.me/silly-rss" + def fetch_json(url: str) -> dict: result = subprocess.run( @@ -170,11 +175,15 @@ def build_event_items() -> list[str]: 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")) + # Un evenement recurrent partage le meme UID sur toutes ses + # occurrences : on suffixe le guid avec la date de debut pour + # rester unique par occurrence, comme l'exige la spec RSS. + occurrence_guid = f"{guid}-{item.get('start', '')}" entries.append( " \n" f" {escape(title)}\n" f" {escape(link)}\n" - f" {escape(guid)}\n" + f" {escape(occurrence_guid)}\n" f" {rfc822(pub_dt)}\n" f" {escape(description)}\n" " " @@ -182,14 +191,15 @@ def build_event_items() -> list[str]: return entries -def write_feed(path: str, title: str, link: str, description: str, items: list[str]) -> None: +def write_feed(path: str, title: str, link: str, description: str, items: list[str], self_url: str) -> None: now = rfc822(datetime.now(timezone.utc)) xml = ( '\n' - '\n' + '\n' " \n" f" {escape(title)}\n" f" {escape(link)}\n" + f' \n' f" {escape(description)}\n" " fr-be\n" f" {now}\n" @@ -211,6 +221,7 @@ def main() -> None: link=f"{SITE}/actualites", description="Actualités de la commune de Silly (Belgique)", items=news_items, + self_url=f"{SELF_BASE_URL}/actualites.xml", ) print(f"[OK] actualites.xml : {len(news_items)} items") @@ -221,6 +232,7 @@ def main() -> None: link=f"{SITE}/agenda", description="Agenda / événements de la commune de Silly (Belgique)", items=event_items, + self_url=f"{SELF_BASE_URL}/agenda.xml", ) print(f"[OK] agenda.xml : {len(event_items)} items")