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:
@@ -25,6 +25,7 @@ from plugins.rss import ( # noqa: E402
|
||||
_feeds,
|
||||
_load,
|
||||
_parse_atom,
|
||||
_parse_date,
|
||||
_parse_feed,
|
||||
_parse_rss,
|
||||
_poll_once,
|
||||
@@ -52,16 +53,19 @@ RSS_FEED = b"""\
|
||||
<guid>item-1</guid>
|
||||
<title>First Post</title>
|
||||
<link>https://example.com/1</link>
|
||||
<pubDate>Mon, 10 Feb 2026 09:00:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>item-2</guid>
|
||||
<title>Second Post</title>
|
||||
<link>https://example.com/2</link>
|
||||
<pubDate>Tue, 11 Feb 2026 14:30:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>item-3</guid>
|
||||
<title>Third Post</title>
|
||||
<link>https://example.com/3</link>
|
||||
<pubDate>Wed, 12 Feb 2026 08:00:00 +0000</pubDate>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -88,11 +92,13 @@ ATOM_FEED = b"""\
|
||||
<id>atom-1</id>
|
||||
<title>Atom First</title>
|
||||
<link href="https://example.com/a1"/>
|
||||
<published>2026-02-15T10:00:00Z</published>
|
||||
</entry>
|
||||
<entry>
|
||||
<id>atom-2</id>
|
||||
<title>Atom Second</title>
|
||||
<link href="https://example.com/a2"/>
|
||||
<published>2026-02-16T15:30:00Z</published>
|
||||
</entry>
|
||||
</feed>
|
||||
"""
|
||||
@@ -333,6 +339,20 @@ class TestParseRSS:
|
||||
assert items[0]["title"] == "First Post"
|
||||
assert items[0]["link"] == "https://example.com/1"
|
||||
|
||||
def test_parses_pubdate(self):
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(RSS_FEED)
|
||||
_, items = _parse_rss(root)
|
||||
assert items[0]["date"] == "2026-02-10"
|
||||
assert items[1]["date"] == "2026-02-11"
|
||||
assert items[2]["date"] == "2026-02-12"
|
||||
|
||||
def test_no_pubdate_empty_string(self):
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(RSS_NO_GUID)
|
||||
_, items = _parse_rss(root)
|
||||
assert items[0]["date"] == ""
|
||||
|
||||
def test_fallback_to_link_as_id(self):
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(RSS_NO_GUID)
|
||||
@@ -364,6 +384,19 @@ class TestParseAtom:
|
||||
assert items[0]["title"] == "Atom First"
|
||||
assert items[0]["link"] == "https://example.com/a1"
|
||||
|
||||
def test_parses_published_date(self):
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(ATOM_FEED)
|
||||
_, items = _parse_atom(root)
|
||||
assert items[0]["date"] == "2026-02-15"
|
||||
assert items[1]["date"] == "2026-02-16"
|
||||
|
||||
def test_no_published_empty_string(self):
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(ATOM_NO_ID)
|
||||
_, items = _parse_atom(root)
|
||||
assert items[0]["date"] == ""
|
||||
|
||||
def test_fallback_to_link_as_id(self):
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(ATOM_NO_ID)
|
||||
@@ -755,6 +788,9 @@ class TestCmdRssCheck:
|
||||
assert len(announcements) == 2
|
||||
assert "[news]" in announcements[0]
|
||||
assert "Second Post" in announcements[0]
|
||||
# Verify date suffix
|
||||
assert "| 2026-02-11" in announcements[0]
|
||||
assert "| 2026-02-12" in announcements[1]
|
||||
|
||||
asyncio.run(inner())
|
||||
|
||||
@@ -1073,3 +1109,27 @@ class TestCmdRssUsage:
|
||||
bot = _FakeBot()
|
||||
asyncio.run(cmd_rss(bot, _msg("!rss foobar")))
|
||||
assert "Usage:" in bot.replied[0]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TestParseDate
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestParseDate:
|
||||
def test_iso_format(self):
|
||||
assert _parse_date("2026-02-15T10:00:00Z") == "2026-02-15"
|
||||
|
||||
def test_iso_with_offset(self):
|
||||
assert _parse_date("2026-02-15T10:00:00+00:00") == "2026-02-15"
|
||||
|
||||
def test_rfc2822_format(self):
|
||||
assert _parse_date("Mon, 10 Feb 2026 09:00:00 +0000") == "2026-02-10"
|
||||
|
||||
def test_empty_string(self):
|
||||
assert _parse_date("") == ""
|
||||
|
||||
def test_garbage(self):
|
||||
assert _parse_date("not a date") == ""
|
||||
|
||||
def test_date_only(self):
|
||||
assert _parse_date("2026-01-01") == "2026-01-01"
|
||||
|
||||
Reference in New Issue
Block a user