Add three default-off capabilities that stop the play queue from being lost,
all gated behind admin/independent toggles so existing behavior is unchanged
until an operator opts in:
- Named save/load of queues (Feature 1): new saved_queues table (per-user +
reserved __shared__ owner, capped at 50 queues / 1000 songs, JSON song blob
that degrades to empty on corruption); /api/saved-queues router (list/save/
load/delete with ownership 404s, inert 403 when disabled); chat commands
!save / !load [-a] / !queues; BotInstance.loadSavedQueue (replace/append).
- Auto-restore live queue across restart (Feature 2): PlayQueue.snapshot/restore,
queue_state table (one row per bot), a debounced snapshot writer driven off
stateChange, and restore+resume on connect. Cancels the pending snapshot on
disconnect so a stale write can't wipe the row a restart must restore.
- playKeepsQueue (Feature 3): BotInstance.playSingleSong funnels chat !play and
the web /play-song route through one place; when enabled a single-song play
inserts-after-current and jumps instead of clearing the queue.
Config gains savedQueuesEnabled + playKeepsQueue (both default false, strict-
coerced on load like spotify.enabled); the settings API round-trips them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runtime playback settings were kept only in memory (AudioPlayer.volume,
PlayQueue.mode, each provider's quality field), so restarting the bot reset
them to defaults and users had to re-tune volume and quality every time (#125).
Persist and restore them via the repo's existing storage:
- Volume and play mode are per-bot, stored on new bot_instances columns
(volume, play_mode) with a schema migration; restored when the instance is
(re)built, written by cmdVol / cmdMode which every entry point (chat command,
WebUI, REST) funnels through. Volume and mode are written independently so a
transient !fm/!artist mode switch never overwrites the user's saved !mode.
- Per-provider audio quality is global (shared providers), stored in a new
config.json `audioQuality` block; applied to the providers at startup and
re-snapshotted on POST /api/music/quality.
Queue, current song, progress and FM/artist sessions stay ephemeral.
Adds tests for config sanitize/round-trip, DB player-settings + migration,
cmdVol/cmdMode persistence + construction-time restore, and quality persistence
through the REST endpoint. Documents the behavior in the README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds an admin/member role to WebUI auth. /api/users and /api/audit
are now gated by a requireAdmin middleware; all other authenticated
endpoints accept both roles. Schema migration defaults all existing
users to admin to preserve access. POST /api/users defaults new users
to member; first-run setup always creates an admin. Adds PATCH
/api/users/:id/role with last-admin demotion and deletion guards.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Library.vue: use Song type from store so SongCard's strict platform
union accepts the data (was platform: string, broke after merge tightened
SongCard prop type).
database.test.ts: switch toEqual -> toMatchObject so getBotInstances()
returning extra profile_* schema columns no longer fails the assertion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Critical:
- Persist serverProtocol/ts6ApiKey in database schema so TS6 config
survives restarts (added columns + manager save/load)
Medium:
- Clear udpErrorTimer on disconnect to prevent memory leak and stale
log messages from firing after teardown
- Guard against double connect() by disconnecting old client first
- Add settled guard in TS6HttpQuery.request() to prevent double
reject when both res error and req error fire
- Add res.on("error") handler to TS6HttpQuery response stream
Low:
- Cap probeTS3Query banner buffer at 256 bytes to prevent memory abuse
from non-TS3 services sending large data on port 10011
- Remove unnecessary EventEmitter inheritance from TS6HttpQuery
- Update database test fixtures with new serverProtocol/ts6ApiKey fields
https://claude.ai/code/session_016WhH58avUD9xy2dgADJgTh