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

@@ -19,6 +19,7 @@ _spec.loader.exec_module(_mod)
from plugins.youtube import ( # noqa: E402
_MAX_ANNOUNCE,
_channels,
_compact_num,
_delete,
_derive_name,
_errors,
@@ -44,26 +45,48 @@ from plugins.youtube import ( # noqa: E402
YT_ATOM_FEED = b"""\
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns:yt="http://www.youtube.com/xml/schemas/2015"
xmlns="http://www.w3.org/2005/Atom">
xmlns="http://www.w3.org/2005/Atom"
xmlns:media="http://search.yahoo.com/mrss/">
<title>3Blue1Brown - Videos</title>
<author><name>3Blue1Brown</name></author>
<entry>
<id>yt:video:abc123</id>
<yt:videoId>abc123</yt:videoId>
<title>Linear Algebra</title>
<published>2026-01-15T12:00:00+00:00</published>
<link rel="alternate" href="https://www.youtube.com/watch?v=abc123"/>
<media:group>
<media:community>
<media:statistics views="1500000"/>
<media:starRating count="45000"/>
</media:community>
</media:group>
</entry>
<entry>
<id>yt:video:def456</id>
<yt:videoId>def456</yt:videoId>
<title>Calculus</title>
<published>2026-02-01T08:30:00+00:00</published>
<link rel="alternate" href="https://www.youtube.com/watch?v=def456"/>
<media:group>
<media:community>
<media:statistics views="820000"/>
<media:starRating count="32000"/>
</media:community>
</media:group>
</entry>
<entry>
<id>yt:video:ghi789</id>
<yt:videoId>ghi789</yt:videoId>
<title>Neural Networks</title>
<published>2026-02-10T14:00:00+00:00</published>
<link rel="alternate" href="https://www.youtube.com/watch?v=ghi789"/>
<media:group>
<media:community>
<media:statistics views="250000"/>
<media:starRating count="12000"/>
</media:community>
</media:group>
</entry>
</feed>
"""
@@ -362,6 +385,28 @@ class TestParseFeed:
channel_name, _ = _parse_feed(YT_ATOM_FEED)
assert channel_name == "3Blue1Brown"
def test_parses_published_date(self):
_, items = _parse_feed(YT_ATOM_FEED)
assert items[0]["date"] == "2026-01-15"
assert items[1]["date"] == "2026-02-01"
assert items[2]["date"] == "2026-02-10"
def test_parses_views(self):
_, items = _parse_feed(YT_ATOM_FEED)
assert items[0]["views"] == 1500000
assert items[1]["views"] == 820000
def test_parses_likes(self):
_, items = _parse_feed(YT_ATOM_FEED)
assert items[0]["likes"] == 45000
assert items[1]["likes"] == 32000
def test_no_media_defaults_zero(self):
_, items = _parse_feed(YT_ATOM_NO_VIDEOID)
assert items[0]["views"] == 0
assert items[0]["likes"] == 0
assert items[0]["date"] == ""
# ---------------------------------------------------------------------------
# TestResolveChannel
@@ -789,6 +834,11 @@ class TestCmdYtCheck:
assert len(announcements) == 2
assert "[news]" in announcements[0]
assert "Calculus" in announcements[0]
# Verify metadata suffix (views, likes, date)
assert "| " in announcements[0]
assert "820kv" in announcements[0]
assert "32klk" in announcements[0]
assert "2026-02-01" in announcements[0]
asyncio.run(inner())
@@ -1103,3 +1153,27 @@ class TestCmdYtUsage:
bot = _FakeBot()
asyncio.run(cmd_yt(bot, _msg("!yt foobar")))
assert "Usage:" in bot.replied[0]
# ---------------------------------------------------------------------------
# TestCompactNum
# ---------------------------------------------------------------------------
class TestCompactNum:
def test_zero(self):
assert _compact_num(0) == "0"
def test_small(self):
assert _compact_num(999) == "999"
def test_one_k(self):
assert _compact_num(1000) == "1k"
def test_fractional_k(self):
assert _compact_num(1500) == "1.5k"
def test_one_m(self):
assert _compact_num(1_000_000) == "1M"
def test_fractional_m(self):
assert _compact_num(2_500_000) == "2.5M"