Documentation
Wiki
How BogaBot is used and how to stand up your own instance on your server. The bot is free to use: you clone the repo and run it with your own bot and your own tokens.
What it is
BogaBot is a modular Discord bot. Module 1 (the current MVP) is a group of friends' League of Legends ranking: link a Riot account → ingest matches from the Riot API once a day → compute a configurable score per player → publish rankings in Discord.
It's built in replaceable layers: storage sits behind an interface (today it's “Discord as a database”; moving to SQLite or Postgres is a new class), modules are self-contained cogs, and the ranking formula is a YAML file you can edit without touching code.
For players
If the bot is already on your server, the only thing you have to do is link yourself:
- Run
/link Name#TAGwith your full Riot ID (the name and tag you see in the LoL client, e.g.Faker#KR1). - The bot validates the account against the Riot API. If it exists, it's linked and your matches this week start counting.
- Use
/rankingwhenever you want to see the table, or wait for the automatic post every night.
Requirements
Runtime | Python · discord.py · aiohttp |
|---|---|
Discord account | With permission to create an Application and a bot |
Riot API key | Development (expires every 24 h) or Production |
Discord server | Where you invite the bot, with its own channels and roles |
Hosting | Local (python run.py) or a VPS with systemd |
Setup on Windows / PowerShell: python -m venv venv → venv\Scripts\activate → pip install -r requirements.txt.
Create the bot in Discord
Step 1
Create the bot in Discord
In the Developer Portal: New Application → Bot tab → Reset Token. That value goes in DISCORD_TOKEN. Leave the Privileged Gateway Intents off, the bot doesn't need them.
Step 2
Invite it to your server
OAuth2 → URL Generator. Scopes: bot and applications.commands. Permissions: Send Messages, Read Message History, Embed Links, View Channel and Manage Roles (that last one only if you'll use LOL_ROLE_ID).
Step 3
Turn on developer mode
User Settings → Advanced → Developer Mode. With that, right-clicking any channel, role or server lets you copy its ID.
Permissions when inviting it
In OAuth2 → URL Generator, scopes bot and applications.commands. Bot permissions:
- Send Messages, Embed Links, View Channel and Read Message History on the storage and rankings channels.
- Manage Roles only if you're going to use
LOL_ROLE_ID(so the bot assigns that role on linking). - It needs no privileged intents: leave them off.
Channels and roles
With developer mode on (Settings → Advanced), right-clicking each channel or role lets you copy its ID. You also need the server ID for DISCORD_GUILD_ID.
| Create this | Type | Variable | What for |
|---|---|---|---|
| Private text channel (only the bot sees it) | channel | STORAGE_CHANNEL_ID | The bot uses it as a database: it stores JSON messages here. No one else should see it or write to it. |
| Public channel for the rankings | channel | RANKING_CHANNEL_ID | Where the daily ranking and the weekly recap are posted. |
| Match-finished alerts channel · optional | channel | MATCH_NOTIFY_CHANNEL_ID | If you set it, the bot posts live whenever a match played by the group finishes. |
| General channel (blowouts) · optional | channel | GENERAL_CHANNEL_ID | Where blowout alerts land (a loss before 25 min). |
| Bot log channel · optional | channel | LOG_CHANNEL_ID | The bot forwards its WARNING+ logs here (Riot down, expired key, etc.) on top of the console. |
| Channel for admin commands · optional | channel | ADMIN_CHANNEL_ID | If you set it, /link-admin and /ingest-now can only be run there. |
| Role for whoever administers the bot | role | DEV_ROLE_ID | Enables /link-admin, /unlink-admin and /ingest-now. |
| Player / group member role · optional | role | PLAYER_ROLE_ID | Only affects which commands /help and /ayuda show. |
| Role assigned automatically on linking · optional | role | LOL_ROLE_ID | On /link, the bot gives you this role (needs the Manage Roles permission). |
STORAGE_CHANNEL_ID channel is the bot's database (JSON messages). No one but the bot should see it or write to it.Riot API key
At developer.riotgames.com you generate the key. It goes in RIOT_API_KEY.
| Key type | Lifetime | When |
|---|---|---|
| Development | Expires every 24 h — you have to regenerate it by hand | Testing, development |
| Production | Stable | The bot running for real |
Also set RIOT_PLATFORM and RIOT_REGION to your region. It defaults to LAS: RIOT_PLATFORM=la2, RIOT_REGION=americas (LAS, LAN and NA route to americas).
CRITICAL (with a 3 h cooldown) that reaches LOG_CHANNEL_ID if you configured it.Environment variables
Everything comes from a .env file — the code never hardcodes secrets or IDs. None of the real .env.* files are committed.
| Variable | Description |
|---|---|
DISCORD_TOKEN | Bot token (Bot tab → Reset Token). Discord won't show it again. |
DISCORD_GUILD_ID | Server ID. Optional, but registers the slash commands instantly. |
STORAGE_CHANNEL_ID | Private channel the bot uses as a database. |
RANKING_CHANNEL_ID | Channel where it publishes the rankings. |
DEV_ROLE_ID | Role enabled for the administration commands. |
ADMIN_CHANNEL_ID | Channel where those commands can be run (optional). |
PLAYER_ROLE_ID | Player role; defines what /help and /ayuda show. |
LOL_ROLE_ID | Role the bot assigns when linking an account (optional). |
MATCH_NOTIFY_CHANNEL_ID | Match-finished alerts channel (optional). |
GENERAL_CHANNEL_ID | Channel where blowouts are announced (optional). |
MATCH_POLL_INTERVAL_MINUTES | How many minutes between checks for new matches for the live alert (default 5). |
RIOT_API_KEY | Riot API key. The development key expires every 24 h. |
RIOT_PLATFORM | Region platform (LAS → la2). |
RIOT_REGION | Regional routing (LAS/LAN/NA → americas). |
TIMEZONE | Time zone for the day/week cutoffs (default America/Argentina/Buenos_Aires). |
DAILY_POST_HOUR / DAILY_POST_MINUTE | Local time of the daily job. 23:55 recommended, so today's ranking isn't empty. |
LOG_CHANNEL_ID | Channel where the bot sends its own logs (optional). |
LOG_CHANNEL_LEVEL | Minimum level forwarded to that channel (default WARNING). |
Staging vs. production
The environment is chosen by the shell variable BOGABOT_ENV (set it in the terminal before running, not inside the .env):
| BOGABOT_ENV | Loads | Use |
|---|---|---|
unset or staging | .env.staging | Default. Development and testing. |
production | .env.production | The real bot, on the real server. |
# local, default staging python run.py # explicit $env:BOGABOT_ENV = "production" python run.py # with the script (console + log in logs\) .\scripts\run_bot.ps1 -Environment production
The default is staging on purpose: if you forget to set the variable, you never run against production by accident. Recommended: staging points at a different bot and a different server, not the same one, because storage lives in Discord channels and you'd mix test data with real data.
Commands
| Command | What it does | Who |
|---|---|---|
/link <Name#TAG> | Links your Riot account to your Discord. It's validated against the Riot API; if the Riot ID doesn't exist, nothing is stored. | everyone |
/unlink | Unlinks your account. Your matches stop counting toward the ranking. | everyone |
/ranking [Today | Week] | Shows the group ranking on demand, without waiting for the automatic post. | everyone |
/help · /ayuda | List the commands available for your role. They're two names for the same thing. | everyone |
/link-admin <user> <Name#TAG> | Links another server user's Riot account. Requires the dev role and, if configured, must be run in the admin channel. | dev role |
/unlink-admin <user> | Unlinks another server user's account. | dev role |
/ingest-now | Forces a manual match ingestion. It's idempotent: the dedup by (match_id, puuid) skips what's already stored, so running it extra times duplicates nothing. | dev role |
/ingest-now is idempotent: the dedup by (match_id, puuid) skips what's already stored, so running it extra times duplicates nothing.
How the ranking is calculated
- Each match is stored as one record per player, but only if you played it alongside at least one other linked member of the group. Solo games are discarded on ingestion (it checks
metadata.participantsof the match-v5 JSON). - It excludes remakes (under 5 min or early surrender).
- The windows are day and week from Monday 00:00 local time (
TIMEZONE, default Buenos Aires). - In each window the stats are aggregated per player and the scoring engine applies the formula from
config/scoring.yaml. - The daily job runs at
DAILY_POST_HOUR:DAILY_POST_MINUTE(23:55 recommended): ingestion → the day's ranking. On Mondays it also posts the weekly “Trolls and Pros” recap for the week that just closed — counting Ranked Flex only (queue 440), to measure the group's serious play. - Dedup by
(match_id, puuid)with no cursor: each run asks “since Monday” and skips what's already stored.
Scoring engine
The formula lives in config/scoring.yaml and is edited without touching code. For each player every metric is computed (averaged per match), normalized across all players, and the final score is the sum of weight × normalized_value.
aggregation | per_game_average — per-match average — fair to whoever plays little vs. a lot |
|---|---|
normalization | zscore — (value − mean) / stdev, so no metric dominates just by having bigger numbers |
Metrics active by default
| Metric | Weight | What it measures |
|---|---|---|
win_rate | 5.0 | win percentage |
kda | 3.0 | (kills + assists) / deaths |
avg_deaths | 2.0 ↓ | average deaths per match |
damage_per_min | 1.5 | damage to champions per minute |
avg_vision | 1.0 | average vision score |
games_played | 0.5 | matches played (rewards participation) |
↓ = lower is better (the normalized value is inverted). The engine also supports avg_kills, avg_assists, cs_per_min, off by default — enable them by adding them to the YAML.
Live alerts and logs
Match finished
If MATCH_NOTIFY_CHANNEL_ID is set, every MATCH_POLL_INTERVAL_MINUTES (5 by default) the bot checks for new matches and, for each one played with another linked member, posts a message tagging the group's players who were in it — with each one's result, in case they ended up on opposing teams.
Trolls and blowouts
Every scheduler run detects, within the last 48 h, trolled games (KDA < 0.5 and FF before 20 min) and blowouts (a loss before 25 min). Trolls are announced in RANKING_CHANNEL_ID; blowouts in GENERAL_CHANNEL_ID. There's an all-time trolls ranking, and the alert queries the match timeline to show the minute you first died.
Logs to Discord
If LOG_CHANNEL_ID is set, logs at level LOG_CHANNEL_LEVEL (WARNING by default) or higher are forwarded to that channel too, on top of the console. Meant to let you catch errors (Riot down, expired key) without watching the terminal.
Deploy
Local
python run.py directly, or .\scripts\run_bot.ps1 [-Environment production], which opens a visible console and mirrors everything to logs\<ambiente>\. Don't start two instances against the same environment/server: they'd answer commands twice.
VPS (systemd)
The repo ships deploy/bogabot.service. Setup is one-time: clone, venv + pip install, copy the .env over scp, install the service and systemctl enable --now bogabot. After that, every push to main that passes the tests deploys itself via GitHub Actions (git pull + pip install + systemctl restart).
sudo cp deploy/bogabot.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now bogabot
sudo journalctl -u bogabot -f # live logsStatus and limits
- Scope: module 1 (LoL ranking). No AI features yet — they're in the backlog as a separate cog.
- Storage: today it's Discord (JSON messages in a private channel + an in-memory index). It's O(n) messages; moving to a real DB is a new class in
storage/and one line inbot.py. - Queues: the daily/weekly ranking counts every queue (ARAM and rotating modes included); the “Trolls and Pros” recap is Ranked Flex only.
- Region: decided for LAS (
la2/americas); configurable for others.
Bugs, ideas and requests → the repo's issues.