TDDiscordBot
TDDiscordBot
A Discord bot that forwards Elite Dangerous Background Simulation (BGS) tick notifications from Zoy’s Tick Detector to Discord servers. Each server independently configures which in-game faction it wants to track.
How it works
The bot subscribes to Zoy’s Tick Detector via ZeroMQ and receives real-time BGS events (~10,000/day). When an event affects a system belonging to a server’s configured faction, the bot posts a notification to a dedicated read-only channel in that server.
Events forwarded:
| Event | Description |
|---|---|
| GalaxyTick | First tick detection of the day — marks the start of a new BGS cycle |
| SystemTick | A system has ticked (influence/state change detected) |
| FactionChanges | A faction has arrived in or retreated from a system |
| FactionExpandedFrom | A faction has expanded outward from a system |
Prerequisites
- Node.js 18+ (via nvm recommended)
- PostgreSQL 16+ with the
ed_tickbotschema (see Database Setup) - A Discord application with a bot token (see Discord Setup)
- Network access to Zoy’s Tick Detector on the same VPS (ZMQ local TCP)
Discord Setup
1. Create a Discord Application
- Go to the Discord Developer Portal
- Click New Application — give it a name (e.g.
ZoyTDBot) - Go to the General Information tab and copy the Application ID — this is your
DISCORD_CLIENT_ID
2. Disable the default install link
Before you can mark the bot as private, the portal requires the install link to be cleared first — if you skip this step you’ll get a “Private application cannot have a default authorisation link” error.
- Go to the Installation tab
- Under Install Link, change the dropdown from Discord Provided Link to None
- Save
3. Create a Bot and get the token
- In your application, go to the Bot tab
- Click Reset Token (or Add Bot if first time) — copy the token immediately, it is only shown once
- This is your
DISCORD_TOKEN— treat it like a password, never commit it
- This is your
- Uncheck Public Bot — this bot is invite-only; the setting only saves cleanly after step 2 above
- Leave Requires OAuth2 Code Grant unchecked
- Leave all Privileged Gateway Intents unchecked — the bot doesn’t need Presence, Server Members, or Message Content
4. Set Bot Permissions
The bot requires these permissions in any server it joins:
- Manage Channels — to create the
#zoy-td-tick-botchannel on join - View Channel
- Send Messages
- Manage Messages — to bulk-delete messages older than 3 days
5. Generate an Invite URL
- Go to the OAuth2 → URL Generator tab
- Under Scopes, select
botandapplications.commands - Under Bot Permissions, select the four permissions listed above
- Copy the generated URL — use this to invite the bot to servers
Database Setup
Run the following scripts against your PostgreSQL instance as a superuser:
psql -U postgres -d your_database -f schema/01_role.sql
psql -U postgres -d your_database -f schema/02_schema.sql
Edit schema/01_role.sql first to set a strong password for the edtickbot_role role, and replace your_database with your actual database name.
First-Time Configuration
1. Copy the config template
cp src/config/config.mjs.template src/config/config.mjs
config.mjs is in .gitignore — it must never be committed.
2. Edit src/config/config.mjs
Fill in the following values:
config.discord.token = 'your bot token from the Discord Developer Portal';
config.discord.clientId = 'your application ID from the Discord Developer Portal';
config.zmq.address = 'tcp://127.0.0.1:5555'; // port where Tick Detector publishes
config.postgres.user = 'edtickbot_role';
config.postgres.host = 'localhost';
config.postgres.database = 'your_database'; // same database used in schema setup
config.postgres.password = 'your role password set in 01_role.sql';
config.postgres.port = 5432;
config.factionApi.baseUrl = 'http://tickapi.infomancer.uk'; // leave as-is unless self-hosting
3. Install dependencies
npm install
4. Register slash commands with Discord
This step sends the /setfaction, /status, and /help command definitions to Discord’s API. It only needs to be run once (or again if commands change).
npm run register
Global command registration can take up to an hour to propagate across Discord. For immediate testing, guild-scoped registration is faster — see Registering commands to a specific server.
Running the Bot
Development / direct Node
npm start
Production via PM2
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup # follow the printed instructions to enable auto-start on reboot
Logs are written to log/ with daily rotation (14-day retention).
Per-Server Configuration
Once the bot has joined a server, an administrator runs:
/setfaction <exact faction name>
For example:
/setfaction Sirius Corp
The faction name must match exactly as it appears in Elite Dangerous (case-insensitive lookup is used internally). The bot will confirm the faction and the number of systems it currently occupies.
Other commands:
/refreshfaction — (Admin) immediately refresh the faction's system list, e.g. after a known expansion
/status — shows last Tick Detector heartbeat and configured faction
/help — lists available commands
What happens when the bot joins a server
- A channel named
#zoy-td-tick-botis created automatically - The channel is locked — only the bot can post; members can read but not write
- A config row is created in the database for the server
- An administrator then runs
/setfactionto configure which faction to track
The bot begins forwarding notifications immediately after /setfaction is run.
Message Retention
Messages in the bot’s channel are automatically deleted after 3 days. This runs daily at 03:00 server time. The 3-day window is a fixed architectural decision — it ensures Discord’s fast bulkDelete path is always used and keeps the channel clean.
Registering commands to a specific server
For faster testing during development, you can register commands to a single guild rather than globally. Edit scripts/register-commands.mjs and replace:
Routes.applicationCommands(config.discord.clientId)
with:
Routes.applicationGuildCommands(config.discord.clientId, 'YOUR_GUILD_ID')
Guild-scoped commands appear instantly. Remember to revert to global registration before wider deployment.
Tests
Tests use Vitest and are co-located with source files (*.test.mjs). No database, Discord, or network connection is required — all external I/O is mocked.
Run all tests
npm test
Run all tests with verbose output (per-test results)
npx vitest run --reporter=verbose
Run a single test file
npx vitest run src/processors/systemTick.test.mjs
Run tests matching a name pattern
npx vitest run -t "dispatches with systemAddress"
Test coverage by module
| Test file | Module under test |
|---|---|
src/processors/heartbeat.test.mjs |
HeartBeat message handling, botState update |
src/processors/systemTick.test.mjs |
SystemTick formatting and dispatch |
src/processors/galaxyTick.test.mjs |
GalaxyTick: faction refresh ordering, dispatch |
src/processors/factionChanges.test.mjs |
FactionChanges formatting and dispatch |
src/processors/factionExpandedFrom.test.mjs |
FactionExpandedFrom formatting and dispatch |
src/cache/guildCache.test.mjs |
In-memory config/system cache, DB load, reverse index |
src/dispatcher.test.mjs |
System address → faction → guild routing |
src/discord/publisher.test.mjs |
Rate-limited send queue, error handling |
src/api/factionApi.test.mjs |
Faction API HTTP client, URL encoding, error cases |
Project Structure
src/
config/
config.mjs.template — copy to config.mjs and populate
api/factionApi.mjs — HTTP client for faction-systems lookup
cache/guildCache.mjs — in-memory guild config + faction→systems maps
dao/db.mjs — PostgreSQL pool
discord/
client.mjs — Discord client setup
publisher.mjs — rate-limited message send queue
commands/ — slash command definitions and handlers
events/ — guildCreate, guildDelete, interactionCreate
processors/ — per-topic message formatters
zmq/subscriber.mjs — ZeroMQ subscriber loop
cleanup.mjs — daily message deletion
dispatcher.mjs — routes system address to matching guilds
main.mjs — entry point
scripts/
register-commands.mjs — one-shot Discord command registration
schema/
01_role.sql — PostgreSQL role creation
02_schema.sql — schema and table definitions
ecosystem.config.cjs — PM2 configuration
License
MIT — see LICENSE