A lightweight Discord music bot that plays audio from YouTube (single tracks, playlists, or search queries) directly in a voice channel.
Playback
/join— connects the bot to your current voice channel/leave— disconnects the bot and clears the queue/play <query>— plays a track/playlist from a URL, or searches YouTube if a plain query is given/skip— skips the currently playing track/stop— stops playback and clears the queue
Queue
/queue [page]— shows the current track and what is coming next, 10 per page/playnext <query>— puts a track at the front of the queue, right after the current one/playnow <query>— plays a track immediately, dropping the one that was playing/move <position> <destination>— reorders a queued track (positions as shown by/queue)/nowplaying— shows the current track with a progress bar
Tracks are queued per-server, and playback automatically advances to the next track when one finishes.
- Dropped voice connections are repaired automatically. A watchdog supervises every active player; if the voice connection stays down for 15 seconds it is rebuilt from scratch and the current track resumes from the position it stopped at. A playback loop that dies for any other reason is caught and reported instead of leaving the bot silently unresponsive.
- Stream links are resolved at playback time, never when queueing. The queue only stores page
URLs, so links cannot expire while they wait. Every resolved stream is verified before ffmpeg
touches it, and rejected links (the usual
403 Forbidden) are retried against other YouTube player clients. - Failures always answer. Extraction is bounded by a timeout and every command reports what went wrong, so a track that cannot be fetched produces an error message rather than a command that never finishes.
- Python 3.14+
- uv for dependency management
- FFmpeg installed and available on your
PATH(required for audio playback) - A Discord bot application/token with the following:
- Privileged Gateway Intents: none required beyond defaults (Guilds, Voice States)
- Scopes:
bot,applications.commands - Bot Permissions: Connect, Speak, Send Messages
-
Clone the repository and install dependencies:
uv sync
-
Copy the example environment file and add your bot token:
cp .env.example .env
TOKEN="your-discord-bot-token" -
Run the bot:
uv run main.py
On startup, the bot syncs its slash commands with Discord and logs a confirmation message once ready.
Alternatively, you can run the bot in a container — FFmpeg and Opus are already included in the image, so no local dependencies are needed beyond Docker itself.
With Compose (recommended — build, run and restart policy in one command):
cp .env.example .env # then put your token in it
docker compose up -d --builddocker compose logs -f follows the logs, docker compose down stops the bot, and
docker compose up -d --build again picks up code changes.
Plain Docker works too:
docker build -t echoflow .
docker run -d --name echoflow --restart unless-stopped --env-file .env echoflowyt-dlphandles metadata extraction and streaming for both direct links and search queries (ytsearch1:<query>).- Playlists are limited to the first 100 entries (
MAX_PLAYLIST_ITEMSinytdl_source.py). - The code is split into
main.py(slash commands),player.py(per-guild queue, playback loop and connection watchdog) andytdl_source.py(yt-dlp extraction and stream resolution).