Skip to content

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.

Python · discord.py · aiohttpLAS (la2 / americas) by default, configurable per regionMVP · Module 1 (LoL)

For players

If the bot is already on your server, the only thing you have to do is link yourself:

  1. Run /link Name#TAG with your full Riot ID (the name and tag you see in the LoL client, e.g. Faker#KR1).
  2. The bot validates the account against the Riot API. If it exists, it's linked and your matches this week start counting.
  3. Use /ranking whenever you want to see the table, or wait for the automatic post every night.
A match only counts if you played it with at least one other linked member of the group. Matches you play with no one known are discarded.

Requirements

RuntimePython · discord.py · aiohttp
Discord accountWith permission to create an Application and a bot
Riot API keyDevelopment (expires every 24 h) or Production
Discord serverWhere you invite the bot, with its own channels and roles
HostingLocal (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 thisTypeVariableWhat for
Private text channel (only the bot sees it)channelSTORAGE_CHANNEL_IDThe 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 rankingschannelRANKING_CHANNEL_IDWhere the daily ranking and the weekly recap are posted.
Match-finished alerts channel · optionalchannelMATCH_NOTIFY_CHANNEL_IDIf you set it, the bot posts live whenever a match played by the group finishes.
General channel (blowouts) · optionalchannelGENERAL_CHANNEL_IDWhere blowout alerts land (a loss before 25 min).
Bot log channel · optionalchannelLOG_CHANNEL_IDThe bot forwards its WARNING+ logs here (Riot down, expired key, etc.) on top of the console.
Channel for admin commands · optionalchannelADMIN_CHANNEL_IDIf you set it, /link-admin and /ingest-now can only be run there.
Role for whoever administers the botroleDEV_ROLE_IDEnables /link-admin, /unlink-admin and /ingest-now.
Player / group member role · optionalrolePLAYER_ROLE_IDOnly affects which commands /help and /ayuda show.
Role assigned automatically on linking · optionalroleLOL_ROLE_IDOn /link, the bot gives you this role (needs the Manage Roles permission).
The 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 typeLifetimeWhen
DevelopmentExpires every 24 h — you have to regenerate it by handTesting, development
ProductionStableThe 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).

If the key expires while the bot is running, ingestion stops and the bot logs a 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.

VariableDescription
DISCORD_TOKENBot token (Bot tab → Reset Token). Discord won't show it again.
DISCORD_GUILD_IDServer ID. Optional, but registers the slash commands instantly.
STORAGE_CHANNEL_IDPrivate channel the bot uses as a database.
RANKING_CHANNEL_IDChannel where it publishes the rankings.
DEV_ROLE_IDRole enabled for the administration commands.
ADMIN_CHANNEL_IDChannel where those commands can be run (optional).
PLAYER_ROLE_IDPlayer role; defines what /help and /ayuda show.
LOL_ROLE_IDRole the bot assigns when linking an account (optional).
MATCH_NOTIFY_CHANNEL_IDMatch-finished alerts channel (optional).
GENERAL_CHANNEL_IDChannel where blowouts are announced (optional).
MATCH_POLL_INTERVAL_MINUTESHow many minutes between checks for new matches for the live alert (default 5).
RIOT_API_KEYRiot API key. The development key expires every 24 h.
RIOT_PLATFORMRegion platform (LAS → la2).
RIOT_REGIONRegional routing (LAS/LAN/NA → americas).
TIMEZONETime zone for the day/week cutoffs (default America/Argentina/Buenos_Aires).
DAILY_POST_HOUR / DAILY_POST_MINUTELocal time of the daily job. 23:55 recommended, so today's ranking isn't empty.
LOG_CHANNEL_IDChannel where the bot sends its own logs (optional).
LOG_CHANNEL_LEVELMinimum 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_ENVLoadsUse
unset or staging.env.stagingDefault. Development and testing.
production.env.productionThe 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

CommandWhat it doesWho
/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
/unlinkUnlinks 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 · /ayudaList 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-nowForces 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

  1. 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.participants of the match-v5 JSON).
  2. It excludes remakes (under 5 min or early surrender).
  3. The windows are day and week from Monday 00:00 local time (TIMEZONE, default Buenos Aires).
  4. In each window the stats are aggregated per player and the scoring engine applies the formula from config/scoring.yaml.
  5. 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.
  6. 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.

aggregationper_game_average — per-match average — fair to whoever plays little vs. a lot
normalizationzscore — (value − mean) / stdev, so no metric dominates just by having bigger numbers

Metrics active by default

MetricWeightWhat it measures
win_rate5.0win percentage
kda3.0(kills + assists) / deaths
avg_deaths2.0 average deaths per match
damage_per_min1.5damage to champions per minute
avg_vision1.0average vision score
games_played0.5matches 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 logs

Status 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 in bot.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.