Discord <-> Minecraft chat bridge bot for MTR France (chat relay, join/leave/advancement/death embeds, Chunky pregen progress with smoothed ETA)
  • Python 78.2%
  • HTML 21.8%
Find a file
Aca. 154ace40a2 Fix chunky_loop stuck announcing "Pregen en pause" forever
chunky cancel doesn't delete the task file, it just writes
cancelled=true into it - the paused/finished check only tested file
existence, so a cancelled pregen looked identical to a paused one
forever, spamming the same presence update every tick with no way
to resolve. Now reads the cancelled flag and treats it like a
finished task. Bump to 1.16.1.
2026-08-09 01:03:25 +02:00
.gitignore Initial commit: Yor, the MTR France Discord <-> Minecraft bridge bot 2026-07-31 16:51:20 +02:00
admin_page.html Add yor.adlmrl.fr/mods upload page + mobile padding tweaks 2026-08-07 17:46:23 +02:00
bot.py Fix chunky_loop stuck announcing "Pregen en pause" forever 2026-08-09 01:03:25 +02:00
chunky_toggle.py Initial commit: Yor, the MTR France Discord <-> Minecraft bridge bot 2026-07-31 16:51:20 +02:00
config.ini.example Add owner-only !rcon and !whitelist Discord commands 2026-08-05 18:22:21 +02:00
mods_page.html Add yor.adlmrl.fr/mods upload page + mobile padding tweaks 2026-08-07 17:46:23 +02:00
README.md Add !profiler command: VPS + Minecraft health from Discord 2026-08-04 21:29:35 +02:00
status_page.html Redesign status page as a MEE6-style dashboard 2026-08-07 17:49:06 +02:00

Yor — MTR France Discord ↔ Minecraft bridge bot

A discord.py bot bridging a Discord channel with the MTR France Minecraft server over RCON and Docker log tailing. Named after Yor Forger (Spy x Family), matching this project's anime-character naming convention for tooling (see also "Levi", the modpack sync mod, in this same org).

What it does

  • Chat relay, both directions. Messages sent in the configured Discord channel are pushed into the server via RCON tellraw; server chat, joins, leaves, advancements, and deaths are tailed from docker logs -f and posted as Discord messages/embeds.
  • Container status tracking. Polls docker inspect every 30s; announces the container stopping/restarting, and reports if RCON becomes unreachable (once per outage, not repeatedly).
  • Presence, topic, and MOTD, kept in sync with player count / uptime, or — while a Chunky pregen is active — with pregen progress instead.
  • Chunky pregen tracking with a smoothed ETA. Chunky's own chunky progress RCON output includes an ETA, but it's recomputed from a short-term instantaneous rate every time it's queried, so it swings wildly (chunk generation speed varies a lot with terrain/structures). Yor keeps its own rolling window of (timestamp, percent) samples and derives a much steadier completion estimate from the slope across that whole window instead — see smoothed_eta_epoch() in bot.py. That estimate is shown as a live, auto-localizing Discord timestamp (<t:...:f> (<t:...:R>)) in actual channel messages, and as a plain "Xh Ymin" duration in contexts that don't render Discord markup (bot presence, server MOTD, channel topic).
  • Auto-triggers a Distant Horizons LOD pregen once Chunky's own pregen crosses 95%, and announces genuine completion (distinguishing "paused" from "finished" by checking Chunky's on-disk task file, so a multi-minute pause during testing doesn't get misread as completion).
  • !pregen / !chunky / !progress — on-demand progress check. !help / !aide — usage reminder.

Configuration

Real secrets (config.ini) are not committed — see config.ini.example for the expected shape:

[bot]
token = YOUR_DISCORD_BOT_TOKEN
channel_id = YOUR_DISCORD_CHANNEL_ID

[minecraft]
container_name = YOUR_PTERODACTYL_CONTAINER_ID
rcon_host = 172.18.0.2
rcon_port = 25575
rcon_password = YOUR_RCON_PASSWORD

Copy it to config.ini and fill in real values before running. rcon_host is the Docker-internal IP of the game server container (Pterodactyl's own network, not localhost — the bot and the game container are separate Docker containers/processes).

chunky_toggle.py (a standalone helper for pausing/resuming Chunky manually, independent of the bot process) reads the same config.ini — it used to have the RCON password hardcoded directly in source, which has been fixed to read from config instead.

Running

Requires Python 3 with discord.py and psutil (pip install discord.py psutil inside a venv is what production actually uses — see the systemd unit below). aiohttp (used for the status page's tiny HTTP server) comes in as a dependency of discord.py already.

python3 -m venv venv
./venv/bin/pip install discord.py psutil
./venv/bin/python3 bot.py

Production deployment (systemd)

MTR France runs this as a systemd service, mc-discord-bot.service:

[Unit]
Description=Minecraft <-> Discord chat bridge bot
After=network-online.target docker.service
Wants=network-online.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/mc-discord-bot
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/mc-discord-bot/venv/bin/python3 -u /opt/mc-discord-bot/bot.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Deployed at /opt/mc-discord-bot/ on the same VPS as the Pterodactyl panel (so docker/RCON access to the game container is local). After changing bot.py:

sudo cp bot.py /opt/mc-discord-bot/bot.py
sudo systemctl restart mc-discord-bot
sudo systemctl status mc-discord-bot --no-pager

Known constraints / things to know before touching this

  • The bot needs docker CLI access (it shells out to docker inspect/docker logs) and network-level RCON access to the game container — it currently runs as root specifically for unrestricted docker access; tightening this (e.g. a docker group membership instead of root) hasn't been done.
  • Discord hard-limits channel topic edits to roughly 2 per 10 minutes per channel — the pregen loop deliberately throttles topic updates to once per 15 minutes (topic_counter >= 15, one tick per minute) to stay well under that, and the hourly detailed channel message is pinned to a fixed wall-clock minute (:18) rather than a tick counter, so it doesn't drift or double-fire after a bot restart.
  • The smoothed ETA needs at least 5 minutes of accumulated history (MIN_HISTORY_SPAN_SECONDS) before it will show a real estimate — shows "calcul en cours..." until then, including right after every bot restart (the rolling history is in-memory only, not persisted).