- Create a file named
docker-compose.ymlin an empty folder:
services:
bot:
image: ghcr.io/haswelldev/tod-bot:latest
container_name: tod-bot
environment:
- DISCORD_TOKEN=${DISCORD_TOKEN}
- TZ=UTC
volumes:
- ./data:/app/data
restart: unless-stopped- Start it:
export DISCORD_TOKEN=your_token
docker compose up -d
-
In Discord, run
.initin the channel where the bot is present to register it and choose a language. -
After registration, start tracking:
.tod hallate 14:30 Europe/Kyiv
TodBot is a lightweight Discord bot to record boss Time of Death (ToD), show respawn windows, and post reminders when a window opens and closes.
- Commands:
.init,.tod,.window/.w,.del,.list/.ls/.all,.remind,.reminders - Per-channel registration via
.init— the bot ignores unregistered channels - Per-channel language chosen during
.init(English, Russian, French, Greek, Portuguese, Ukrainian) - Configurable reminders — off by default per-channel; enable for all bosses with
.reminders onor request a one-time alert for a specific boss with.remind BossName - Configurable respawn windows — global defaults via env vars, per-boss overrides via
config/bosses.yaml - Partial/alias boss name matching —
taras→antharas,tezza→frintezza,aq/qa→queen ant - Epic boss windows pre-configured (Queen Ant 24+4h, Antharas 192+4h, Valakas 264+4h, etc.)
- User-local time display using Discord dynamic timestamps
- Auto-deletes the invoking user message after handling (if the bot has permissions)
- Storage backends: JSON (default) or MySQL
- Multi-server / multi-channel support with isolated data per channel
- Native run:
- PHP 8.4 or newer
- Composer
pdo_mysqlextension for MySQL backend
- Docker run:
- Docker Engine 24 or newer
- Clone and install dependencies:
git clone <repo-url> cd TodBot composer install - Copy the example env file and set your token:
cp .env.example .env # edit .env and set DISCORD_TOKEN - Run:
php bin/bot.php
JSON (default):
export DISCORD_TOKEN=your_token
make up # builds image and starts bot with JSON storage
MySQL:
export DISCORD_TOKEN=your_token
make mysql-up # creates .env if missing, builds image, starts MySQL + bot
Other useful targets:
make logs # follow bot logs
make mysql-logs # follow MySQL bot logs
make mysql-db # open MySQL shell in the db container
make shell # open shell in the bot container
make down # stop default stack
make mysql-down # stop MySQL stack
make test # run PHPUnit tests locally
services:
bot:
image: ghcr.io/haswelldev/tod-bot:latest
container_name: tod-bot
environment:
- DISCORD_TOKEN=${DISCORD_TOKEN}
- TZ=UTC
volumes:
- ./data:/app/data
restart: unless-stoppedexport DISCORD_TOKEN=your_token
docker compose up -d
Use docker-compose.mysql.yml from the repo, or see the included file for a full example. The MySQL database and tables are created automatically on first start.
export DISCORD_TOKEN=your_token
docker compose -f docker-compose.mysql.yml up -d --build
docker build -t tod-bot:latest .
docker run -d --name tod-bot \
-e DISCORD_TOKEN=your_token \
-e TZ=UTC \
-v "$(pwd)/data:/app/data" \
--restart unless-stopped \
tod-bot:latest
| Variable | Default | Description |
|---|---|---|
DISCORD_TOKEN |
— | Required. Your Discord bot token |
TOD_STORAGE |
json |
Storage backend: json or mysql |
TOD_WINDOW_START |
12 |
Default window start offset in hours |
TOD_WINDOW_RANDOM |
9 |
Default window random range in hours (start + random = end) |
BOSS_CONFIG |
config/bosses.yaml |
Path to boss config YAML for custom windows and aliases |
BOT_LOCALE |
en |
Fallback locale when a channel has no locale set |
TZ |
— | System timezone; the app uses UTC internally |
MYSQL_HOST |
127.0.0.1 |
MySQL host (MySQL backend only) |
MYSQL_PORT |
3306 |
MySQL port |
MYSQL_DATABASE |
todbot |
MySQL database name |
MYSQL_USER |
todbot |
MySQL username |
MYSQL_PASSWORD |
— | MySQL password |
- JSON:
./data/tods.json,./data/channels.json - MySQL: tables
todsandchannels(auto-created on first start)
- Required: Read Messages/View Channel, Send Messages
- Recommended: Manage Messages (lets the bot delete user command messages)
Boss names are case-insensitive and support partial/alias matching. Displayed times are rendered by Discord in each viewer's local timezone.
Registers the current channel with the bot. The bot will not respond to any commands in a channel until .init has been run there.
Steps:
- Run
.initin the channel. - The bot replies with a numbered language menu — send the number or code for your language.
- The bot asks for confirmation — reply
yesorno. - The bot asks whether to enable automatic reminders for all bosses — reply
yesorno.
Sets ToD for a boss. If time is omitted, now is used. If timezone is omitted, UTC is used.
Re-recording a boss resets any pending one-time reminder set with .remind.
Examples:
.tod skylancer
.tod skylancer now
.tod skylancer 1700000000
.tod skylancer 14:30 Europe/Kyiv
.tod skylancer 1430 UTC+2
.tod skylancer 2025-11-28 14:00 UTC
.tod skylancer 28.11.2025 14:00 UTC
.tod skylancer 28-11 14:00 UTC
.tod skylancer 30m ago
.tod skylancer 2h
.tod taras 14:30 (resolves to antharas)
.tod aq 14:30 (resolves to queen ant)
Shows last ToD and window start/end for the boss.
Deletes the stored ToD.
Lists bosses for the current channel whose window has not yet closed. Shows "opens in …" for upcoming windows and "closes in …" for active ones.
Requests a one-time window-open reminder for a specific boss.
Only useful when channel reminders are off — the bot will notify once when the window opens, then clear the flag.
If the boss is re-recorded with .tod before the window opens, the flag is reset and must be set again.
Enables or disables automatic reminders for all bosses in this channel.
on— bot notifies when every boss window opens and closes.off— no automatic reminders (use.remind BossNamefor one-time alerts).
now- Unix timestamp (10 digits)
- Relative:
30m ago,2h,-45m - Clock:
HH:MMorHHMM - Full date-time:
Y-m-d H:i,d.m.Y H:i,d-m-Y H:i,d/m/Y H:i - Short date-time:
d-m H:i,d.m H:i(current year assumed)
- IANA (e.g.
Europe/Kyiv,America/New_York) UTCorGMT- Offsets like
UTC+2,+2,GMT-3
Partial name matching resolution order:
- Exact canonical name match
- Exact alias match
- Input is a substring of a canonical name
- Input is a substring of an alias
- Unknown boss — use default window
Pre-configured epic bosses (in config/bosses.yaml):
| Boss | Aliases | Window |
|---|---|---|
| queen ant | qa, aq, ant queen | 24+4h |
| core | — | 48+4h |
| orfen | — | 33+4h |
| zaken | — | 45+4h |
| baium | — | 125+4h |
| frintezza | tezza | 48+4h |
| antharas | taras | 192+4h |
| valakas | — | 264+4h |
To add or override bosses, edit config/bosses.yaml:
bosses:
"queen ant":
aliases: [qa, aq, "ant queen"]
respawn: 24
random: 4
antharas:
aliases: [taras]
respawn: 192
random: 4- Supported locales:
en,ru,fr,el,pt,uk - Locale is set per channel during
.init— each channel can use a different language. BOT_LOCALEis the fallback for channels without a locale set (default:en).- Translation files:
translations/messages.<locale>.php
Each channel has its own ToD list, reminders, and language. Data from different servers and channels is fully isolated.
- Entry point:
bin/bot.php - Key classes:
src/Bot/DiscordBot.php— bootstraps Discord client, wires events, routes messagessrc/Service/InitHandler.php— three-step channel registration state machine (language → confirm → reminders)src/Service/CommandHandler.php— parses and responds to commandssrc/Service/ReminderScheduler.php— 60s periodic checks to post start/end reminderssrc/Service/BossRegistry.php— canonical name resolution and window lookupsrc/Service/TimeParser.php— parses flexible time inputs into UTCsrc/Service/TimeFormatter.php— formats Discord timestamp tokenssrc/Repository/— JSON and MySQL backends implementing repository interfaces
- Storage schema:
todstable/file: primary key isboss + channel_id; flagsstart_reminded,end_reminded,remindchannelstable/file: primary key ischannel_id; storesguild_id,guild_name,channel_name,locale,reminders_enabled
composer test # run PHPUnit locally
make test # alias
DISCORD_TOKEN is not set— runexport DISCORD_TOKEN=your_tokenor set it in.env- Bot does not respond — the channel must be registered with
.initfirst - Bot does not delete user messages — grant Manage Messages permission
- MySQL errors — ensure
MYSQL_*env vars are set and the MySQL container is healthy - No reminders — check that reminders are enabled (
.reminders on) or use.remind BossNamefor a one-time alert; the scheduler ticks every 60 seconds - Time parsing failed — the bot will respond with examples; try another format or specify a timezone