feat: metadata enrichment for alerts and subscription plugins

Alert backends now populate structured `extra` field with engagement
metrics (views, stars, votes, etc.) instead of embedding them in titles.
Subscription plugins show richer announcements: Twitch viewer counts,
YouTube views/likes/dates, RSS published dates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user
2026-02-19 10:00:17 +01:00
parent c3b19feb0f
commit 1fe7da9ed8
10 changed files with 614 additions and 52 deletions

View File

@@ -135,6 +135,21 @@ def _fetch_feed(url: str, etag: str = "", last_modified: str = "") -> dict:
# -- Feed parsing ------------------------------------------------------------
def _parse_date(raw: str) -> str:
"""Try to extract a YYYY-MM-DD date from a raw date string."""
import re as _re
m = _re.search(r"\d{4}-\d{2}-\d{2}", raw)
if m:
return m.group(0)
# Try RFC 2822 (common in RSS pubDate)
from email.utils import parsedate_to_datetime
try:
dt = parsedate_to_datetime(raw)
return dt.strftime("%Y-%m-%d")
except (ValueError, TypeError):
return ""
def _parse_rss(root: ET.Element) -> tuple[str, list[dict]]:
"""Parse RSS 2.0 feed."""
channel = root.find("channel")
@@ -146,8 +161,13 @@ def _parse_rss(root: ET.Element) -> tuple[str, list[dict]]:
item_id = item.findtext("guid") or item.findtext("link") or ""
item_title = (item.findtext("title") or "").strip()
item_link = (item.findtext("link") or "").strip()
pub_date = (item.findtext("pubDate") or "").strip()
date = _parse_date(pub_date) if pub_date else ""
if item_id:
items.append({"id": item_id, "title": item_title, "link": item_link})
items.append({
"id": item_id, "title": item_title,
"link": item_link, "date": date,
})
return (title, items)
@@ -162,8 +182,14 @@ def _parse_atom(root: ET.Element) -> tuple[str, list[dict]]:
if not entry_id:
entry_id = entry_link
entry_title = (entry.findtext(f"{_ATOM_NS}title") or "").strip()
published = (entry.findtext(f"{_ATOM_NS}published") or "").strip()
updated = (entry.findtext(f"{_ATOM_NS}updated") or "").strip()
date = _parse_date(published or updated)
if entry_id:
items.append({"id": entry_id, "title": entry_title, "link": entry_link})
items.append({
"id": entry_id, "title": entry_title,
"link": entry_link, "date": date,
})
return (title, items)
@@ -246,7 +272,10 @@ async def _poll_once(bot, key: str, announce: bool = True) -> None:
for item in shown:
title = _truncate(item["title"]) if item["title"] else "(no title)"
link = item["link"]
date = item.get("date", "")
line = f"[{name}] {title}"
if date:
line += f" | {date}"
if link:
line += f" -- {link}"
await bot.send(channel, line)