Modular TeamSpeak Server Query bot. Currently includes automatic private channel creation and inactive channel/client cleanup, with more modules planned.
Developed with assistance from Claude (Anthropic).
- Automatic private channel creation when a user joins a lobby channel
- Persistent channel ownership across restarts (MariaDB)
- Flexible channel placement (subchannel, root level, sorted after another channel)
- Per-lobby configuration — multiple lobbies supported
- Optional group restriction per lobby
- Automatic cleanup of channels inactive for X days
- Optional cleanup of inactive client database entries
- Automatic reconnect with exponential backoff
- Structured logging (console + file)
- Graceful shutdown (SIGINT / SIGTERM)
- Modular architecture — add new features without touching existing code
- Node.js >= 24
- A running TeamSpeak 3 server with Server Query enabled
- A MariaDB or MySQL server
- A Server Query login
git clone <this-repo>
cd ts3-bot
npm installmysql -u root -p < schema.sqlOr manually:
CREATE DATABASE ts3bot CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'ts3bot'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON ts3bot.* TO 'ts3bot'@'localhost';
FLUSH PRIVILEGES;cp config.example.js config.jsEdit config.js with your TS3 and database credentials. All available options are documented inline.
mkdir logs
npm start| Section | Purpose |
|---|---|
host, queryPort, serverId, username, password |
Server Query connection |
db |
MariaDB connection details |
lobbies[] |
One entry per lobby channel that triggers private channel creation |
cleaner.channel |
Auto-delete bot-created channels inactive for X days |
cleaner.client |
Auto-delete inactive entries from the TS3 client database (disabled by default) |
| Field | Description |
|---|---|
channelId |
The lobby channel that triggers creation |
channelNameTemplate |
Name template, %c is replaced with the client's nickname |
subchannel |
true = create under parentChannelId, false = root level |
sortAfter / sortAfterId |
Sort the new channel after a specific channel ID |
adminGroupId |
Channel group assigned to the channel owner |
channelType |
permanent | semipermanent | temporary |
restrictToGroups |
Server group IDs allowed to use this lobby (empty = everyone) |
src/
├── index.js # Entry point — connects, loads modules
├── lib/
│ ├── db.js # MariaDB connection pool
│ └── logger.js # Shared logger (winston)
└── modules/
├── channels.js # Private channel creation & persistence
└── cleaner.js # Inactive channel/client cleanup
config.example.js # Configuration template
schema.sql # Initial database schema
Each module exports a register(ts3, db) function that receives the connected
TeamSpeak client and the MariaDB pool, and sets up its own event listeners.
// src/modules/example.js
async function register(ts3, db) {
ts3.on("clientmoved", (evArr) => {
const ev = Array.isArray(evArr) ? evArr[0] : evArr;
// your logic here
});
}
module.exports = { register };Then add it to the module list in src/index.js:
const modules = [
require("./modules/channels.js"),
require("./modules/cleaner.js"),
require("./modules/example.js"), // ← new module
];Note on event names:
node-tsstrips thenotifyprefix from TS3 server notifications but keeps everything else, e.g. the server sendsnotifyclientmovedand the library emits it asclientmoved— with the payload wrapped in an array. Always unwrap withArray.isArray(ev) ? ev[0] : evbefore reading properties.
# /etc/systemd/system/ts3bot.service
[Unit]
Description=TS3 Bot
After=network.target
[Service]
Type=simple
User=ts3bot
WorkingDirectory=/opt/ts3bot
ExecStart=/usr/bin/node src/index.js
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now ts3bot
sudo journalctl -u ts3bot -flogs/combined.log— all log entries (JSON)logs/error.log— errors only (JSON)- Console — colorized output
Log level is set in config.js: error | warn | info | debug
MIT