Non-invasive playtime tracking Discord bot, compatible with all server types
  • Python 99.1%
  • Dockerfile 0.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
ultrablob 47c3a0fc07 Fix log timezone handling and tail bootstrap races
- Parse server timestamps with configurable MC_LOG_TZ
- Read bootstrap offsets and inode from the same file handle
- Document timezone behavior and freeze dependencies
2026-08-10 20:16:10 -04:00
playtime_bot Fix log timezone handling and tail bootstrap races 2026-08-10 20:16:10 -04:00
.dockerignore initial vibe commit 2026-07-03 19:39:35 -04:00
.env.example Fix log timezone handling and tail bootstrap races 2026-08-10 20:16:10 -04:00
.gitignore initial vibe commit 2026-07-03 19:39:35 -04:00
docker-compose.yml Fix log timezone handling and tail bootstrap races 2026-08-10 20:16:10 -04:00
Dockerfile initial vibe commit 2026-07-03 19:39:35 -04:00
main.py initial vibe commit 2026-07-03 19:39:35 -04:00
playtime.db.pre-tz-fix.bak Fix log timezone handling and tail bootstrap races 2026-08-10 20:16:10 -04:00
README.md Fix log timezone handling and tail bootstrap races 2026-08-10 20:16:10 -04:00
requirements-frozen.txt Fix log timezone handling and tail bootstrap races 2026-08-10 20:16:10 -04:00
requirements.txt initial vibe commit 2026-07-03 19:39:35 -04:00

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.gz is parsed once into join/leave sessions stored in SQLite (playtime.db). Processed files are remembered, so restarts are instant.
  • Live tail: latest.log is 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.log rotates, dangling sessions are closed at the last log timestamp and the new .gz is 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 to UTC, which is what a Dockerized server logs in unless its container sets TZ. 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 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 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.db to force a full re-ingest.