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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user