A modular Discord bot system built with discord.js v14, running three independent bots in a single Node.js process.
| Bot | Purpose |
|---|---|
| Commands Bot | Server management slash commands: rules, roles, information, orders, support, admin tools, utility |
| Events Bot | Guild event logging, context menus, automated message handling |
| Minigames Bot | 12 interactive minigames with a shared points & reward system |
All three bots run in parallel. If one crashes, it automatically restarts after 10 seconds without affecting the others.
- Node.js >= 22 (LTS — built-in
fetchandAbortSignal.timeout()required for the Trivia API) - npm
- A Discord application with three bot tokens (one per bot)
- A Discord server (guild) with the required roles and channels configured
# 1. Clone the repository
git clone https://github.com/MSK-Scripts/discord_multibot.git
cd discord_multibot
# 2. Install dependencies
npm install
# 3. Configure environment variables
cp .env.example .env
# → Fill in your tokens, IDs and database credentials in .env
# 4. Start all bots
node main.jsA ready-to-use systemd unit file is included at multibot.service
(user discord, working directory /opt/discord_multibot).
1. Adjust the paths and user in the file if necessary.
2. Copy the service file to systemd:
sudo cp multibot.service /etc/systemd/system/3. Reload systemd and enable the service:
sudo systemctl daemon-reload
sudo systemctl enable --now multibot4. Check the status:
sudo systemctl status multibotUseful commands:
# View live logs
journalctl -u multibot -f
# Restart after update
sudo systemctl restart multibot
# Stop the bots
sudo systemctl stop multibotNote: The service reads the
.envfile viaEnvironmentFile=. Make sure the file exists at the configured path and is readable by the service user.
.github/workflows/deploy.yml deploys every push to main that touches actual
code: it runs the CI gate first, then connects over SSH, resets
/opt/discord_multibot onto origin/main, runs npm ci --omit=dev, restarts
the service and fails the run if the bot is not alive afterwards.
Four repository secrets are required:
| Secret | Meaning |
|---|---|
DEPLOY_HOST |
Server hostname or IP |
DEPLOY_USER |
SSH user (needs sudo systemctl and write access to /opt/discord_multibot) |
DEPLOY_SSH_KEY |
Private SSH key for that user |
DEPLOY_PORT |
SSH port |
Two things stay outside the pipeline on purpose. The production .env is never
transferred through git, the deploy aborts if it is missing. And
multibot.service is not installed automatically, a changed unit has to be
copied over and reloaded by hand.
The deploy uses git clean -df without -x, so everything in .gitignore
survives: .env, data/ (points and database backups), assets/, the log files
and the untracked bots/commands/commands/orders.js.
All configuration is done via a .env file in the root directory. Copy .env.example and fill in the values.
COMMANDS_BOT_TOKEN=your_commands_bot_token
EVENTS_BOT_TOKEN=your_events_bot_token
MINIGAMES_BOT_TOKEN=your_minigames_bot_token
GUILD_ID=your_guild_id# Logging & channels
LOG_CHANNEL_ID=
FEEDBACK_CHANNEL_ID=
MEMBER_COUNT_CHANNEL_ID=
# Role IDs (FOUNDER / MANAGER / DEVELOPER / TEAM gate the staff commands)
MEMBER_ROLE_ID=
FOUNDER_ROLE_ID=
MANAGER_ROLE_ID=
DEVELOPER_ROLE_ID=
TEAM_ROLE_ID=
GIVEAWAY_NOTIFY_ROLE_ID=
GARAGE_ROLE_ID=
HANDCUFFS_ROLE_ID=
STORAGE_ROLE_ID=
VEHICLEKEYS_ROLE_ID=
# Database (for /backup_database)
DB_HOST=localhost
DB_USER=
DB_PASS=
DB_NAME=es_extended| Command | Description | Role-restricted |
|---|---|---|
/information |
Posts a branded server information embed | ✅ Manager / Founder |
/rules |
Posts the server rules with Verification & Giveaway Notify buttons | ✅ Manager / Founder |
/roles |
Posts script update notification role buttons | ✅ Manager / Founder |
/script_guides |
Links to documentation for a chosen script | ✅ Support+ |
/donation |
Displays donation options with payment links | ✅ Manager / Founder |
/order_terms |
Sends the terms of service PDF with Accept / Reject buttons | ✅ Developer / Manager / Founder |
/order_price |
Shows an order price with Accept / Reject buttons | ✅ Developer / Manager / Founder |
/send_message |
Sends a custom message to any channel via modal | ✅ Manager / Founder |
/send_embed |
Sends a fully customizable embed to any channel | ✅ Manager / Founder |
/backup_database |
Creates a MySQL database backup and uploads it to the log channel | ✅ Founder |
/ping |
Shows bot latency and API response time | — |
/userinfo |
Shows info and minigame points for a user | — |
/clear |
Bulk-deletes up to 100 messages | ✅ Team |
/random |
Picks a random number in a given range (for guess games) | ✅ Team |
/rg |
Guess the currently active secret number | — |
/flachwitz |
Posts a random flat joke from the local collection | — |
/add_flachwitz |
Adds a new joke to the collection | ✅ Team |
Persistent role-toggle buttons (survive bot restarts):
✅ Verification— assigns the Member role🎁 Giveaway Notify— toggles the Giveaway notification role⏰ Garage / Handcuffs / Storage / Vehicle Keys— toggles script update notification roles
Guild logging — All events are posted as colored embeds to the configured log channel:
| Category | Events logged |
|---|---|
| Members | Join, Leave, Kick, Ban, Unban, Timeout set/removed |
| Roles | Role Added (incl. added by), Role Removed (incl. removed by) |
| Username / Nickname | Username changed, Nickname changed |
| Messages | Edited, Deleted (incl. deleted by), Bulk delete (incl. deleted by) |
| Channels | Created, Deleted, Updated (name, topic, slowmode, NSFW) |
| Roles | Created, Deleted, Updated (name, color, permissions diff) |
| Voice | Joined, Left, Moved, Server Muted/Unmuted, Server Deafened/Undeafened |
| Invites | Created (with max uses & expiry), Deleted |
- Auto-reply — Automatically responds when non-team members mention "Musiker15"
- Feedback embed — Messages posted in the feedback channel are auto-converted to branded embeds and the original is deleted
- Context menus (right-click on messages):
📝 Comment Feedback— Adds a moderator comment to a feedback embed and DMs the author💬 Answer a Message— Sends a reply to any message via modal✏️ Edit Message— Edits a bot message via modal🖼️ Edit Embed— Edits a bot embed (title, description, thumbnail, image, footer) via modal
All minigames are session-based (no global state) and award or deduct points on each outcome.
| Command | Description |
|---|---|
/8ball |
Magic 8-Ball — asks a yes/no question |
/dice |
Roll a die — d4 to d100, 1–10 dice |
/flipcoin |
Flip a coin — Heads or Tails |
/rps |
Rock Paper Scissors vs. the bot |
/slots |
Slot machine with animated spin and 7 symbol tiers |
/trivia |
Multiple-choice trivia (OpenTrivia DB + local fallback) |
/hangman |
Classic Hangman with letter-modal input |
/wordle |
Wordle — guess the 5-letter word in 6 tries |
/tictactoe |
TicTacToe — Easy / Medium / Minimax Hard AI |
/connect4 |
Connect Four — bot AI with win/block/center logic |
/blackjack |
Blackjack — Hit, Stand, Double Down vs. the dealer |
/points |
Shows your current point balance with a progress bar |
Points are stored persistently in data/points.json. Each game awards or deducts points depending on the outcome and difficulty. Reward milestones automatically assign Discord roles and notify the user.
Default reward thresholds:
| Points | Reward |
|---|---|
| 500 | 🥉 Bronze Player |
| 1,500 | 🥈 Silver Player |
| 4,000 | 🥇 Gold Player |
| 10,000 | 💎 Diamond Player |
Point values per game are configurable in bots/minigames/points_config.json.
npm testtest/harness.js boots every bot module against a fake client, exactly the way
main.js does but without logging in. It unions the intents, builds the shared
command registry through all three attach() calls, serializes the exact
payload that goes to Discord, and dispatches mock interactions through the real
InteractionCreate listeners. It also round trips the points system and
restores data/points.json afterwards, so it is safe to run on a machine that
holds real data.
The last section makes one unauthenticated request to Discord: it logs in with
an obviously invalid token and asserts that Discord rejects it. That proves the
REST stack works end to end. Set SKIP_NETWORK_TESTS=1 to skip it offline.
What it cannot cover. An invalid token fails at REST authentication, so the
gateway WebSocket is never opened and @discordjs/ws never runs. Anything that
touches the gateway needs a real login with a real token. This is not a
theoretical gap: an @discordjs/ws override once passed this harness 47 checks
out of 47 and still crashed the bot on startup.
The harness runs in CI on every pull request.
discord_multibot/
├── main.js ← Starts all 3 bots, handles auto-restart
├── package.json
├── .env ← Not committed (see .gitignore)
├── .github/
│ ├── dependabot.yml ← Weekly dependency update checks
│ └── workflows/
│ ├── ci.yml ← Syntax + JSON + audit check on every PR
│ ├── codeql.yml ← Code scanning
│ ├── deploy.yml ← SSH deploy + systemd restart on push to main
│ └── release.yml ← GitHub release on v* tags
├── test/
│ └── harness.js ← Boot harness, run via npm test
├── core/
│ ├── config.js ← Environment config
│ ├── utils.js ← Shared helpers (makeEmbed, readJson, …)
│ └── pointsManager.js ← Points read/write + reward notifications
├── data/
│ ├── points.json ← Persistent user points (auto-created)
│ └── backups/ ← Database backup files (auto-created, auto-deleted)
├── assets/ ← Static files, e.g. terms PDF
└── bots/
├── commands/
│ ├── bot.js
│ └── commands/
│ ├── community.js ← /information, /rules, /roles
│ ├── admin.js ← /backup_database, /send_message, /send_embed
│ ├── support.js ← /script_guides
│ ├── orders.js ← /donation, /order_terms, /order_price
│ ├── utility.js ← /ping, /userinfo, /clear
│ └── minigames.js ← /random, /rg, /flachwitz, /add_flachwitz
├── events/
│ ├── bot.js
│ └── handlers/
│ ├── logging.js
│ ├── messageHandler.js
│ └── contextMenus.js
└── minigames/
├── bot.js
├── points_config.json
└── commands/
├── eightball.js
├── dice.js
├── flipcoin.js
├── rps.js
├── slots.js
├── trivia.js
├── hangman.js
├── wordle.js
├── tictactoe.js
├── connect4.js
├── blackjack.js
└── points.js
| Package | Version |
|---|---|
| discord.js | ^14.27.0 |
| dotenv | ^17.4.2 |
Dependency updates are monitored automatically via Dependabot (weekly, grouped).
npm outdated --all reports major updates for @discordjs/collection,
@discordjs/ws and undici. They belong to the discord.js 15 line and are
deliberately left alone. Both attempts to force them failed:
undici@8kills the REST layer.@discordjs/restputs Node'ssensitiveHeaderssymbol into the request headers and undici 8 rejects it (Key Symbol(sensitiveHeaders) in init is a symbol, which cannot be converted to a ByteString). No command registration, no interaction reply.npm testcatches this one.@discordjs/ws@2.0.4passed every test and crashed the bot on startup in production. Note that discord.js 14.27.0 pins@discordjs/collectionto exactly1.5.3, without a caret.
Patch level updates inside the existing ranges are fine and need no override.
- Website: msk-scripts.de
- Documentation: docu.msk-scripts.de
- GitHub: github.com/MSK-Scripts
- Discord: discord.gg/5hHSBRHvJE
© MSK Scripts – Musiker15