You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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_HOURS and SHARD_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) and backups/. Backups contain those secrets: store them like a password.
YouTube cookies: mount a cookies.txt read-only outside ./data (see docker-compose.yml) and set YTDLP_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 !list shows them, with a queue autocomplete on the slash versions), !previous and !clear. !move, !previous and !clear need the DJ role when one is set; !remove also 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 than VOTESKIP_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 /play cooldown, with .env defaults 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, !stop does not leave) and rejoins it after a restart. Dropped by Discord, it rejoins once, then turns itself off; !reset and 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] (or queue). 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 with BACKUP_INTERVAL_HOURS and BACKUP_KEEP.
Spanish and German (!language es|de): bot replies, !help, the status message and the clip page. scripts/i18n-missing.py lists 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 AutoShardedBot with an optional SHARD_COUNT; /healthz and /metrics report 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
/healthz reports status and discord as degraded (still 200) when only some shards are down; a shard briefly closed by a gateway resume still counts as up. Prometheus latency gauges print +Inf instead of NaN without a connection.
Every module that saves JSON in DATA_DIR uses 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_COOKIES is deleted on failure and at exit; cookies.txt files are ignored by git and Docker.
backup restore holds 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.