Add DM support, friend identification, and target pruning

React in 1:1 DMs alongside servers/groups, surface the resolved friend
username at startup and in the stats panel, verify reactions actually
stick after adding them (with jump links for manual spot-checking),
and prune stale targets from stats on each run so only the currently
mutual set is shown.
This commit is contained in:
2026-08-01 18:03:02 +02:00
parent 3118f7b9d5
commit c8359ef4bb
7 changed files with 116 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
import asyncio
from typing import List
from typing import List, Optional
from rich.console import Console
from rich.live import Live
@@ -8,8 +8,10 @@ from rich.table import Table
from bot.storage import Storage
_TYPE_LABELS = {"guild": "server", "group_dm": "group", "dm": "DM"}
def _render(stats: List[dict]) -> Panel:
def _render(stats: List[dict], friend_label: Optional[str] = None) -> Panel:
table = Table(expand=True)
table.add_column("Target")
table.add_column("Type")
@@ -21,7 +23,7 @@ def _render(stats: List[dict]) -> Panel:
total += row["reaction_count"]
table.add_row(
row["name"] or row["target_id"],
"server" if row["target_type"] == "guild" else "group",
_TYPE_LABELS.get(row["target_type"], row["target_type"]),
str(row["reaction_count"]),
row["last_reaction_at"] or "-",
)
@@ -29,7 +31,8 @@ def _render(stats: List[dict]) -> Panel:
if not stats:
table.add_row("(no targets discovered yet)", "-", "-", "-")
return Panel(table, title=f"Reaction stats — total: {total}", border_style="cyan")
who = f" for {friend_label}" if friend_label else ""
return Panel(table, title=f"Reaction stats{who} — total: {total}", border_style="cyan")
class StatsDisplay:
@@ -39,6 +42,7 @@ class StatsDisplay:
def __init__(self, console: Console, storage: Storage):
self.console = console
self.storage = storage
self.friend_label: Optional[str] = None
self._live = Live(
_render([]),
console=console,
@@ -53,10 +57,13 @@ class StatsDisplay:
def __exit__(self, *exc_info):
return self._live.__exit__(*exc_info)
def set_friend(self, label: str) -> None:
self.friend_label = label
def refresh_sync(self) -> None:
stats = self.storage.get_stats()
self._live.update(_render(stats))
self._live.update(_render(stats, self.friend_label))
async def refresh(self) -> None:
stats = await asyncio.to_thread(self.storage.get_stats)
self._live.update(_render(stats))
self._live.update(_render(stats, self.friend_label))