# 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` | | add_photo | `!dodaj` `!dodaj_zdjecie` `!dodajzdjecie` `!add_photo` `!addphoto` | | random | `!losowe` `!losuj` `!random` `!rand` | | suggest | `!zaproponuj` `!propozycja` `!sugestia` `!suggest` `!pomysl` | 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. ## 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. `add_photo` and `random` exist **only in the photo album's own groups**, and only on an installation that has an album at all — everywhere else they are not commands, not refused and never logged. Both take 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` and `DOCS-add-photo.md`. ## 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.