A minimal Discord music bot with real-time web control, manual track selection, tag-based filtering, and smooth crossfading. Perfect for gaming sessions, streams, or any Discord community.
✅ Node.js 20+ - Download from nodejs.org
✅ Python 3.6+ - Required for building native dependencies (sqlite3, sodium)
✅ Discord Bot - Create at discord.com/developers/applications
✅ FFmpeg - Automatically installed via npm package (no manual setup needed)
✅ yt-dlp - For YouTube audio downloads
✅ Build Tools - build-essential on Linux, or Visual Studio Build Tools on Windows
Ubuntu/Debian:
sudo apt update
sudo apt install python3 python3-pip build-essentialmacOS:
# Python usually comes pre-installed
# Install build tools via Xcode Command Line Tools:
xcode-select --installWindows:
- Install Python from python.org
- Install Visual Studio Build Tools (see node-gyp requirements)
git clone <your-repo-url>
cd ddj
# Install bot dependencies (includes FFmpeg automatically)
cd bot
npm install
# Install web dependencies
cd ../web
npm install
# Return to project root
cd ..Copy the example and fill in your details:
cd bot
cp env.example .envEdit bot/.env:
DISCORD_TOKEN=your_bot_token_here
GUILD_ID=your_server_id_here
VOICE_CHANNEL_ID=your_voice_channel_id_here
MUSIC_DIR=/path/to/your/music/folder
PORT=5140
CROSSFADE_SECS=4
VOLUME=0.35
AUTO_DJ=0
AUTO_LOOP=0To get your Discord IDs:
- Enable Developer Mode in Discord (User Settings → Advanced → Developer Mode)
- Right-click your server → "Copy ID" (for GUILD_ID)
- Right-click your voice channel → "Copy ID" (for VOICE_CHANNEL_ID)
Environment Variables:
MUSIC_DIR: Path to your music folder (defaults to../../musicrelative to bot directory if not set)PORT: Web API port (default: 5140)CROSSFADE_SECS: Crossfade duration in seconds (default: 4)VOLUME: Default volume 0.0-1.0 (default: 0.35)AUTO_DJ: When1, auto-play the next queued track when a track ends instead of waiting for a manual click (default:0, manual)AUTO_LOOP: WithAUTO_DJon, re-queue each played track so a queued playlist loops forever (default:0)
First, add some music to your MUSIC_DIR folder, then start the bot:
cd bot
npm startYou should see:
Seeding music database...
✅ Logged in as YourBot#1234
REST API running on: http://localhost:5140/api
Music library: /path/to/your/music/folder
In a new terminal:
cd web
npm run devOpen http://localhost:3000 in your browser to access the control panel.
- Import Music: Bot automatically scans your MUSIC_DIR on startup
- Add Tags: Browse tracks in the web UI and add tags like:
tavern,combat,peaceful,tensemysterious,epic,ambient,dramatic
The web UI is a single control surface — no tab switching mid-session:
- Now Playing: Current track, elapsed time, and progress
- Playback Controls: Play/pause, skip, stop, force-stop, and seek
- Volume & Duck: Adjust master volume (0–100%) or temporarily duck under voice
- Backend Switcher: Point the UI at a different bot instance (handy for multiple servers)
- Filter Rail: Filter by tags, full-text search, sort, and a one-click "untagged" view
- Track List: Every matching track with metadata, tags, and inline Play / Queue actions
- Recents Strip: Quick replay of recently played tracks
- YouTube Import: Paste a YouTube URL to download audio straight into the library
- Tag Management: Add/remove tags live, rename tracks, or refresh metadata
- Add & Reorder: Queue tracks and drag-and-drop to rearrange
- Play Next: Jump a track to the front of the queue
- Skip / Clear: Advance through or empty the queue
DM: "You enter a suspicious tavern..."
→ Music Library tab → Filter: "tavern" + "tense"
→ See 5 matching tracks with metadata visible
→ Click "Play" on "Shady_Negotiations.mp3"
→ Smooth 4-second crossfade begins
→ Add "investigation" tag while playing for future sessions
→ Queue next tracks for continuous music flow
- Manual Control: You choose every track, no random auto-play
- Auto-DJ Mode (optional): Flip
AUTO_DJ=1to auto-advance through the queue when a track ends, andAUTO_LOOP=1to loop a queued playlist for hands-off, unattended playback - Tag-Based Filtering: Build your musical vocabulary over time
- Smooth Crossfading: Professional transitions via FFmpeg (configurable duration)
- Live Tagging: Add tags during sessions to enhance future discovery
- Local-First: Your music, your tags, your control
- Real-Time: Instant response to scene changes
- Queue Management: Add, reorder, and manage playback queues
- YouTube Integration: Download audio directly from YouTube URLs
- Metadata Support: Automatic extraction of title, artist, album from audio files
- Custom Naming: Override default track names for better organization
- Volume Control: Real-time volume adjustment (0-100%)
- Force Stop: Emergency stop for all audio playback
- Voice Connection Management: Manual connect/disconnect controls
- SQLite Database: Persistent storage for tracks, tags, and metadata
- REST API: Full programmatic access to all bot functions
- Modern Web UI: Responsive interface built with Next.js + Tailwind CSS
- Cross-Platform: Works on Windows, macOS, and Linux
- FFmpeg Integration: High-quality audio processing with automatic installation
Bot won't start:
- Check your Discord token is valid and has proper bot permissions
- Ensure Python 3.6+ is installed (required for native dependencies)
- Verify GUILD_ID and VOICE_CHANNEL_ID are correct (enable Developer Mode in Discord)
- Check that build tools are installed for native module compilation
Voice connection fails:
- Ensure bot has proper voice channel permissions in your Discord server
- Check that the voice channel exists and bot can access it
- Try manually disconnecting and reconnecting the bot
Audio playback issues:
- Verify FFmpeg is working (automatically installed via npm)
- Check audio file formats are supported (.mp3, .flac, .wav, .m4a, .ogg)
- Ensure MUSIC_DIR path is correct and accessible
Web UI can't connect:
- Ensure bot is running on port 5140 (check bot startup logs)
- Verify no firewall is blocking localhost connections
- Check browser console for network errors
UI not loading:
- Ensure Node.js dependencies are installed (
npm installin web directory) - Check that port 3000 is available
- Try clearing browser cache and cookies
Music not importing:
- Check MUSIC_DIR contains valid audio files
- Ensure write permissions for database file creation
- Try deleting
catalog.dband restarting bot to re-import
Tags not saving:
- Check database file permissions
- Ensure bot has write access to its directory
Download fails:
- Ensure
yt-dlpis installed and in system PATH - Check YouTube URL is valid and accessible
- Verify write permissions to MUSIC_DIR
ddj/
├── .gitignore # Git ignore rules
├── LICENSE # MIT license
├── README.md # This file
├── bot/ # Discord bot backend
│ ├── src/
│ │ ├── index.js # Main bot entry point & voice connection
│ │ ├── audioEngine.js # FFmpeg crossfade engine & audio player
│ │ ├── catalog.js # SQLite database & track management
│ │ └── routes.js # REST API endpoints
│ ├── catalog.db # SQLite database (auto-created)
│ ├── env.example # Environment configuration template
│ ├── package.json # Bot dependencies
│ └── package-lock.json # Dependency lock file
└── web/ # Next.js web interface
├── src/
│ ├── app/
│ │ ├── layout.tsx # Root layout (transport bar + queue drawer chrome)
│ │ ├── page.tsx # Main page (renders LibraryView)
│ │ └── error.tsx # Error boundary
│ ├── components/
│ │ ├── LibraryView.tsx # Main library surface
│ │ ├── TransportBar.tsx # Persistent playback controls
│ │ ├── FilterRail.tsx # Tag filter / search / sort sidebar
│ │ ├── TrackRow.tsx # Individual track row
│ │ ├── QueueDrawer.tsx # Slide-out queue
│ │ ├── RecentsStrip.tsx # Recently played strip
│ │ ├── YouTubeImport.tsx # YouTube download input
│ │ ├── BackendSwitcher.tsx # Multi-bot backend selector
│ │ ├── TagAutocompleteInput.tsx # Tag entry with autocomplete
│ │ ├── TagManageOverlay.tsx # Bulk tag management
│ │ ├── ThemeProvider.tsx # Dark/light theme
│ │ └── ui/ # Shadcn/ui primitives
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── input.tsx
│ │ ├── progress.tsx
│ │ ├── scroll-area.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ └── slider.tsx
│ ├── hooks/
│ │ ├── useNow.ts # Poll now-playing state
│ │ ├── useFavorites.ts # Favorites (localStorage)
│ │ ├── useRecents.ts # Recently played (localStorage)
│ │ └── useKeyboard.ts # Keyboard shortcuts
│ ├── lib/
│ │ ├── client.ts # Bot REST API client
│ │ ├── backends.ts # Backend switcher state
│ │ ├── uiState.tsx # UI context (queue drawer, etc.)
│ │ ├── types.ts # Shared types
│ │ └── utils.ts # Utility functions
│ └── styles/
│ └── globals.css # Global styles
├── components.json # Shadcn/ui configuration
├── next.config.js # Next.js configuration
├── package.json # Web dependencies
├── package-lock.json # Dependency lock file
├── postcss.config.js # PostCSS configuration
├── tailwind.config.js # Tailwind CSS configuration
└── tsconfig.json # TypeScript configuration
The bot API has no authentication and binds to 0.0.0.0 with permissive CORS.
Anyone who can reach port 5140 can play, stop, delete tracks, and trigger
YouTube downloads. This is fine for the intended use — running the bot and web UI
on the same machine (or a trusted LAN behind a firewall). Do not expose port
5140 directly to the internet. If you need remote access, put it behind a
reverse proxy with authentication (e.g. nginx basic-auth or an SSO proxy), or a
VPN/Tailscale, rather than opening the port.
For production use, you may want to:
-
Point the web UI at the bot API. By default the UI auto-targets the page's own hostname on port
5140, and you can switch backends at runtime via the in-app Backend Switcher. To hard-code a base at build time, set:# web/.env.local NEXT_PUBLIC_API_BASE=https://your-domain.com -
Set up reverse proxy (nginx/apache) to serve both web UI and API from same domain
-
Configure firewall to allow access to your chosen ports
-
Use process manager like PM2 for production bot deployment:
npm install -g pm2 cd bot pm2 start src/index.js --name "ddj-bot"
The bot provides a REST API on port 5140 (configurable):
GET /api/health- Health checkGET /api/now- Current track, progress, volume, pause/duck stateGET /api/tracks- List tracks with optional tag filtering (?tags=a,b)POST /api/seed- Re-scanMUSIC_DIRfor new filesPOST /api/play- Play specific track by ID (optionalcrossfadeSecs)POST /api/next- Skip to next trackPOST /api/stop/POST /api/force-stop- Stop playbackPOST /api/pause/POST /api/resume- Pause / resume playbackPOST /api/duck/POST /api/unduck- Duck volume under voice (level0.0-1.0)POST /api/seek- Seek current track tosecondsPOST /api/volume- Set volume (0.0-1.0)POST /api/disconnect- Disconnect bot from voice channelGET /api/tags/available- List all tags with per-tag countsPOST /api/tag/:id- Add tag to trackDELETE /api/tag/:id- Remove tag from trackPOST /api/track/:id/rename- Set a custom display namePOST /api/track/:id/update-metadata- Re-read embedded metadataDELETE /api/track/:id- Delete track (DB only; file kept)POST /api/youtube/download- Download from YouTube URLGET/POST/DELETE /api/queue/*- Queue management endpoints
Start simple and build over time:
Session 1: tavern, combat, peaceful, tense
Session 5: Add mysterious, epic, dramatic, chase, victory
Session 20: 50+ precise tags for perfect atmospheric control
The system becomes more powerful with every session as your tagging vocabulary expands!
Released under the MIT License.
Ready to DJ your next Discord session? 🎵🤖