signal-bot/DOCS-commands.md
Krzysztof kuhy Rudnicki 77795ee6f8
Some checks are pending
CI / check (push) Waiting to run
CI / web (push) Waiting to run
Today's food and workout logs, read from where diet_guard and screen_locker keep them
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>
2026-09-04 22:07:46 +02:00

8.7 KiB

Commands

The copy that matters is not this file: !pomoc prints the same list in the group, in Polish, one line of description per command. That version is built from the command registry at run time and cannot drift, because CommandRegistry.register takes the description as a required argument and help_command.render_help prints any command its GROUPS table forgot to file anyway, under no heading.

This file is the trigger reference — every spelling that resolves, which !pomoc deliberately does not print.

Command Aliases
cat !kot !koty !kots !cat !cats !meow !miau + styled variants
dog !pies !psy !dog !dogs !woof !szczek + styled variants
gecko !gekon !gekony !geko !gecko !geckon !geckos
art !sztuka !sztukę !sztuki !art
poem !wiersz !wiersze !wiersza !wierszyk !poem !poems !poezja !poezje !poetry
trap !pułapka !pulapka !pułapki !pulapki !pułapkę !pulapke !trap !traps !trapy !trapper !sidła !sidla !sidło !sidlo !wnyk !wnyki !potrzask !zasadzka !klatka !samołówka !samolowka !beartrap !bear_trap !snare !snares !mousetrap
pope !papież !papiez !papieze !papieża !papa !pope !popes !pontifex + styled variants
stats_today !staty !statystyki !stats !dziś !dzis
stats_yesterday !wczoraj !yesterday
stats_week !tydzień !tydzien !week
stats_month !miesiąc !miesiac !month
stats_year !rok !year
stats_records !rekordy !rekord !records
stats_crown !korona !crown
stats_weekdays !dni !days
stats_me !ja !moje !mnie
command_stats !analityka !command_stats !commandstats !analytics !uzycie !użycie !ranking
help !pomoc !pomocy !help !komendy !commands
random !losowe !losuj !random !rand
suggest !zaproponuj !propozycja !sugestia !suggest !pomysl
good_bot !good bot !dobry bot !goodbot !dobrybot !good_bot !dobry_bot
food_today !co_jadł !co_jadl !cojadł !cojadl !jedzenie !dieta + the operator's own in aliases_custom.json
workout_today !co_ćwiczył !co_cwiczyl !coćwiczył !cocwiczyl !trening !workout + the operator's own in aliases_custom.json
mute !cisza !cicho !mute
unmute !odcisz !gadaj !wracaj

Aliases live in signal_bot/aliases.json, not in source, because they are data: adding one should never mean touching Python. Two constraints on a new one. It is subtracted from the personal-data gate's word list by _repo_vocabulary(), so it must not be name-shaped; and tests/test_nearest.py fails if it lands within edit distance of a different command's trigger, because the near-miss suggestion below would then be a coin toss. !poems is the one trigger in the table that is only just legal: it sits two edits from !pies, and passes solely because !poem is one edit away and wins the tie. Deleting !poem would fail the suite at !poems, nowhere near where the change was made.

The operator can add words of their own without a commit: the Commands tab of the management page writes aliases_custom.json in the bot's state directory (CUSTOM_ALIASES_FILE names another path), keyed by command name, and the bot appends them to the shipped list when it builds the table. They are extra spellings only -- !pomoc keeps showing the shipped one -- and they go through the same clash check as aliases.json, so a word another command already answers to is refused, as is a name no command has. That is what makes the page's save a reload: the bot proves the file before it is kept. The tagger refuses to give a person a trigger that is already somebody's custom alias, for the same reason it refuses the shipped ones.

good_bot is the one two-word trigger. It works because fold() keeps inner spaces and the registry matches the whole text before it looks at the first word, so !good bot resolves and !good alone does not. Two spaces between the words resolve to nothing, which is what the one-word spellings are for. It is also the one command that answers with no message: it puts an emoji on the compliment itself (POST /v1/reactions/<number>), drawn from a small set, and says nothing in the group.

food_today and workout_today read two logs other programs keep -- diet_guard's food_log.json and screen_locker's log.json, at those programs' default paths unless FOOD_LOG_FILE / WORKOUT_LOG_FILE say otherwise -- and print the current Warsaw day out of them, fresh on every call. The spelling the group actually uses for each has a surname in it, so it is a custom alias in the state directory and never appears in this repository. Food prints every entry and the day's total, never the daily budget, which diet_guard keeps on the machine on purpose. Workouts print every field an entry carries except a reservation phone number, a screenshot path and the sync record id.

A word nothing claims

An unrecognised !word used to be dropped in silence, which made a typo indistinguishable from the bot being down. Now it is answered, and kept:

  • within one edit (two, from six characters up) of a real trigger, the bot asks whether that is what you meant — !cta → chodziło o !kot? The distance is optimal string alignment rather than difflib's ratio, because a transposition costs one edit there and shreds the matching blocks a ratio counts.
  • otherwise it points at !zaproponuj, which files the idea.
  • either way the word and the message go into unknown_commands, which the management page shows. That is the list of commands worth writing next.

Three things stay silent, and each for its own reason. !kot ładny is a real trigger with words after it, and always has been silent. !! and !? are punctuation, not a mistyped command. And a command scoped to some other group — the album's, whose triggers are real people's names — is neither answered nor recorded, because a refusal would announce that the album exists.

random exists only in the photo album's own groups, and only on an installation that has an album at all — everywhere else it is not a command, not refused and never logged. It takes arguments, and those arguments are resolved to pseudonym keys by the registry before any handler, log line or command_uses row sees them. See DOCS-photo-pack.md.

mute (!cisza [1-10]) silences the bot in that group only, for the minutes asked or ten by default, and ten at most. It confirms once and then answers nothing: no commands, no near-miss suggestion, and the nightly report is held back (not marked, so the next run still says that day). Messages are still counted. Anybody may ask -- ten quiet minutes is the whole downside. unmute (!odcisz) is the one command that runs through a mute, and only for TRUSTED_INVITERS; anyone else is refused without a reply, because a refusal is a reply. With TRUSTED_INVITERS unset, anybody may. There is no !unmute alias: it sits one edit from !mute, inside the near-miss budget.

add_photo (!dodaj) is unwired since 2026-09-04: a photo posted to a group must not reach the album without somebody approving it, and no approval queue exists yet. It comes back when one does; DOCS-add-photo.md describes the path as it was.

Any command can be switched off from the management page (Commands tab), outright or until a moment, and given its own cooldown. The bot reads the switch on every attempt, so it is live without a reload; a command that is off answers BEEP BOOP !kot WYŁĄCZONE. (or … WYŁĄCZONE DO 14:01., Warsaw time) rather than going quiet, and the refusal neither starts the cooldown nor counts as a use. !pomoc still lists it. DOCS-web-ui.md has the page, and the same row is where a custom alias is typed.

Why !pomoc shows the first alias and not a trigger

Command.triggers is a frozenset of folded skeletons — folding.py runs NFKC, strips diacritics and maps Cyrillic/Greek/small-caps/leet lookalikes onto ASCII, so that !gecko typed on a Russian keyboard still resolves. Everything that makes those triggers robust also makes them unprintable: the set has no order, !papież survives only as !papiez, and the styled variants come out as zalgo.

So Command.primary holds the first raw trigger instead, which aliases.json lists as the canonical Polish spelling for every command.

Two consequences worth remembering, both from folding.py:

  • A command name can never contain a digit — fold maps 0134578 onto letters, so !7dni would resolve as !tdni.
  • The styled Unicode aliases in the table above are redundant now; folding covers them. They are kept because removing an alias can only take something away.