add spec.md
This commit is contained in:
718
spec.md
Normal file
718
spec.md
Normal file
@@ -0,0 +1,718 @@
|
|||||||
|
# Discord Music Bot with Web DJ Interface
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
A self-hosted Discord bot that plays local MP3 files in a voice channel on continuous loop, paired with a web-based DJ interface that allows users to view and control playback.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- **Always-on music**: Bot joins a designated voice channel and plays music 24/7
|
||||||
|
- **Local library**: All audio files are stored locally on the server (no streaming services)
|
||||||
|
- **Web control**: Browser-based interface for viewing now-playing and controlling the queue
|
||||||
|
- **Simple deployment**: Dockerized for easy VPS hosting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Docker Host │
|
||||||
|
│ ┌───────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Docker Compose │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
|
||||||
|
│ │ │ Bot Service │◄────►│ Web Backend (API) │ │ │
|
||||||
|
│ │ │ (Python/JS) │ │ (Node/Python) │ │ │
|
||||||
|
│ │ └────────┬────────┘ └───────────┬─────────────┘ │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ ▼ ▼ │ │
|
||||||
|
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
|
||||||
|
│ │ │ Discord │ │ Web Frontend │ │ │
|
||||||
|
│ │ │ Voice API │ │ (React/Vue/Static) │ │ │
|
||||||
|
│ │ └─────────────────┘ └─────────────────────────┘ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ Shared Volume: /music │ │ │
|
||||||
|
│ │ │ (MP3 files) │ │ │
|
||||||
|
│ │ └─────────────────────────────────────────────────┘ │ │
|
||||||
|
│ └───────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### 1. Discord Bot Service
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Connect to Discord and join a configured voice channel
|
||||||
|
- Play MP3 files from local storage
|
||||||
|
- Loop through playlist continuously
|
||||||
|
- Accept commands from the web backend via internal API or message queue
|
||||||
|
- Report current playback state
|
||||||
|
|
||||||
|
**Technology Options:**
|
||||||
|
- **Python**: `discord.py` with `PyNaCl` for voice
|
||||||
|
- **Node.js**: `discord.js` with `@discordjs/voice`
|
||||||
|
|
||||||
|
### 2. Web Backend (API Server)
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Serve REST API for frontend
|
||||||
|
- Provide WebSocket connection for real-time updates
|
||||||
|
- Communicate with bot service to control playback
|
||||||
|
- Scan and manage the music library
|
||||||
|
|
||||||
|
**Technology Options:**
|
||||||
|
- **Node.js**: Express or Fastify
|
||||||
|
- **Python**: FastAPI or Flask
|
||||||
|
|
||||||
|
### 3. Web Frontend
|
||||||
|
|
||||||
|
**Responsibilities:**
|
||||||
|
- Display now-playing information (track name, progress, album art if available)
|
||||||
|
- Show upcoming tracks in queue
|
||||||
|
- Provide DJ controls (skip, pause, reorder, shuffle)
|
||||||
|
- Playlist management
|
||||||
|
|
||||||
|
**Technology Options:**
|
||||||
|
- React, Vue, Svelte, or plain HTML/JS
|
||||||
|
- Tailwind CSS for styling
|
||||||
|
|
||||||
|
### 4. Shared Storage
|
||||||
|
|
||||||
|
- Docker volume mounted to both bot and backend
|
||||||
|
- Contains all MP3 files
|
||||||
|
- Optionally: SQLite database for metadata/state
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### MVP (Phase 1)
|
||||||
|
|
||||||
|
| Feature | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| Bot auto-join | Bot joins configured voice channel on startup |
|
||||||
|
| Continuous playback | Loops through all MP3s in the music directory |
|
||||||
|
| Now playing display | Web UI shows current track name and artist (from ID3 tags) |
|
||||||
|
| Skip track | Web UI button to skip to next track |
|
||||||
|
| Pause/Resume | Web UI controls for pausing and resuming playback |
|
||||||
|
| Track list | Web UI displays full playlist |
|
||||||
|
| MP3 upload | Upload MP3 files via web interface |
|
||||||
|
|
||||||
|
### Phase 2
|
||||||
|
|
||||||
|
| Feature | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| Queue reordering | Drag-and-drop to reorder upcoming tracks |
|
||||||
|
| Shuffle mode | Toggle shuffle on/off |
|
||||||
|
| Search | Filter/search tracks in the library |
|
||||||
|
| Track progress | Show playback progress bar with seek capability |
|
||||||
|
| Album art | Display embedded album art from MP3 metadata |
|
||||||
|
| Volume control | Adjust bot playback volume from web UI |
|
||||||
|
|
||||||
|
### Phase 3 (Nice-to-Have)
|
||||||
|
|
||||||
|
| Feature | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| Multi-channel support | Configure multiple channels with separate playlists |
|
||||||
|
| User authentication | Password-protect the DJ interface |
|
||||||
|
| Discord slash commands | Control bot via Discord commands (e.g., `/skip`, `/nowplaying`) |
|
||||||
|
| Listening history | Log of recently played tracks |
|
||||||
|
| Favorites/playlists | Create and manage multiple playlists |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Specification
|
||||||
|
|
||||||
|
### REST Endpoints
|
||||||
|
|
||||||
|
#### Library
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| GET | `/api/tracks` | List all tracks in library |
|
||||||
|
| GET | `/api/tracks/:id` | Get track metadata |
|
||||||
|
| GET | `/api/tracks/:id/art` | Get album art image |
|
||||||
|
| POST | `/api/tracks/upload` | Upload new MP3 file(s) |
|
||||||
|
| DELETE | `/api/tracks/:id` | Delete a track from library |
|
||||||
|
| POST | `/api/tracks/scan` | Rescan music directory for new files |
|
||||||
|
|
||||||
|
#### Playback
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| GET | `/api/playback` | Get current playback state |
|
||||||
|
| POST | `/api/playback/play` | Resume playback |
|
||||||
|
| POST | `/api/playback/pause` | Pause playback |
|
||||||
|
| POST | `/api/playback/skip` | Skip to next track |
|
||||||
|
| POST | `/api/playback/previous` | Go to previous track |
|
||||||
|
| POST | `/api/playback/seek` | Seek to position (body: `{ position: seconds }`) |
|
||||||
|
| POST | `/api/playback/volume` | Set volume (body: `{ volume: 0-100 }`) |
|
||||||
|
|
||||||
|
#### Queue
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| GET | `/api/queue` | Get current queue |
|
||||||
|
| POST | `/api/queue/add` | Add track to queue (body: `{ trackId }`) |
|
||||||
|
| POST | `/api/queue/remove` | Remove track from queue (body: `{ index }`) |
|
||||||
|
| POST | `/api/queue/reorder` | Reorder queue (body: `{ from, to }`) |
|
||||||
|
| POST | `/api/queue/shuffle` | Shuffle the queue |
|
||||||
|
| POST | `/api/queue/clear` | Clear queue (except now playing) |
|
||||||
|
|
||||||
|
#### Bot Status
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| GET | `/api/status` | Bot connection status, channel info |
|
||||||
|
|
||||||
|
### WebSocket Events
|
||||||
|
|
||||||
|
**Server → Client:**
|
||||||
|
```json
|
||||||
|
{ "event": "trackChange", "data": { "track": {...}, "queue": [...] } }
|
||||||
|
{ "event": "playbackUpdate", "data": { "state": "playing", "position": 45.2 } }
|
||||||
|
{ "event": "queueUpdate", "data": { "queue": [...] } }
|
||||||
|
{ "event": "volumeChange", "data": { "volume": 75 } }
|
||||||
|
{ "event": "libraryUpdate", "data": { "action": "added", "tracks": [...] } }
|
||||||
|
{ "event": "libraryUpdate", "data": { "action": "removed", "trackIds": [...] } }
|
||||||
|
{ "event": "uploadProgress", "data": { "filename": "song.mp3", "progress": 45 } }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client → Server:**
|
||||||
|
```json
|
||||||
|
{ "event": "subscribe" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Upload Specification
|
||||||
|
|
||||||
|
### Upload Endpoint Details
|
||||||
|
|
||||||
|
**POST `/api/tracks/upload`**
|
||||||
|
|
||||||
|
Accepts multipart/form-data with one or more MP3 files.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```
|
||||||
|
Content-Type: multipart/form-data
|
||||||
|
|
||||||
|
file: <binary MP3 data>
|
||||||
|
-- or for multiple files --
|
||||||
|
files[]: <binary MP3 data>
|
||||||
|
files[]: <binary MP3 data>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (Success):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"uploaded": [
|
||||||
|
{
|
||||||
|
"id": "abc123",
|
||||||
|
"filename": "song.mp3",
|
||||||
|
"title": "Song Title",
|
||||||
|
"artist": "Artist Name",
|
||||||
|
"duration": 215
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (Partial/Failure):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"uploaded": [],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"filename": "badfile.txt",
|
||||||
|
"error": "Invalid file type. Only MP3 files are accepted."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "toobig.mp3",
|
||||||
|
"error": "File exceeds maximum size of 50MB."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload Validation Rules
|
||||||
|
|
||||||
|
| Rule | Constraint |
|
||||||
|
|------|------------|
|
||||||
|
| File type | Must be `audio/mpeg` MIME type or `.mp3` extension |
|
||||||
|
| File size | Maximum 50MB per file (configurable) |
|
||||||
|
| Filename | Sanitized to remove special characters |
|
||||||
|
| Duplicates | Option to skip, overwrite, or rename duplicates |
|
||||||
|
| Batch limit | Maximum 20 files per upload request |
|
||||||
|
|
||||||
|
### Upload Processing Pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||||
|
│ Receive │────►│ Validate │────►│ Extract │────►│ Save │
|
||||||
|
│ File │ │ Type/Size │ │ Metadata │ │ to Disk │
|
||||||
|
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────┐ ┌──────────────┐ ┌──────────────────────────────────┐
|
||||||
|
│ Notify │◄────│ Index │◄────│ Generate ID & Store in DB │
|
||||||
|
│ Bot │ │ Library │ │ │
|
||||||
|
└──────────────┘ └──────────────┘ └──────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metadata Extraction
|
||||||
|
|
||||||
|
On upload, the following ID3 tags are extracted (if present):
|
||||||
|
- Title (falls back to filename)
|
||||||
|
- Artist
|
||||||
|
- Album
|
||||||
|
- Duration
|
||||||
|
- Album art (stored separately for serving)
|
||||||
|
- Track number
|
||||||
|
- Year
|
||||||
|
- Genre
|
||||||
|
|
||||||
|
**Recommended library:** `music-metadata` (Node.js) or `mutagen` (Python)
|
||||||
|
|
||||||
|
### Frontend Upload Component
|
||||||
|
|
||||||
|
The web UI should include:
|
||||||
|
|
||||||
|
| Feature | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| Drag & drop zone | Drop MP3 files directly onto the page |
|
||||||
|
| File browser | Traditional file picker button |
|
||||||
|
| Progress indicator | Upload progress bar for each file |
|
||||||
|
| Batch upload | Support multiple files at once |
|
||||||
|
| Validation feedback | Show errors inline before/during upload |
|
||||||
|
| Success confirmation | Show newly added tracks with option to queue |
|
||||||
|
|
||||||
|
### Upload Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Additional environment variables
|
||||||
|
MAX_UPLOAD_SIZE_MB=50
|
||||||
|
MAX_BATCH_SIZE=20
|
||||||
|
DUPLICATE_HANDLING=rename # skip | overwrite | rename
|
||||||
|
ALLOWED_EXTENSIONS=.mp3 # Could expand to .mp3,.flac,.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Track
|
||||||
|
```typescript
|
||||||
|
interface Track {
|
||||||
|
id: string; // Unique identifier (hash of filepath or UUID)
|
||||||
|
filename: string; // Original filename
|
||||||
|
filepath: string; // Path relative to music directory
|
||||||
|
title: string; // From ID3 tag or filename
|
||||||
|
artist: string; // From ID3 tag
|
||||||
|
album: string; // From ID3 tag
|
||||||
|
duration: number; // Duration in seconds
|
||||||
|
hasArt: boolean; // Whether album art is available
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PlaybackState
|
||||||
|
```typescript
|
||||||
|
interface PlaybackState {
|
||||||
|
currentTrack: Track | null;
|
||||||
|
state: 'playing' | 'paused' | 'stopped';
|
||||||
|
position: number; // Current position in seconds
|
||||||
|
volume: number; // 0-100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Queue
|
||||||
|
```typescript
|
||||||
|
interface Queue {
|
||||||
|
tracks: Track[]; // Ordered list of upcoming tracks
|
||||||
|
history: Track[]; // Recently played (optional)
|
||||||
|
loopMode: 'all' | 'one' | 'off';
|
||||||
|
shuffled: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Discord
|
||||||
|
DISCORD_BOT_TOKEN=your_bot_token_here
|
||||||
|
DISCORD_GUILD_ID=your_server_id
|
||||||
|
DISCORD_CHANNEL_ID=voice_channel_id
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
MUSIC_DIRECTORY=/music
|
||||||
|
|
||||||
|
# Web Server
|
||||||
|
WEB_PORT=3000
|
||||||
|
API_PORT=3001
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
ADMIN_PASSWORD=optional_password
|
||||||
|
LOG_LEVEL=info
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
bot:
|
||||||
|
build: ./bot
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN}
|
||||||
|
- DISCORD_GUILD_ID=${DISCORD_GUILD_ID}
|
||||||
|
- DISCORD_CHANNEL_ID=${DISCORD_CHANNEL_ID}
|
||||||
|
- API_URL=http://api:3001
|
||||||
|
volumes:
|
||||||
|
- ./music:/music:ro
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
|
||||||
|
api:
|
||||||
|
build: ./api
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3001:3001"
|
||||||
|
environment:
|
||||||
|
- MUSIC_DIRECTORY=/music
|
||||||
|
- BOT_INTERNAL_URL=http://bot:8080
|
||||||
|
- MAX_UPLOAD_SIZE_MB=50
|
||||||
|
- MAX_BATCH_SIZE=20
|
||||||
|
volumes:
|
||||||
|
- ./music:/music # Read-write for uploads
|
||||||
|
- ./data:/data
|
||||||
|
|
||||||
|
web:
|
||||||
|
build: ./web
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
discord-dj-bot/
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── .env
|
||||||
|
├── .env.example
|
||||||
|
├── README.md
|
||||||
|
│
|
||||||
|
├── bot/
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── package.json (or requirements.txt)
|
||||||
|
│ └── src/
|
||||||
|
│ ├── index.js (or main.py)
|
||||||
|
│ ├── discord/
|
||||||
|
│ │ ├── client.js
|
||||||
|
│ │ └── voice.js
|
||||||
|
│ └── api/
|
||||||
|
│ └── internal.js
|
||||||
|
│
|
||||||
|
├── api/
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── package.json (or requirements.txt)
|
||||||
|
│ └── src/
|
||||||
|
│ ├── index.js
|
||||||
|
│ ├── routes/
|
||||||
|
│ │ ├── tracks.js
|
||||||
|
│ │ ├── playback.js
|
||||||
|
│ │ ├── queue.js
|
||||||
|
│ │ └── upload.js
|
||||||
|
│ ├── services/
|
||||||
|
│ │ ├── library.js
|
||||||
|
│ │ ├── bot-bridge.js
|
||||||
|
│ │ └── metadata.js
|
||||||
|
│ ├── middleware/
|
||||||
|
│ │ └── upload.js # Multer/busboy config
|
||||||
|
│ └── websocket/
|
||||||
|
│ └── handler.js
|
||||||
|
│
|
||||||
|
├── web/
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── public/
|
||||||
|
│ └── src/
|
||||||
|
│ ├── App.jsx
|
||||||
|
│ ├── components/
|
||||||
|
│ │ ├── NowPlaying.jsx
|
||||||
|
│ │ ├── Queue.jsx
|
||||||
|
│ │ ├── Controls.jsx
|
||||||
|
│ │ ├── TrackList.jsx
|
||||||
|
│ │ └── UploadZone.jsx
|
||||||
|
│ └── hooks/
|
||||||
|
│ ├── useWebSocket.js
|
||||||
|
│ └── useUpload.js
|
||||||
|
│
|
||||||
|
├── music/ # Mount your MP3s here
|
||||||
|
│ ├── song1.mp3
|
||||||
|
│ ├── song2.mp3
|
||||||
|
│ └── ...
|
||||||
|
│
|
||||||
|
└── data/ # Persistent data (optional)
|
||||||
|
└── library.db
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bot-API Communication
|
||||||
|
|
||||||
|
The bot and API need to communicate bidirectionally:
|
||||||
|
|
||||||
|
### Option A: HTTP Internal API (Recommended for MVP)
|
||||||
|
- Bot exposes a simple HTTP server on internal network
|
||||||
|
- API calls bot endpoints to send commands
|
||||||
|
- Bot polls API or uses WebSocket for queue updates
|
||||||
|
|
||||||
|
### Option B: Redis Pub/Sub
|
||||||
|
- Both services connect to a shared Redis instance
|
||||||
|
- Commands and state updates flow through Redis channels
|
||||||
|
|
||||||
|
### Option C: Shared SQLite + File Watching
|
||||||
|
- State stored in SQLite database
|
||||||
|
- Bot watches for changes and reacts
|
||||||
|
- Simple but less real-time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Bot Token**: Never commit to version control; use environment variables
|
||||||
|
2. **Web Interface**: Consider adding basic auth for public-facing deployments
|
||||||
|
3. **Internal Network**: Bot and API should communicate over Docker internal network only
|
||||||
|
4. **File Access**: Mount music directory as read-only for bot; API needs write access for uploads
|
||||||
|
|
||||||
|
### Upload Security
|
||||||
|
|
||||||
|
5. **File Type Validation**: Validate MIME type AND file extension; optionally verify MP3 magic bytes
|
||||||
|
6. **File Size Limits**: Enforce maximum file size to prevent disk exhaustion attacks (100mb)
|
||||||
|
7. **Filename Sanitization**: Strip path traversal characters (`../`, `./`), null bytes, and special characters
|
||||||
|
8. **Rate Limiting**: Limit uploads per IP/session to prevent abuse
|
||||||
|
9. **Disk Space Monitoring**: Check available disk space before accepting uploads
|
||||||
|
|
||||||
|
### Recommended Upload Middleware
|
||||||
|
|
||||||
|
**Node.js:**
|
||||||
|
```javascript
|
||||||
|
// Using multer with limits
|
||||||
|
const upload = multer({
|
||||||
|
dest: '/tmp/uploads',
|
||||||
|
limits: {
|
||||||
|
fileSize: 50 * 1024 * 1024, // 50MB
|
||||||
|
files: 20
|
||||||
|
},
|
||||||
|
fileFilter: (req, file, cb) => {
|
||||||
|
if (file.mimetype !== 'audio/mpeg') {
|
||||||
|
return cb(new Error('Only MP3 files allowed'));
|
||||||
|
}
|
||||||
|
cb(null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Python:**
|
||||||
|
```python
|
||||||
|
# Using FastAPI with validation
|
||||||
|
from fastapi import UploadFile
|
||||||
|
|
||||||
|
async def validate_upload(file: UploadFile):
|
||||||
|
if file.content_type != "audio/mpeg":
|
||||||
|
raise HTTPException(400, "Only MP3 files allowed")
|
||||||
|
if file.size > 50 * 1024 * 1024:
|
||||||
|
raise HTTPException(400, "File too large")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Discord Setup Guide
|
||||||
|
|
||||||
|
Before deploying the bot, you'll need to create a Discord Application and Bot account. This is free and takes about 5-10 minutes.
|
||||||
|
|
||||||
|
### Step 1: Create a Discord Application
|
||||||
|
|
||||||
|
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications)
|
||||||
|
2. Log in with your Discord account
|
||||||
|
3. Click **"New Application"** (top right)
|
||||||
|
4. Enter a name for your application (e.g., "DJ Bot")
|
||||||
|
5. Accept the terms of service and click **"Create"**
|
||||||
|
|
||||||
|
You'll land on the "General Information" page. You can optionally add a description and app icon here.
|
||||||
|
|
||||||
|
### Step 2: Create the Bot User
|
||||||
|
|
||||||
|
1. In the left sidebar, click **"Bot"**
|
||||||
|
2. Click **"Add Bot"** and confirm
|
||||||
|
3. (Optional) Customize the bot's username and avatar—this is what users see in Discord
|
||||||
|
|
||||||
|
**Important Bot Settings:**
|
||||||
|
| Setting | Value | Why |
|
||||||
|
|---------|-------|-----|
|
||||||
|
| Public Bot | Off (recommended) | Prevents others from adding your bot to their servers |
|
||||||
|
| Requires OAuth2 Code Grant | Off | Not needed for this use case |
|
||||||
|
| Presence Intent | Off | Not needed |
|
||||||
|
| Server Members Intent | Off | Not needed |
|
||||||
|
| Message Content Intent | Off | Not needed (unless adding text commands later) |
|
||||||
|
|
||||||
|
### Step 3: Get Your Bot Token
|
||||||
|
|
||||||
|
1. On the **"Bot"** page, find the **"Token"** section
|
||||||
|
2. Click **"Reset Token"** (or "Copy" if visible)
|
||||||
|
3. **Copy and save this token securely**—you won't be able to see it again
|
||||||
|
|
||||||
|
```
|
||||||
|
⚠️ CRITICAL: Never share your bot token or commit it to version control.
|
||||||
|
Anyone with this token has full control of your bot.
|
||||||
|
If leaked, immediately reset it in the Developer Portal.
|
||||||
|
```
|
||||||
|
|
||||||
|
This token goes in your `.env` file as `DISCORD_BOT_TOKEN`.
|
||||||
|
|
||||||
|
### Step 4: Configure OAuth2 Permissions
|
||||||
|
|
||||||
|
1. In the left sidebar, click **"OAuth2"** → **"URL Generator"**
|
||||||
|
2. Under **"Scopes"**, select:
|
||||||
|
- `bot`
|
||||||
|
3. Under **"Bot Permissions"**, select:
|
||||||
|
- `Connect` — Join voice channels
|
||||||
|
- `Speak` — Play audio in voice channels
|
||||||
|
- `View Channels` — See available channels
|
||||||
|
|
||||||
|
**Minimal permissions integer:** `3145728`
|
||||||
|
|
||||||
|
The generated URL at the bottom will look like:
|
||||||
|
```
|
||||||
|
https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&permissions=3145728&scope=bot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Invite the Bot to Your Server
|
||||||
|
|
||||||
|
1. Copy the generated OAuth2 URL from Step 4
|
||||||
|
2. Paste it in your browser
|
||||||
|
3. Select the server you want to add the bot to (you need "Manage Server" permission)
|
||||||
|
4. Click **"Authorize"**
|
||||||
|
5. Complete the CAPTCHA
|
||||||
|
|
||||||
|
The bot should now appear in your server's member list (it will be offline until you run it).
|
||||||
|
|
||||||
|
### Step 6: Get Server and Channel IDs
|
||||||
|
|
||||||
|
You'll need the Guild (server) ID and Voice Channel ID for your configuration.
|
||||||
|
|
||||||
|
**Enable Developer Mode first:**
|
||||||
|
1. Open Discord Settings (gear icon)
|
||||||
|
2. Go to **"App Settings"** → **"Advanced"**
|
||||||
|
3. Enable **"Developer Mode"**
|
||||||
|
|
||||||
|
**Get the Guild ID:**
|
||||||
|
1. Right-click on your server icon in the server list
|
||||||
|
2. Click **"Copy Server ID"**
|
||||||
|
|
||||||
|
This goes in your `.env` file as `DISCORD_GUILD_ID`.
|
||||||
|
|
||||||
|
**Get the Voice Channel ID:**
|
||||||
|
1. Right-click on the voice channel where the bot should play music
|
||||||
|
2. Click **"Copy Channel ID"**
|
||||||
|
|
||||||
|
This goes in your `.env` file as `DISCORD_CHANNEL_ID`.
|
||||||
|
|
||||||
|
### Summary: What You Need
|
||||||
|
|
||||||
|
After completing setup, you should have these three values:
|
||||||
|
|
||||||
|
| Value | Example | Environment Variable |
|
||||||
|
|-------|---------|---------------------|
|
||||||
|
| Bot Token | `MTIzNDU2Nzg5MDEyMzQ1Njc4OQ.Gh7K2j.xxxxx...` | `DISCORD_BOT_TOKEN` |
|
||||||
|
| Guild ID | `123456789012345678` | `DISCORD_GUILD_ID` |
|
||||||
|
| Channel ID | `987654321098765432` | `DISCORD_CHANNEL_ID` |
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Bot won't connect | Verify token is correct and hasn't been reset |
|
||||||
|
| Bot can't join voice | Check bot has `Connect` permission in that channel |
|
||||||
|
| Bot joins but no audio | Check bot has `Speak` permission; verify audio encoding setup |
|
||||||
|
| "Bot is not in this guild" | Confirm Guild ID is correct; re-invite bot if needed |
|
||||||
|
| Can't see channel | Bot needs `View Channels` permission |
|
||||||
|
|
||||||
|
### Discord API Rate Limits
|
||||||
|
|
||||||
|
Be aware of Discord's rate limits when developing:
|
||||||
|
|
||||||
|
| Action | Limit |
|
||||||
|
|--------|-------|
|
||||||
|
| Global requests | 50 requests/second |
|
||||||
|
| Gateway connects | 1 per 5 seconds |
|
||||||
|
| Voice state updates | ~5 per minute recommended |
|
||||||
|
|
||||||
|
The bot libraries (`discord.py`, `discord.js`) handle most rate limiting automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment Checklist
|
||||||
|
|
||||||
|
### Discord Setup (see detailed guide above)
|
||||||
|
- [ ] Create Discord application at https://discord.com/developers
|
||||||
|
- [ ] Create bot user and configure settings
|
||||||
|
- [ ] Copy and securely store bot token
|
||||||
|
- [ ] Generate OAuth2 invite URL with correct permissions
|
||||||
|
- [ ] Invite bot to your server
|
||||||
|
- [ ] Copy Guild ID and Voice Channel ID
|
||||||
|
|
||||||
|
### Server Setup
|
||||||
|
- [ ] Provision VPS (recommended: 1GB+ RAM, Ubuntu 22.04+)
|
||||||
|
- [ ] Install Docker and Docker Compose
|
||||||
|
- [ ] Configure firewall (allow ports 3000, 3001 or your chosen ports)
|
||||||
|
- [ ] (Optional) Set up domain name and DNS
|
||||||
|
|
||||||
|
### Application Deployment
|
||||||
|
- [ ] Clone repository to VPS
|
||||||
|
- [ ] Copy `.env.example` to `.env`
|
||||||
|
- [ ] Fill in Discord credentials (token, guild ID, channel ID)
|
||||||
|
- [ ] Configure upload limits and other settings
|
||||||
|
- [ ] Upload initial MP3 files to `./music` directory
|
||||||
|
- [ ] Run `docker-compose build`
|
||||||
|
- [ ] Run `docker-compose up -d`
|
||||||
|
- [ ] Verify bot comes online in Discord
|
||||||
|
- [ ] Test web interface loads correctly
|
||||||
|
- [ ] Test playback controls work
|
||||||
|
- [ ] Test file upload works
|
||||||
|
|
||||||
|
### Production Hardening (Optional)
|
||||||
|
- [ ] Set up reverse proxy (nginx/Caddy) with SSL
|
||||||
|
- [ ] Configure `ADMIN_PASSWORD` for web interface
|
||||||
|
- [ ] Set up log rotation
|
||||||
|
- [ ] Configure automatic container restarts
|
||||||
|
- [ ] Set up monitoring/alerting (optional)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Future Considerations
|
||||||
|
|
||||||
|
- **Horizontal scaling**: If needed, the web frontend can scale independently
|
||||||
|
- **Mobile app**: API is designed to support any client
|
||||||
|
- **Multiple bots**: Could extend to support multiple Discord servers
|
||||||
|
- **Transcoding**: Convert non-MP3 files on the fly
|
||||||
|
- **External storage**: Support S3 or other cloud storage for music files
|
||||||
Reference in New Issue
Block a user