A modern Discord music bot for shared listening in voice channels. Built with
discord.js v14 and discord-player v7, native ESM JavaScript.
- Node.js 20+ (active LTS recommended)
- System FFmpeg on PATH or configured via
FFMPEG_PATH - A Discord application with a bot token
Voice connections use Discord's DAVE encryption protocol via
@snazzah/davey (an optional dependency of discord-voip). It is installed
automatically; do not remove it or the bot cannot join voice channels.
The bot uses only slash commands and buttons. There is no prefix command
parser and no MessageContent intent.
-
Install dependencies:
npm install
-
Create your environment file:
cp .env.example .env
-
Fill in
.env:DISCORD_TOKEN— bot token (required).DISCORD_CLIENT_ID— application ID (required for deployment and invite).DISCORD_DEV_GUILD_ID— test server ID for guild-scoped deployment.
-
Verify FFmpeg:
ffmpeg -version
SoulStream runs an FFmpeg preflight at startup and refuses to start if it cannot execute. Only the system FFmpeg binary is used; no static FFmpeg npm packages are added.
-
Start the bot:
npm start
Enable only:
- Server Members — not required.
- Presence — not required.
- Message Content — not required.
The bot connects with Guilds and GuildVoiceStates gateway intents only.
Invite the bot with the scopes bot and applications.commands, and the
least-privilege permissions:
- View Channels
- Send Messages
- Embed Links
- Read Message History
- Connect
- Speak
Deployment is a standalone script. It never runs from the ready event.
Deploy to your development guild (updates fast, good for testing):
npm run deploy:devDeploy globally (only after manual acceptance testing):
npm run deploy:globalThe deployment uses a full PUT so stale commands are removed. It requires
DISCORD_CLIENT_ID, and DISCORD_DEV_GUILD_ID for guild scope.
| Command | Description |
|---|---|
/about |
Bot, source, and version information. |
/help [command] |
Command index or focused help. |
/invite |
OAuth2 invite link. |
/ping |
Heartbeat and round-trip latency. |
/play <song> |
Search, play a URL, or queue a playlist. |
/playfile <file> |
Play an attached audio file. |
/pause /resume |
Pause/resume playback. |
/skip |
Skip the current track. |
/stop |
Stop and leave the voice channel. |
/back |
Return to the previous track. |
/clear |
Remove all upcoming tracks. |
/queue [page] |
Queue view with pager buttons. |
/remove <position> |
Remove an upcoming track by position. |
/shuffle |
Shuffle upcoming tracks. |
/loop <off|track|queue|autoplay> |
Set repeat mode. |
/filter <name> |
Toggle FFmpeg audio filters. |
/volume <1-100> |
Set playback volume. |
/seek <time> |
Seek, e.g. 1m30s, 2:15, 90s. |
/progress |
Progress bar for the current track. |
/nowplaying |
Show/refresh the now-playing panel. |
/lyrics [title] |
Lyrics for the current track or a search. |
/save |
DM the current track link. |
The now-playing panel exposes Save Track, Pause/Resume, Skip, and Stop buttons. Panel and pager buttons carry session-bound custom IDs; old buttons from a finished queue are rejected as stale. The panel thumbnail is rendered as a spinning vinyl disc (circular crop of the track art, pre-rendered per track with FFmpeg).
All settings live in .env. Notable options:
MAX_VOLUME— volume ceiling (1–100).MAX_QUEUE_SIZE,PLAYLIST_MAX_TRACKS,ATTACHMENT_MAX_BYTES— limits.LEAVE_ON_END/LEAVE_ON_STOP/LEAVE_ON_EMPTY+ cooldowns — queue lifecycle.SELF_DEAF— bot deafens itself (defaulttrue).DJ_ENABLED— when enabled, restricts the DJ command list to users holding the configured role (byDJ_ROLE_IDor exactDJ_ROLE_NAMEmatch). The same restriction applies to the pause/skip/stop buttons.LYRICS_ENABLED— toggle lyrics lookups (LRCLIB).DISC_ROTATION_SPEED— now-playing disc rotations per second (default0.5, clamped 0.05–4).DEBUG_PLAYER,LOG_LEVEL— diagnostics.
YouTube playback uses the community discord-player-youtubedlp extractor,
which combines youtubei.js metadata/search with yt-dlp resolution and
streaming. Optional settings:
YTDLP_PATH— explicit yt-dlp executable path.YOUTUBE_COOKIES_FILE— path to a Netscape cookies file (never commit it).YOUTUBE_COOKIES_FROM_BROWSER—chrome,brave,firefox, oredge.
Browser cookie auto-detection is disabled by default (autoCookiesFromBrowser: false); yt-dlp never reads a browser profile unless you configure one
explicitly. /play autocomplete uses a direct youtubei.js client for
fast suggestions, with discord-player-youtubedlp search as fallback.
The YouTube extractor streams raw PCM audio, so the player runs with
skipFFmpeg: true (the library default) to pass PCM through directly.
When FFmpeg filters are active, the raw stream is muxed into a WAV container
on the fly so Discord Player's FFmpeg stage can process it. Playback, volume,
pause/resume, seek, and filters all work.
Do not reuse a personal browser session by default. Cookies expire and have source-platform policy implications.
- YouTube and other platform extraction is unofficial. Operators are responsible for source-platform terms, licenses, and content rights. Use the bot only with content you are allowed to play.
- Spotify and Apple Music entries resolve metadata only and are bridged to a streaming extractor. They are not audio-download services.
- No stream is recorded or intercepted, and nothing is downloaded permanently.
Queues live in memory only. A process restart ends active queues and old control buttons become stale. Persistent playlists are future work.
npm test # node:test unit + mock interaction tests
npm run lint # ESLint (flat config)
npm audit # dependency auditProject baseline: Node >=20.0.0. See .node-version (20).
