Both session paths called GenerateAndUploadCrossSigningKeys unconditionally on
every start. That published a fresh cross-signing identity each time the bot
restarted, so every user's client saw the bot's verification break and had to
re-verify it. The freshly-minted recovery key was only ever logged, never kept,
so the identity couldn't be recovered either.
bootstrapCrossSigning now mints once, persists the recovery key to
DATA_DIR/cross_signing.json (0600), and on later starts reuses the stored
identity untouched. Two escape hatches, both normally unset:
CROSS_SIGNING_REGENERATE=1 forces exactly one remint (every user must then
re-verify), and CROSS_SIGNING_RECOVERY_KEY imports an identity created before
the bot persisted its own key.
Shared by the appservice and masdevice paths, which had drifted into two copies
of the same block.
Two things, entangled in the working tree because 20d1f92 was mislabeled
(its message claimed "device grant" but it only deleted registration.yaml.example
and left the failed appservice+/sync hybrid in client.go):
1. Land the MAS OAuth 2.0 device grant that was running in prod but never
committed: masauth.go + client.go rewrite. Bot stays a normal user so /sync
works; refresh token persisted in data/mas_auth.json.
2. Add "appservice" as an alternate transport behind AUTH_MODE (default
"masdevice" = unchanged device-grant path, instant rollback):
- internal/bot/session.go: Session abstraction unifying both transports
(OnEventType/Run/Stop); NewSession dispatches on AuthMode.
- internal/bot/appservice.go: as_token auth (MAS-durable, no login/MFA/expiry),
cryptohelper MSC4190 device creation, crypto machine fed from Synapse
transaction pushes (to-device/device-lists/OTK) instead of /sync, inbound
decrypt+redispatch, and lazy per-room StateStore backfill (encryption +
members) so replies in encrypted rooms encrypt to the right recipients.
- main.go: NewSession/sess.OnEventType/sess.Run in place of the /sync loop.
- .env.example: AUTH_MODE, AS_REGISTRATION, AS_LISTEN_HOST/PORT, HOMESERVER_DOMAIN.
The appservice path fixes the earlier hybrid's fatal flaw (Synapse forbids AS
users from /sync) by using the transaction/push model. Bot-side only; Synapse
registration + experimental_features (msc2409/msc3202/msc4190) and E2EE cutover
are the next steps. Build + vet green (CGO=1 -tags goolm).
IGNORED_BOTS was set in .env but never read by any code; only the
URL-preview plugin filtered, and via a different unset var
(URL_PREVIEW_IGNORE_USERS). Parse IGNORED_BOTS in NewRegistry and
short-circuit DispatchMessage/DispatchReaction so ignored senders
(e.g. @pete:parodia.dev) are dropped before any plugin runs.
- Add forex plugin (Frankfurter v2 API) with rate lookups, analysis,
DM-based alerts, and daily cron poll. Backfills 1 year of history
on startup for moving averages and buy signal scoring.
- Fix bot hang caused by SQLite lock contention in reminder polling:
rows cursor was held open while writing to the same DB. Collect
results first, close cursor, then process. Same fix in milkcarton.
- Add sync retry loop so the bot reconnects after network drops
instead of silently exiting. StopSync() for clean Ctrl+C shutdown.
- Add panic recovery to all dispatch, syncer, and cron paths.
- Make all HTTP-calling plugin commands async (goroutines) so a slow
or dead external API cannot block the message dispatch pipeline.
Affects: lookup, stocks, forex, anime, movies, concerts, gaming,
retro, wotd, urls, howami.
- Extract DisplayName to Base, add db.Exec helper, convert silent
error discards across the codebase.
- Fix UNO mercy-kill bug (eliminated bot continues playing), adventure
DM nag spam, stats column mismatch, per-call regex/replacer allocs.
- Update README: forex commands, Finance section, 47 plugins.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete rewrite of the Freebee Matrix bot as GogoBee using mautrix-go.
- E2EE with goolm (pure Go, no CGo/libolm) and cross-signing bootstrap
- 35+ plugins with dependency injection and ordered registration
- SQLite storage via modernc.org/sqlite (no CGo)
- Scheduler via robfig/cron for WOTD, holidays, birthdays, releases, etc.
- Optional LLM integration (Ollama) for sentiment, profanity, roasts, vibes
- Threaded trivia, three-tier profanity tracking, WOTD usage verification
- Multi-country holiday support with deduplication
- Quadratic XP curve, configurable bot display name, encrypted DMs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>