mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 19:27:20 +01:00
Bot.listen awaited every frame to completion, so one Commons fetch or one attachment upload held up every text reply behind it (DOCS-latency.md). Each frame now runs in its own task, never more than LIMIT at once; the reader waits for a slot before spawning, so a burst applies back-pressure at the websocket instead of fanning out upstream. One person's commands stay serialised (user_lock.py): two quick !kot would otherwise both pass the cooldown gate before either recorded a use. Every served command reports its timings split into the bot's own time and everybody else's (served.py); /_selftest carries that block and tool/latency.py holds own_ms to 100 ms at p95, with the command's cooldown switched off for the run and put back afterwards. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
2.2 KiB
Markdown
57 lines
2.2 KiB
Markdown
# Testing a command without a second Signal account
|
|
|
|
Signal never delivers an account its own sends. The bot cannot type `!kot` at
|
|
itself, so until now every command was verified one of two weak ways: by asking
|
|
a human to type it, or by rebuilding the object graph in a scratch script. The
|
|
first proves nothing about the path between the websocket and the handler; the
|
|
second is not the wiring that runs in production.
|
|
|
|
`POST /_selftest` injects a real envelope frame into the **live** dispatcher.
|
|
Everything downstream of the socket read is the code that actually serves the
|
|
group: envelope parsing, the scoping gate, the tally, trigger resolution, the
|
|
cooldown decorator and the real REST transport. Only the socket read is skipped.
|
|
|
|
## Using it
|
|
|
|
Name the groups it may post into -- in `.env`, because the service reads it:
|
|
|
|
```
|
|
SELFTEST_GROUPS=testbot,catbot,finaltestbot
|
|
```
|
|
|
|
Then:
|
|
|
|
```sh
|
|
scripts/selftest.sh testbot '!papież' 3
|
|
```
|
|
|
|
`SELFTEST_INTERVAL` (default 11s) spaces the calls past the per-user cooldown;
|
|
the self-test is always the same author, so a tighter loop just exercises the
|
|
cooldown instead of the command.
|
|
|
|
## What it answers with
|
|
|
|
`delivered`, the group id, the text -- and, when the text was a command,
|
|
`served`: what that one reply cost, split into the bot's own time and the
|
|
time spent on signal-cli and upstream (`signal_bot/served.py`, and the table
|
|
in `DOCS-latency.md`). A plain message or an unknown word is delivered and
|
|
counted but has no `served`, because nothing ran. `tool/latency.py` reads
|
|
that block repeatedly to hold the bot to its budget.
|
|
|
|
## What it will not do
|
|
|
|
It **posts for real**, into a real group. Two things keep that from becoming a
|
|
way to make the bot say anything anywhere:
|
|
|
|
- The endpoint lives on the same loopback bind as `/healthz`, never a public one.
|
|
- `SELFTEST_GROUPS` is **empty by default**, and any group not named in it is
|
|
refused with 403. So a fresh checkout cannot post into the production group
|
|
even by accident.
|
|
|
|
## Why the author matters
|
|
|
|
The reply quotes whoever asked, and `quote_author` has to be somebody Signal can
|
|
resolve -- a made-up uuid comes back as `The user ... is not registered` and the
|
|
whole send fails. `resolve_group` therefore attributes the message to a real
|
|
member of the target group who is not the bot.
|