Skip to content

v1.5.0

Latest

Choose a tag to compare

@github-actions github-actions released this 26 Oct 21:13
· 22 commits to main since this release

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_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.