Repository navigation
Releases: selfhostr/djsnake
Releases · selfhostr/djsnake
Release list
v1.5.0
Upgrade notes
- New optional settings in
.env(see.env.example):VOTESKIP_PERCENT,MAX_QUEUE_SIZE,MAX_TRACKS_PER_USER,MAX_TRACK_MINUTES,PLAY_COOLDOWN_SECONDS,STAY_247,YTDLP_COOKIES,BACKUP_INTERVAL_HOURS,BACKUP_KEEP,LASTFM_API_KEY/LASTFM_API_SECRET,LIVE_MAX_HOURSandSHARD_COUNT. Every default keeps the 1.4 behaviour, except the queue, which is now capped at 200 tracks (MAX_QUEUE_SIZE). - New files in
DATA_DIR:voteskip.json,limits.json,stay.json,lastfm.json(0600, holds Last.fm session keys) andbackups/. Backups contain those secrets: store them like a password. - YouTube cookies: mount a
cookies.txtread-only outside./data(seedocker-compose.yml) and setYTDLP_COOKIES; use a throwaway Google account. - Backups:
docker compose exec dj-snake python -m djsnake.backup create; restoring needs the bot stopped (see the backup docs). - Saved playlists can no longer be named
queue(it means the live queue in/playlist export). - New slash commands (
/remove,/move,/previous,/clear,/nowplaying,/history,/voteskip,/limits,/stay,/filter,/lastfm) take up to an hour to appear when synced globally.
Added
- Queue commands:
!remove <position>and!move <from> <to>(positions as!listshows them, with a queue autocomplete on the slash versions),!previousand!clear.!move,!previousand!clearneed the DJ role when one is set;!removealso works on a track you queued yourself. !nowplaying(!np): an embed with the title, requester, source, artwork, a progress bar, the loop, autoplay and volume state and the clip page link (the view-only one when control is limited).!history [count]: the last tracks played in the session (default 10, max 25), newest first; it survives!reset.- Vote to skip:
/voteskip(!vs) for members in the voice channel who cannot use/next. More thanVOTESKIP_PERCENT(50) of the non-deafened humans in the channel must vote; DJs and the requester skip at once, votes are recounted as people join, leave or deafen and reset with each track./voteskip threshold <1-100>(DJs) sets it per server. The panel and the clip page show the count. - Queue limits against abuse: queue size, tracks per member, track length and a
/playcooldown, with.envdefaults and/limits(Manage Server) per server. They apply to/play, the page's add, playlist loads and autoplay (queue size only); a too-long single link is refused from its metadata, before anything is downloaded. DJs skip the per-member limit and the cooldown. - 24/7 mode:
!stay [on|off]keeps the bot in its voice channel (no idle disconnect,!stopdoes not leave) and rejoins it after a restart. Dropped by Discord, it rejoins once, then turns itself off;!resetand channel moves do not count as drops. - Saved playlist import and export:
/playlist import <name> <link>from a YouTube, SoundCloud, Bandcamp, Spotify, Deezer or Apple Music list or album, or from an attached JSON, M3U or TXT file;/playlist export <name> [json|m3u|txt](orqueue). One import runs at a time per server. - Audio filters per session:
!filter [off|bassboost|nightcore|vaporwave|8d|karaoke|soft]and!filter eq <low> <mid> <high>, also on the clip page. A change restarts the track where it is; nightcore and vaporwave change the real speed and the page follows it. - Live streams: YouTube lives, Twitch channels and internet radios (Icecast, Shoutcast,
.pls,.m3u) play straight from the stream, with a LIVE badge and a radio's current song on the clip page. Seek and crossfade are off for them,LIVE_MAX_HOURS(4, pauses not counted) stops a forgotten one, and they are left out of the listening stats. - Optional YouTube cookies (
YTDLP_COOKIES) for age-restricted and members-only videos, with specific messages for age-restricted, members-only, private and sign-in-required videos. - Optional Last.fm scrobbling (
LASTFM_API_KEY,LASTFM_API_SECRET):/lastfm link|unlink|status; linked listeners who are not deafened get "now playing" and scrobbles. - Backup and restore of
DATA_DIR:python -m djsnake.backup create|restore|verify|list(checksummed.tar.gz, SQLite copied consistently while the bot runs), and automatic rotated backups withBACKUP_INTERVAL_HOURSandBACKUP_KEEP. - Spanish and German (
!language es|de): bot replies,!help, the status message and the clip page.scripts/i18n-missing.pylists missing translations. - Clip page: Media Session support (lock screen, headset and media keys; control only with the control link).
- Clip page: installable as an app (PWA), following the server's theme name, colour and logo, with a "Session offline" page; the API, streams and media are never cached.
- Clip page: keyboard shortcuts and a help dialog (
?). - Sharding: the bot is an
AutoShardedBotwith an optionalSHARD_COUNT;/healthzand/metricsreport each shard. - End-to-end tests (
pytest -m e2e, a new CI job): the real FFmpeg playback chain and the clip page in headless Chromium, plus a manual checklist for a real Discord server.
Changed
/healthzreportsstatusanddiscordasdegraded(still 200) when only some shards are down; a shard briefly closed by a gateway resume still counts as up. Prometheus latency gauges print+Infinstead ofNaNwithout a connection.- Every module that saves JSON in
DATA_DIRuses one atomic write helper (fileio.py).
Fixed
- Clip page: at 320 px wide (and Cobalt at 360 px) the loop button of the transport row was clipped. The row now tightens its buttons and spacing on narrow phones.
- A flaky control-link rotation test.
Security
- Internet radios cannot reach the bot's own network: the bot resolves a radio's host once, requires a public address and connects to that very address (no DNS rebinding), re-checks every redirect, reads the stream itself and pipes it to FFmpeg, which may open nothing else (
-protocol_whitelist pipe). HLS radios are refused. YouTube and Twitch lives are read by FFmpeg with HTTP(S) and HLS protocols only, and their expiring URLs are refreshed just before playing. - The private copy of
YTDLP_COOKIESis deleted on failure and at exit;cookies.txtfiles are ignored by git and Docker. backup restoreholds the data lock for the whole restore, refuses archive members whose size differs from the manifest and checks free space first.- Playlist import refuses deeply nested JSON instead of crashing.
v1.4.0
Added
GET /healthz: 200 with status, Discord latency, guild and player counts, uptime and version when the bot is connected, 503 with the reason otherwise.- Docker
HEALTHCHECKon/healthz(60 s start period), sodocker compose psshows whether the bot is healthy. - Optional
GET /metricsin the Prometheus text format (players, queued tracks, downloads, viewers, Discord latency, tracks played), off unlessMETRICS=1. - Search by text:
!play daft punk one more time(any argument that is not a link) and the clip page's add box search YouTube and queue the best match, scored like Spotify tracks. When nothing scores high enough the bot says so. - Loop and shuffle.
!loop [off|track|queue](no argument cycles) replays the current track or sends finished tracks back to the end of the queue;!shuffleshuffles the upcoming queue once. The clip page has matching shuffle and loop buttons, the control API getsloopandshuffleactions,/api/{sid}/stateexposesloop, and the status panel shows the loop mode. - Per-server volume:
!volumeshows it,!volume <0-150>sets it (percent, 100 = the normalised level). It applies to the playing track without a restart, glides over a few frames to avoid clicks, is soft-limited above 100%, works with crossfade and is saved inDATA_DIR/volume.json. - Clip page: a volume button with a slider popover next to Sync, a
volumecontrol action and avolumefield in/api/{sid}/state. The status panel shows the volume only when it is not 100%. - Autoplay ("radio"):
!autoplay [on|off], a switch on the clip page and theAUTOPLAYdefault. When the queue runs out, a track related to the last one (YouTube'sRD<id>mix) is prepared during playback and plays next; recent tracks, live streams, tracks over 10 minutes and non-music titles are skipped, and the bot lingers idle when nothing is found. Loop modes take precedence. Autoplay tracks are marked in the status panel and on the page. - Saved playlists per server:
!playlist save|add|load|show|delete <name>and!playlist list, kept inplaylists.dbunderDATA_DIR(links and titles only, up to 50 playlists of 200 tracks). The clip page can save the queue and load a playlist (GET /api/{sid}/playlists,save_playlistandload_playlistcontrol actions). - SoundCloud and Bandcamp links:
!playand the clip page's add action accept tracks and sets/albums (soundcloud.com,*.bandcamp.com). Playlists respectMAX_PLAYLIST_SIZE; the page shows the artwork for tracks without video (now_playing.audio_onlyin the state), and the "Downloading" list names the platform. - Deezer and Apple Music links (tracks, albums, playlists), read without credentials (Deezer's public API, Apple's iTunes lookup API and playlist page) and matched on YouTube like Spotify tracks (new
djsnake/catalog.py). - Synced lyrics on the clip page: a Lyrics button shows the words over the clip, the sung line highlighted and scrolled in time, or plain lyrics when no timing exists. Lyrics come from LRCLIB (free, no key), are looked up in the background when a track is queued and cached in
DATA_DIR/lyrics/.LYRICS=0turns them off. - Slash commands: every command also works as a slash command (
/play,/next,/list,/seek,/pause,/resume,/stop,/reset,/crossfade,/theme,/loop,/shuffle,/volume,/autoplay,/playlist,/djrole,/weblink,/language), with suggestions for the preset and playlist names. The!versions are unchanged. Re-invite the bot with theapplications.commandsscope if the commands do not show up.SLASH_SYNC_GUILDpublishes them to one server at once, for development, and the number of synced commands is logged at startup. - Live updates: the clip page gets each change pushed by the bot over a Server-Sent Events stream (
GET /api/{sid}/events) instead of polling every 2 seconds, and falls back to polling when the stream cannot get through. Behind nginx, turn offproxy_buffering(see "Exposing the page"). - View-only clip page link: every session also has a second unguessable link that shows the same page with the controls hidden and a "View only" badge; the server refuses controls sent with it (403).
- DJ role:
!djrole <role>|off(Manage Server, saved inDATA_DIR/permissions.json). When set,!next,!pause,!resume,!seek,!crossfade,!loop,!shuffle,!volume,!autoplay,!stopand!resetneed the role or Manage Server (whoever queued the playing track may still skip it), and the status message shows only the view-only link. Changing the role gives a live session a new control link and retires the server's!theme editlinks. !weblinkDMs the control link and the view-only link (DJs only when control is limited).WEB_CONTROL=everyone|dj(defaulteveryone): withdj, the control link is never posted publicly.- French translation, and a framework for more languages:
!language en|fr(Manage Server) switches a server's command replies, errors,!help, status message and web API error messages; the clip page follows it (or the browser's language) and has a language picker in its settings menu.BOT_LANGUAGEsets the default (en). Messages live indjsnake/locales/{en,fr}.json.
Changed
- The "link not accepted" message lists every supported source.
!playnow rebuilds links like the clip page's add action (same host allowlist, canonical URL); links from other hosts are refused with the "link not accepted" message.- The status message now shows the view-only link next to the control link. With the defaults, everyone can still control the music as before.
Security
- Session ids are no longer written to the logs.
v1.3.0
Added
- Theme preset
cobalt: a flat ultramarine page with blush-pink and butter-yellow accents, wide Unbounded headings, borderless rounded panels and springy controls (!theme cobalt,WEB_THEME, base for custom).
v1.2.1
Version 1.2.0 was never published as a Docker image or GitHub Release (its
release build failed); use 1.2.1 instead.
Fixed
- The arm64 Docker image failed to build on Python 3.15:
daveyhas no
prebuilt aarch64 wheel there, so it is now compiled from source in a separate
builder stage, keeping the compiler out of the final image.
v1.1.0
Added
- Spotify links:
!playand the clip page's add action accept Spotify
playlists, albums and tracks (open.spotify.comlinks andspotify:URIs).
The track list is read from the public embed page (no configuration, 100
tracks max) or from the Web API whenSPOTIFY_CLIENT_IDand
SPOTIFY_CLIENT_SECRETare set. Each track is searched on YouTube and the
best match (duration, title and artist, official video first) is queued;
tracks without a match are skipped and counted. Nothing is downloaded from
Spotify. - GitHub link in the clip page header.
Changed
- Long track titles are clamped to two lines, and queue titles are more
readable. - Refreshed demo and showcase (README, project page, social preview), which
now highlight Spotify links.
v1.0.0
Added
- Discord music bot:
!playdownloads audio and video from YouTube with
yt-dlp and plays it in a voice channel, with an independent queue per server. - Playlists: whole playlists load in the background (up to
MAX_PLAYLIST_SIZE),
and radio/mix links ask whether to play one track or the full list. - Playback commands:
!list,!next,!pause,!resume,!seek,!stop
and!reset. - Clip live page: every voice session gets a link to a synced web page that
shows the muted video of the current track, the queue and a progress bar. - Web controls: skip, previous, pause, resume, seek, jump to a queued track,
reorder, remove and add YouTube links from the page. - Automix: off-topic segments are cut with SponsorBlock, loudness is
normalised, silence at the start and end is trimmed, and tracks fade in and
out. - Optional crossfade between tracks (
!crossfade on|offor from the page). - Live status message per session, edited in place instead of spamming the
channel. - Selectable clip page themes, with a per-server default and a theme editor.
- Docker Compose deployment with a PO token provider sidecar to get past
YouTube's "Sign in to confirm you're not a bot" check. - yt-dlp self-update on every container start, plus a weekly refresh script
for long-running hosts. - Retries with backoff for transient download failures.
- Sessions expire after
SESSION_IDLE_HOURSwithout activity and clean up
their files. - Automated test suite (pytest) run in CI, with the code split into the
djsnake/package. - Prebuilt multi-architecture (amd64, arm64) Docker images on GitHub Container
Registry.
Fixed
- HTTP 403 errors on YouTube downloads.
- Queued tracks being wiped on routine gateway reconnects.
- Python 3.13+ compatibility.