A Discord bot that tracks Mythic+ dungeon runs for your World of Warcraft guild or friend group. Never miss a key completion again—get automatic notifications when followed characters finish dungeons.
For guilds and friend groups:
- Use
/followto open a private Battle.net-authenticated character picker - Follow or unfollow only characters returned by your Battle.net WoW profile
- Get automatic Discord notifications when followed characters complete dungeons
- Slash command registration (
/follow) for Battle.net-verified character management - Automatic polling of Raider.IO every 5 minutes for runs
- SQLite storage for followed characters and run history—no external database needed
- React Router SSR management UI orchestrated by .NET Aspire, backed by ASP.NET Core JSON API endpoints
- Lightweight—runs anywhere .NET runs
- Create a Discord bot at discord.com/developers and invite it to your server
- Create a Battle.net application and configure the redirect URI to match
Web:PublicBaseUrlplus/api/auth/blizzard/callback - Copy the example config and add your Discord and Battle.net credentials:
cp appsettings.example.json appsettings.json # Edit appsettings.json with your Discord token and channel name - Run it:
dotnet run --project src/MPlusKeybot.AppHost
The bot will create mplus-data.db in the working directory.
- .NET 10 SDK
- Node.js/npm for building the React web UI
- Discord bot token (create one at Discord Developer Portal)
- Discord channel where the bot can post messages
- Battle.net application credentials with the
wow.profilescope - Public HTTPS URL for the follow management web UI
The bot uses standard .NET configuration. You can use appsettings.json, environment variables, or command-line arguments.
Copy the example and edit:
cp appsettings.example.json appsettings.json{
"Discord": {
"Token": "your-bot-token-here",
"Channel": "mythic-plus"
},
"Web": {
"PublicBaseUrl": "https://example.com/mplus-keybot",
"PathBase": "/mplus-keybot"
},
"Blizzard": {
"ClientId": "battle-net-client-id",
"ClientSecret": "battle-net-client-secret",
"Region": "us"
}
}Web:PublicBaseUrl is used for Discord link buttons, Battle.net redirect URIs, and form redirects. If your reverse proxy forwards /mplus-keybot to the app, set Web:PathBase to /mplus-keybot; if it strips the prefix, leave Web:PathBase empty while keeping the public URL prefixed.
⚠️ appsettings.jsonis git-ignored to prevent accidentally committing secrets.
export Discord__Token=your-bot-token-here
export Discord__Channel=mythic-plus
export Web__PublicBaseUrl=https://localhost:5142/mplus-keybot
export Web__PathBase=/mplus-keybot
export Blizzard__ClientId=battle-net-client-id
export Blizzard__ClientSecret=battle-net-client-secret
export Blizzard__Region=us
dotnet run --project src/MPlusKeybot.AppHostdotnet run --project src/MPlusKeybot.Api/MPlusKeybot.Api.csproj -- --Discord:Token=your-token --Discord:Channel=mythic-plusIn development, you can omit Discord:Token and use https://localhost:5173/mplus-keybot/api/dev/follow?discordUserId=dev-user to create the same short-lived follow flow and complete the real Battle.net sign-in. Configure your Battle.net app with https://localhost:5173/mplus-keybot/api/auth/blizzard/callback as the redirect URI. The Discord/dev follow link is one-time use and expires quickly; the resulting management session lasts 24 hours.
The Aspire AppHost pins the local web endpoint to https://localhost:5173/mplus-keybot by default so Battle.net redirect URI registration is stable. Override the port with Web:LocalPort if needed, or set AppHost:PublicBaseUrl explicitly when testing through a tunnel or reverse proxy.
The local topology is Aspire-orchestrated:
Browser -> React Router SSR Node app in src/MPlusKeybot.Web
/api/** -> thin frontend proxy -> ASP.NET Core API in src/MPlusKeybot.Api
/** -> React Router SSR routes
Install frontend dependencies once:
cd src/MPlusKeybot.Web
npm installThen run the AppHost from the repository root and open https://localhost:5173/mplus-keybot or the web endpoint shown in the Aspire dashboard:
dotnet restore
dotnet build
dotnet run --project src/MPlusKeybot.AppHostBrowser-side React fetches use same-origin /api/... URLs, handled by a generic React Router proxy route. Server-side React Router loaders and the proxy use the API_BASE_URL runtime environment variable injected by Aspire.
dotnet testThe Playwright character-management e2e tests run when Chromium is installed and are skipped otherwise:
dotnet build tests/MPlusKeybot.Tests/MPlusKeybot.Tests.csproj
pwsh tests/MPlusKeybot.Tests/bin/Debug/net10.0/playwright.ps1 install chromium
dotnet test --filter CharacterManagementE2ETestsThe repository includes a Nix flake for reproducible builds and deployment:
# Enter development shell
nix develop
# Build the bot
nix build
# Run directly
nix runFor production deployments on NixOS, use the provided module:
{
inputs.mplus-keybot.url = "github:adampoit/mplus-keybot";
outputs = { nixpkgs, mplus-keybot, ... }: {
nixosConfigurations.mplus-bot = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
mplus-keybot.nixosModules.default
{
services.mplus-keybot = {
enable = true;
environmentFile = "/run/secrets/mplus-keybot.env";
};
}
];
};
};
}Create /run/secrets/mplus-keybot.env with:
Discord__Token=your-bot-token-here
Discord__Channel=mythic-plus
Web__PublicBaseUrl=https://example.com/mplus-keybot
Web__PathBase=/mplus-keybot
Blizzard__ClientId=battle-net-client-id
Blizzard__ClientSecret=battle-net-client-secret
Blizzard__Region=us
After updating NuGet dependencies, regenerate the lock file:
nix run .#fetch-deps -- ./nix/nuget-deps.jsonYou can also build a self-contained binary:
dotnet publish -c Release -r linux-x64 \
--self-contained=true \
-p:PublishSingleFile=true \
-p:GenerateRuntimeConfigurationFiles=true \
-o ./artifacts
# Run on target server
./artifacts/mplus-keybotMIT
