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 for a group of friends. The core module is the League of Legends ranking: link a Riot account → ingest matches from the Riot API → compute a configurable score per player → publish rankings in Discord. Around that there's a troll detector that judges every match, live alerts, sounds in voice channels and a points system you can redeem.

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), each module is a self-contained cog, and both the ranking formula and the troll detector are YAML files you can edit without touching code.

Python · discord.py[voice] · aiohttpLAS (la2 / americas) by default, configurable per regionFree to use — clone the repo and run it on your server

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, and only Ranked (Flex or Solo/Duo) counts for the ranking. Matches you play with no one known are discarded.

Also

  • /trolls shows who's been trolling the most; /troll-analizar tells you why your last match added (or didn't add) troll points.
  • /sonido makes the bot join your voice channel and play a clip.
  • If you have the points role, you earn points hanging out in voice with friends and playing LoL. Check how many you have with /puntos and redeem them for your own sound with /canjear-sonido.
  • If the server set up a commands channel, use them there. The ones marked 🔒 in /help reply only to you.

Requirements

RuntimePython 3.12+ · discord.py[voice] · aiohttp
ffmpegOn the PATH, to play and measure the sounds (without it, everything else works)
Discord accountWith permission to create an Application and a bot
Riot API keyPersonal API Key (doesn't expire) or Development (expires every 24 h)
Discord serverWhere you invite the bot, with its own channels and roles
HostingLocal (python run.py) or a Linux server with systemd

Setup on Windows / PowerShell: python -m venv venv → venv\Scripts\activate → pip install -r requirements.txt. On Linux: sudo apt install ffmpeg for the sounds.

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: View Channel, Send Messages, Read Message History and Embed Links; Connect and Speak for the sounds; Manage Roles 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.

Step 4

Create channels and roles

At minimum a private storage channel, a public rankings channel, the general one and an administration role. The rest is optional (see the full table in the wiki).

Step 5

Get the Riot API key

At developer.riotgames.com → Register Product → Personal API Key: free and it doesn't expire (Riot approves it by hand). Meanwhile the Development Key works; it expires every 24 h and is renewed with /riot-key without restarting.

Step 6

Fill in the .env and run

With all the IDs and tokens, fill in .env.staging (or .env.production) and run python run.py. On the first start it registers the slash commands. For the sounds, ffmpeg has to be installed.

Permissions when inviting it

In OAuth2 → URL Generator, scopes bot and applications.commands. Bot permissions:

  • View Channel, Send Messages, Read Message History and Embed Links on the storage, rankings, general, trolls and alerts channels.
  • Connect and Speak for the sounds in voice 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_IDDaily ranking, the weekly "Trolls and Pros" recap and, if there's no trolls channel, the troll alerts.
The server's general channelchannelGENERAL_CHANNEL_IDIt only gets one short line per historic blowout (15+ troll points). Nothing else.
Troll alerts channel · optionalchannelTROLL_CHANNEL_IDTrolled games with their anecdote and the details, plus the daily troll ranking. If missing, they go to the rankings channel.
Match-finished alerts channel · optionalchannelMATCH_NOTIFY_CHANNEL_IDA live alert for every group match: the 10 players and the troll-o-meter.
Bot commands channel · optionalchannelBOT_CHANNEL_IDIf you set it, regular members can only use commands there (or in the admin one). Devs, anywhere.
Channel for admin commands · optionalchannelADMIN_CHANNEL_IDAdministration commands can only be run there, and it's where the Riot key expiry warnings land.
Bot log channel · optionalchannelLOG_CHANNEL_IDThe bot forwards its WARNING+ logs here (Riot down, expired key, etc.) on top of the console.
Role for whoever administers the botroleDEV_ROLE_IDEnables the administration commands, and the sound and points commands marked as dev.
Player / group member role · optionalrolePLAYER_ROLE_IDOnly affects which sections /help and /ayuda show.
Role that earns points ("Boguero") · optionalrolePOINTS_ROLE_IDWhoever has it earns points in voice and by playing, and can redeem them. Without this role, the points module isn't loaded.
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
Personal API KeyDoesn't expire. Requested under Register Product; Riot approves it by hand (it can take a few days)The bot running for real. Set RIOT_KEY_TTL_HOURS=0
DevelopmentExpires every 24 hFor testing while the other one arrives. Renewed with /riot-key

/riot-key <key> (dev role, in the admin channel) validates the new key against Riot, applies it without restarting and stores it in RIOT_KEY_FILE so it survives restarts. With no argument, it shows when the current one expires. With RIOT_KEY_TTL_HOURS above 0, the bot warns in the admin channel (tagging the dev role) RIOT_KEY_WARN_MINUTES before it expires and when it does.

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 the log channel. If you switch to a key from another Riot app, puuids change (they're encrypted per app): the bot re-resolves them on its own by Riot ID.

Environment variables

Everything comes from a .env file — the code never hardcodes secrets or IDs, and if a required variable is missing the bot won't start and says which one. None of the real .env.* files are committed.

Discord: token, channels and roles

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.
GENERAL_CHANNEL_IDGeneral channel: only historic blowouts go there.
TROLL_CHANNEL_IDTroll alerts and ranking channel (optional; defaults to RANKING_CHANNEL_ID).
MATCH_NOTIFY_CHANNEL_IDMatch-finished alerts channel (optional).
MATCH_POLL_INTERVAL_MINUTESHow many minutes between checks for new matches, for the alerts and troll warnings (default 5).
BOT_CHANNEL_IDChannel where regular members can use commands (optional).
ADMIN_CHANNEL_IDChannel where the administration commands run (optional).
DEV_ROLE_IDRole enabled for the administration commands.
PLAYER_ROLE_IDPlayer role; defines what /help and /ayuda show.
LOL_ROLE_IDRole the bot assigns when linking an account (optional).

Riot API

VariableDescription
RIOT_API_KEYRiot API key. In production, the Personal API Key (doesn't expire). Rotated on the fly with /riot-key.
RIOT_PLATFORMRegion platform (LAS → la2).
RIOT_REGIONRegional routing (LAS/LAN/NA → americas).
RIOT_KEY_FILEWhere the key loaded with /riot-key is stored (default data/riot_key.json, mode 600).
RIOT_KEY_TTL_HOURSKey lifetime in hours for the reminder (default 24, the dev key). With the Personal API Key: 0 = never expires.
RIOT_KEY_WARN_MINUTESHow many minutes before expiry the admin channel is warned (default 120).

Schedule

VariableDescription
TIMEZONETime zone for the day/week cutoffs (default America/Argentina/Buenos_Aires).
DAILY_POST_HOUR / DAILY_POST_MINUTELocal time of the daily job (default 10:00). 23:55 recommended, so today's ranking isn't empty.

Config and state files

VariableDescription
SCORING_CONFIG_PATHRanking formula (default config/scoring.yaml).
TROLLS_CONFIG_PATHTroll detector thresholds and points (default config/trolls.yaml).
TROLLS_STATE_FILESince when the troll ranking counts; written by /trolls-reiniciar (default data/trolls_state.json).

Logs

VariableDescription
LOG_LEVELConsole log level (default INFO).
LOG_CHANNEL_IDChannel where the bot sends its own logs (optional).
LOG_CHANNEL_LEVELMinimum level forwarded to that channel (default WARNING).

Sounds

VariableDescription
SOUNDS_ENABLEDRandom sounds in voice channels (default true). /sonido works even when it's off.
SOUNDS_DIRFolder with the audio files, outside git (default sounds/).
SOUNDS_CHANCEChance that one plays on each check, from 0 to 1 (default 0.15).
SOUNDS_CHECK_INTERVAL_MINUTESHow often the dice are rolled (default 5).
SOUNDS_COOLDOWN_MINUTESMinimum gap between two random sounds (default 45).
SOUNDS_ACTIVE_FROM / SOUNDS_ACTIVE_TOHours when they play (0-23; can wrap past midnight). 0 and 0 = all day.

Points and redemptions

VariableDescription
POINTS_ROLE_IDRole that earns and redeems points. Without it, the points module isn't loaded.
POINTS_FILEWhere points are stored (default data/points.json).
POINTS_VOICE_INTERVAL_MINUTES / POINTS_VOICE_AMOUNTPoints for being in voice: how many and how often (default 1 every 5 min).
POINTS_VOICE_DAILY_CAPDaily cap on voice points (default 60).
POINTS_LOL_GAME / POINTS_LOL_WINPoints per stored LoL match and extra if you won it (default 5 and 5).
SOUND_REDEEM_COSTWhat redeeming a sound costs (default 100).
SOUND_REDEEM_DAYSDays a redeemed sound lasts (default 7).
SOUND_REDEEM_MAX_SECONDSMaximum length of a redeemed clip (default 8 s).
SOUND_REDEEM_MAX_ACTIVEActive redeemed sounds per person (default 1).

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

If BOT_CHANNEL_ID is set, regular members can only use commands in that channel (or the admin one); devs, anywhere. The administration commands (/link-admin, /unlink-admin, /ingest-now, /riot-key, /trolls-reiniciar, /trolls-recalcular) also require the admin channel.

🎮 LoL ranking

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. Only Ranked games count (Flex and Solo/Duo).everyone
/link-admin <user> <Name#TAG>Links another server user's Riot account.dev role
/unlink-admin <user>Unlinks another server user's account.dev role
/ingest-nowForces a match ingestion. It's idempotent: the dedup by (match_id, discord_id) skips what's already stored.dev role
/riot-key [key]Loads a new RIOT_API_KEY without restarting the bot (validates and stores it). With no key, shows when the current one expires. The reply is ephemeral: the key never stays visible.dev role

🤡 Trolls

CommandWhat it doesWho
/trolls [Week | Last week | Month | All-time]Troll ranking by index (average troll points per match): tier, specialty, worst game and trend versus the previous period.everyone
/troll-analizar [user] [match]Judges a match (the latest stored one by default): which troll charges it has, how many points each adds and why.everyone
/trolls-reglasWhat the bot detects and how many points each thing adds, with the active config.everyone
/trolls-reiniciarThe troll ranking starts from zero as of now. Old matches stay stored.dev role
/trolls-recalcularRe-fetches from Riot the stored matches analyzed with an old version to fill in stats and timeline. It runs by itself on startup; this forces it by hand, silently.dev role

🔊 Sounds

CommandWhat it doesWho
/sonido [name]The bot joins your voice channel, plays the sound (or a random one) and leaves.everyone
/sonidosLists the loaded sounds.everyone
/canjear-sonido <name> <audio>Trade points to upload a temporary sound of your own (100 points, up to 8 s, lasts 7 days by default).points role
/sonido-del <name>Deletes a sound. The owner can delete the one they redeemed; a dev, any of them.points role
/sonido-add <name> <audio>Uploads a permanent sound (mp3, ogg or wav, up to 2 MB).dev role
/sonido-forzar [name] [channel]The bot joins a voice channel and plays that sound. If another one is playing, it waits its turn.dev role

💰 Points

CommandWhat it doesWho
/puntos [user]How many points you have (or someone else) and how they're earned.everyone
/puntos-topThe server's points leaderboard.everyone
/puntos-dar <user> <amount>Adds or removes points from someone (negative amount to subtract).dev role

ℹ️ Help

CommandWhat it doesWho
/help · /ayudaList the commands you can use based on your roles. They're two names for the same thing and only you see the reply.everyone

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 daily and weekly rankings count Ranked only (Solo/Duo and Flex). Normals, ARAM and rotating modes are still stored (they feed the troll detector and the points), but don't add to the ranking.
  4. The windows are day and week from Monday 00:00 local time (TIMEZONE, default Buenos Aires).
  5. In each window the stats are aggregated per player and the scoring engine applies the formula from config/scoring.yaml.
  6. The daily job runs at DAILY_POST_HOUR:DAILY_POST_MINUTE (23:55 recommended): ingestion → the day's ranking → if there were trolled games, how the week's troll ranking is going. 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 — and crowns the Troll of the week.
  7. Dedup by (match_id, discord_id) with no cursor: each run asks “since Monday” (with a one-day margin, for matches that cross midnight) 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 — quality over quantity: playing lots of mediocre games doesn't lift you
normalizationzscore — (value − mean) / stdev, so no metric dominates just by having bigger numbers

normalization accepts zscore (recommended), minmax (0..1 scale between worst and best) or none (raw value; weights are calibrated by hand).

Metrics active by default

MetricWeightWhat it measures
carry_rate5.0share of carried games (win + KDA ≥ 5)
troll_rate4.0 ↓share of trolled games (KDA < 0.5)
kda3.0(kills + assists) / deaths
win_rate2.0win percentage
avg_deaths1.5 ↓average deaths per match
damage_per_min1.0damage to champions per minute
avg_vision0.5average vision score

↓ = lower is better (the normalized value is inverted). A match is carried if you won it with KDA ≥ 5 and trolled if you finished with KDA < 0.5. The engine also supports these metrics, off by default — enable them by adding them to the YAML: avg_kills, avg_assists, cs_per_min, games_played.

Troll detector

After every match, each group player is judged with the rules below. The ones marked timeline use one more Riot query (what happened and when); the rest come from the match stats. Everything is tuned in config/trolls.yaml without touching code (restarting the bot), and since the troll ranking is computed on the fly, a change there recalculates the history too.

How points add up

  • Each rule that's met is a charge with its troll points.
  • In ranked (Solo/Duo or Flex) points are multiplied by ×1.25; if the team won anyway, by ×0.5 (they carried them).
  • Minor charges (🔸 in the table: poor performance, low KP, low damage, lost lane, vision, farm, first blood, FF…) add up to 4 at most between all of them. A trolled game takes a strong signal: AFK, 0 kills and 0 assists, sold items, letting the base fall, feeding, a throw or being the team's anchor.
  • Thresholds change by mode: ARAM and chaotic modes (URF, One for All…) tolerate more deaths, and the lane and base rules are Summoner's Rift only.
  • Rules that measure the same thing don't stack: feeder replaces tragic KDA and time dead; blind replaces no control wards.

What happens depending on the match's points

PointsWhat happens
under 8Nothing extra: adds to the troll ranking and shows in the match alert's troll-o-meter.
8 or more🤡 Trolled game: in TROLL_CHANNEL_ID (or the rankings one), a line with the anecdote tagging the player, plus the compact details.
15 or more💀 Blowout: also, one short line in `GENERAL_CHANNEL_ID`. It's the only thing that reaches #general.

Only matches from the last 36 h are announced (alert_max_age_hours): if the bot was down or someone just linked, old games aren't posted.

Troll ranking (/trolls)

It sorts by troll index — points per match —, not by the total, so playing more doesn't add up: whoever trolls hard in 2 matches ranks above whoever played 18 and trolled 2. So a single match doesn't decide by chance, each player starts with 2 “ghost” matches worth the group average (index.prior_games) and one match counts 30 points at most (index.max_game_points). It shows the tier, the share of trolled matches and the trend versus the previous period (📈 / 📉 / ➡️ / 🆕). /trolls-reiniciar resets it to zero from that moment.

Tiers (by index)

😇 Santo · < 8/12 ≈ 0,7🙂 Tranqui · < 8/4 = 2😬 Sospechoso · < 8/2 = 4🤡 Troll · < 8💀 Leyenda troll · ≥ 8

The rules

RulePtsWhen
💤 AFK timeline8Minutes in a row standing still, alive and gaining no experience.
👻 ¿Estaba AFK?50 kills and 0 assists in a game where their team did get kills.
💸 Liquidación total timeline5Sold 6 or more items during the game (inting).
🏚️ Nos tiraban la base timeline5Inhibitors or nexus turrets fell while they were alive and far away, and not split pushing. +3 if they were farming.
🍽️ Feeder profesional3Deaths relative to game length (≈3.3 per 10 min, at least 8) with KDA < 1. Extra points for every 2 deaths over.
💥 Throw timeline3Caught alone, with no fight around, and within a minute the team lost Baron, Elder or the nexus.
⚓ Ancla del equipo2Died more than the other four on their team combined (at least 7 deaths).
📉 KDA de la vergüenza2KDA under 0.5 with at least 5 deaths (not added if already a feeder).
⚰️ Veraneando en la fuente2Spent a quarter of the game or more dead.
⏰ Speedrun de muertes timeline23 or more deaths before minute 10.
🚜 Le pasaron el trapo en línea timeline22,500 gold behind their lane opponent at minute 15 (1,500 for supports).
🎁 Delivery a domicilio timeline24 or more deaths to their lane opponent, making up a good share (40%+) of all their deaths.
🪶 Daño de cotillón2Under 10% of their team's damage, unless they were tanking.
🏝️ Jugando otra partida2Took part in under 20% of their team's kills.
🩸 Regaló la primera sangre timeline1First death of the game before minute 5 (+1 before minute 3).
🕊️ Pacifista10 kills in 20 minutes or more, with low damage (not for supports).
🙈 Ciego voluntario1Very low vision score per minute (supports are held to a higher bar).
🧿 Ni un control ward1Zero control wards in 25 minutes, and weak vision on top.
🌾 Alérgico al farm1Low farm per minute for their role, unless they carried with damage.
🤖 Ejecutado timeline12 or more deaths to turrets, minions or monsters.
💰 Ahorrista timeline1Died holding more than 3,000 gold, before minute 25.
🏳️ FF al 152Lost by surrender before minute 20. Aggravating only: it counts if they already have a charge of their own.
🧹 Barrida histórica1Lost by 20 or more kills. Aggravating only, like the FF.
❓ Tóxico del '?'115 or more "?" pings.
🥄 Cuchara de madera2Last place in Arena.
Matches stored before a new version of the analysis are recalculated on their own, silently, when the bot starts: no messages, no visible logs and no alerts; the only thing that changes is the troll table. /trolls-recalcular forces the same by hand.

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 that counts, posts a board with the 10 positions, tagging the group's players who were in it (with each one's result, in case they ended up on opposing teams) and a troll-o-meter with everyone's troll points.

Alerts come out of any ingestion — the periodic check, the daily job or /ingest-now — and once per match: ingestion holds a lock and the dedup means running everything together duplicates nothing.

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. Network errors against Riot are retried with backoff before warning.

Sounds

The bot joins a voice channel, plays a short clip and leaves. One playback at a time across the whole bot, and any clip is cut at 20 s just in case. It needs ffmpeg installed and the Connect and Speak permissions.

Three ways to trigger one

  • On demand: /sonido [name] in your voice channel (empty = a random one).
  • Forced (dev): /sonido-forzar [name] [channel] — in the channel you pick, in yours or in a random one with people. If another one is playing, it waits its turn.
  • At random: every SOUNDS_CHECK_INTERVAL_MINUTES (5) the bot rolls a die with SOUNDS_CHANCE (15%). If it hits, within the active hours and past the cooldown (SOUNDS_COOLDOWN_MINUTES, 45), it picks a channel with at least one person (the AFK one doesn't count) and a random clip. Turned off with SOUNDS_ENABLED=false.

How they're loaded

  • /sonido-add (dev): permanent. mp3, ogg or wav, up to 2 MB.
  • /canjear-sonido (points role): temporary, paid with points. See below.
  • Clips live in SOUNDS_DIR, outside git. Owner and expiry of redeemed ones are kept in SOUNDS_DIR/_meta.json; a sound with no entry there is permanent.

ffmpeg.org ↗

Points

Members with POINTS_ROLE_ID earn points and redeem them. Without that role configured, the module isn't loaded.

How they're earned

  • Voice: 1 point every 5 minutes to each member with the role who's in a voice channel with at least one other human (the AFK channel and being deafened don't count). Cap: 60 per day.
  • LoL: 5 for each new match stored and 5 more if you won it. The ingestion dedup guarantees each match pays out only once.
  • By hand: a dev can add or subtract with /puntos-dar.

Redeem: your own sound

/canjear-sonido <name> <audio> costs 100 points: the clip can be up to 8 s, stays loaded for 7 days and is deleted on its own when it expires. At most 1 active per person (you can delete it earlier with /sonido-del). The bot measures the length with ffprobe and only charges if the clip passes the check, then announces the redemption in the channel. Every number is changed through environment variables.

Deploy

Local

python run.py directly, or .\scripts\run_bot.ps1 [-Environment production], which opens a visible console and mirrors everything to logs\<environment>\. Don't start two instances against the same environment/server: they'd answer commands twice.

Linux server (systemd)

The repo ships deploy/bogabot.service. Setup is one-time: clone, venv + pip install -r requirements.txt, install ffmpeg, copy the .env.production over scp (mode 600), install the service and systemctl enable --now bogabot. State (points, Riot key, troll ranking reset) lives in data/, outside git.

sudo apt install ffmpeg
sudo cp deploy/bogabot.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now bogabot
sudo journalctl -u bogabot -f # live logs

To update: git pull + pip install if requirements.txt changed + systemctl restart bogabot. Changing the Riot key needs no deploy: /riot-key from Discord. The repo also ships a GitHub Actions workflow that deploys every push to main that passes the tests, meant for when there's a VPS.

Status and limits

  • Scope: beta with the group. LoL (ranking and trolls), sounds, points and help modules. 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. Points and redeemed sounds go to JSON files in data/ and sounds/.
  • Queues: the daily and weekly ranking counts Ranked only (Solo/Duo and Flex); the “Trolls and Pros” recap, Flex only. The troll detector and the points look at every queue.
  • Region: decided for LAS (la2 / americas); configurable for others.
  • Tests: python -m pytest — scoring, dedup, match-v5 mapper with timeline, troll rules, config, troll ranking, ingestion and alert routing.

Bugs, ideas and requests → the repo's issues.