Make the default playback source a user-configurable setting so servers
that mostly play e.g. Bilibili no longer need to type `-b` on every
`!play`. Previously defaultPlatform() always picked the first enabled
provider by a fixed priority order, with no way to override it.
- config: add optional `defaultPlatform: GateableProvider | null`.
loadConfig sanitizes it — kept only when it names a known provider that
is also currently enabled, else null. defaultPlatform() returns the
preference when enabled, otherwise falls back to the fixed priority order.
- POST /api/bot/settings accepts `defaultPlatform` (validated against the
possibly-updated enabledProviders; null/"" clears it), and reconciles a
stored default that a new enabledProviders list no longer allows. GET and
POST responses expose the field.
- WebUI: new "默认音源" section with a source picker; saving refreshes the
store's default source so it takes effect immediately without a restart.
- Tests: extend config defaultPlatform priority tests and add coverage for
the settings endpoint and /providers routing.
- README: document `defaultPlatform` in the enabledProviders section.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An open guest WebSocket stamped isGuest/botScope once at upgrade and
never rechecked them, so it kept streaming bot state after an admin
disabled guest mode or narrowed guestMode.bots. setupWebSocket now
returns { cleanup, refreshGuestPolicy }; POST /api/bot/settings invokes
refreshGuestPolicy after saving a guestMode change, force-closing guest
sockets when disabled and live re-scoping them otherwise.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Conflict resolution + cross-PR integration:
- player.ts: kept #88's POST /:botId/fm route AND gated it with
requirePermission('player.control') so the new control endpoint honors #80's
permission model (it was added without gating).
- bot.ts: kept #81's relocated /settings routes (the relocation fixes the GET
/settings shadow bug) and dropped #80's now-duplicate bottom copy; gated
POST /settings with requirePermission('bot.manage').
- Navbar.vue: composed #82's dedicated-link scope with #80's permission filter —
displayedBots is now the INTERSECTION (scope ∩ controllable allow-list).
- database.ts: kept BOTH new table sets (#87 favorite_playlists + #80
user_permissions/user_bot_access).
- bot.test.ts: updated to createRequireAuth(sessions, permissions) for #80's new
two-arg signature.
#80 review fixes (credential exposure / IDOR, adversarially verified):
- GET /:id/config now requires bot.manage + bot access AND redacts ts6ApiKey +
identity from the response (was readable by any authenticated member).
- GET /:id and GET /:id/avatar now require bot access (were ungated read oracles).