Skip to content

Commit b75a37f

Browse files
authored
Merge branch 'main' into main
2 parents 1339f23 + 0d0b9c6 commit b75a37f

243 files changed

Lines changed: 6812 additions & 7627 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
node_modules
2+
npm-debug.log*
3+
.env
4+
.env.*
5+
!.env.example
6+
.git
7+
.github
8+
.cursor
9+
tests
10+
backups
11+
logs
12+
*.md
13+
!README.md
14+
.DS_Store
15+
coverage
16+
.agent-tools

.env.example

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@
33
# Discord Bot Configuration
44
DISCORD_TOKEN=your_discord_bot_token_here
55
CLIENT_ID=your_discord_client_id_here
6+
# Your server ID (used in the setup tutorial; slash commands register globally via CLIENT_ID).
67
GUILD_ID=your_discord_guild_id_here
78
OWNER_IDS=your_discord_id_here (optional)
89

9-
# Optional — enable slash commands in every server the bot is invited to.
10-
# Leave unset or false for single-server setup (recommended for most users).
11-
MULTI_GUILD=false
12-
1310
# Bot Runtime Configuration
1411
NODE_ENV=production
1512
LOG_LEVEL=warn
@@ -54,16 +51,18 @@ BACKUP_DIR=./backups
5451
BACKUP_RETENTION_DAYS=14
5552
POSTGRES_RESTORE_URL=
5653

57-
# Music (Lavalink + Riffy) — requires a Lavalink v4 server
58-
# Local dev: run Lavalink via docker compose (see README), then use localhost below.
59-
# Docker Compose bot service uses LAVALINK_HOST=lavalink instead.
60-
LAVALINK_HOST=localhost
61-
LAVALINK_PORT=2333
62-
LAVALINK_PASSWORD=youshallnotpass
63-
LAVALINK_SECURE=false
64-
LAVALINK_NAME=Main
65-
# Optional: override nodes as JSON array (for multiple Lavalink nodes)
54+
# Music (Lavalink + Riffy) — requires Lavalink v4 nodes
55+
# Default: loads public v4 SSL nodes from lavalink/nodes.json
56+
# Source: https://lavalink.darrennathanael.com/SSL/Lavalink-SSL/
57+
# LAVALINK_NODES_FILE=lavalink/nodes.json
58+
# Optional: override nodes as JSON array (takes priority over nodes file)
6659
# LAVALINK_NODES=[{"host":"lavalink","port":2333,"password":"youshallnotpass","secure":false,"name":"Main"}]
60+
# Self-hosted single node fallback (used only if nodes file/env array are absent):
61+
# LAVALINK_HOST=localhost
62+
# LAVALINK_PORT=2333
63+
# LAVALINK_PASSWORD=youshallnotpass
64+
# LAVALINK_SECURE=false
65+
# LAVALINK_NAME=Main
6766
LAVALINK_SEARCH_PLATFORM=ytmsearch
6867
LAVALINK_REST_VERSION=v4
6968

.github/workflows/tests.yml

Lines changed: 0 additions & 29 deletions
This file was deleted.

CONTRIBUTING.md

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ Thank you for your interest in contributing to TitanBot! This guide covers local
77
- Bug fixes and reliability improvements
88
- New commands or enhancements to existing features
99
- Documentation updates
10-
- Test coverage for behavior that is easy to regress
1110

1211
Before starting large features, open an issue or discuss in the [support server](https://discord.gg/8kJBYhTGW9) so we can align on scope and avoid duplicate work.
1312

1413
## Getting Started
1514

1615
### Prerequisites
1716

18-
- **Node.js 18+** (CI uses Node 20)
17+
- **Node.js 20+** (Docker and CI use Node 20)
1918
- **PostgreSQL** (recommended for development; the bot can fall back to in-memory storage if PostgreSQL is unavailable)
2019
- A **Discord bot application** with the intents listed in [README.md](README.md#required-bot-intents)
2120

@@ -46,31 +45,14 @@ For Docker-based setup, see [README.md](README.md#docker-deployment-recommended)
4645

4746
1. **Fork the repository** and create a branch from `main`.
4847
2. **Make focused changes** — one logical change per pull request when possible.
49-
3. **Run tests** before opening a PR (see below).
50-
4. **Open a pull request** with a clear description of what changed and why.
48+
3. **Open a pull request** with a clear description of what changed and why.
5149

5250
Use descriptive branch names, for example:
5351

5452
- `fix/ticket-panel-refresh`
5553
- `feat/economy-shop-filter`
5654
- `docs/contributing-guide`
5755

58-
## Running Tests
59-
60-
Tests use Node's built-in test runner:
61-
62-
```bash
63-
npm test
64-
```
65-
66-
Test files live in `tests/` and follow the `*.test.js` naming pattern. When adding or changing behavior, add or update tests for:
67-
68-
- Permission checks and command access rules
69-
- Parsing, validation, and utility logic
70-
- UI/panel builders and status helpers
71-
72-
CI runs `npm test` on every pull request and on pushes to `main` and `master`. A separate workflow also validates database migrations against PostgreSQL when migration-related code changes.
73-
7456
## Database & Migrations
7557

7658
TitanBot uses PostgreSQL as its primary store. If PostgreSQL is unreachable at startup, the bot can operate in a **degraded in-memory mode** — but that mode is not suitable for production and should not be the only way you test persistence-related changes.
@@ -91,7 +73,7 @@ Test features that read or write guild data with **both** PostgreSQL and the mem
9173

9274
- **Match existing style** — ES modules (`import`/`export`), async/await, and the conventions used in neighboring files.
9375
- **Handle errors gracefully** — catch failures, log with context, and send user-friendly embed replies where appropriate.
94-
- **Avoid breaking guild isolation** — guild-specific config and data must stay scoped per server, especially when `MULTI_GUILD=true`.
76+
- **Avoid breaking guild isolation** — guild-specific config and data must stay scoped per server (`guild:{guildId}:...` keys, `interaction.guildId`).
9577
- **Keep changes minimal** — prefer extending existing utilities and services over duplicating logic.
9678
- **Document user-facing behavior** — update README.md when setup steps or configuration change; mention new env vars in `.env.example`.
9779

Dockerfile

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
FROM node:20-alpine
22

3-
# Create app directory
43
WORKDIR /usr/src/app
54

6-
# Install app dependencies
7-
# A wildcard is used to ensure both package.json AND package-lock.json are copied
8-
COPY package*.json ./
5+
ENV NODE_ENV=production
96

10-
# Install only production dependencies
7+
COPY package*.json ./
118
RUN npm ci --omit=dev
129

13-
# Bundle app source
1410
COPY . .
1511

16-
# Expose the health check port from src/app.js
1712
EXPOSE 3000
1813

19-
# Start the bot
20-
CMD [ "npm", "start" ]
14+
CMD ["npm", "start"]

README.md

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -109,28 +109,38 @@ TitanBot is fully containerized for easy deployment.
109109
```
110110

111111
2. **Configure environment variables:**
112-
Create a `.env` file from `.env.example` and fill in your bot details and PostgreSQL credentials.
112+
```bash
113+
cp .env.example .env
114+
```
115+
Set at minimum `DISCORD_TOKEN`, `CLIENT_ID`, and `GUILD_ID`. Docker Compose also reads `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` from `.env` (defaults: `titanbot` / `password` / `titanbot`).
113116

114-
3. **Start the containers:**
117+
3. **Build and start the containers:**
115118
```bash
116-
docker-compose up -d
119+
docker compose up -d --build
117120
```
118121

119-
This will start the bot, PostgreSQL, and Lavalink (when music is enabled).
122+
4. **Check status:**
123+
```bash
124+
docker compose ps
125+
curl http://localhost:3000/health
126+
```
127+
128+
This starts the bot and PostgreSQL. The compose file sets `POSTGRES_SSL=false` and `AUTO_MIGRATE=true` for the bundled database. Music uses public Lavalink v4 nodes from `lavalink/nodes.json` by default.
120129

121130
### Music
122131

123132
Music uses [Lavalink v4](https://github.com/lavalink-devs/Lavalink) via [Riffy](https://github.com/riffy-rb/riffy), similar to [Musicify](https://github.com/codebymitch/Musicify).
124133

125-
1. Set in `.env`:
134+
1. By default, the bot loads multiple public v4 SSL nodes from [`lavalink/nodes.json`](lavalink/nodes.json) (sourced from [lavalink.darrennathanael.com](https://lavalink.darrennathanael.com/SSL/Lavalink-SSL/)). Edit that file to add or remove nodes.
135+
2. To self-host Lavalink instead, run `docker compose --profile local-lavalink up -d` and set single-node env vars in `.env`:
126136
```env
127137
LAVALINK_HOST=lavalink
128138
LAVALINK_PORT=2333
129139
LAVALINK_PASSWORD=youshallnotpass
130140
LAVALINK_SECURE=false
131141
```
132-
2. With Docker Compose, Lavalink is included automatically when you `docker compose up`.
133-
3. On Railway, deploy Lavalink separately or as another service and point `LAVALINK_HOST` at the private hostname.
142+
Remove or rename `lavalink/nodes.json` so the bot falls back to those env vars.
143+
3. Override nodes inline with `LAVALINK_NODES` (JSON array) or point at another file with `LAVALINK_NODES_FILE`.
134144
4. Use `/play <song>` from a voice channel, or `/join` to connect without playing. Prefix shortcuts: `join`, `np`, `leave`, `pause`, `resume`, `skip`, `stop`, `volume <0-100>`, or `music <subcommand>`. Use `/nowplaying` and `/queue` for status; `/music` for loop, shuffle, seek, and other controls.
135145

136146
### Using GitHub Container Registry
@@ -145,7 +155,7 @@ docker pull ghcr.io/codebymitch/titanbot:main
145155
## Manual Installation Steps
146156

147157
### Prerequisites
148-
- Node.js 18.0.0 or higher
158+
- Node.js 20.10.0 or higher
149159
- PostgreSQL server (recommended) or memory storage fallback
150160
- Discord bot application with proper intents
151161

@@ -202,18 +212,13 @@ docker pull ghcr.io/codebymitch/titanbot:main
202212
This gives clear startup/online status messages while keeping logs simple for non-technical operators.
203213
If port `3000` is busy, the bot tries the next available ports automatically (up to `PORT_RETRY_ATTEMPTS`).
204214

205-
### Running in multiple servers (optional)
206-
207-
Most users run TitanBot on a **single server** with `GUILD_ID` set (default tutorial setup). If you want commands to work in **every server** the bot is invited to, opt in with:
215+
### Multiple servers
208216

209-
```env
210-
MULTI_GUILD=true
211-
```
217+
Slash commands are registered **globally** on startup (via `CLIENT_ID`), so the bot works in every server it is invited to. `GUILD_ID` stays in the tutorial `.env` for setup steps but is not used for command registration.
212218

213-
Notes for multi-server mode:
214-
- `GUILD_ID` is not used for command registration when `MULTI_GUILD=true` (you can leave it set or remove it)
219+
Notes:
215220
- Global slash commands may take up to about an hour to propagate on first deploy
216-
- Each server still has **isolated** config, economy, tickets, leveling, and other data
221+
- Each server has **isolated** data: config, economy, tickets, leveling, dashboards, warnings, etc. (all keys are scoped as `guild:{guildId}:...`)
217222
- In the [Discord Developer Portal](https://discord.com/developers/applications), ensure your bot is not restricted to a single guild if you plan to invite it elsewhere
218223
- Generate an OAuth2 invite URL from the [Discord Developer Portal](https://discord.com/developers/applications) (OAuth2 → URL Generator, scopes: `bot` and `applications.commands`)
219224

@@ -235,6 +240,12 @@ Notes for multi-server mode:
235240
```bash
236241
npm start
237242
```
243+
244+
> **Note on database migrations:** Schema tables and legacy key migrations run
245+
> **automatically on startup**, so` managed hosts like **Railway** need no manual
246+
> migration step — just deploy/restart. To disable auto-migration set
247+
> `AUTO_MIGRATE=false`. You can still run a manual key migration locally with
248+
> `node scripts/migrate-keys.js --dry-run` (preview) or `node scripts/migrate-keys.js`.
238249
<a name="bot-intents"></a>
239250
240251
## Required Bot Intents

docker-compose.yml

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,43 @@ services:
33
build: .
44
container_name: titanbot
55
restart: unless-stopped
6+
env_file:
7+
- .env
68
environment:
79
- NODE_ENV=production
810
- DISCORD_TOKEN=${DISCORD_TOKEN}
911
- CLIENT_ID=${CLIENT_ID}
1012
- GUILD_ID=${GUILD_ID}
11-
- MULTI_GUILD=${MULTI_GUILD:-false}
12-
- DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
13-
- LAVALINK_HOST=lavalink
14-
- LAVALINK_PORT=2333
15-
- LAVALINK_PASSWORD=${LAVALINK_PASSWORD:-youshallnotpass}
16-
- LAVALINK_SECURE=false
13+
- DATABASE_URL=postgres://${POSTGRES_USER:-titanbot}:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-titanbot}
14+
- POSTGRES_URL=postgres://${POSTGRES_USER:-titanbot}:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-titanbot}
15+
- POSTGRES_HOST=db
16+
- POSTGRES_PORT=5432
17+
- POSTGRES_DB=${POSTGRES_DB:-titanbot}
18+
- POSTGRES_USER=${POSTGRES_USER:-titanbot}
19+
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password}
20+
- POSTGRES_SSL=false
21+
- AUTO_MIGRATE=true
22+
- LOG_LEVEL=${LOG_LEVEL:-warn}
23+
- LAVALINK_NODES_FILE=lavalink/nodes.json
1724
- PORT=3000
25+
- WEB_HOST=0.0.0.0
26+
ports:
27+
- "${PORT:-3000}:3000"
1828
depends_on:
1929
db:
2030
condition: service_healthy
21-
lavalink:
22-
condition: service_started
31+
healthcheck:
32+
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/health').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
33+
interval: 30s
34+
timeout: 5s
35+
retries: 3
36+
start_period: 40s
2337
networks:
2438
- titan-network
2539

2640
lavalink:
41+
profiles:
42+
- local-lavalink
2743
image: ghcr.io/lavalink-devs/lavalink:4
2844
container_name: titanbot-lavalink
2945
restart: unless-stopped

lavalink/nodes.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"host": "lavalinkv4.serenetia.com",
4+
"port": 443,
5+
"password": "https://seretia.link/discord",
6+
"secure": true,
7+
"name": "Serenetia"
8+
},
9+
{
10+
"host": "lavalink.jirayu.net",
11+
"port": 443,
12+
"password": "youshallnotpass",
13+
"secure": true,
14+
"name": "Jirayu"
15+
},
16+
{
17+
"host": "lava-v4.millohost.my.id",
18+
"port": 443,
19+
"password": "https://discord.gg/mjS5J2K3ep",
20+
"secure": true,
21+
"name": "MilloHost"
22+
},
23+
{
24+
"host": "lavalink-v4.triniumhost.com",
25+
"port": 443,
26+
"password": "free",
27+
"secure": true,
28+
"name": "TriniumHost"
29+
}
30+
]

0 commit comments

Comments
 (0)