Files
gogobee/README.md
prosolis 0fc15668da 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>
2026-03-11 00:45:25 -07:00

23 KiB
Raw Blame History

GogoBee

Matrix community bot with E2EE, 37 plugins, passive tracking, scheduled posts, and optional LLM features.

Written in Go using mautrix-go for encryption and modernc.org/sqlite for storage.


Table of Contents


Features

  • E2EE that actually works - mautrix-go with goolm (pure Go). Crypto state lives in SQLite so device keys survive restarts. Cross-signing bootstraps on first run — the bot self-verifies its own device.
  • No CGo, no system deps - builds to a single static binary. Cross-compile to whatever you want.
  • 37 plugins with dependency injection and ordered registration
  • Passive tracking - XP, stats, streaks, achievements, markov corpus, keyword alerts, all running silently
  • Scheduled posts via robfig/cron - WOTD, holidays, game releases, birthdays, anime/movie releases, concert digests, esteemed members
  • LLM integration (optional) - Ollama-powered sentiment analysis, roast profiles, room vibes, tarot readings, conversation summaries
  • Encrypted quote wall - AES-256-GCM encrypted quotes at rest, reply-to-save, search, leaderboard
  • Space groups - automatic room grouping via membership overlap. Leaderboards, stats, and trivia scores span all rooms in a group. No Matrix Spaces API needed — the bot infers community boundaries from shared members.
  • SQLite everything - one file, no external database needed

Requirements

  • Go 1.22+
  • A Matrix homeserver account for the bot

Optional:


Installation

From Source

git clone https://github.com/prosolis/gogobee.git
cd gogobee
cp .env.example .env   # edit with your settings
go build -tags goolm -o gogobee .
./gogobee

Docker

docker build -t gogobee .
docker run --env-file .env -v ./data:/app/data gogobee

Docker Compose

docker compose up -d

Configuration

Everything is configured through environment variables or a .env file.

Required

Variable Description
HOMESERVER_URL Matrix homeserver URL, e.g. https://matrix.org
BOT_USER_ID Bot's Matrix user ID, e.g. @gogobee:matrix.org
BOT_PASSWORD Bot's Matrix password

Core (optional)

Variable Default Description
DATA_DIR ./data Where the database and device files live
BOT_DISPLAY_NAME GogoBee Display name
LOG_LEVEL info debug, info, warn, or error
ADMIN_USERS Comma-separated admin user IDs
BROADCAST_ROOMS Comma-separated room IDs for scheduled posts

API Keys (optional)

Variable Service Used By
RAWG_API_KEY RAWG !game, !retro, !releases
WORDNIK_API_KEY Wordnik Word of the Day
CALENDARIFIC_API_KEY Calendarific Holiday posts
OPENWEATHER_API_KEY OpenWeather !weather
FINNHUB_API_KEY Finnhub !stock
BANDSINTOWN_API_KEY Bandsintown !concerts
TMDB_API_KEY TMDB !movie, !tv, !upcoming
SERPAPI_KEY SerpAPI Esteemed member image fetching

Services (optional)

Variable Description
OLLAMA_HOST Ollama server URL, e.g. http://localhost:11434
OLLAMA_MODEL Model name, e.g. llama3.2
LIBRETRANSLATE_URL LibreTranslate instance for !translate
LLM_SAMPLE_RATE Fraction of messages to classify (0.01.0, default 0.15)

Encryption

Variable Description
QUOTE_ENCRYPTION_KEY Base64-encoded 32-byte AES-256 key for encrypted quote storage. Generate with openssl rand -base64 32. If unset, !quote is disabled.

Feature Flags

Variable Description
FEATURE_URL_PREVIEW Set to anything to enable automatic URL previews
FEATURE_SHADE Set to anything to enable the shade plugin (stub)
FEATURE_TRIVIA Set to false to disable trivia (default: enabled)
FEATURE_ESTEEMED Set to anything to enable satirical esteemed member posts
ESTEEMED_ROOM Room ID for esteemed member posts (separate from broadcast rooms)

Missing Members

Variable Default Description

Space Groups

Variable Default Description
SPACE_GROUP_THRESHOLD 50 Percentage of the smaller room's members that must overlap to group rooms together (1100)
HOLIDAY_COUNTRIES US Comma-separated ISO country codes for Calendarific holiday posts

Missing Members

Variable Default Description
MISSING_THRESHOLD_DAYS 14 Days of inactivity before considered missing
MISSING_MAX_DAYS 90 Days after which they're considered gone, not missing
MISSING_MIN_MESSAGES 10 Minimum lifetime messages to be eligible
MISSING_EXCLUDE_USERS Comma-separated user IDs to never list as missing

Rate Limits

Variable Default Description
RATELIMIT_WEATHER 5 Daily weather lookups per user
RATELIMIT_TRANSLATE 20 Daily translation limit per user
RATELIMIT_CONCERTS 10 Daily concert searches per user

Running the Bot

# dev
go run -tags goolm .

# prod
go build -tags goolm -o gogobee .
./gogobee

The -tags goolm flag selects the pure-Go crypto implementation. No C compiler or libolm needed.

First Run

  1. Start the bot. It logs in, creates a device, bootstraps cross-signing, and self-verifies automatically.
  2. That's it. E2EE works across restarts from here on out.

Commands

XP & Leveling

Command Description
!rank Your level, XP, and progress
!leaderboard Top 10 by XP

Reputation

Command Description
!rep [@user] Reputation count
!repboard Top 10 by rep

Rep is earned when someone thanks you. The bot detects this automatically.

Stats & Personality

Command Description
!stats [@user] Message statistics
!rankings [category] Rankings by words, links, questions, or emojis
!personality Your community archetype

Streaks

Command Description
!streak Current and longest streak
!firstboard Top first-posters-of-the-day

Trivia

Command Description
!trivia [category] [difficulty] Start a question
!trivia scores Room leaderboard
!trivia categories List categories
!trivia fastest Fastest answers
!trivia stop End current question

Reminders

Command Description
!remindme <time> <message> Set a reminder (natural language)
!reminders Your pending reminders
!unremind <id> Cancel a reminder

Presence

Command Description
!away [message] Set away status
!afk [message] Same as away
!back Clear away/afk
!whois @user Profile card

Fun

Command Description
!roll [NdM+X] Dice (default 1d6)
!8ball <question> Magic 8-ball
!coin Coin flip
!time [city|@user] World clock (cities or user's timezone)
!hltb <game> How Long To Beat
!twinbee Twinbee series lore
!poll <q> | <a> | <b>... Reaction poll
!weather <location> Weather (needs API key)
!dadjoke Dad joke
!randomwiki Random Wikipedia article

Tools

Command Description
!calc <expression> Calculator, understands "5 plus 3"
!qr <text> Generate QR code

Games & Entertainment

Command Description
!game <query> / !retro <query> Game lookup (RAWG)
!releases [month|search <q>] Game releases
!releasewatch add|list|remove Release watchlist
!stock <ticker> Stock quote (Finnhub)
!stockwatch add|list|remove Stock watchlist
!concerts <artist> Concert search
!concerts watch|watching|unwatch Concert watchlist
!anime <title> Anime search (MAL)
!anime watch|watching|unwatch|season|upcoming Anime features
!movie <title> / !tv <title> Movie/TV search (TMDB)
!movie watch|watching|unwatch Movie watchlist
!upcoming movies Upcoming movies

Lookup

Command Description
!wiki <topic> Wikipedia summary
!define <word> Dictionary definition
!urban <term> Urban Dictionary
!translate [lang] <text> Translate (needs LibreTranslate)

Personal

Command Description
!settz <timezone> Set timezone (IANA)
!mytz Your timezone and current time
!timezone list Common timezones
!np [track] Now playing
!backlog add|list|random|done Personal backlog
!watch <keyword> DM alerts for a keyword
!watching List keyword watches
!unwatch <keyword> Remove watch

Countdowns

Command Description
!countdown add "<label>" <YYYY-MM-DD> Public countdown
!countdown private "<label>" <YYYY-MM-DD> Private countdown
!countdown mine Your countdowns
!countdown remove <id> Remove one
!countdown [id] List all or show one

Birthdays

Command Description
!birthday set <MM-DD[-YYYY]> Set birthday
!birthday remove Remove birthday
!birthday show Show yours
!birthdays Upcoming (next 30 days)
!horoscope Daily horoscope (requires birthday to be set)

Community

Command Description
!quote Random quote (or reply to a message to save it)
!quote @user Random quote from a specific user
!quote search <text> Search quotes
!quote "text" -- @user Manually save a quote attributed to a user
!quote delete <id> Delete a quote (admin only)
!quoteboard Top 5 most-quoted members
!missing List members who haven't posted recently
!missing post [@user] Generate a milk carton poster for the longest-absent (or specified) member
!haveyouseenthem @user Generate a milk carton missing poster for a user

LLM (requires Ollama)

Command Description
!howami [@user] Roast profile
!vibe Room energy check
!tldr Summarize recent chat
!sentiment [@user] Sentiment breakdown (10 categories)
!potty [@user] Profanity count
!pottyboard Profanity leaderboard
!insults [@user] Insult stats
!insultboard Insult leaderboard
!tarot [@user] Draw a tarot card + LLM reading
!tarotspread [@user] Three-card spread (Past/Present/Future)

Other

Command Description
!achievements [@user] Unlocked achievements
!wotd Today's Word of the Day (use it in chat for 25 XP)
!botinfo Bot diagnostics (admin only)
!help DMs the full command list

Passive Features

All of these run in the background without any commands:

  • XP - 10 XP per message with 30s cooldown. Level-up announcements use Twinbee/Parodius themed messages.
  • Stats - tracks words, chars, links, images, questions, emojis, and time-of-day patterns
  • Streaks - consecutive days active, first poster of the day
  • Reputation - detects "thanks", "ty", "thx", etc. with 24h cooldown per pair
  • Achievements - 32 of them, checked silently on every message
  • Markov chains - collects messages for !markov generation (10k cap per user)
  • Keyword alerts - DMs you when someone says your watched keywords
  • Presence - auto-clears away/afk when you send a message
  • Room milestones - announces at 1k, 5k, 10k, 25k, 50k, 100k, 250k, 500k, 1M messages
  • URL previews - OG tag extraction (feature-flagged, off by default)
  • Reactions - logs all reactions for !emojiboard
  • Space groups - rooms with sufficient member overlap are automatically grouped. Leaderboards, trivia scores, and other per-room features aggregate across the group. Recomputed hourly; persisted to SQLite. Uses strict clique-based grouping (every room must meet the threshold with every other room in the group).
  • LLM classification - sentiment (10 categories), profanity, insults, WOTD usage (needs Ollama)
  • Message buffer - last 50 messages per room held in memory for !vibe and !tldr. Not persisted to disk; resets on restart. Uptime reported when insufficient messages are buffered.

Scheduled Posts

Uses robfig/cron. All times UTC.

Time Job What it does
00:05 Prefetch Grabs WOTD data ahead of time
06:00 Birthdays Birthday shoutouts + 100 XP
07:00 Holidays Multi-calendar holidays (US, Asian, Jewish, Islamic)
08:00 WOTD Posts the Word of the Day
09:00 Mon Releases Weekly game releases
10:00 Anime Anime airing today
11:00 Movies Movie releases today
12:00 Sun Concerts Weekly concert digest
13:00 Wed/Sun Esteemed Satirical esteemed community member posts (feature-flagged)
Every 30s Reminders Fires pending reminders
Hourly Space groups Refreshes room membership overlap and group mappings
03:00 Maintenance Purges stale caches, old rate limits, expired logs; runs SQLite optimize

Achievements

32 achievements:

Message Milestones - first_message, 100_messages, 1000_messages, 10000_messages

Time-Based - night_owl (100 night msgs), early_bird (100 morning msgs)

Content - wordsmith (avg >8 words), link_collector (50 links), shutterbug (20 images), question_master (100 questions)

Social - welcome_wagon (first message), rep_magnet (10 rep received)

Streaks - week_streak (7 days), month_streak (30 days)

Trivia - trivia_novice (10 correct), trivia_master (100 correct)

Special - markov_victim (got markov'd), logophile (used 10 WOTDs)


Personality Archetypes

Assigned based on your message patterns:

Archetype What it means
Chatterbox You talk a lot
Novelist Long messages
Inquisitor Always asking questions
Linkmaster Shares lots of links
Shutterbug Lots of images
Enthusiast Exclamation marks!
Regular Solid community member

Sentiment Classification

Every message (subject to LLM_SAMPLE_RATE) is classified by Ollama into one of 10 sentiment categories. The bot reacts with an emoji when the confidence score is strong enough (|score| > 0.5). Per-user counts are tracked in the database and viewable via !sentiment.

Sentiment Emoji Score range Example
Positive 👍 > 0.5 "This is awesome, great work!"
Excited 🔥 > 0.5 "OH MY GOD I can't wait for this!!"
Supportive 🤗 > 0.5 "You've got this, don't give up"
Grateful 💜 > 0.5 "Thank you so much for helping me"
Humorous 😂 > 0.5 "lmao that's the funniest thing I've seen all day"
Curious 🧐 > 0.5 "How does that work exactly?"
Neutral "I'll be back in 10 minutes"
Sarcastic 🤨 < -0.5 "Oh sure, that'll definitely work"
Frustrated 😮‍💨 < -0.5 "I've been trying to fix this for three hours"
Negative 👎 < -0.5 "This is broken and nobody cares"

The LLM also returns a float score (-1.0 to 1.0) for each message. These scores are averaged per user to derive an overall mood shown in !sentiment output and fed into !howami roast profiles.


External APIs

All optional. The bot works fine without any of them, you just won't have those specific features.

Service Free? What for
RAWG Yes Game lookups, releases
Wordnik Yes Word of the Day
Calendarific Yes (1k/mo) Holiday calendar
HebCal Yes, no key Jewish holidays
Aladhan Yes, no key Islamic dates
OpenWeather Yes (1k/day) Weather
Finnhub Yes Stock quotes
Bandsintown Yes Concert data
Jikan/MAL Yes, no key Anime data
TMDB Yes Movie/TV data
OpenTDB Yes, no key Trivia questions
Wikipedia Yes, no key Wiki summaries
Free Dictionary Yes, no key Definitions
Urban Dictionary Yes, no key Slang
icanhazdadjoke Yes, no key Dad jokes
SerpAPI Free (100/mo) Image search for esteemed members
HowLongToBeat Yes, no key Game completion times
LibreTranslate Self-host Translation
Ollama Self-host LLM features (sentiment, tarot, vibes, roasts)

Architecture

gogobee/
├── main.go                  # Entry point, plugin registration, cron setup
├── go.mod / go.sum
├── internal/
│   ├── bot/
│   │   ├── client.go        # mautrix client + E2EE (cryptohelper + goolm)
│   │   └── dispatch.go      # Plugin registry, event dispatch
│   ├── crypto/
│   │   └── crypto.go        # AES-256-GCM encryption, HMAC-SHA256
│   ├── db/
│   │   └── db.go            # SQLite schema (40+ tables), migrations
│   ├── plugin/
│   │   ├── plugin.go        # Plugin interface, Base helpers, context types
│   │   ├── xp.go            # XP & leveling
│   │   ├── reputation.go    # Thanks detection
│   │   ├── stats.go         # Message stats & milestones
│   │   ├── streaks.go       # Daily streaks
│   │   ├── trivia.go        # OpenTDB trivia
│   │   ├── reminders.go     # Reminders
│   │   ├── presence.go      # Away/AFK
│   │   ├── fun.go           # Dice, 8ball, polls, weather, etc.
│   │   ├── tools.go         # Calculator, QR codes
│   │   ├── user.go          # Timezone, quotes, backlog, keyword watches
│   │   ├── welcome.go       # New user detection, !help
│   │   ├── achievements.go  # 32 achievements
│   │   ├── reactions.go     # Reaction logging, emojiboard
│   │   ├── markov.go        # Markov chains
│   │   ├── urls.go          # URL previews
│   │   ├── llm_passive.go   # Ollama sentiment/profanity
│   │   ├── wotd.go          # Word of the Day
│   │   ├── holidays.go      # Multi-calendar holidays
│   │   ├── gaming.go        # Game releases
│   │   ├── birthday.go      # Birthdays
│   │   ├── retro.go         # Game lookups (RAWG)
│   │   ├── lookup.go        # Wiki, dictionary, urban, translate
│   │   ├── countdown.go     # Countdowns
│   │   ├── stocks.go        # Stocks
│   │   ├── concerts.go      # Concerts
│   │   ├── anime.go         # Anime
│   │   ├── movies.go        # Movies/TV
│   │   ├── botinfo.go       # Admin diagnostics
│   │   ├── howami.go        # LLM roasts
│   │   ├── vibe.go          # Room vibe, TLDR
│   │   ├── shade.go         # Stub
│   │   ├── milkcarton.go    # Missing member milk carton posters
│   │   ├── quotewall.go     # Encrypted quote wall (AES-256-GCM)
│   │   ├── tarot.go         # LLM-powered tarot readings
│   │   ├── horoscope.go     # Daily horoscopes
│   │   ├── esteemed.go      # Satirical esteemed member posts
│   │   └── ratelimits.go    # Rate limiting
│   └── util/
│       ├── auth.go          # Matrix login, token check
│       ├── logger.go        # slog logging
│       └── parser.go        # Message parsing, XP math, archetypes

Why Go?

E2EE - This project went through three SDK iterations: matrix-bot-sdk (no E2EE support), matrix-js-sdk (E2EE via fake-indexeddb with an in-memory crypto store that wiped device keys on every restart), and finally mautrix-go which stores crypto state in SQLite with cross-signing bootstrap. The bot self-verifies its own device on startup.

Deployment - Pure Go, no CGo. go build -tags goolm gives you a static binary with zero system dependencies. The TypeScript version needed Node.js, npm, a C compiler for better-sqlite3, and libolm.

Scheduler - Replaced a hand-rolled 60s tick loop with robfig/cron. Standard cron expressions, less code, fewer bugs.

Plugins - Go interfaces + struct embedding instead of abstract classes. Same pattern, less boilerplate.


Database

Single SQLite file at $DATA_DIR/gogobee.db. Schema auto-creates on first run. WAL mode enabled.

40+ tables covering users, XP, stats, streaks, reputation, reminders, trivia, achievements, encrypted quotes (AES-256-GCM), backlog, keyword watches, scheduler config, birthdays, horoscopes, LLM classifications, stocks, concerts, anime, movies, countdowns, presence, markov corpus, reaction log, and various caches.

Backup

# safe to run while the bot is up (WAL mode)
sqlite3 data/gogobee.db ".backup data/gogobee-backup.db"

Troubleshooting

E2EE

E2EE should just work. The bot bootstraps cross-signing and self-verifies its device on first run.

  1. After restarts, the bot reuses its saved device and crypto state. No manual steps needed.
  2. If things are really broken, delete data/device.json and data/gogobee.db to start fresh.

Bot not responding in encrypted rooms

  • Check that the bot joined the room (it auto-joins on invite)
  • Look at logs for decryption errors

API commands not working

  • Check that the relevant API key is set in .env
  • Look at logs for error messages
  • Most APIs have rate limits; the bot caches responses to stay within them

Build errors about libolm

# use the goolm tag:
go build -tags goolm -o gogobee .

License

MIT