This repository is a maintained fork focused on keeping YouTube playback working after upstream stalled.
Important differences from the original project:
- This fork supports YouTube OAuth playback fallback.
- This fork supports direct Spotify playback through a local
go-librespotbackend. - Spotify support is per-bot and can be enabled or disabled independently at startup.
- If two bots use the same Spotify account, they will compete for the same Spotify Connect session.
- For parallel Spotify playback on two bots, use two separate Spotify accounts.
- If you want reliable playback for blocked or age-restricted videos, you should use a dedicated burner Google account.
- Do not use your main Google account.
- If the burner account cannot play a video in the normal YouTube browser UI, the bot will usually not be able to play it either.
If you only care about unrestricted videos, the bot may still work without OAuth.
If you want restricted videos, age-gated videos, or better resilience against This video requires login, follow the full setup below.
- A Discord bot token as usual.
- A separate Google burner account just for this bot.
- That Google account must be able to play the target video directly on YouTube in a browser.
- Create or pick a burner Google account.
- Set the birthday on that account to 18+.
- Complete any Google or YouTube age verification required for that account.
- Open YouTube in a normal browser while signed into that burner account.
- Confirm that the exact age-restricted video plays in the browser UI.
If step 5 fails, the bot is not ready yet. Fix the account first.
This fork adds the following config keys:
ytoauth = true
ytoauthrefreshtoken = "YT_OAUTH_REFRESH_TOKEN_HERE"
When using OAuth, do not use a poToken at the same time.
Leave these at their placeholders:
ytpotoken = "PO_TOKEN_HERE"
ytvisitordata = "VISITOR_DATA_HERE"
- Open your
config.txt. - Set
ytoauth = true. - Leave
ytoauthrefreshtoken = "YT_OAUTH_REFRESH_TOKEN_HERE"for the first run. - Make sure
ytpotokenandytvisitordataare still placeholders. - Start the bot.
- Watch the bot logs.
- The bot will print a message telling you to go to
https://www.google.com/deviceand enter a code. - Open that URL in a browser.
- Sign in with the burner Google account.
- Enter the device code shown in the bot logs.
- Approve the connection.
- Wait for the bot logs to print the refresh token.
The log line will look similar to this:
OAUTH INTEGRATION: Token retrieved successfully. Store your refresh token as this can be reused. (your_refresh_token_here)
- Stop the bot.
- Open
config.txt. - Replace the placeholder with the real refresh token:
ytoauth = true
ytoauthrefreshtoken = "paste_the_refresh_token_here"
- Save the file.
- Start the bot again.
- Check the logs for:
YouTube access token refreshed successfully
At that point the token is persisted and future restarts should not require the device flow again unless Google invalidates the token.
- Join a voice channel.
- Use the bot to play a normal YouTube video.
- Use the bot to play an age-restricted YouTube video.
- If the browser account can play the video but the bot cannot, check the bot logs for the current playability reason.
This video requires loginThis usually means OAuth is not enabled, the refresh token is missing, the refresh token is invalid, or the account is not sufficiently verified.Sign in to confirm your ageThe burner account still does not pass YouTube age verification for that video.- The video works in Discord for some links but not others The failing video may be private, region-blocked, removed, or age-gated beyond what the account currently allows.
- The device flow appears again after restart
The refresh token was not copied into
config.txtcorrectly or Google invalidated it.
This fork can be tested locally with multiple isolated bot containers.
Recommended local layout:
- One container per bot token.
- One data directory per bot under
docker/instances/<bot-name>. - One generated compose file for all active local test bots.
Quick start:
- Create a bot scaffold:
powershell -ExecutionPolicy Bypass -File scripts/add-bot.ps1 devshmusic-test1 - Copy or edit
docker/instances/devshmusic-test1/bot.env. - Repeat for more bots.
- Regenerate compose:
powershell -ExecutionPolicy Bypass -File scripts/render-compose.ps1 - Start all bots:
docker compose -f docker-compose.generated.yml up -d --build
Container-specific behavior:
BOT_TOKENandBOT_OWNERcan be provided frombot.env.- Relative state paths are resolved from
JMUSICBOT_HOME, which defaults to/datain the container. - Each bot keeps its own
config.txt,serversettings.json, andPlaylists/under its own instance directory.
This fork can play Spotify tracks, albums, and playlists directly without YouTube conversion.
- Spotify URLs are resolved as Spotify content, not mapped to YouTube.
- Playback is done through a local
go-librespotbackend plus anffmpegbridge to Discord voice. - The feature is enabled per bot instance, not globally for the whole host.
- It does not use the official Spotify Web API for full audio playback.
- It does not allow two bots on the same Spotify account to play different things at the same time.
- It does not bypass normal Spotify account limits or Spotify Connect session rules.
- Spotify playback is optional and disabled by default.
- It requires the Docker image or runtime environment to start the bundled
go-librespotsidecar. - It requires a Spotify Premium account for playback.
- The bot instance must have persistent writable storage under its data directory.
For one bot instance with Spotify enabled, the runtime does the following:
- starts the Java bot process
- starts a local
go-librespotdaemon - creates a local PCM pipe under the bot data directory
- authenticates the selected Spotify account
- uses that backend for Spotify URLs while keeping normal lavaplayer behavior for non-Spotify URLs
Spotify is controlled per bot instance through environment variables.
Example:
SPOTIFY_ENABLED=true
SPOTIFY_DEVICE_NAME=devshmusic-test1-spotify
SPOTIFY_CALLBACK_PORT=0If SPOTIFY_ENABLED=false or unset:
- the Spotify sidecar is not started
- direct Spotify playback is disabled for that bot
- the bot continues to work as a normal YouTube/lavaplayer bot
This means you can run:
- one bot with Spotify enabled
- another bot with Spotify disabled
- both at the same time on the same host
The smallest useful setup in bot.env is:
BOT_TOKEN=replace_with_discord_bot_token
BOT_OWNER=replace_with_discord_owner_id
SPOTIFY_ENABLED=true
SPOTIFY_DEVICE_NAME=devshmusic-spotify-1
SPOTIFY_CALLBACK_PORT=0Notes:
SPOTIFY_DEVICE_NAMEshould be unique per bot.SPOTIFY_CALLBACK_PORT=0lets the backend choose a free local callback port automatically.- If Spotify is disabled for a bot, you can omit all Spotify variables entirely.
When SPOTIFY_ENABLED=true and no prior Spotify state exists:
- pick the bot instance that should get Spotify support
- set
SPOTIFY_ENABLED=truein that bot'sbot.env - make sure the instance has its own persistent data directory
- start or restart that bot container
- watch the logs for the Spotify authorization URL
- open the URL in a browser
- log into the Spotify Premium account intended for that bot
- finish the callback flow
- let the bot finish initialization
- test a Spotify track, album, or playlist URL on Discord
After that, restarts should reuse the stored Spotify session state.
For a bot instance running with JMUSICBOT_HOME=/data, the Spotify runtime stores:
- Spotify auth and daemon state under
/data/spotify - the PCM pipe under
/data/spotify.pipe
This means:
- deleting the instance data directory will force a fresh Spotify login
- moving the instance to another host requires moving the bot's data volume as well
This is the cleanest production split.
Example:
bot-a:SPOTIFY_ENABLED=truebot-b:SPOTIFY_ENABLED=false
Result:
bot-acan handle Spotify URLs and normal YouTube URLsbot-bstays a normal non-Spotify bot- only one container pays the operational cost of the Spotify sidecar
This is valid only if you understand the account model.
- If both bots use the same Spotify account, they will fight over one Spotify Connect session.
- If you want both bots to play Spotify independently, use two separate Spotify accounts.
- Give each bot a unique
SPOTIFY_DEVICE_NAME.
- Two bots can both have Spotify enabled.
- If both bots use the same Spotify account, they will fight over one Spotify Connect session.
- If you need two bots to play different Spotify content at the same time, use two separate Spotify accounts.
Before enabling Spotify on a production bot:
- verify the bot can still play a normal YouTube URL
- verify the bot can play a Spotify track URL
- verify the bot can play a Spotify playlist URL
- verify
skip,pause,resume,seek,volume, andqueue - confirm the instance data directory is persistent
- confirm no second bot is using the same Spotify account unless that is intentional
Check:
SPOTIFY_ENABLED=trueis actually present in that bot's runtime env- the bot instance was restarted after editing env
- the Spotify sidecar started successfully in logs
Check:
- the bot is using persistent storage
/data/spotifyis not being discarded between restarts- the container is not mounting a fresh empty instance directory
Cause:
- both bots are logged into the same Spotify account
Fix:
- use one Spotify-enabled bot and one non-Spotify bot
- or use two separate Spotify accounts
This should not happen by design. The fork keeps Spotify and lavaplayer on separate playback paths. If this happens:
- test a plain YouTube URL on the same bot
- check bot logs for the failure path
- verify the bot is not stuck in an active Spotify session that should first be stopped
GitHub Actions now builds the Docker image from this repository and publishes it to GHCR.
Published image:
ghcr.io/devsh-graphics-programming/discordmusicbot:latestfrommasterghcr.io/devsh-graphics-programming/discordmusicbot:sha-<commit>for immutable rollbacksghcr.io/devsh-graphics-programming/discordmusicbot:<version>for matching Git tags
Recommended deployment flow:
- Build and verify locally first.
- Push to
masterwhen ready. - On the target host run
docker compose pull. - Then run
docker compose up -d.
That lets consumers update by pulling a new image instead of rebuilding the jar on the server.
A cross-platform Discord music bot with a clean interface, and that is easy to set up and run yourself!
- Easy to run (just make sure Java is installed, and run!)
- Fast loading of songs
- No external keys needed (besides a Discord Bot token)
- Smooth playback
- Server-specific setup for the "DJ" role that can moderate the music
- Clean and beautiful menus
- Supports many sites, including Youtube, Soundcloud, and more
- Supports many online radio/streams
- Supports local files
- Playlist support (both web/youtube, and local)
JMusicBot supports all sources and formats supported by lavaplayer:
- YouTube
- SoundCloud
- Bandcamp
- Vimeo
- Twitch streams
- Local files
- HTTP URLs
- MP3
- FLAC
- WAV
- Matroska/WebM (AAC, Opus or Vorbis codecs)
- MP4/M4A (AAC codec)
- OGG streams (Opus, Vorbis and FLAC codecs)
- AAC streams
- Stream playlists (M3U and PLS)
Please see the Setup Page to run this bot yourself!
Please read the Issues List before suggesting a feature. If you have a question, need troubleshooting help, or want to brainstorm a new feature, please start a Discussion. If you'd like to suggest a feature or report a reproducible bug, please open an Issue on this repository. If you like this bot, be sure to add a star to the libraries that make this possible: JDA and lavaplayer!
This bot (and the source code here) might not be easy to edit for inexperienced programmers. The main purpose of having the source public is to show the capabilities of the libraries, to allow others to understand how the bot works, and to allow those knowledgeable about java, JDA, and Discord bot development to contribute. There are many requirements and dependencies required to edit and compile it, and there will not be support provided for people looking to make changes on their own. Instead, consider making a feature request (see the above section). If you choose to make edits, please do so in accordance with the Apache 2.0 License.



