Review feedback on PR #56:
1. setCustomAvatar(buf) now triggers applyIdleAvatar when the bot is idle
(currentSong=null OR avatarEnabled=false). Spec said this; original impl
only stored the buffer, so a fresh upload from Settings was invisible
until next stop event. Track currentSong in BotProfileManager for the
idle check.
2. onConnect drops the !avatarEnabled guard — on a fresh connect there's
no song playing yet, so the spec matrix wants the custom avatar shown
regardless of sync. Previously bots reconnected with a stale TS3
server-side avatar.
3. CustomAvatarRow: defer the initializing=false flip to nextTick so the
load-time data-url assignment's queued watcher sees initializing=true
and bails. Removes the redundant PUT-on-mount that echoed the just-
loaded bytes back to the server.
4. BotInstance avatar load wrapped in try/catch — a corrupt/locked file
no longer crashes startup; we log and continue with no custom avatar.
Tests rewritten: 10 cases covering the full behavior matrix
(idle vs playing × sync on/off × custom set/null × stop/connect).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Silent failure: logs showed "Client properties updated" / "Description
updated" / "Avatar updated" even though the bot's nickname never
changed and the avatar stayed as "loading image" on clients.
Root causes:
- TS6HttpQuery.clientUpdate ignored non-2xx responses, so 400 (bad
parameter) and 403 (insufficient permission) were reported as success.
- updateClientProperties built TS3-escaped strings (\\s for space) then
split them back into JSON props, so TS6 received literal backslashes
and rejected the nickname silently.
- handleFeatureError only matched textual "permission" errors; HTTP
4xx statuses weren't recognised and the feature retried every song.
- doAvatarUpload had no per-step logging, making it impossible to tell
whether a broken avatar came from init, the TCP 30033 transfer, or
the client_flag_avatar command.
Fixes:
- Add HttpQueryError (status/body/path); clientUpdate throws on non-2xx.
- Build a raw property map in updateClientProperties; escape only on
the TS3 wire path.
- Log HTTP status and updated prop names on success.
- handleFeatureError now treats HTTP 400/401/403 as unrecoverable.
- Debug-log each step of doAvatarUpload plus bytes/elapsedMs on success.
https://claude.ai/code/session_018NrpGWbQQTrahUVXyea5Jy
- Wrap clientedit (description) with 5s timeout to prevent blocking
channel description and now-playing updates if the command hangs
- Check generation counter in clearAvatar to avoid clearing a newer
song's avatar when stop→play happens in quick succession
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add generation counter to prevent stale avatar uploads from
overwriting newer song's profile when rapidly skipping tracks
- Fix nickname truncation to use UTF-8 byte length instead of JS
string length (TS3 counts bytes, Chinese chars are 3 bytes)
- Add timeout to clearAvatar file transfer (was missing)
- Extract withTimeout helper to deduplicate timeout logic
- Add BiliBili CDN thumbnail resize support (@200w_200h)
- Bump generation on reconnect to discard in-flight updates from
the old connection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Description via clientupdate is rejected (error 1538) on TS3 full-client
protocol. Switch to clientedit on the bot's own clid, which is how
TS3AudioBot handles it. Requires b_client_modify_description permission.
Also add sendCommandNoWait to TS3Client for fire-and-forget commands
(clientupdate, channeledit) that don't return timely responses.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add BotProfileManager that updates the bot's TeamSpeak presence when
songs change: album cover as avatar, song info in nickname, away status
toggled on stop/play. Each feature is independently configurable via
REST API and persisted to the database. Permission-safe — features that
fail due to insufficient server permissions are silently disabled until
reconnect. Description falls back to nickname display on TS3 (only
supported via TS6 HTTP Query).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>