Add space groups, fix HLTB scraper, fix quote wall E2EE, improve tarot prompts

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>
This commit is contained in:
prosolis
2026-03-11 00:45:25 -07:00
parent 1c02732445
commit 0fc15668da
12 changed files with 460 additions and 103 deletions

View File

@@ -15,6 +15,7 @@ import (
"gogobee/internal/db"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@@ -89,12 +90,13 @@ func (p *QuoteWallPlugin) handleQuote(ctx MessageContext) error {
}
// !quote delete [id]
if strings.HasPrefix(strings.ToLower(args), "delete") {
argsLower := strings.ToLower(args)
if argsLower == "delete" || strings.HasPrefix(argsLower, "delete ") {
return p.handleQuoteDelete(ctx, strings.TrimSpace(args[6:]))
}
// !quote search [keyword]
if strings.HasPrefix(strings.ToLower(args), "search") {
if argsLower == "search" || strings.HasPrefix(argsLower, "search ") {
keyword := strings.TrimSpace(args[6:])
return p.handleQuoteSearch(ctx, keyword)
}
@@ -125,10 +127,27 @@ func (p *QuoteWallPlugin) handleQuoteSaveReply(ctx MessageContext, replyEventID
return p.SendReply(ctx.RoomID, ctx.EventID, "Failed to fetch the original message.")
}
err = evt.Content.ParseRaw(evt.Type)
if err != nil {
slog.Error("quotewall: parse reply event content", "err", err)
return p.SendReply(ctx.RoomID, ctx.EventID, "Failed to parse the original message.")
// Decrypt if the event is encrypted
if evt.Type == event.EventEncrypted {
if p.Client.Crypto != nil {
if parseErr := evt.Content.ParseRaw(evt.Type); parseErr != nil {
slog.Error("quotewall: parse encrypted event", "err", parseErr)
return p.SendReply(ctx.RoomID, ctx.EventID, "Failed to parse the encrypted message.")
}
decrypted, decErr := p.Client.Crypto.Decrypt(context.Background(), evt)
if decErr != nil {
slog.Error("quotewall: decrypt reply event", "err", decErr)
return p.SendReply(ctx.RoomID, ctx.EventID, "Failed to decrypt the original message.")
}
evt = decrypted
} else {
return p.SendReply(ctx.RoomID, ctx.EventID, "Cannot read encrypted messages without crypto support.")
}
} else {
if parseErr := evt.Content.ParseRaw(evt.Type); parseErr != nil {
slog.Error("quotewall: parse reply event content", "err", parseErr)
return p.SendReply(ctx.RoomID, ctx.EventID, "Failed to parse the original message.")
}
}
msgContent := evt.Content.AsMessage()