signal-bot/signal_bot/counting.py
Krzysztof kuhy Rudnicki 5d76adf4db feat: watch several groups, and answer only where you were asked
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 15:03:24 +02:00

58 lines
2.0 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
"""Which messages count toward the daily tally, and which chats we listen to."""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from signal_bot.config import Config
from signal_bot.models import IncomingMessage
def watched_group(message: IncomingMessage, config: Config) -> str | None:
"""Return the canonical id of the watched group a message came from.
A linked Signal device receives every direct conversation and every group
on the account. This function is the entire boundary between "a bot in
its groups" and "a bot reading all of your chats", so it is deliberately
written as an explicit group-id lookup rather than a falsy check: a
direct message has no group id at all and must never reach a handler.
It returns the id rather than a bool so that the gate and the reply
router are the same decision. Answering "may this run?" and "where does
the answer go?" separately is how a bot ends up replying to the wrong
group.
Args:
message: The parsed inbound message.
config: Resolved runtime configuration.
Returns:
The canonical group id, or None for a direct message or any group
the bot is not scoped to.
"""
if message.group_id is None:
return None
return config.resolve_group(message.group_id)
def should_count(message: IncomingMessage) -> bool:
"""True when a message should add to its sender's tally.
Reactions and stickers are not messages for tally purposes, and a frame
with no text body carries nothing to count.
"""
if message.is_reaction or message.has_sticker:
return False
return bool(message.text)
def format_tally(rows: list[tuple[str, int]]) -> str:
"""Render the daily report, or a placeholder when nobody spoke."""
if not rows:
return "Brak wiadomosci dzisiaj."
width = max(len(name) for name, _ in rows)
lines = [f"{name.ljust(width)} {count}" for name, count in rows]
return "\n".join(lines)