Repository navigation
Releases
v0.9.1 — Whisper service now opt-in
Fixes a startup error that occurred when the Whisper transcription container image was unavailable. The whisper service is now opt-in via a Docker Compose profile and is no longer pulled on a standard docker compose up -d.
Changed
docker-compose.yml—whisperservice moved toprofiles: [whisper]; removeddepends_on: - whisperfrom thebotservice so the bot starts cleanly without itREADME.md— Quick Start adds the optional--profile whispercommand; Recording section documents both transcription backend options; added Read Message History to the required bot permissions list; addedgit clonestep.env.example— added comment explaining thatWHISPER_MODE=localrequires--profile whisper
Upgrading
If you were previously running the bundled Whisper container, start it explicitly going forward:
docker compose --profile whisper up -dv0.9.0 — Session commands & campaign Q&A
Session commands and campaign Q&A. All four slash commands are implemented bot-side; /next, /recap, and /note require the Quest Board v0.9.0 backend endpoints; /ask requires the v0.10.0 endpoint. Until those are deployed the commands return a clear error rather than crashing.
Added
-
bot/cogs/sessions.py— new cog with four slash commands:/next— fetches the next confirmed session for the guild viaGET /api/bot/guilds/{guild_id}/next-sessionand posts a green embed with<t:{ts}:F>/<t:{ts}:R>countdown; graceful 404 handling/recap <session_id>— fetches summary and GM notes viaGET /api/bot/sessions/{session_id}/summary; shows summary as embed description, GM notes as a field, or a prompt to use/record startif neither is present yet/note <text> [session_id]— verifies the user is linked, auto-resolves to the next upcoming session if no session_id is given, callsPOST /api/bot/sessions/{session_id}/notes; all responses ephemeral (notes are private)/ask <question>— fetches up to 10 completed sessions with summaries, passes them toservices/qa.answer_question, and posts the answer as an embed; footer shows how many sessions were searched
-
bot/services/qa.py— campaign Q&A service:answer_question(question, sessions, campaign_name, game_system, settings)- Reuses the same Ollama / Anthropic / OpenAI backends and mode selection as
summarisation.py; no new config required - System prompt instructs the LLM to answer only from provided summaries and say clearly when it lacks information
- Per-session summaries truncated to 800 chars to stay within model limits
Changed
-
bot/api_client.py— four new response models and async methods:NextSessionResponse+get_next_session(guild_id)SessionSummaryResponse+get_session_summary(session_id)post_session_note(session_id, discord_user_id, note)SessionHistoryItem+get_session_history(guild_id, limit=10)
-
bot/main.py— added"bot.cogs.sessions"to_COGS
Notes
/next, /recap, and /note require Quest Board backend endpoints added in v0.9.0 of the Quest Board app. /ask additionally requires the v0.10.0 history endpoint. See docs/questboard-improvements.md for implementation details.
v0.8.0 — Attendance RSVP
Attendance RSVP. Session-confirmed embeds now include ✅ / ❌ reactions so players can indicate attendance directly from Discord. Reactions write to Quest Board's existing attendance table — no new backend endpoints required.
Changed
-
bot/cogs/notifications.py_handle_confirmed— adds an "Attendance" field to the confirmed embed, seeds ✅ then ❌ reactions (0.5 s apart), and stores the message→session mapping withtype: "attendance"so the voting cog routes reactions correctly_store_message_map— accepts a newmap_typeparameter (default"voting"); stores"type"key in the mapping payload; backwards-compatible with existing Redis entries (missing key defaults to"voting")- Added
_ATTENDANCE_EMOJIS = ["✅", "❌"]constant
-
bot/cogs/voting.py_handle_reactionnow readsmapping["type"]and branches:"attendance"→ delegates to new_handle_attendance_reaction: ✅ callsput_attendance(attended=True), ❌ callsput_attendance(attended=False); other emojis ignored"voting"(default) → delegates to new_handle_vote_reaction(existing logic, now extracted to its own method)
- Added
_ATTEND_YES/_ATTEND_NOconstants
-
docs/questboard-improvements.md— added four new entries for the Quest Board backend endpoints required by bot v0.9.0 and v0.10.0
v0.7.0 — Polish & hardening
Polish and hardening. Remote settings, pre-commit security hooks, and a full setup guide in the README.
Added
.pre-commit-config.yaml— pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files (500 KB limit), check-merge-conflict, detect-private-key, and a localpip-audithook that runs on every change torequirements.txt- README — full setup guide covering: Discord Developer Portal walkthrough, Quest Board admin configuration, account linking, reaction voting, recording pipeline, Whisper model trade-offs, Ollama model recommendations, and a troubleshooting section
Changed
bot/main.py—on_readynow calls_apply_remote_settings, which fetchesGET /api/bot/settingsfrom Quest Board and applies admin-configured Whisper/LLM endpoints, models, and API keys on top of env-var defaults; failure is logged as a warning and does not prevent the bot from starting; API keys are never logged (placeholder string used in log line)
v0.6.0 — Recording & transcription
Voice recording, transcription, and summarisation pipeline. GMs can record a voice session directly from Discord; the audio is automatically transcribed and summarised, and the results are uploaded to Quest Board.
Added
-
bot/cogs/recording.py— full implementation replacing the v0.1.0 stub:/record start <session_id>— validates the UUID, requires the GM to be in a voice channel, fetches session metadata from the API for the announcement embed, connects to voice and starts adiscord.sinks.WaveSink; schedules an auto-stop atMAX_RECORDING_HOURS/record stop— stops the sink, disconnects from voice, hands off to the processing pipeline as a background task- Processing pipeline: collects per-user WAV files from the sink → mixes to mono MP3 (
utils/audio) → deletes WAVs → transcribes (services/transcription) → summarises (services/summarisation) → uploads viaPOST /api/bot/sessions/{id}/transcript→ posts a summary embed; cleans up the MP3 on completion or error - Auto-stop fires after
MAX_RECORDING_HOURSwith a channel notice - One active recording per guild enforced; clear errors if already recording or if the GM is not in a voice channel
- Transcript content is never logged — only metadata (duration, word count, file sizes)
-
bot/utils/audio.py—mix_to_mp3(wav_paths, output_path)wraps an FFmpeg subprocess; single-input path skipsamixfilter; logs file size on completion; raisesRuntimeErroron non-zero FFmpeg exit -
bot/services/transcription.py—transcribe(mp3_path, settings):WHISPER_MODE=local— multipart POST to ahmetoner/whisper-asr-webservice atWHISPER_API_URL/asrWHISPER_MODE=api— OpenAIPOST /v1/audio/transcriptions(whisper-1)- 10-minute timeout; raises
RuntimeErroron failure
-
bot/services/summarisation.py—summarise(transcript, campaign_name, game_system, settings):SUMMARISER_MODE=ollama—POST /api/generateto local OllamaSUMMARISER_MODE=api— Anthropic claude-haiku-4-5-20251001 ifANTHROPIC_API_KEYset, otherwise OpenAI gpt-4o-mini- TTRPG-focused system prompt: key events, NPC introductions, player decisions, and a "What's next?" hook
- 5-minute timeout; raises
RuntimeErroron failure
v0.5.0 — Reaction voting
Reaction voting. Emoji reactions on session_proposed messages are now translated into Quest Board availability votes in real time.
Changed
bot/cogs/voting.py— full implementation replacing the v0.1.0 stub:on_raw_reaction_add— recordsavailability="yes"when a user adds a slot emoji (🇦–🇪) to a session_proposed messageon_raw_reaction_remove— recordsavailability="no"when a user removes a slot emoji- Looks up the message→session mapping via
NotificationsCog.get_message_mapping(Redis-backed, 30-day TTL) to resolve emoji position to aslot_idwithout a Quest Board round-trip - Calls
GET /api/bot/platform-links/discord/{user_id}before voting; on 404 sends the user a DM prompting them to run/link - Ignores the bot's own seed reactions, non-slot emojis, and messages with no stored mapping; logs and swallows API errors so transient failures don't surface as Discord errors
v0.4.0 — Account linking
Discord account linking. Players can connect their Discord identity to Quest Board so that emoji reactions on session messages are recorded as votes against their Quest Board account.
Changed
bot/cogs/linking.py— full implementation replacing the v0.1.0 stub:/link— generates asecrets.token_hex(32)token, registers it in Quest Board's Redis viaPOST /api/bot/linking-tokens(TTL 10 min), sends the user a DM with{questboard_public_url}/auth/link?token=<token>, then pollsGET /api/bot/link-status/{token}every 30 s for up to 10 min; sends a confirmation DM on success or an expiry DM on timeout; handlesdiscord.Forbidden(DMs disabled) and Quest Board API errors gracefully/unlink— directs the user to their Quest Board profile page; a dedicated bot-facing unlink endpoint is tracked indocs/questboard-improvements.md(Priority 3)
v0.3.0 — Discord notifications
Bot-driven Discord notifications. Quest Board's Celery tasks now call the bot's HTTP server instead of posting plain webhook embeds on campaigns that have a guild_id configured.
Added
bot/cogs/notifications.py— full implementation replacing the v0.1.0 stub:on_bot_notifylistener routes incoming payloads to per-event handlers_resolve_channel— fetchesTextChannelby ID with clear logging on NotFound / Forbidden errorssession_proposed— rich embed with one field per time slot using Discord<t:timestamp:F>localised timestamps; slot details fetched fromGET /api/bot/sessions/{id}/timeslots; seed reactions 🇦–🇪 added immediately after posting (0.5 s apart to respect rate limits)session_confirmed— embed with confirmed time; campaign name fetched from APIsession_reminder— embed with human-readable label (_reminder_labelconvertshours_untilto "2 hours", "3 days", etc.) and confirmed time; campaign name fetched from APIsession_cancelled— embed using title and campaign name from payloadvote_update— embed with per-slot yes / maybe / no counts fetched from APIget_message_mapping(message_id)— public method used by the voting cog (v0.5.0) to resolve a Discord message ID back to a session ID and ordered slot list- Redis-backed message→session store (
qb_msg:{message_id}, 30-day TTL); falls back to an in-memory dict ifREDIS_URLis not set or Redis is unreachable
v0.2.0 — API client alignment
Aligns the API client with the Quest Board v0.2.0 bot endpoints now live on the backend. No new bot behaviour in this release — all functional work was Quest Board-side (Campaign fields, five new bot endpoints, /auth/link, linking token Redis flow, and Celery notification routing).
Changed
bot/api_client.py—BotSettingsResponseupdated to match the actual shape returned byGET /api/bot/settings: fields renamed from bot-centric names (whisper_mode,ollama_url, …) to the names Quest Board uses (whisper_endpoint_url,llm_endpoint_url,llm_model, …)
v0.1.0 — Initial scaffold
Initial scaffold. Project structure, configuration, typed API client, and stub cogs. The bot connects to Discord and starts an HTTP server but does not yet send messages or record votes.
Added
- Project structure —
bot/package withmain.py,config.py,api_client.py;cogs/,services/,utils/sub-packages bot/config.py— Pydantic Settings reading from.env; all variables documented with defaultsbot/api_client.py— Asynchttpxwrapper for all Quest Board bot endpoints; sendsX-Bot-Keyheader; typed Pydantic response models for every endpoint (existing and to-be-added in v0.2.0)bot/main.py—QuestBoardBot(discord.pycommands.Botsubclass) with required intents; aiohttp HTTP server atPOST /notifyandGET /health; startup audio-temp cleanup; graceful API client shutdown- Cog stubs —
notifications,linking,voting,recordingcogs all load cleanly; slash commands/link,/unlink,/record start,/record stopregistered but return "coming soon" responses Dockerfile— Python 3.12-slim, FFmpeg, non-rootbotuserdocker-compose.yml—bot+whisperservices; audio temp volume;ollamaservice commented out (assumed running on home server)requirements.txt— pinned direct and transitive dependenciesrequirements.in— unpinned direct dependencies forpip-compilerequirements-dev.txt—pip-audit,pip-tools,pytest,pytest-asyncio,pre-commit.env.example— all variables with inline documentation.gitignore—.env, audio files, transcripts, Python artefacts