Revert the jellyfin-only default introduced by PR #123 so upgrading
users keep their online sources; Jellyfin becomes opt-in:
- default enabledProviders is now the online set (netease/qq/bilibili/
youtube/kugou); defaultPlatform() uses a fixed priority order
(netease -> qq -> kugou -> jellyfin -> bilibili -> youtube) instead
of jellyfin-first, so chat/REST/WebUI default to netease again
- Settings: Jellyfin card is always visible with a new enable toggle
(its enabled bit is enabledProviders membership); guards against
clobbering other providers before the list loads
- Setup wizard: saving the Jellyfin step auto-enables the source when
a server URL was entered
- Search/player store fallbacks flip from jellyfin to netease; !help
no longer hardcodes Jellyfin lines
- tests: update default-platform assertions, add coverage for the new
default set, legacy configs without enabledProviders, priority
order, and explicit jellyfin-only configs
- README: reframe Jellyfin as optional (badges, command table, quality
tiers, dedicated section, changelog), document the enabledProviders
default and the v1.10.0 jellyfin-only window fix, credit @ItsEricRao
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New guest flag playCollection (default OFF), gates play-playlist/play-album
- Keeps playNow's non-destructive semantics intact (Play All clears the queue)
- Admin-toggleable in Settings → 游客模式; default-off, backward-compatible
- Frontend: gate the 播放全部 button on the flag + surface 403 as a toast
instead of failing silently (the silent-failure half of the issue)
loadConfig now sanitizes guestMode the same way the write path does: bots is
coerced to "all" | string[] (numbers/objects/missing fall back to the default
"all"), and permissions are rebuilt from defaults with each known flag
strict-coerced to a boolean so a hand-edited/legacy/corrupt config.json can no
longer crash the gate or leak garbage index keys. The authorize guest gate now
uses === true instead of a truthy check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Auto-pause within the first seconds of playback (and re-pause after a manual
play) whenever a listener is actually in the channel.
Root cause (confirmed live against a TS3 server): the full-client
`clientlist -uid -away -voice -groups` command TIMES OUT when other clients are
present in the bot's channel. `getClientsInChannel()` catches the error and
returns `[]`, so the occupancy callers computed `userCount = [].length - 1 = -1`,
which `decideOccupancyAction` reads as `-1 <= 0` → "channel empty" → pause. With
the bot alone, clientlist succeeds (returns just the bot), so the bug only
surfaced when someone was listening — exactly the report.
Fix: a connected bot is always a member of its own channel, so a valid query
returns >= 1 (itself). A length of 0 therefore means the query FAILED, not that
the channel is empty. New pure helper `occupancyFromClientList()` maps a
0-length result to `null` ("occupancy unknown"); `refreshOccupancy()` and the
30s idle poller skip the auto-pause / idle-disconnect decision when the count is
unknown instead of mis-reading it as empty. This also removes a latent
false-positive idle-disconnect on the same failed query.
Also default `autoPauseOnEmpty` to OFF (occupancy detection is unreliable on
some servers); users can opt in from Settings.
Verified live with two clients in one channel: clientlist returns 0 → helper
returns null → no false pause (control: bot alone returns 1 → 0 others, normal).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CONFIG_PATH resolved to ROOT_DIR/config.json (/app/config.json in Docker), but only
DATA_DIR (/app/data) is the mounted volume — every other artifact (DB, cookies, logs,
avatars) already lives under DATA_DIR. So on first run the default config was written
into the ephemeral image layer (never appearing in the volume), and a manually-placed
data/config.json was ignored because the bot read/wrote the root path.
- Move CONFIG_PATH to DATA_DIR/config.json so it lands in the volume and manual edits
take effect.
- Add migrateLegacyConfig(): one-time move of an existing root-level config.json into
the data dir, so existing local installs keep their settings (no silent reset).
- Tests for first-run persistence + the three migration cases.
- README directory tree updated to data/config.json.
Fixes#86
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>