feat(bot): hardening + auto-responder, query commands, quiet hours

Reply-bot overhaul on top of the trigger/reply auto-reply:

Robustness
- single botTriggerMatches() shared by DM/channel paths; named constants
  (BOT_REPLY_COOLDOWN_MS / BOT_SCRATCH) replace magic numbers.
- per-contact DM throttle (8-entry ring) so a second sender isn't starved
  while one contact is on cooldown.
- channel anti-loop: skip only when an incoming message equals our own reply
  (was: any reply containing the trigger word — which silently killed channel
  replies for the common "trigger word in reply" setup).

Features
- away / reply-to-all: a lone "*" trigger matches every message.
- independent DM vs channel triggers (bot_trigger / bot_trigger_ch); "*" works
  on the channel too, bounded by cooldown + echo guard.
- query commands: a DM or monitored-channel message is scanned for "!word"
  tokens — !ping/!batt/!loc/!time/!temp/!hops/!status/!help — merged into one
  combined reply. DM = per-contact throttle, ignores quiet hours; channel =
  broadcast, per-channel cooldown, respects quiet hours. !hops uses
  getPathHashCount() (0 = direct).
- quiet hours (bot_quiet_start/end, local, wraps midnight) silence push replies.
- reply counter shown in the BotScreen header.

UI / storage
- BotScreen: 9 rows, scrolling list (no more cramming), field I/O via
  fieldBuf()/fieldCap().
- prefs bot_commands_enabled / bot_quiet_start / bot_quiet_end / bot_trigger_ch
  persisted; schema 0xC0DE000A → 0xC0DE000C with clamping + migration
  (bot_trigger_ch seeded from bot_trigger on upgrade).
- docs: tools_screen bot section rewritten; FEATURES.md refreshed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-15 11:57:27 +02:00
parent 8c0ccaed73
commit 8bd6fbf1cb
8 changed files with 410 additions and 100 deletions

View File

@@ -252,11 +252,38 @@ When the bot is enabled it listens to DMs by default. Channel monitoring is an o
| Setting | Description |
| ------------- | -------------------------------------------------------------------------------- |
| Bot | ON / OFF — enables DM listening |
| Channel mode | ON / OFF — additionally monitors a selected channel |
| Channel | Which channel to monitor (only relevant when channel mode is ON) |
| Trigger | Word or phrase that activates the reply (shared by both modes, case-insensitive) |
| DM reply | Reply text for DMs; supports `{time}` and `{loc}` placeholders |
| Channel reply | Reply text for channel messages; supports `{time}` and `{loc}` placeholders |
| Enable | ON / OFF — enables DM listening |
| Channel | OFF, or which channel to additionally monitor (cycles through your channels) |
| Trigger DM | Word or phrase that activates the DM reply (case-insensitive). A lone `*` means **reply to every DM** (away mode) and is shown as `(any msg)`. |
| Reply DM | Reply text for DMs; supports `{time}`, `{loc}` and sensor placeholders |
| Trigger Ch | Independent trigger for the monitored channel. `*` means **reply to every channel message** — bounded by the per-channel cooldown, but use sparingly on a busy channel. |
| Reply Ch | Reply text for channel messages; supports the same placeholders |
| Commands | ON / OFF — answer `!` query commands (see below) |
| Quiet from/to | Local-time window during which auto-replies stay silent; set both equal (`OFF`) to disable |
A 10-second cooldown prevents repeated replies in quick succession.
The DM and channel triggers are independent, so you can run e.g. an away-message (`*`) in DMs while the channel reacts only to a specific keyword (or vice-versa).
The header shows a running count of auto-replies sent since boot.
**Throttle.** Auto-replies are rate-limited **per contact** (10 s), so a second sender is never starved while one contact is on cooldown. The channel bot keeps a single 10 s cooldown and won't echo a message identical to its own reply (so two bots running the same reply text on one channel can't ping-pong); the cooldown caps any residual back-and-forth.
**Quiet hours** suppress the push (trigger) replies between the configured local hours; a window where *from* is later than *to* wraps past midnight. Commands are a pull (explicitly requested), so they answer even during quiet hours.
### Commands
With **Commands** ON, a DM beginning with `!` is answered with live node data, independent of the trigger:
| Command | Reply |
| --------- | --------------------------------------- |
| `!ping` | `pong` |
| `!batt` | battery voltage |
| `!loc` | GPS coordinates (or `no GPS`) |
| `!time` | local time `HH:MM` |
| `!temp` | temperature (or `n/a` if no sensor) |
| `!hops` | how many hops the command message took to reach the node (`direct` if heard directly) |
| `!status` | combined battery / location / time |
| `!help` | list of available commands |
Several commands can be combined in one message — `!batt !time !hops` is answered with a single `4.10V | 14:30 | 3 hops` reply (one transmission). A message with no recognised command falls through to the trigger bot.
Commands also work on the **monitored channel** (the one selected for channel mode) — anyone there can query the node. Channel replies are broadcast to everyone, so unlike DM commands they respect quiet hours and use the shared per-channel cooldown. DM commands use the per-contact throttle.