mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-11 14:07:51 +01:00
Two commands, food_today and workout_today, each printing the current Warsaw day out of a log another program owns: every meal with its time, calories and source plus the day's total and macros; every workout with every field it carries. Three workout fields are withheld (a reservation phone number, a screenshot path, the sync record id), and the food budget is never printed -- diet_guard keeps that number on the machine on purpose. The spellings the group uses carry a surname, so they are custom aliases in the state directory; aliases.json ships only plain words. The suite points both logs at absent temp files so it can never read the operator's own, and the e2e run writes one invented entry of each under today's date. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
123 lines
6.0 KiB
Python
123 lines
6.0 KiB
Python
# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence.
|
|
"""Which commands exist: the one function that fills a CommandRegistry.
|
|
|
|
Split out of bot.py when the registry stopped being built once. ``build_bot``
|
|
still assembles the object graph; this is the part of it that reads data --
|
|
aliases.json, every album's manifests -- and so the part ``RegistryHolder``
|
|
runs again when that data changes. Everything with a lifetime longer than
|
|
one table (the HTTP client, the portrait cache, the store) is passed in, so
|
|
a rebuild reuses them rather than opening a second of each.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Final
|
|
|
|
from signal_bot.analytics_command import register_analytics_command
|
|
from signal_bot.animals import load_aliases, register_animal_commands
|
|
from signal_bot.art_command import register_art_command
|
|
from signal_bot.custom_aliases import default_path as custom_aliases_path
|
|
from signal_bot.custom_aliases import load as load_custom_aliases
|
|
from signal_bot.decorators import cooldown
|
|
from signal_bot.food_command import register_food_command
|
|
from signal_bot.good_bot_command import register_good_bot_command
|
|
from signal_bot.groups import ConfigError
|
|
from signal_bot.help_command import register_help_command
|
|
from signal_bot.mute import UNMUTE_NAME
|
|
from signal_bot.mute_command import MUTE_NAME, register_mute_commands
|
|
from signal_bot.pack_command import register_pack_commands
|
|
from signal_bot.pack_holder import PackHolder
|
|
from signal_bot.pack_load import load as load_pack
|
|
from signal_bot.pack_random import register_random_command
|
|
from signal_bot.poem_command import register_poem_command
|
|
from signal_bot.popes_command import register_pope_command
|
|
from signal_bot.registry import CommandRegistry
|
|
from signal_bot.stats.commands import register_stats_commands
|
|
from signal_bot.suggest_command import register_suggest_command
|
|
from signal_bot.traps_command import register_trap_command
|
|
from signal_bot.workout_command import register_workout_command
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
from signal_bot.config import Config
|
|
from signal_bot.decorators import Decorator
|
|
from signal_bot.http_client import ImageFetcher
|
|
from signal_bot.portraits import PortraitCache
|
|
|
|
COOLDOWN_MESSAGE = "BEEP BOOP POCZEKAJ {seconds} {unit}."
|
|
|
|
|
|
# The commands registered below without ``decorate=rate_limit``. Named so
|
|
# the switchboard can say "no cooldown" for them instead of showing the
|
|
# deployment's number next to an input that would do nothing.
|
|
# test_wiring_ungated walks the real table to keep this honest.
|
|
UNGATED: Final = frozenset({MUTE_NAME, UNMUTE_NAME})
|
|
|
|
|
|
def _register_album(registry: CommandRegistry, directory: Path, rate_limit: Decorator) -> None:
|
|
"""Wire the album's commands, all of them reading one mutable holder.
|
|
|
|
!random takes the same cooldown as every other photo command. !add_photo
|
|
is not wired: a photo posted to a group must not reach the album until
|
|
somebody has approved it, and no approval queue exists yet.
|
|
"""
|
|
aliases = load_aliases()
|
|
holder = PackHolder(load_pack(directory))
|
|
register_pack_commands(registry, holder, decorate=rate_limit)
|
|
register_random_command(registry, holder, tuple(aliases["random"]), decorate=rate_limit)
|
|
|
|
|
|
def build_registry(
|
|
config: Config, fetcher: ImageFetcher, portraits: PortraitCache
|
|
) -> CommandRegistry:
|
|
"""Read the data and register every command on a fresh table.
|
|
|
|
Raises:
|
|
ConfigError: An album no longer loads, the operator's alias file does
|
|
not, or it names a command that does not exist.
|
|
DuplicateTriggerError: Two commands live in one group claim a word.
|
|
"""
|
|
registry = CommandRegistry(load_custom_aliases(custom_aliases_path()))
|
|
rate_limit = cooldown(config.cooldown_seconds, COOLDOWN_MESSAGE)
|
|
register_animal_commands(
|
|
registry,
|
|
fetcher,
|
|
decorate=rate_limit,
|
|
endpoints=config.image_api_urls,
|
|
)
|
|
register_pope_command(registry, portraits, decorate=rate_limit)
|
|
register_trap_command(registry, fetcher, config.commons_api_url, decorate=rate_limit)
|
|
register_art_command(registry, fetcher, config.commons_api_url, decorate=rate_limit)
|
|
register_poem_command(registry, fetcher, config.poem_text_url, decorate=rate_limit)
|
|
register_stats_commands(registry, decorate=rate_limit)
|
|
# Reads the *command* history rather than the message history, and
|
|
# reads the registry when it runs -- so the album's commands, which
|
|
# are registered below this line, are still in the near-miss set.
|
|
register_analytics_command(registry, decorate=rate_limit)
|
|
# Rate-limited like every other command. The cost is real and accepted:
|
|
# somebody told "napisz !zaproponuj ..." who then files two ideas in a
|
|
# row is asked to wait for the second. The alternative is one person
|
|
# being able to fill a table nothing prunes.
|
|
register_suggest_command(registry, decorate=rate_limit)
|
|
register_good_bot_command(registry, decorate=rate_limit)
|
|
# Two logs other programs keep, read where they lie; the spellings the
|
|
# group uses for them carry a surname and come from aliases_custom.json.
|
|
register_food_command(registry, decorate=rate_limit)
|
|
register_workout_command(registry, decorate=rate_limit)
|
|
# No cooldown: "wait before you may ask for quiet" is the wrong answer,
|
|
# and the unmute is gated by trust, not by time.
|
|
register_mute_commands(registry)
|
|
# Absent unless this installation has an album, and scoped to that
|
|
# album's own groups when it does -- so on every other machine, and in
|
|
# every other group, these commands simply do not exist.
|
|
for album in config.photo_pack_dirs:
|
|
_register_album(registry, album, rate_limit)
|
|
# Last, so !pomoc trails the list it prints; it reads the registry
|
|
# when it runs, so the order is presentation only.
|
|
register_help_command(registry, decorate=rate_limit)
|
|
if registry.unclaimed_aliases:
|
|
msg = f"aliases for commands that do not exist: {sorted(registry.unclaimed_aliases)}"
|
|
raise ConfigError(msg)
|
|
return registry
|