mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-11 12:37:32 +01:00
One row per command attempt, not two aggregates written only on success. command_uses holds last-use only and daily_uses counts successes; both are rate-limiter state and both are written by store.record_use, which the dispatcher calls only when a handler returns cleanly. A gate refusal and a crash returned before it, so a command that failed every time looked exactly like one nobody ran, and nothing knew which command a counted message was. command_events now takes every attempt with its outcome, its duration and the item it served; upstream_calls takes every outbound request made while serving one. The two writes stay separate on purpose -- recording a refusal as a use is what slid the cooldown window forward every time somebody was told to wait -- and test_dispatcher_outcomes asserts last_used does not move. Backfilled from journald joined against message_events, with daily_uses filling the residual so the two sources cannot double-count. Two things were wrong on the first attempt and both were found by measuring rather than by reasoning: the window has to be symmetric, because sent_at_ms comes from the sender's phone and the journal stamp from this host (194 of 299 lines landed *before* their own message, by up to 3.2 s); and matching has to be a global closest-pair assignment, because per-row greedy cannot separate two commands seconds apart. Together those took the dropped rows from 137 to zero, and recovered 418 invocations back to 29 August. Also: !analityka in the group, twelve Polish lines, naming at most three people and only ones whose display name has been seen; an Analytics tab with Nivo charts; and continuous latency measurement, which answers the open question in DOCS-latency.md -- a text send is 235 ms and an image send about 2.2 s, so the upload really is where the time goes. EPHEMERAL_TABLES was doing two unrelated jobs and is now two tuples: what a schema bump rebuilds, and what leaving a group clears. daily_uses left the first (it is a backfill source now) and stayed in the second, and _drop_reshaped took over the repair a bump used to do for it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4QTp3h5Q3MvfrwGV8xqPf
94 lines
5.1 KiB
Markdown
94 lines
5.1 KiB
Markdown
# 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.
|