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).
One-shot MAS-migration helper. DMs a fixed target set of users with no
email on file in Authentik, collects an address, verifies inbox ownership
with an emailed 6-digit code (via Resend), and only writes it back to
Authentik after confirmation — so a mistyped/hostile address never becomes
a live recovery address. FSM state persists in email_nag_prompts so
restarts never re-nag anyone done or mid-flow.
- Per-user rolling-1h cap on verification emails (EMAIL_NAG_MAX_SENDS_PER_HOUR)
to prevent send-spam abuse.
- Misconfig disables only this plugin instead of aborting bot startup.
URL link previews now post the page's og:image as an inline m.image
thumbnail above the title/description reply. All outbound fetches in the
preview path (page scrape, image HEAD probe, image download) route through
a new SSRF/DoS-hardened safehttp client so a posted link can't steer
fetches at loopback/RFC1918/cloud-metadata IPs or OOM the parser.
Also flips the daily WOTD auto-post to opt-in (ENABLE_WOTD_POST=true)
instead of opt-out.
Cancels the planned ARENA_BOSS_FLOW soak. The boss flow is shipped
unconditionally with no legacy fallback — boss-flow errors now surface
to the player and abort the round rather than silently falling back to
the old CombatPower path.
Deleted:
- runArenaCombat (combat_bridge.go) — legacy CombatPower-vs-Lethality
arena combat driver.
- RenderCombatLogArena, renderArenaOutcome (combat_narrative.go) — legacy
arena renderers. RenderCombatLogArena was a thin alias for
RenderCombatLog; renderArenaOutcome was already dead.
- renderArenaCombatFinalMessage (combat_bridge.go) — legacy "rewards +
closer" trailing text.
- arenaWinCloser, arenaLoseCloser (adventure_arena_combat.go) — flavor
helpers that only fed the deleted renderers. File now just holds
arenaParticipationXP.
- arenaBossFlowEnabled + the ARENA_BOSS_FLOW env var (adventure_arena.go,
.env.example). The env gate served only the now-removed fallback.
- sendArenaCombatMessages (adventure_arena.go) — wrapper that switched
pacing between boss flow and legacy. Replaced with direct
sendZoneCombatMessages calls at the three call sites.
- TestArenaBossFlowEnabled (bossflow test) and
TestRenderCombatLogArena_ProducesPhaseMessages (narrative test).
Simplified resolveArenaRound / resolveArenaSurvival / resolveArenaDeath:
the bossNarr-vs-nil branches collapsed since narration is now always
produced by resolveArenaBoss. Equipment degradation and the death-save
reprieve hook are unchanged — both already worked off the boss-flow
CombatResult.
Migration doc (§4) updated to record the cancellation and the
remaining tuning approach (playtest-driven, no flag soak window).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds ARENA_BOSS_FLOW to .env.example. Local .env flipped to "1" to
start the production soak (2026-05-09). Empty / "0" / "false" keeps the
legacy CombatPower path; anything else routes arena rounds through
runZoneCombat + renderBossOutcome (staged combat log, TwinBee mood
lines, phase-two narration on T3+).
Soak window per §11 of gogobee_legacy_migration.md = one real
production week before flipping default-on. Flag is removed in the
same commit that lands L4f.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adventure — Masterwork Rework:
- Expand from 3 generic items to 15 tiered masterwork items across
mining (weapon), fishing (armor), and foraging (boots) with per-tier
drop rates (5%/4%/3%/2%/1.5%) and location gating
- Skill-specific cross-skill bonuses replace flat masterwork check
- Auto-equip if better, silent discard for duplicates, inventory for rest
- Add !adventure equip command for manual masterwork equipping
- Tiered room announcements: T1-2 DM only, T3 quiet, T4-5 full
- First-drop detection with special message
- Character sheet shows ⭐/⚔️ markers for masterwork/arena gear
- Sell protection for special gear in shop
Adventure — Arena Combat Log:
- Turn-based Dragon Quest style narrative for arena fights
- Outcome-first design: roll determines win/loss, log assembled backward
- No-repeat flavor text via actionPicker with per-pool tracking
- 60 participation XP on arena loss
Adventure — Death's Reprieve Fix:
- All equipment set to 1 condition instead of death-level degradation
Wordle:
- Refactored word sourcing and expanded fallback word list
- Simplified Wordnik integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New plugins: encrypted Markov chain generation (9 commands), emojiboard
reaction stats (5 views), Miniflux RSS feed integration (8 commands),
and 48 new achievements across 9 categories. Adventure startup now
unconditionally resets daily actions and respawns expired deaths to
handle missed resets from SQLite contention. README updated with all
new features.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Multiplayer UNO: lobby system (2-4 humans + bot), DM-based turns,
30s auto-play timer, winner-takes-all pot, forfeit handling
- Solo UNO: refactor bot AI to shared standalone functions, support
starting games from DM, passive UNO call system
- Room sentiment: new room_sentiment_stats table and !roomsentiment
command showing per-room sentiment breakdown with percentages
- Stop purging llm_classifications (retain indefinitely for analytics)
- Fix multiplayer UNO audit issues: dead code in initMultiGame,
double game.turns++ on human plays, missing !uno status command
- Update README with UNO commands, config, and architecture
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add full UNO game plugin (1v1 vs bot via DM, community pot, bot personality)
- Move hangman into threads with direct reply guessing (no !hangman prefix needed)
- Fix DM room duplication by checking m.direct account data with in-memory cache
- Auto-draw when player has no playable cards instead of requiring manual draw
- Remove forced UNO call phase — players are penalized naturally if they forget
- Fix mutex held during network I/O in hangman start
- Fix infinite loop when both sides have no playable cards and deck is empty
- Fix Wild Draw Four penalty being skipped when UNO prompt triggered
- Fix hangman displayPhrase word boundary clarity (triple-wide gaps for spaces)
- Add UNO env vars, db tables, and README updates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Euro economy: virtual currency earned passively per message (tiered by
word count, 30s cooldown). Starting balance seeded from corpus character
count. Balance, leaderboard, and transfer commands. Debt system with
configurable limit.
Hangman: collaborative game with phrase pool loaded from file. ASCII
gallows display, tiered scoring (Easy/Medium/Hard/Extreme), early
solution bonus, participant tracking with DM verification. Community
phrase submission with LLM screening.
Blackjack: 1-2 players vs dealer. Standard casino rules (hit soft 17).
Auto-play on timeout. Bet validation against balance and debt limit.
Blackjack pays 1.5x. Separate leaderboard.
Games channel restriction: all game commands (trivia, hangman, blackjack,
flip) restricted to GAMES_ROOM. Dice (!roll) exempt. Trivia also
restricted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Deterministic-only detection (no LLM): word list with precompiled
leetspeak variation matching, text/image flood, wall of text, repeated
messages (Levenshtein similarity), mention flooding, link rate limiting
for new members, invite flooding, join/leave cycling detection.
Three-strike response ladder: warn + redact → temp mute → permanent ban.
Strikes expire after configurable period. Admin room notifications with
context cards. DMs over public callouts.
Admin commands: !mod warn/mute/unmute/ban/strikes/forgive/history/reload/
status/test. All require ADMIN_USERS membership.
Feature-flagged: disabled by default, set FEATURE_MODERATION=true to
enable. All thresholds configurable via env vars. New member grace
period with stricter thresholds. Word list hot-reload via !mod reload.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Space groups: automatic room grouping via membership overlap percentage.
Rooms with sufficient shared members are grouped together so leaderboards,
trivia scores, and stats aggregate across the community. Uses strict
clique-based algorithm (every room must meet threshold with every other
room in group). Configurable via SPACE_GROUP_THRESHOLD env var. Persisted
to SQLite, recomputed hourly and on startup.
HLTB scraper: rewrote for new howlongtobeat.com API (two-step token auth
via /api/finder/init + /api/finder endpoint).
Quote wall: fix E2EE decrypt flow for reply-to-save (ParseRaw before
Decrypt, skip ParseRaw after Decrypt returns pre-parsed event). Fix
subcommand matching for delete/search. Remove broken star-reaction handler
and old quote handler from user.go.
Other fixes:
- Strip Matrix reply fallback before command detection (main.go)
- Increase Ollama context window to 8192
- Improve tarot spread prompts (~4x longer, narrative arc)
- Add "cards are props" instruction to tarot LLM prompt
- Fix movies.go CommandDef name mismatch
- Add Community category to !help
- Remove daily horoscope broadcast cron
- Update README with all changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New features:
- !tarot and !tarotspread commands with LLM-powered readings (zodiac-aware)
- !quote system with AES-256-GCM encryption at rest, reply-to-save, search, and quoteboard
- Shared crypto utility (internal/crypto) for AES-256-GCM encrypt/decrypt/HMAC
- ResolveUser now matches display names via room membership as fallback
- !vibe and !tldr show bot uptime when insufficient messages buffered
Bug fixes:
- Fix duplicate quotes table schema that would crash quotewall at runtime
- Fix biased random selection in quote retrieval (use rand.Intn)
- Fix XP rank calculation to handle tied scores with COUNT(DISTINCT xp)
- Scope all leaderboards to room members (repboard, pottyboard, insultboard, firstboard, rankings, emojiboard)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generates milk carton style "missing person" PNG images using gg library
with embedded Go fonts. Tracks inactive members via daily_activity, with
configurable thresholds (MISSING_THRESHOLD_DAYS, MISSING_MAX_DAYS,
MISSING_MIN_MESSAGES, MISSING_EXCLUDE_USERS). Rate limited to 1 carton
per room per day. Derives flavor text characteristics from user stats
and sentiment data. Avatar fetching with 3-tier fallback.
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>
Full-featured Matrix bot with E2EE via matrix-js-sdk, 32+ plugins,
LLM-powered passive classification, XP/leveling, trivia, game releases,
anime/movie tracking, and more.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>