A self-hosted Discord music bot that streams high-quality audio from YouTube and Spotify directly into your voice channel โ with zero file downloads and a full subscription-based web dashboard for managing servers, plans, and settings.
Built with Python 3.11, discord.py 2.x, yt-dlp, Flask, and SQLite.
Runs on any machine including Raspberry Pi 4 via Docker (ARM64 native).
- YouTube โ video URLs, search queries, Shorts, and full playlists
- Spotify โ single tracks, playlists, and albums
(Spotify audio is sourced from YouTube; Spotify API is used only to look up artist + title) - Highest audio quality โ
FFmpegOpusAudio.from_probeauto-detects Opus streams and copies them without re-encoding; non-Opus streams are transcoded via libopus at maximum Discord bitrate - Pure streaming, zero downloads โ yt-dlp fetches a temporary CDN URL at play time; nothing is written to disk
- Per-guild queue โ shuffle, remove by position, auto-advance, auto-disconnect after 60 s alone
- Friendly messages โ the bot always speaks in first-person conversational tone
- Discord OAuth2 login โ users sign in with their Discord account
- Subscription plans โ Free, Pro ($1.99/mo), Max ($3.99/mo) stored in SQLite
- Per-server settings โ custom prefix, volume, queue length cap, auto-disconnect, announce-songs toggles
- Server assignment โ Pro allows 2 premium servers, Max allows 10
- Admin panel โ admins bypass payment; can set any plan and promote other users
- Live status โ dashboard polls
/api/statusevery 30 s to show bot online state
Buff-Discord-MusicPlayer/
โ
โโโ main.py # Entry point โ starts bot + Flask in daemon thread
โโโ config.py # Env-var loader (fails fast if BOT_TOKEN missing)
โโโ db.py # SQLite helper โ WAL mode, per-call connections
โ
โโโ commands/
โ โโโ music.py # Single discord.py Cog โ all 10 bot commands
โ
โโโ player/
โ โโโ queue_manager.py # Per-guild Track list โ TrackSource enum, Track dataclass
โ โโโ ytplayer.py # Audio engine: yt-dlp โ FFmpeg โ Discord voice
โ
โโโ search/
โ โโโ youtube.py # YouTube URL detection + search + playlist extraction
โ โโโ spotify.py # Spotify metadata lookup via spotipy (lazy init)
โ
โโโ webapp/
โ โโโ __init__.py # create_app(bot) factory โ registers blueprints
โ โโโ auth.py # Discord OAuth2 login/logout, Flask-Login user model
โ โโโ views.py # Dashboard, server settings, plans, admin routes
โ โโโ api.py # JSON endpoints: /api/status, /api/guild/:id/*
โ
โโโ static/webapp/
โ โโโ style.css # Dark theme โ CSS variables, sidebar, cards, badges
โ โโโ app.js # Sidebar toggle, flash dismiss, live status poll
โ
โโโ templates/
โ โโโ webapp/
โ โโโ base.html # Base layout (Inter font, CSS/JS includes)
โ โโโ landing.html # Marketing page โ hero, stats, features, pricing
โ โโโ dashboard.html # Server grid with bot-present/plan indicators
โ โโโ server_settings.html # Per-server config form (prefix, volume, queue, toggles)
โ โโโ plans.html # Plan cards + server assignment UI
โ โโโ admin.html # Admin panel โ stat grid + user table
โ โโโ _sidebar.html # Reusable sidebar partial
โ
โโโ Dockerfile # python:3.11-slim + ffmpeg + libopus (multi-arch)
โโโ docker-compose.yml # One-command deployment
โโโ .env.example # Template for all environment variables
User: #play https://open.spotify.com/track/โฆ
โ
โผ
search/spotify.py โ Spotify Web API โ { title, artist }
โ
โผ
Track(url="ytsearch1:artist - title", source=SPOTIFY)
โ stored in queue โ no YouTube call yet
โผ
player/ytplayer.py โ yt-dlp resolves "ytsearch1:โฆ" at play time
โ picks format based on guild plan (see below)
โผ
discord.FFmpegOpusAudio.from_probe
โ codec=copy if stream is already Opus (zero re-encode)
โ libopus re-encode otherwise
โผ
Discord Voice Channel ๐
| Plan | yt-dlp format selector | Result |
|---|---|---|
| Free | bestaudio[abr<=128]/bestaudio/best |
128 kbps cap |
| Pro | bestaudio[acodec=opus]/bestaudio[ext=webm]/bestaudio[ext=m4a]/bestaudio/best |
Native Opus passthrough โ zero re-encode |
| Max | same as Pro | Native Opus passthrough โ zero re-encode |
FFmpeg is called with -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 so brief network drops during playback don't kill the stream.
The bot reads the command prefix from SQLite on every message, so each server can have its own prefix set via the dashboard without a restart.
| Credential | Where to get it |
|---|---|
BOT_TOKEN |
discord.com/developers/applications โ New Application โ Bot โ Reset Token |
DISCORD_CLIENT_ID |
Same application โ OAuth2 โ Client ID |
DISCORD_CLIENT_SECRET |
Same application โ OAuth2 โ Client Secret |
SPOTIFY_CLIENT_ID / SPOTIFY_CLIENT_SECRET |
developer.spotify.com/dashboard โ Create app |
ADMIN_USER_IDS |
Your Discord user ID (Settings โ Advanced โ Developer Mode โ right-click your name โ Copy ID) |
OAuth2 redirect URI โ in your Discord application's OAuth2 settings, add:
http://localhost:5000/auth/callback
(or your public domain if deploying remotely)
Spotify credentials are optional โ YouTube-only usage works without them.
cp .env.example .envEdit .env:
# โโ Bot โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
BOT_TOKEN=your_discord_bot_token
PREFIX=#
# โโ Spotify (optional) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SPOTIFY_CLIENT_ID=your_spotify_client_id
SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
# โโ Web Dashboard โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
FLASK_PORT=5000
SECRET_KEY=generate-with-python-secrets-token-hex-32
DISCORD_CLIENT_ID=your_discord_application_client_id
DISCORD_CLIENT_SECRET=your_discord_oauth2_client_secret
OAUTH_REDIRECT_URI=http://localhost:5000/auth/callback
ADMIN_USER_IDS=your_discord_user_idGenerate a SECRET_KEY:
python -c "import secrets; print(secrets.token_hex(32))"docker compose up --build- Bot starts, connects to Discord, and begins listening for commands
- Dashboard is available at
http://localhost:5000 - Restarts automatically on crash (
restart: unless-stopped)
To run in the background:
docker compose up -d --build
docker compose logs -f # stream logsInstall system dependencies first:
# Ubuntu / Debian / Raspberry Pi OS
sudo apt update && sudo apt install -y ffmpeg libopus0
# macOS
brew install ffmpeg opus
# Windows
winget install ffmpeg # then download libopus.dll separatelyInstall Python dependencies and run:
pip install -r requirements.txt
python main.pyThe bot and web dashboard both start from main.py. Dashboard runs on port 5000 by default.
The Docker image uses python:3.11-slim which is a multi-arch image โ the same docker compose up command works on both linux/amd64 and linux/arm64 with no changes.
# On your Raspberry Pi 4 (requires 64-bit Raspberry Pi OS)
sudo apt update && sudo apt install -y docker.io docker-compose-plugin
sudo usermod -aG docker $USER # log out and back in after this
git clone https://github.com/sayanpramanik2012/Buff-Discord-MusicPlayer.git
cd Buff-Discord-MusicPlayer
cp .env.example .env
nano .env # fill in your tokens
docker compose up -d --build
docker compose logs -fTypical resource usage on Pi 4:
| State | CPU | RAM |
|---|---|---|
| Idle (bot + dashboard) | < 2% | ~85 MB |
| Playing audio | 8โ15% | ~115 MB |
Default prefix is # โ configurable per-server from the dashboard.
| Command | Aliases | Description |
|---|---|---|
#play <query or URL> |
#p |
Play a song or queue a playlist. Accepts YouTube URL, Spotify URL, or any search text. |
#pause |
โ | Pause the current song. |
#resume |
#r |
Resume a paused song. |
#skip |
#s, #next |
Skip the current song; plays next in queue. |
#nowplaying |
#np, #current |
Embed with current song, duration, source, and thumbnail. |
#queue |
#q |
List the upcoming queue (up to 15 shown). |
#shuffle |
โ | Randomly shuffle the upcoming queue. |
#remove <#> |
โ | Remove a track by its position number in the queue. |
#disconnect |
#dc, #leave, #stop |
Stop playback, clear the queue, and leave the channel. |
#join |
#j |
Join your current voice channel (happens automatically on #play). |
# YouTube
https://www.youtube.com/watch?v=dQw4w9WgXcQ
https://youtu.be/dQw4w9WgXcQ
https://www.youtube.com/shorts/โฆ
https://www.youtube.com/playlist?list=โฆ
# Spotify
https://open.spotify.com/track/โฆ
https://open.spotify.com/playlist/โฆ
https://open.spotify.com/album/โฆ
spotify:track:โฆ (URI format also works)
Access at http://localhost:5000 (or your server's IP/domain).
| Route | Description |
|---|---|
/ |
Public landing page โ bot stats, feature list, pricing |
/dashboard |
Your servers โ bot-present indicator, current plan badge, manage/invite buttons |
/servers/<id> |
Per-server settings โ prefix, volume, queue length cap, behaviour toggles |
/plans |
Subscription plan cards + server assignment UI |
/admin |
Admin panel โ user table, set plans, grant/revoke admin (admin-only) |
/auth/login |
Redirects to Discord OAuth2 |
/auth/logout |
Clears session |
| Endpoint | Method | Description |
|---|---|---|
/api/status |
GET | { online, guilds, users } โ public |
/api/me |
GET | Current user info + subscription |
/api/guild/<id>/plan |
GET | Guild's current plan |
/api/guild/<id>/settings |
GET / POST | Guild settings โ requires MANAGE_GUILD permission |
| Plan | Price | Premium Servers | Queue Cap | Audio Quality |
|---|---|---|---|---|
| Free | $0 | 0 | 50 tracks | Standard (โค128 kbps) |
| Pro | $1.99/mo | 2 | 200 tracks | Highest (Opus passthrough) |
| Max | $3.99/mo | 10 | Unlimited | Highest (Opus passthrough) |
Admins bypass all plan restrictions and can set any plan without payment.
| Component | Library | Notes |
|---|---|---|
| Discord bot framework | discord.py 2.x |
Cog extension system, FFmpegOpusAudio.from_probe |
| Audio streaming | yt-dlp |
Streaming only (download=False); no disk writes |
| Audio encoding | FFmpeg + libopus |
Zero-copy Opus passthrough on Pro/Max plans |
| Spotify metadata | spotipy |
Lazy client init โ no crash if credentials are absent |
| Web framework | Flask 3.x |
Runs in daemon thread alongside the bot |
| Auth | Flask-Login + Discord OAuth2 |
Per-session login, secure cookie |
| Database | SQLite (WAL mode) |
db.py โ per-call connections for thread safety |
| Config | python-dotenv |
.env file for local dev and Docker |
| Voice encryption | PyNaCl |
Required by discord.py for voice channels |
| HTTP client | requests |
OAuth2 token exchange + Discord API calls |
"BOT_TOKEN is not set"
Copy .env.example to .env and fill in all required values.
No sound / bot immediately disconnects
Make sure ffmpeg is installed (ffmpeg -version) and libopus0 is present on the host, or that the Docker image built successfully with apt-get install ffmpeg libopus0.
Spotify links don't work
Check that SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET are set. The bot logs a warning on startup if they are missing.
Dashboard login doesn't redirect back
The OAUTH_REDIRECT_URI in .env must exactly match one of the redirect URIs registered in your Discord application's OAuth2 settings.
"Sign in to confirm you're not a bot"
YouTube increasingly challenges non-browser clients running on server IPs. The bot already tries alternative player clients (tv_embedded, ios) automatically. If the error persists:
- Export a
cookies.txt(Netscape format) from a browser where you're logged into YouTube โ e.g. with the Get cookies.txt LOCALLY Chrome extension. - Place it in the project root (or any path the bot can read).
- Set
YT_COOKIES_FILE=cookies.txtin.env(or the full path if it's elsewhere). - In Docker, also mount the file: uncomment the
volumeshint indocker-compose.yml.
"No results found"
yt-dlp can occasionally be rate-limited by YouTube. Wait a moment and retry, or use a direct YouTube URL.
Raspberry Pi 4: ffmpeg not found in Docker
Make sure you're running the 64-bit (aarch64) version of Raspberry Pi OS. The python:3.11-slim base image does not support 32-bit ARM (armv7).
Dashboard shows wrong server list
The server list comes from Discord's OAuth2 guilds endpoint at login time. Log out and back in to refresh it.
MIT โ see LICENSE for details.