- Python 99.1%
- Dockerfile 0.9%
Names like _LLE123_ were italicized in embeds. Escape them everywhere markdown renders (descriptions, field values, messages) — embed titles don't render markdown, so they stay raw. Comment out MC_LOG_DIR/PLAYTIME_DB in .env.example: env_file passes them into the container, overriding the image's /logs and /data paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| playtime_bot | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| main.py | ||
| README.md | ||
| requirements.txt | ||
Playtime Bot
A Discord bot that tracks player playtime on a Minecraft (Forge) server by
reading the server logs — live-tailing latest.log and backfilling every
rotated *.log.gz.
How it works
- Backfill: on startup, every
logs/*.log.gzis parsed once into join/leave sessions stored in SQLite (playtime.db). Processed files are remembered, so restarts are instant. - Live tail:
latest.logis polled every 2 seconds. Completed sessions are written to the database immediately; players with an open session show as 🟢 online, and their in-progress time counts in every stat. - Rotation-safe: when the server restarts and
latest.logrotates, dangling sessions are closed at the last log timestamp and the new.gzis ingested. Sessions are keyed on(player, start), so overlap between the live stream and the rotated file never double-counts. - Sessions left open when a server run ends (crash/stop) are closed at the file's last timestamp.
Setup
python -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env # fill in DISCORD_TOKEN, adjust paths if needed
.venv/bin/python main.py
Create the bot at the Discord Developer Portal,
enable no special intents (slash commands only), and invite it with the
applications.commands + bot scopes.
To sanity-check ingestion without Discord:
.venv/bin/python main.py stats
Docker
cp .env.example .env # fill in DISCORD_TOKEN
docker compose up -d --build
The compose file mounts ./logs read-only at /logs — point that volume at
your Minecraft server's logs/ directory if it lives elsewhere. The SQLite
database persists in the playtime-data named volume.
Set TZ (in .env or the shell) to the server's timezone; log timestamps
are parsed as local time, so a mismatched container timezone shifts day
boundaries and hourly stats. It defaults to America/Toronto.
Sanity-check inside the container:
docker compose run --rm playtime-bot python main.py stats
Commands
| Command | What it shows |
|---|---|
/leaderboard [period] |
Playtime ranking — today, 7 days, 30 days, or all time |
/playtime [player] |
Personal card: total, rank, today/week, sessions, average, longest, favorite hour, first/last seen |
/online |
Who is on right now and for how long |
/topdays [player] |
Most active calendar days (server-wide or per player) |
/sessions [player] |
Longest single sessions ever |
/streaks |
Longest consecutive-day play streaks, with current 🔥 streaks |
/hours [player] |
ASCII histogram of activity by hour of day |
/records |
Hall of fame: longest session, biggest day, peak concurrent players, longest streak, night owl 🦉, early bird 🌅 |
/link <player> |
Link your Discord account so /playtime defaults to you |
Player arguments autocomplete from every name seen in the logs.
Notes
- Log timestamps are interpreted in the machine's local timezone; day boundaries are local midnight, and sessions crossing midnight are split across days for daily stats.
- The parser expects the Forge log format
(
[03Jul2026 16:28:06.262] ... <name> joined the game). Vanilla logs (time-only timestamps) are not supported. - Delete
playtime.dbto force a full re-ingest.