Non-invasive playtime tracking Discord bot, compatible with all server types
- Python 99.1%
- Dockerfile 0.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
- Parse server timestamps with configurable MC_LOG_TZ - Read bootstrap offsets and inode from the same file handle - Document timezone behavior and freeze dependencies |
||
| playtime_bot | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| main.py | ||
| playtime.db.pre-tz-fix.bak | ||
| README.md | ||
| requirements-frozen.txt | ||
| 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.
Two timezones matter:
MC_LOG_TZ— the zone the server writes log timestamps in. Defaults toUTC, which is what a Dockerized server logs in unless its container setsTZ. Getting this wrong shifts every session by the UTC offset.TZ— the display zone: day boundaries ("today", streaks) and hourly stats use its midnight. Defaults toAmerica/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
MC_LOG_TZ(default UTC); day boundaries are local (TZ) 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.