Compare commits
No commits in common. "170faf7d018bac969d4467f3816236452b1091cf" and "74dfdbf667c2de293eac428bce5477183d129ac8" have entirely different histories.
170faf7d01
...
74dfdbf667
23
Dockerfile
23
Dockerfile
@ -1,23 +0,0 @@
|
|||||||
# Use official Node.js LTS image
|
|
||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
# Create app directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install build dependencies and copy manifest
|
|
||||||
RUN apk add --no-cache python3 make g++
|
|
||||||
|
|
||||||
COPY package.json pnpm-lock.yaml ./
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Expose no ports (Discord bot)
|
|
||||||
# Define default environment variables (optional)
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
|
|
||||||
# Start the bot
|
|
||||||
CMD ["node", "src/index.js"]
|
|
||||||
68
README.md
68
README.md
@ -1,69 +1,57 @@
|
|||||||
# discord-music-bot
|
# discord-music-bot
|
||||||
|
|
||||||
Discord music bot template written in NodeJS using `discord.js` and `erela.js`, with Lavalink support.
|
Discord music bot template written in Rust using `serenity` and `lavalink-rs`.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Slash commands: `/ping`, `/join`, `/play`, `/leave`
|
- Slash commands: `/ping`, `/join`, `/play`, `/leave`
|
||||||
- Lavalink integration for audio playback
|
- LavaLink integration for audio playback
|
||||||
- Modular command handler structure
|
- Modular command handler structure
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
- Rust (stable toolchain)
|
||||||
- Node.js (>=14)
|
|
||||||
- pnpm or npm
|
|
||||||
- A Discord application with bot token
|
- A Discord application with bot token
|
||||||
- LavaLink server for audio streaming
|
- LavaLink server running (see [LavaLink](https://github.com/freyacodes/Lavalink))
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. Copy `.env.example` to `.env` and fill in your credentials:
|
1. Copy `.env.example` to `.env` and fill in your credentials:
|
||||||
```env
|
```
|
||||||
DISCORD_TOKEN=your_discord_bot_token
|
DISCORD_TOKEN=your_discord_bot_token
|
||||||
CLIENT_ID=your_discord_application_id
|
|
||||||
LAVALINK_HOST=127.0.0.1
|
LAVALINK_HOST=127.0.0.1
|
||||||
LAVALINK_PORT=2333
|
LAVALINK_PORT=2333
|
||||||
LAVALINK_PASSWORD=your_lavalink_password
|
LAVALINK_PASSWORD=your_lavalink_password
|
||||||
```
|
```
|
||||||
2. Install dependencies:
|
|
||||||
|
2. Build the project:
|
||||||
```sh
|
```sh
|
||||||
pnpm install
|
cargo build --release
|
||||||
```
|
|
||||||
3. Run tests:
|
|
||||||
```sh
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
4. Register slash commands:
|
|
||||||
```sh
|
|
||||||
npm start # or node deploy-commands.js
|
|
||||||
```
|
|
||||||
5. Start the bot:
|
|
||||||
```sh
|
|
||||||
npm start
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker
|
3. Run the bot:
|
||||||
|
|
||||||
A `Dockerfile` and `docker-compose.yml` are provided for containerized deployment.
|
|
||||||
|
|
||||||
- Build and run with Docker Compose:
|
|
||||||
```sh
|
```sh
|
||||||
docker-compose up --build
|
cargo run --release
|
||||||
```
|
```
|
||||||
- Environment variables are loaded from `.env`.
|
|
||||||
- Lavalink service is configured in `docker-compose.yml` alongside the bot.
|
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
- `src/index.js` — Entry point
|
- `src/main.rs` - Bot entry point, initializes Serenity and LavaLink clients
|
||||||
- `src/commands/` — Slash command modules
|
- `src/handler.rs` - Serenity event handlers (ready, interaction, voice updates)
|
||||||
- `src/events/` — Discord event handlers
|
- `src/lavalink_handler.rs` - LavaLink event handlers
|
||||||
- `src/structures/` — Erela.js (Lavalink) event wiring
|
- `src/state.rs` - TypeMap keys for shared state
|
||||||
- `src/utils/logger.js` — Logging setup
|
- `src/utils.rs` - Utility functions (env management)
|
||||||
- `deploy-commands.js` — Slash command registration script
|
- `src/commands/` - Modular slash command definitions and handlers
|
||||||
- `Dockerfile` — Bot container image
|
|
||||||
- `docker-compose.yml` — Multi-service setup (bot + Lavalink)
|
## Commands
|
||||||
|
|
||||||
|
- `/ping` - Replies with "Pong!"
|
||||||
|
- `/join` - Bot joins your voice channel
|
||||||
|
- `/play url:<URL>` - Plays audio from the given URL
|
||||||
|
- `/leave` - Bot leaves the voice channel
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- Implement actual command logic in `src/commands/*.rs`
|
||||||
|
- Add error handling and command concurrency management
|
||||||
|
- Expand LavaLink event handlers
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
@ -1,24 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
lavalink:
|
|
||||||
image: jagrosh/lavalink:latest
|
|
||||||
container_name: lavalink
|
|
||||||
ports:
|
|
||||||
- "2333:2333"
|
|
||||||
environment:
|
|
||||||
- LAVALINK_PASSWORD=${LAVALINK_PASSWORD}
|
|
||||||
volumes:
|
|
||||||
# Optional: mount custom configuration if needed
|
|
||||||
# - ./application.yml:/opt/Lavalink/application.yml
|
|
||||||
|
|
||||||
bot:
|
|
||||||
build: .
|
|
||||||
container_name: discord-music-bot
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
- LAVALINK_HOST=lavalink
|
|
||||||
- LAVALINK_PORT=2333
|
|
||||||
depends_on:
|
|
||||||
- lavalink
|
|
||||||
@ -4,8 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js",
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
"test": "jest"
|
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@ -15,9 +14,5 @@
|
|||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"erela.js": "^2.4.0",
|
"erela.js": "^2.4.0",
|
||||||
"winston": "^3.17.0"
|
"winston": "^3.17.0"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"jest": "^29.7.0",
|
|
||||||
"js-yaml": "^4.1.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
jest.mock('discord.js', () => {
|
|
||||||
const original = jest.requireActual('discord.js');
|
|
||||||
const mockRest = {
|
|
||||||
put: jest.fn().mockResolvedValue([{ length: 1 }]),
|
|
||||||
setToken: jest.fn().mockReturnThis(),
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...original,
|
|
||||||
REST: jest.fn(() => mockRest),
|
|
||||||
Routes: {
|
|
||||||
applicationCommands: jest.fn().mockReturnValue('/fake-route'),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('fs', () => ({
|
|
||||||
readdirSync: jest.fn(() => ['ping.js']),
|
|
||||||
}));
|
|
||||||
jest.mock('node:path', () => {
|
|
||||||
const actual = jest.requireActual('node:path');
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
join: (...args) => args.join('/'),
|
|
||||||
resolve: (...args) => args.join('/'),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('deploy-commands.js', () => {
|
|
||||||
let origEnv;
|
|
||||||
beforeAll(() => {
|
|
||||||
origEnv = { ...process.env };
|
|
||||||
process.env.CLIENT_ID = '12345';
|
|
||||||
process.env.DISCORD_TOKEN = 'token';
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
process.env = origEnv;
|
|
||||||
jest.resetModules();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('registers commands via REST API', async () => {
|
|
||||||
const mockLogger = { info: jest.fn(), warn: jest.fn(), error: jest.fn() };
|
|
||||||
jest.mock('../src/utils/logger', () => mockLogger);
|
|
||||||
|
|
||||||
// Run the script
|
|
||||||
await require('../deploy-commands.js');
|
|
||||||
|
|
||||||
const { REST } = require('discord.js');
|
|
||||||
expect(REST).toHaveBeenCalled();
|
|
||||||
const restInstance = REST.mock.results[0].value;
|
|
||||||
expect(restInstance.setToken).toHaveBeenCalledWith('token');
|
|
||||||
expect(restInstance.put).toHaveBeenCalledWith('/fake-route', { body: expect.any(Array) });
|
|
||||||
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining('Started refreshing'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
const { spawnSync } = require('child_process');
|
|
||||||
|
|
||||||
describe('NPM Start Script', () => {
|
|
||||||
test('npm start exits without error when DISCORD_TOKEN is provided', () => {
|
|
||||||
const env = { ...process.env, DISCORD_TOKEN: 'dummy-token', CLIENT_ID: '123', LAVALINK_HOST: 'localhost', LAVALINK_PORT: '2333', LAVALINK_PASSWORD: 'pass' };
|
|
||||||
const result = spawnSync('pnpm', ['start'], { env, encoding: 'utf-8' });
|
|
||||||
// The script starts the bot; if it reaches login attempt, exit code is 0
|
|
||||||
expect(result.status).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
const { spawnSync } = require('child_process');
|
|
||||||
|
|
||||||
describe('Bot Startup', () => {
|
|
||||||
test('exits with code 1 if DISCORD_TOKEN is missing', () => {
|
|
||||||
// Clear DISCORD_TOKEN
|
|
||||||
const env = { ...process.env };
|
|
||||||
delete env.DISCORD_TOKEN;
|
|
||||||
|
|
||||||
const result = spawnSync('node', ['src/index.js'], { env, encoding: 'utf-8' });
|
|
||||||
expect(result.status).toBe(1);
|
|
||||||
expect(result.stderr || result.stdout).toMatch(/DISCORD_TOKEN is missing/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
x
Reference in New Issue
Block a user