Shen is a personal Discord music bot with a full website bolted on. You search
YouTube or
YouTube Music from the site, hit play, and the track streams straight in your browser — or you add it to the queue and the bot plays it in your voice channel.
It is one Node project that does two jobs: an Express API that serves the site and streams audio, and a
discord.js client that joins voice channels and plays tracks. The same queue powers both surfaces, so the website and the bot never get out of sync.
Two surfaces, one queue
The interesting part is the shared state. Search results from the web can be sent to the Discord queue, tracks played in Discord appear in the site's live "now playing" state, and a pending queue holds tracks until someone runs /join. Updates flow to the website over SSE (/api/events), so the player bar ticks live without polling.
Streaming that keeps up with YouTube
Audio extraction relies on yt-dlp (shipped as the bundled binary from
@distube/yt-dlp) with play-dlas a fallback — YouTube's player/signing changes often, so two extraction paths give a decent buffer. ffmpeg-static supplies ffmpeg so any audio format can be transcoded: Opus for the Discord stream and MP3 for the browser player.
YouTube URL
↓ yt-dlp (primary) / play-dl (fallback)
direct audio URL
↓ ffmpeg
Discord: Opus 48kHz stream Web: MP3 in <audio>Full feature list
- Website player — search, stream in-browser, autocomplete, playlists expand to their first 20 tracks.
- Discord control —
/join,/leave,/play,/add,/queue,/skip,/pause,/resume,/loop,/stop,/save. - Cross-surface queue — web "Pending queue" runs automatically when the bot joins, auto-advance through search results / pending / library.
- Live status — SSE keeps the player bar and queue snapshot ticking in real time.
- Library & history — JSON persistence (
data/library.json,data/history.json). - Discord OAuth + auto-join — signed-in visitors can have the bot join their own voice channel from the phone-friendly site.
- Lyrics — Genius-backed lyrics route on the website.
- Theme engine — the React site ships with several player themes.
Why it was hard
The genuinely nasty part was YouTubedatacenter-IP blocking. On a cloud host like Render, YouTube frequently refuses or alters responses. The fix that works is routing extraction through a
Cloudflare WARP proxy — running
wireproxy so the bot tunnel exits from a residential-looking IP. Combined with the tv player client (tried before android/web) this dodges the sign-in bot wall most of the time.
There were also the classic single-process pitfalls: overnight crashes taking the whole service down (now caught by uncaughtException/unhandledRejection handlers), a double-enqueue bug where the web pending queue andthe bot both enqueued a track, and trusting client-supplied guild/channel IDs (fixed by deriving the play target from the caller's own Discord voice state).
Try it
The site is live at shen.zagan.space with the backend on Render. Source: the shen repository.
