Quota is a Discord quote bot with a web dashboard for managing, browsing, and celebrating the best moments in your server. Save quotes with slash commands, let your community vote, and track the greatest hits without leaving Discord.
Want to test it first? Explore the live dashboard or invite the public bot instance at quota.randware.org.
- Designed for real communities: collect quotes fast and keep them organized.
- Built-in voting and stats so the best moments rise to the top.
- A polished dashboard for search, permissions, and server-level control.
- Simple setup: generate config, run the server, invite the bot.
backend/— .NET 10 solution with API, bot, database, and a config setup TUIfrontend/— SvelteKit dashboard and landing site
- .NET 10 SDK
- Node.js (LTS recommended) and a package manager (
pnpmpreferred) - A Discord application + bot credentials
From backend/, run the interactive setup to generate a config.toml:
dotnet run --project ConfigSetupTUIThis prints a ready-to-use config.toml. Save it as backend/config.toml.
Notes:
oauth.idandoauth.secretare your Discord Application Client ID/Secret.bot.tokenis your Discord bot token.bot.refreshButtonsHourscontrols how many hours of recent quotes get their vote buttons refreshed on bot startup (default: 24).jwt.secretcan be any long random string.server.portdefaults to5000(update if you want a different port).
The setup wizard asks a few questions and prints a complete config.toml you can paste directly into backend/config.toml.
Example (redacted):
[jwt]
secret = "<generated secret>"
issuer = "Randware"
audience = "Quota"
expiryMinutes = 5
[oauth]
id = "<discord client id>"
secret = "<discord client secret>"
apiEndpoint = "https://discord.com/api/v10"
[bot]
token = "<discord bot token>"
refreshButtonsHours = 24
[openapi]
enabled = false
[server]
port = 5000Discord redirect URI:
- You must add your callback URL in the Discord Developer Portal → OAuth2 → Redirects.
- For local nginx:
http://localhost:8080/auth/callback - For production:
https://your-domain.com/auth/callback
This must match DISCORD_REDIRECT_URI exactly (no trailing slash).
Minimal config shape:
[jwt]
secret = "your_jwt_secret"
issuer = "your_issuer"
audience = "your_audience"
expiryMinutes = 5
[oauth]
id = "your_discord_client_id"
secret = "your_discord_secret"
apiEndpoint = "https://discord.com/api/v10"
[bot]
token = "your_bot_token"
refreshButtonsHours = 24
[openapi]
enabled = true
# Rate limiting (optional — sensible defaults are used if omitted)
[ratelimit]
generalPermitLimit = 60 # Max requests per window for general endpoints
generalWindowSeconds = 60 # Window size in seconds for general endpoints
authPermitLimit = 10 # Max requests per window for auth endpoints
authWindowSeconds = 60 # Window size in seconds for auth endpoints
[server]
port = 5000config.toml is gitignored by design.
Copy the example env file and fill it in:
cp frontend/.env.example frontend/.envRequired keys:
DISCORD_CLIENT_IDDISCORD_REDIRECT_URIBACKEND_HOST(e.g.http://localhost:5000)
cd frontend
pnpm installStart the backend (from backend/):
dotnet run --project MainStart the frontend (from frontend/):
pnpm devThe backend will create a local SQLite database at backend/database.db on first run.
This repo ships a docker-compose.yml to run both services behind an Nginx reverse proxy.
Create backend/config.toml via the setup wizard, then copy the env file and fill it in:
cp .env.example .envRun:
docker compose up --buildNginx listens on http://localhost:8080 and proxies:
/→ frontend/api/→ backend
Run the config wizard:
dotnet run --project ConfigSetupTUIRun API + bot:
dotnet run --project MainBuild / restore:
dotnet restore
dotnet buildOptional Nix dev shell (from backend/):
nix developAll scripts are in frontend/package.json:
pnpm dev # start dev server
pnpm build # production build
pnpm preview # preview production build
pnpm check # typecheck + svelte-check
pnpm check:watch # watch mode
pnpm lint # eslint + prettier
pnpm format # format with prettierIf [openapi] enabled = true in config.toml, Swagger UI is available at:
http://localhost:{port}/swagger
Required permissions:
- View Channels
- Send Messages
- Send Messages in Threads
- Embed Links
- Use External Emojis
- Add Reactions
- View Guild Insights
- Manage Guild
You can also hit the backend endpoint /bot/invite to get an invite URL with the required permissions pre-configured.
- Keep secrets out of version control (
backend/config.toml,frontend/.env). - The backend reads config from
backend/config.tomlat startup.
