mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-09-29 02:47:56 +00:00
chore(release): prepare 1.1.0
Fold the Unreleased section into 1.1.0 and date it. The 1.1.0 heading was written on 2026-09-13 but never tagged, so everything merged since belongs in it; the section also carried two separate "### Added" headings from earlier merges, now consolidated. All 79 entries preserved. Extend the v1.0.0 -> v1.1.0 upgrade guide with the meshcore 2.3.14 requirement, migration 24, and the configuration and behavior changes that landed after the heading was first written.
This commit is contained in:
@@ -8,7 +8,7 @@ file is kept only for historical reference.
|
||||
|
||||
## Outstanding known issues
|
||||
|
||||
As of the **v1.1.0 release candidate**.
|
||||
As of **v1.1.0**.
|
||||
|
||||
### High priority
|
||||
|
||||
|
||||
+199
-181
@@ -6,6 +6,8 @@ semantic versioning.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.1.0] - 2026-09-20
|
||||
|
||||
### Added
|
||||
|
||||
- Region-code monitoring and an optional automatic warning to senders whose
|
||||
@@ -73,6 +75,7 @@ semantic versioning.
|
||||
mention takes the place of the random human descriptor, keeping the translated
|
||||
sentence intact, and is dropped when it would push the reply past the channel body
|
||||
budget. DMs are unaffected.
|
||||
|
||||
- `generate_website.py` accepts `--link-css URL` and `--embed-css FILE` to layer
|
||||
custom CSS on top of the built-in style chosen with `--style` (#288). Embedded
|
||||
CSS is appended to the page's `<style>` block, keeping the page a single file;
|
||||
@@ -85,6 +88,7 @@ semantic versioning.
|
||||
card so a user can add the bot and DM it without waiting for an advert. Useful
|
||||
for bots that do not advertise. Enabled by default; disable with
|
||||
`[Contact_Command] enabled = false`.
|
||||
|
||||
- `generate_website.py` now lists the local commands installed under
|
||||
`[Bot] local_dir_path` and omits commands disabled in the configuration file
|
||||
(#287). It also reads the `<local_dir_path>/config.ini` overlay the way the
|
||||
@@ -93,80 +97,6 @@ semantic versioning.
|
||||
own helpers, so `[Jokes] joke_enabled = false` is honored rather than silently
|
||||
ignored.
|
||||
|
||||
### Changed
|
||||
|
||||
- `meshcore` now requires 2.3.14 or newer. Before 2.3.13, `send_msg_with_retry`
|
||||
reported ACKed DMs as failures: it subscribed to the ACK only after `send_msg`
|
||||
returned, so an ACK queued right behind `MSG_SENT` was dispatched with no
|
||||
listener, and each attempt accepted only its own ACK code, so a late ACK
|
||||
answering an earlier attempt was ignored (meshcore_py#108). The bot logged
|
||||
"no ACK received after retries" and skipped the delivery bookkeeping for
|
||||
messages the recipient had in fact received.
|
||||
|
||||
### Fixed
|
||||
|
||||
- MQTT brokers on `waev.app` now default to a JWT lifetime they accept (#248).
|
||||
waev.app refuses a token whose `exp` is more than an hour past its `iat`, so the
|
||||
project-wide 24-hour default never authenticated there and the operator saw only a
|
||||
bare auth failure. Those hosts now get a 3600s TTL with renewal at 3500s unless a
|
||||
value was configured for them, per broker or globally, in which case the configured
|
||||
value still wins. The choice is logged. Only `waev.app` and its subdomains match; no
|
||||
other broker's defaults change.
|
||||
- The `[Keywords]` newline instructions now say the escape is a *backslash* and that
|
||||
`/n` is two literal characters (#277), and they no longer use `test` as the example
|
||||
keyword. `test` and `t` are served by the built-in test command, not the plain
|
||||
keyword path, so the mesh-info placeholders listed in that same block render empty
|
||||
there. Both facts are now stated where the example lives, along with the
|
||||
configparser continuation form, which needs no escape at all.
|
||||
- The packet-capture docs no longer show a naive local timestamp in the published
|
||||
packet and status examples (#276). The bot has published UTC with a `Z` suffix since
|
||||
v1.0.0; only the examples still read as local time.
|
||||
- `outgoing_flood_scope_override = none` is now read as global flood on the
|
||||
send path, as it already was everywhere else. `send_channel_message` tested
|
||||
the raw value against a fixed tuple, so the lowercase spelling became the
|
||||
region `#none` and sent scoped.
|
||||
- Striped and hovered table rows in the web viewer's dark mode no longer render
|
||||
Bootstrap's light-theme text color on a dark background (about 1.3:1
|
||||
contrast). The dark overrides set a background but not a color, so every
|
||||
`.table-striped` page was affected.
|
||||
|
||||
- `[Joke_Command] joke_enabled` and `[DadJoke_Command] dadjoke_enabled` are now
|
||||
listed in the shared legacy-alias table. Both commands accepted that spelling
|
||||
at runtime through their own fallback, but the settings UI read only the
|
||||
`[Jokes]` spelling, so a bot disabled the same-section way showed as enabled
|
||||
on the plugin settings page.
|
||||
|
||||
- A region-scoped channel message now restores global flood even when
|
||||
`set_flood_scope` raises. The restore only ran in the `finally` around the
|
||||
send, so a set that raised left the device pinned to that region and every
|
||||
later send — channel replies, DMs, scheduled sends — went out under it.
|
||||
- A DM waiting for its ACK no longer holds the radio. Radio commands were
|
||||
serialized per call, so a DM's retry loop kept every other command waiting
|
||||
through all of its ACK timeouts (up to ~36 s with the default three attempts),
|
||||
stalling channel replies, other DMs, and scheduled sends. The lock now covers
|
||||
one frame and the radio's immediate reply, and ACK and remote-response waits
|
||||
run outside it, so neighbor scope requests no longer stall replies either. A
|
||||
region-scoped channel message now holds the radio from setting its flood scope
|
||||
through restoring it, so no other send goes out under that scope.
|
||||
- A radio connection is no longer accepted when every channel read times out or
|
||||
returns no usable channel data (#266). Startup and reconnect now retry the
|
||||
channel scan three times, keep an empty result out of the valid cache and
|
||||
database, then fail the connection cleanly so the normal restart/reconnect
|
||||
path can try again instead of running a bot that cannot route replies.
|
||||
- Daily Weather Service forecasts now retry transient Open-Meteo failures at
|
||||
5, 15, and 30 minutes after the original run (#264). HTTP 429, 500, 502, 503,
|
||||
and 504 responses plus transport failures use one replaceable retry job,
|
||||
while permanent HTTP errors stop immediately and a successful retry sends
|
||||
the forecast only once.
|
||||
- `help <command> <subcommand>` now resolves help for the base command while
|
||||
preserving the full message for context-aware help text (#285). Exact
|
||||
multi-word aliases such as `dad joke` and `ps aux` still take precedence
|
||||
over the base-command fallback.
|
||||
|
||||
## [1.1.0] - 2026-09-13
|
||||
|
||||
### Added
|
||||
|
||||
- Local commands and local services (dropped into `local/commands` and
|
||||
`local/service_plugins`) now appear in the web viewer's Plugins settings page
|
||||
alongside the built-in ones, tagged with `source: "local"`. Settings edited
|
||||
@@ -223,8 +153,195 @@ semantic versioning.
|
||||
the bot's default; `wx_international` was reaching for the private
|
||||
`_response_translator` ContextVar to do this.
|
||||
|
||||
- Shlink is now supported as a URL shortener alongside v.gd / is.gd, selected with
|
||||
`short_url_website_service = shlink` under `[External_Data]`. It authenticates with
|
||||
`short_url_website_api_key` in an `X-Api-Key` header and needs `short_url_website`
|
||||
set to your own instance — there is no default, and the bot skips shortening rather
|
||||
than sending the key to a host you did not configure.
|
||||
|
||||
- `shorten` and `if_nonempty` response-template filters. `shorten` runs a value
|
||||
through the configured shortener and falls back to the original URL when shortening
|
||||
fails, so a clause is never lost to a network error. `if_nonempty:L` replaces a
|
||||
non-empty value with literal `L` and clears otherwise, which is how a whole clause
|
||||
is hidden rather than labelled: `{packet_hash|if_nonempty:"https://…/{packet_hash}"|shorten}`
|
||||
prints nothing at all when RF correlation fails, instead of a broken link. Both
|
||||
filters also answer to their other spellings — `shorten_url` in a template,
|
||||
`shorten_url` in a feed format, `if_notempty` — so a chain copied between a feed
|
||||
format and a command `response_format` works unchanged either way.
|
||||
|
||||
- Response-template filter arguments may be double-quoted, and a quoted argument may
|
||||
contain nested `{field}` placeholders: `{d|prefix_if_nonempty:"Dist {sender}: "}`.
|
||||
The quote ends the argument, so further filters can follow it. An unquoted
|
||||
`prefix_if_nonempty` argument still consumes the rest of the placeholder, which is
|
||||
what lets its literal contain `|`, so that form must stay last in its chain.
|
||||
|
||||
- `mqttN_keepalive` (default 60) sets the MQTT PINGREQ interval per broker. It was
|
||||
hardcoded at 60 before, which is long for websockets through a proxy that drops
|
||||
idle connections.
|
||||
|
||||
- `hops_min:N` response-template filter, alongside `pathbytes_min:N`. It clears a
|
||||
field unless the message actually travelled at least N hops, so
|
||||
`{firstlast_distance|hops_min:1|prefix_if_nonempty: | F/L Dist: }` drops the whole
|
||||
clause on a direct message. The distance placeholders render `N/A` when there is no
|
||||
path, and `prefix_if_nonempty` treats that as a value and prints its label, so a
|
||||
gate was needed; `pathbytes_min` was the only one available and it asks how the path
|
||||
is *encoded*, which meant throwing away a measurable one-byte multi-hop distance to
|
||||
suppress the direct case. `hops_min` asks about the route instead. An unknown hop
|
||||
count clears the field rather than guessing.
|
||||
|
||||
- `{packet_hash}` placeholder for `[Keywords]` responses, the test command's
|
||||
`response_format` and the path command's `reply_prefix`: the 16-char MeshCore
|
||||
packet identity hash (uppercase hex) of the packet that carried the request, so a
|
||||
reply can be tied back to a specific transmission when comparing paths. It comes
|
||||
only from the routing info of an RF packet actually correlated to the message, and
|
||||
renders empty otherwise, so a hash from an unrelated transmission is never shown.
|
||||
|
||||
- **Scheduled messages can be managed from the web viewer** (#174). A new Schedule page
|
||||
lists every `[Scheduled_Messages]` entry with its next run time and offers add, edit
|
||||
and delete. Changes are written to `config.ini` and applied by a queued config reload,
|
||||
so no restart is needed. The schedule builder composes the cron key from plain-language
|
||||
options and previews the next five runs; entries the bot cannot run are shown as
|
||||
**Not scheduled** with the reason rather than hidden. It edits the same config section
|
||||
the bot already uses, so there is no second source of truth.
|
||||
|
||||
- Documented installing with `pipx`, which sidesteps PEP 668 on Debian 12+, Ubuntu
|
||||
23.04+, Fedora and Arch (#222), including where `config.ini`, the database and
|
||||
`local/` live — everything resolves relative to the config file's directory, so an
|
||||
absolute `--config` is what makes a pipx install deterministic.
|
||||
|
||||
- Migration 23: nullable `snr` / `rssi` columns on `observed_paths` for
|
||||
zero-hop advert rows.
|
||||
|
||||
- `{cmd:<command> [args]}` placeholders in `[Scheduled_Messages]`: a scheduled message
|
||||
can embed the reply of any bot command, so a recurring forecast is
|
||||
`0 6,12,18 * * * = Public:{cmd:wx Seattle}` rather than a per-service schedule
|
||||
setting. The command runs for its text only and transmits nothing itself
|
||||
(`CommandManager.render_command_output`); unknown, disabled, admin-only, timing-out
|
||||
and silent commands expand to nothing rather than airing raw placeholder text.
|
||||
Bounded by the new `[Bot] scheduled_command_timeout_seconds` (default 30). Two
|
||||
non-configurable airtime guards apply: a schedule using `{cmd:...}` must not fire
|
||||
more often than every 15 minutes (rejected at startup, measured by the tightest gap
|
||||
so `0,1 * * * *` counts as 60 seconds), and the command's own `cooldown_seconds` is
|
||||
still enforced.
|
||||
|
||||
- `{path_distance}` is now available in the path command's `[Path_Command] reply_prefix`,
|
||||
reporting total distance travelled (sender → hops → bot, e.g. `12.4km`) and rendering
|
||||
empty when any node in the chain has no usable coordinates. The prefix now supports the
|
||||
same pipe filters as the test command's `response_format`, so
|
||||
`{path_distance|prefix_if_nonempty:📏 }` drops the label along with the value.
|
||||
|
||||
- `install-service.sh --install-extras` installs the optional profanity-filter and
|
||||
geocoding packages without prompting, for unattended installs and upgrades. It
|
||||
takes precedence over the in-place `--update-venv` path, so the two can be
|
||||
combined.
|
||||
|
||||
- `[PacketCapture] observer_name` — an optional name reported as the `origin` of
|
||||
MQTT packet and status payloads. It lets the observer/analyzer identity differ
|
||||
from the MeshCore RF node, which is useful when one bot name is already taken
|
||||
by the radio's advertised name. Unset (the default) keeps the previous
|
||||
behavior: the connected device name, falling back to `[Bot] bot_name`.
|
||||
|
||||
- `local_translation_path` in `[Localization]` points at your own translation
|
||||
catalog, merged over the distributed one key by key, so you can translate a
|
||||
local command or override a single shipped string without editing a file that
|
||||
an upgrade will replace. Defaults to the `translations/` directory inside
|
||||
`[Bot] local_dir_path`, resolved to an absolute path so it does not depend on
|
||||
the working directory.
|
||||
|
||||
### Changed
|
||||
|
||||
- `meshcore` now requires 2.3.14 or newer. Before 2.3.13, `send_msg_with_retry`
|
||||
reported ACKed DMs as failures: it subscribed to the ACK only after `send_msg`
|
||||
returned, so an ACK queued right behind `MSG_SENT` was dispatched with no
|
||||
listener, and each attempt accepted only its own ACK code, so a late ACK
|
||||
answering an earlier attempt was ignored (meshcore_py#108). The bot logged
|
||||
"no ACK received after retries" and skipped the delivery bookkeeping for
|
||||
messages the recipient had in fact received.
|
||||
|
||||
- `{elapsed}` in test/keyword replies renders as seconds once the delay is a
|
||||
second or more (`1.5s` instead of `1500ms`), so a typical ack stays shorter
|
||||
(#275). Sub-second times still print as milliseconds.
|
||||
|
||||
- Response templates are parsed by a character-by-character state machine rather
|
||||
than by splitting on delimiters. Placeholders can now nest (`{"Dist: {d|hops_min:1}"}`)
|
||||
and filter arguments can be quoted. Field values are substituted into the output
|
||||
and never re-scanned, so a sender-supplied phrase still cannot inject a placeholder.
|
||||
|
||||
- Web viewer navigation is grouped: Radio, Scheduled Messages, Greeter, Feeds, Plugins
|
||||
and Configuration now sit under a single **Settings** gear menu, leaving Dashboard,
|
||||
Real-time, Contacts, Mesh Graph and Logs on the bar. The current page is highlighted,
|
||||
including the gear when a settings page is open.
|
||||
|
||||
- Added notes on connecting to waev.app MQTT brokers to the `packet_capture.md` file.
|
||||
|
||||
### Fixed
|
||||
|
||||
- MQTT brokers on `waev.app` now default to a JWT lifetime they accept (#248).
|
||||
waev.app refuses a token whose `exp` is more than an hour past its `iat`, so the
|
||||
project-wide 24-hour default never authenticated there and the operator saw only a
|
||||
bare auth failure. Those hosts now get a 3600s TTL with renewal at 3500s unless a
|
||||
value was configured for them, per broker or globally, in which case the configured
|
||||
value still wins. The choice is logged. Only `waev.app` and its subdomains match; no
|
||||
other broker's defaults change.
|
||||
|
||||
- The `[Keywords]` newline instructions now say the escape is a *backslash* and that
|
||||
`/n` is two literal characters (#277), and they no longer use `test` as the example
|
||||
keyword. `test` and `t` are served by the built-in test command, not the plain
|
||||
keyword path, so the mesh-info placeholders listed in that same block render empty
|
||||
there. Both facts are now stated where the example lives, along with the
|
||||
configparser continuation form, which needs no escape at all.
|
||||
|
||||
- The packet-capture docs no longer show a naive local timestamp in the published
|
||||
packet and status examples (#276). The bot has published UTC with a `Z` suffix since
|
||||
v1.0.0; only the examples still read as local time.
|
||||
|
||||
- `outgoing_flood_scope_override = none` is now read as global flood on the
|
||||
send path, as it already was everywhere else. `send_channel_message` tested
|
||||
the raw value against a fixed tuple, so the lowercase spelling became the
|
||||
region `#none` and sent scoped.
|
||||
|
||||
- Striped and hovered table rows in the web viewer's dark mode no longer render
|
||||
Bootstrap's light-theme text color on a dark background (about 1.3:1
|
||||
contrast). The dark overrides set a background but not a color, so every
|
||||
`.table-striped` page was affected.
|
||||
|
||||
- `[Joke_Command] joke_enabled` and `[DadJoke_Command] dadjoke_enabled` are now
|
||||
listed in the shared legacy-alias table. Both commands accepted that spelling
|
||||
at runtime through their own fallback, but the settings UI read only the
|
||||
`[Jokes]` spelling, so a bot disabled the same-section way showed as enabled
|
||||
on the plugin settings page.
|
||||
|
||||
- A region-scoped channel message now restores global flood even when
|
||||
`set_flood_scope` raises. The restore only ran in the `finally` around the
|
||||
send, so a set that raised left the device pinned to that region and every
|
||||
later send — channel replies, DMs, scheduled sends — went out under it.
|
||||
|
||||
- A DM waiting for its ACK no longer holds the radio. Radio commands were
|
||||
serialized per call, so a DM's retry loop kept every other command waiting
|
||||
through all of its ACK timeouts (up to ~36 s with the default three attempts),
|
||||
stalling channel replies, other DMs, and scheduled sends. The lock now covers
|
||||
one frame and the radio's immediate reply, and ACK and remote-response waits
|
||||
run outside it, so neighbor scope requests no longer stall replies either. A
|
||||
region-scoped channel message now holds the radio from setting its flood scope
|
||||
through restoring it, so no other send goes out under that scope.
|
||||
|
||||
- A radio connection is no longer accepted when every channel read times out or
|
||||
returns no usable channel data (#266). Startup and reconnect now retry the
|
||||
channel scan three times, keep an empty result out of the valid cache and
|
||||
database, then fail the connection cleanly so the normal restart/reconnect
|
||||
path can try again instead of running a bot that cannot route replies.
|
||||
|
||||
- Daily Weather Service forecasts now retry transient Open-Meteo failures at
|
||||
5, 15, and 30 minutes after the original run (#264). HTTP 429, 500, 502, 503,
|
||||
and 504 responses plus transport failures use one replaceable retry job,
|
||||
while permanent HTTP errors stop immediately and a successful retry sends
|
||||
the forecast only once.
|
||||
|
||||
- `help <command> <subcommand>` now resolves help for the base command while
|
||||
preserving the full message for context-aware help text (#285). Exact
|
||||
multi-word aliases such as `dad joke` and `ps aux` still take precedence
|
||||
over the base-command fallback.
|
||||
|
||||
- Service installers no longer leave `venv/bin/pip` and other console scripts
|
||||
pointing at the temporary build environment after an atomic virtualenv swap.
|
||||
`--update-venv` also repairs already-broken shebangs before replacing the old
|
||||
@@ -432,10 +549,12 @@ semantic versioning.
|
||||
least one operator running under `tmux` with nothing to restart it. The confirmation
|
||||
spells out that the bot stops completely and only returns if systemd or Docker
|
||||
restarts it.
|
||||
|
||||
- Corrected the `[External_Data] repeater_prefix_api_url` comment, which claimed that
|
||||
leaving it empty "disables prefix command functionality" (#70). Empty is the normal
|
||||
setup: the prefix command answers from the bot's own database. The option only adds
|
||||
an optional external dataset, and its JSON contract is now documented.
|
||||
|
||||
- `message_stats.path` no longer reports another packet's route (#80). When RF
|
||||
correlation failed, `find_recent_rf_data` fell back to the most recent packet in the
|
||||
cache, and the caller attributed that packet's route to the message — which is how a
|
||||
@@ -445,24 +564,29 @@ semantic versioning.
|
||||
left unresolved instead of being fabricated. This also stopped a wrong edge being
|
||||
written to the mesh graph, and stopped the `path` command receiving another packet's
|
||||
`routing_info`. SNR and RSSI still use the fallback as before.
|
||||
|
||||
- Dashboard **One-hop neighbours** now lists radios this node heard directly
|
||||
(MeshCore hop count 0: empty RF path), not originators of 1-hop relayed
|
||||
adverts. Empty-path adverts are stored in `observed_paths` with SNR/RSSI;
|
||||
neighbor-discover cycles refresh SNR on those rows. A one-time backfill
|
||||
copies recent zero-hop ADVERTs out of `packet_stream`.
|
||||
|
||||
- **Airplanes / ADS-B** no longer depends on the public airplanes.live API,
|
||||
which now returns HTTP 403 for unregistered clients (#244). Default
|
||||
endpoint is `https://api.adsb.lol/v2/`; existing `api_url` values pointing
|
||||
at `api.airplanes.live` are remapped automatically. Local readsb URLs are
|
||||
unchanged.
|
||||
|
||||
- `cmd` no longer lists commands that are disabled in `config.ini`. Commands
|
||||
with no `[<Name>_Command]` section at all are still listed, as before.
|
||||
|
||||
- Stale-contact cleanup no longer retries forever (#176). When the device refuses to
|
||||
remove a contact the contact stays in the list, so every sweep re-selected it and
|
||||
logged the same failure again — hundreds of `Failed to remove stale contact` warnings
|
||||
that only a restart cleared. A contact is now dropped from cleanup after
|
||||
3 consecutive refusals, with one summary warning explaining that the list may stay
|
||||
near its limit. A successful removal clears the count.
|
||||
|
||||
- Contacts whose device clock was never set are no longer treated as stale. MeshCore
|
||||
seeds an unset clock with a hardcoded time — `1715770351` (15 May 2024) or
|
||||
`1772323200` (1 Mar 2026) — so a never-synced node advertises that seed rather than
|
||||
@@ -472,6 +596,7 @@ semantic versioning.
|
||||
`722 days ago` entries in #176 were: the 15 May 2024 seed, not contacts last heard
|
||||
in 2024. A raw `0` (decoding to 1970) is covered too, and genuine adverts near a
|
||||
seed are unaffected.
|
||||
|
||||
- The same unset-clock check now guards every purge path, not just stale-contact
|
||||
cleanup. `_get_repeaters_for_purging`, `_get_companions_for_purging` and
|
||||
`purge_old_repeaters` all ranked an unset clock as maximum age, so an active node
|
||||
@@ -483,113 +608,6 @@ semantic versioning.
|
||||
The reply sent over the mesh is unchanged — it still carries only the exception
|
||||
text, with no filesystem path and no extra airtime.
|
||||
|
||||
### Changed
|
||||
|
||||
- `{elapsed}` in test/keyword replies renders as seconds once the delay is a
|
||||
second or more (`1.5s` instead of `1500ms`), so a typical ack stays shorter
|
||||
(#275). Sub-second times still print as milliseconds.
|
||||
|
||||
- Response templates are parsed by a character-by-character state machine rather
|
||||
than by splitting on delimiters. Placeholders can now nest (`{"Dist: {d|hops_min:1}"}`)
|
||||
and filter arguments can be quoted. Field values are substituted into the output
|
||||
and never re-scanned, so a sender-supplied phrase still cannot inject a placeholder.
|
||||
|
||||
- Web viewer navigation is grouped: Radio, Scheduled Messages, Greeter, Feeds, Plugins
|
||||
and Configuration now sit under a single **Settings** gear menu, leaving Dashboard,
|
||||
Real-time, Contacts, Mesh Graph and Logs on the bar. The current page is highlighted,
|
||||
including the gear when a settings page is open.
|
||||
- Added notes on connecting to waev.app MQTT brokers to the `packet_capture.md` file.
|
||||
|
||||
### Added
|
||||
|
||||
- Shlink is now supported as a URL shortener alongside v.gd / is.gd, selected with
|
||||
`short_url_website_service = shlink` under `[External_Data]`. It authenticates with
|
||||
`short_url_website_api_key` in an `X-Api-Key` header and needs `short_url_website`
|
||||
set to your own instance — there is no default, and the bot skips shortening rather
|
||||
than sending the key to a host you did not configure.
|
||||
|
||||
- `shorten` and `if_nonempty` response-template filters. `shorten` runs a value
|
||||
through the configured shortener and falls back to the original URL when shortening
|
||||
fails, so a clause is never lost to a network error. `if_nonempty:L` replaces a
|
||||
non-empty value with literal `L` and clears otherwise, which is how a whole clause
|
||||
is hidden rather than labelled: `{packet_hash|if_nonempty:"https://…/{packet_hash}"|shorten}`
|
||||
prints nothing at all when RF correlation fails, instead of a broken link. Both
|
||||
filters also answer to their other spellings — `shorten_url` in a template,
|
||||
`shorten_url` in a feed format, `if_notempty` — so a chain copied between a feed
|
||||
format and a command `response_format` works unchanged either way.
|
||||
|
||||
- Response-template filter arguments may be double-quoted, and a quoted argument may
|
||||
contain nested `{field}` placeholders: `{d|prefix_if_nonempty:"Dist {sender}: "}`.
|
||||
The quote ends the argument, so further filters can follow it. An unquoted
|
||||
`prefix_if_nonempty` argument still consumes the rest of the placeholder, which is
|
||||
what lets its literal contain `|`, so that form must stay last in its chain.
|
||||
|
||||
- `mqttN_keepalive` (default 60) sets the MQTT PINGREQ interval per broker. It was
|
||||
hardcoded at 60 before, which is long for websockets through a proxy that drops
|
||||
idle connections.
|
||||
|
||||
- `hops_min:N` response-template filter, alongside `pathbytes_min:N`. It clears a
|
||||
field unless the message actually travelled at least N hops, so
|
||||
`{firstlast_distance|hops_min:1|prefix_if_nonempty: | F/L Dist: }` drops the whole
|
||||
clause on a direct message. The distance placeholders render `N/A` when there is no
|
||||
path, and `prefix_if_nonempty` treats that as a value and prints its label, so a
|
||||
gate was needed; `pathbytes_min` was the only one available and it asks how the path
|
||||
is *encoded*, which meant throwing away a measurable one-byte multi-hop distance to
|
||||
suppress the direct case. `hops_min` asks about the route instead. An unknown hop
|
||||
count clears the field rather than guessing.
|
||||
|
||||
- `{packet_hash}` placeholder for `[Keywords]` responses, the test command's
|
||||
`response_format` and the path command's `reply_prefix`: the 16-char MeshCore
|
||||
packet identity hash (uppercase hex) of the packet that carried the request, so a
|
||||
reply can be tied back to a specific transmission when comparing paths. It comes
|
||||
only from the routing info of an RF packet actually correlated to the message, and
|
||||
renders empty otherwise, so a hash from an unrelated transmission is never shown.
|
||||
|
||||
- **Scheduled messages can be managed from the web viewer** (#174). A new Schedule page
|
||||
lists every `[Scheduled_Messages]` entry with its next run time and offers add, edit
|
||||
and delete. Changes are written to `config.ini` and applied by a queued config reload,
|
||||
so no restart is needed. The schedule builder composes the cron key from plain-language
|
||||
options and previews the next five runs; entries the bot cannot run are shown as
|
||||
**Not scheduled** with the reason rather than hidden. It edits the same config section
|
||||
the bot already uses, so there is no second source of truth.
|
||||
- Documented installing with `pipx`, which sidesteps PEP 668 on Debian 12+, Ubuntu
|
||||
23.04+, Fedora and Arch (#222), including where `config.ini`, the database and
|
||||
`local/` live — everything resolves relative to the config file's directory, so an
|
||||
absolute `--config` is what makes a pipx install deterministic.
|
||||
- Migration 23: nullable `snr` / `rssi` columns on `observed_paths` for
|
||||
zero-hop advert rows.
|
||||
- `{cmd:<command> [args]}` placeholders in `[Scheduled_Messages]`: a scheduled message
|
||||
can embed the reply of any bot command, so a recurring forecast is
|
||||
`0 6,12,18 * * * = Public:{cmd:wx Seattle}` rather than a per-service schedule
|
||||
setting. The command runs for its text only and transmits nothing itself
|
||||
(`CommandManager.render_command_output`); unknown, disabled, admin-only, timing-out
|
||||
and silent commands expand to nothing rather than airing raw placeholder text.
|
||||
Bounded by the new `[Bot] scheduled_command_timeout_seconds` (default 30). Two
|
||||
non-configurable airtime guards apply: a schedule using `{cmd:...}` must not fire
|
||||
more often than every 15 minutes (rejected at startup, measured by the tightest gap
|
||||
so `0,1 * * * *` counts as 60 seconds), and the command's own `cooldown_seconds` is
|
||||
still enforced.
|
||||
- `{path_distance}` is now available in the path command's `[Path_Command] reply_prefix`,
|
||||
reporting total distance travelled (sender → hops → bot, e.g. `12.4km`) and rendering
|
||||
empty when any node in the chain has no usable coordinates. The prefix now supports the
|
||||
same pipe filters as the test command's `response_format`, so
|
||||
`{path_distance|prefix_if_nonempty:📏 }` drops the label along with the value.
|
||||
- `install-service.sh --install-extras` installs the optional profanity-filter and
|
||||
geocoding packages without prompting, for unattended installs and upgrades. It
|
||||
takes precedence over the in-place `--update-venv` path, so the two can be
|
||||
combined.
|
||||
- `[PacketCapture] observer_name` — an optional name reported as the `origin` of
|
||||
MQTT packet and status payloads. It lets the observer/analyzer identity differ
|
||||
from the MeshCore RF node, which is useful when one bot name is already taken
|
||||
by the radio's advertised name. Unset (the default) keeps the previous
|
||||
behavior: the connected device name, falling back to `[Bot] bot_name`.
|
||||
- `local_translation_path` in `[Localization]` points at your own translation
|
||||
catalog, merged over the distributed one key by key, so you can translate a
|
||||
local command or override a single shipped string without editing a file that
|
||||
an upgrade will replace. Defaults to the `translations/` directory inside
|
||||
`[Bot] local_dir_path`, resolved to an absolute path so it does not depend on
|
||||
the working directory.
|
||||
|
||||
## [1.0.0] — 2026-08-07
|
||||
|
||||
v1.0.0 marks the first stable release. It adds zero-hop neighbor discovery, a
|
||||
|
||||
@@ -7,14 +7,14 @@ tracked here — see [CHANGELOG.md](CHANGELOG.md) for shipped changes and
|
||||
The Inline TODOs section at the bottom is auto-generated by
|
||||
`python scripts/update_todos.py` (see [Auto-Update](#auto-update)).
|
||||
|
||||
**Last updated:** 2026-09-13. The v1.1.0 release-candidate suite reports 55%
|
||||
coverage, and `fail_under = 50`.
|
||||
**Last updated:** 2026-09-20. The v1.1.0 suite reports 57% coverage, and
|
||||
`fail_under = 50`.
|
||||
|
||||
---
|
||||
|
||||
## Test coverage
|
||||
|
||||
Coverage is **55%** at the v1.1.0 release candidate. The enforced floor is 50%.
|
||||
Coverage is **57%** as of v1.1.0. The enforced floor is 50%.
|
||||
|
||||
Hardware- and API-dependent modules cap the realistic ceiling, so the list below
|
||||
is ordered by value rather than by percentage. Per-module figures below are from
|
||||
|
||||
@@ -11,6 +11,15 @@ command, service-plugin, or web-viewer interfaces introduced in v1.0.0. Existing
|
||||
configuration files continue to work. Back up `config.ini`, `local/`, and the bot
|
||||
database before upgrading, then use the normal installer or container update path.
|
||||
|
||||
### Dependencies
|
||||
|
||||
`meshcore` must be 2.3.14 or newer. This is a hard requirement, not a
|
||||
recommendation: before 2.3.13, `send_msg_with_retry` reported ACKed DMs as
|
||||
failures, so a bot on an older library logs "no ACK received after retries" for
|
||||
messages the recipient did receive, and skips the delivery bookkeeping for them.
|
||||
The normal installer and container paths pick this up on their own; a manual venv
|
||||
needs `pip install -U -r requirements.txt`.
|
||||
|
||||
### Database
|
||||
|
||||
Migration 23 adds nullable `snr` and `rssi` columns to `observed_paths`. The bot
|
||||
@@ -18,6 +27,14 @@ applies it automatically and records it in `schema_version`; existing path rows
|
||||
rows without a direct RF measurement retain `NULL` values. The migration is
|
||||
transactional and safe to run more than once.
|
||||
|
||||
Migration 24 creates `region_scope_daily` and `region_warning_events` for the
|
||||
region-scope tallies described below. Both are new tables, so nothing existing is
|
||||
rewritten, and the migration is safe to run more than once. `region_scope_daily`
|
||||
is written for every channel message once `[Region_Warnings] track_traffic` is on
|
||||
(the default); it holds one row per channel per local date, so the growth is
|
||||
bounded by channel count rather than traffic. `region_warning_events` is pruned by
|
||||
`[Data_Retention] region_warning_retention_days` (default 90).
|
||||
|
||||
### Configuration and local extensions
|
||||
|
||||
- `[Test_Command] distance_unit` controls printed path distances. Its default,
|
||||
@@ -33,6 +50,16 @@ transactional and safe to run more than once.
|
||||
- The Plugins page now discovers local commands and services and saves their new
|
||||
sections to `local/config.ini`. A section already present in the base config keeps
|
||||
its existing owner.
|
||||
- `[Region_Warnings]` is new. Scope **tallying** is on by default and costs no
|
||||
airtime; the **warning** is off by default (`enabled = false`) and, when enabled,
|
||||
starts in dry run (`dry_run = true`). Nothing is transmitted on upgrade unless you
|
||||
turn both of those around. See `docs/region-warnings.md`.
|
||||
- `[Data_Retention] region_warning_retention_days` (default 90) prunes the warning
|
||||
event log.
|
||||
- `[Hello_Command] include_sender` (default off) names the user the hello reply is
|
||||
answering. Existing hello replies are unchanged.
|
||||
- `[Contact_Command] enabled` (default on) adds a `contact` command that replies with
|
||||
the bot's own contact card. Set it to `false` if you do not want it.
|
||||
|
||||
### Behavior changes
|
||||
|
||||
@@ -53,6 +80,19 @@ transactional and safe to run more than once.
|
||||
between runs.
|
||||
- Proactive weather output, `!wx`, and `!gwx` share localized alert formatting.
|
||||
Russian is included, and local catalogs can override individual strings.
|
||||
- MQTT brokers on `waev.app` now default to a 3600s JWT TTL with renewal at 3500s,
|
||||
because waev rejects a longer-lived token. An explicit `mqttN_jwt_ttl_seconds` or
|
||||
global `jwt_ttl_seconds` still wins, so existing configurations are unchanged; the
|
||||
substitution is logged when it applies. No other broker's defaults move.
|
||||
- The Radio page gains **Region Scopes** and **Default Region Scope** cards, so the
|
||||
bot's own flood scopes and the radio's firmware default can be set from the web
|
||||
viewer instead of by hand. Note that the bot leaves the radio in forced-unscoped
|
||||
mode after any scoped send, so the firmware default stops applying until the bot
|
||||
scopes another one — the card says so too.
|
||||
- A DM waiting for its ACK no longer holds the radio, so channel replies, other DMs,
|
||||
and scheduled sends are no longer stalled behind one DM's retries. A region-scoped
|
||||
channel send does still hold the radio from setting the scope through restoring it,
|
||||
so nothing else goes out under that scope.
|
||||
|
||||
## Upgrading from v0.8 → v0.9
|
||||
|
||||
|
||||
Reference in New Issue
Block a user