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
The "复制专属链接" button silently failed on public-IP HTTP deployments
because navigator.clipboard requires a secure context. Now the link is
always revealed in a modal with a read-only input (select-all on focus),
so users can copy manually even when clipboard APIs and execCommand both
fail. The dialog still tries to auto-copy when possible.
Also adds a publicUrl config option that overrides window.location.origin
for link generation (useful behind reverse proxies / with custom domains),
exposed via GET /api/config/public-url, and a trustProxy flag so Express
honors X-Forwarded-* when fronted by nginx/Caddy/Cloudflare.
https://claude.ai/code/session_019FSX3S3UUcKEYWanYmoqUv
Three corner cases fixed:
1. Removing the currently-playing song caused the next song in the array
to be silently marked as "played" and skipped. Root cause: playedIndices
was updated in next() by marking currentIndex, but after remove() shifts
currentIndex, it pointed to the wrong song. Fix: mark songs as played
at play-time (in play/playAt/next/prev) instead of at next-request-time.
2. Using prev() in Random mode could cause a song to play twice — the
song navigated to via prev() was not recorded in playedIndices, so
next() could randomly select it again. Fix: prev() now marks the
returned song as played.
3. Switching to Random mode mid-playback could cause the current song to
repeat because setMode() cleared playedIndices without preserving the
currently-playing song. Fix: setMode() now re-adds currentIndex after
clearing.
https://claude.ai/code/session_01W3ZncxL5VfdZeB4qqYWDmY
In Random (shuffle) mode, the queue's next() method would return the same
song indefinitely when only one song was in the playlist, and never terminate
even with multiple songs. This happened because played songs were not tracked.
Added a playedIndices Set to track which songs have already been played in
Random mode. Once all songs have been played once, next() returns null to
stop playback — matching the expected behavior where Random plays each song
once in random order, while RandomLoop is the mode for infinite shuffling.
https://claude.ai/code/session_01W3ZncxL5VfdZeB4qqYWDmY
The ffmpeg-static npm package bundles a pre-compiled binary that passes
`ffmpeg -version` but crashes with SIGSEGV during actual audio processing
inside Docker containers (incompatible glibc/missing shared libraries).
Changes:
- Install system FFmpeg via apt-get in Docker production stage, which is
always compatible with the container runtime
- Reverse FFmpeg resolution priority in player.ts: prefer system FFmpeg,
fall back to ffmpeg-static (for non-Docker environments like Windows)
- Optimize Dockerfile multi-stage build: compile native modules (opus,
better-sqlite3) in builder stage and copy to production, eliminating
the need for build tools (python3, make, g++) in the production image
- Fix qq-music-api dependency from file:../qq-music-api (path outside
Docker build context, breaks npm ci) to npm registry ^2.2.10
https://claude.ai/code/session_01JAV8sBokoifKh4Hc8XbJws
The repo URL was pointing to the old `tsmusicbot.git` instead of the
actual repository name `teamspeak-music-bot.git`. Fixed both occurrences
(方式二 and 方式三 sections).
https://claude.ai/code/session_01VTscSE9PxD6qwF56WBMunQ
Adds a "play" script as an alias for "start" (both run `node dist/index.js`).
This resolves confusion where users might try `npm run play` based on the
bot's music-playing nature.
Fixes#11https://claude.ai/code/session_01GC6qsKsmiroNkruLENDhPq
Root cause: on Docker restart, loadSavedBots() unconditionally connected
ALL saved bots regardless of autoStart flag, causing a rapid
connect/disconnect loop. Additionally, bot identities were regenerated
on every restart, causing TS server conflicts with stale sessions.
Changes:
- loadSavedBots() now only auto-connects bots with autoStart=true
- startBot/stopBot persist autoStart state so restart behavior matches
user intent
- Bot TS3 identity is persisted to database and reused across restarts
- Database schema migrated to include identity column
- TS3Client.connect() cleans up existing connection before reconnecting
- Stagger bot connections by 1s to avoid overwhelming the TS server
https://claude.ai/code/session_01L2kEV2M1QFWMCyPtLU5LgC
Two issues fixed:
1. Cross-platform ffmpeg-static resolution: skip Windows .exe paths on Linux
and always fall back to "ffmpeg" instead of a known-bad path
2. Prevent trackEnd cascade when ffmpeg spawn fails — track consecutive
failures and stop after 3, suppressing trackEnd on spawn errors
https://claude.ai/code/session_013vHRF8BbDGjZLqheFS85Q6
1. Move TS6 handler patch to after client.connect() — the library's
connect() internally replaces handler via #S(), discarding any
patch applied beforehand. Patching after connect() is safe because
clientinit is sent in async message callbacks after Init1 round-trips.
2. Preserve detectedProtocol across reconnect — disconnect() resets it
to "unknown", causing the TS6 patch to be skipped on reconnect.
3. Prevent double "disconnected" event in BotInstance — disconnect()
emitted it directly AND the async TS3Client disconnect triggered
another through the event chain.
4. Guard playNext() against running after disconnect — check connected
flag to avoid ghost queue processing.
5. Fix UDP error timer leak — clear previous timer before setting a new
one to prevent accumulation.
6. Fix isPortFree() FD leak — close the test server on error path.
https://claude.ai/code/session_01QzvMLUT3UkhsffShcY1qzD
The @honeybbq/teamspeak-client library sends clientinit directly via
handler.sendPacket() during the handshake, bypassing the commandMiddleware
chain entirely. This meant the ts6VersionMiddleware never intercepted the
handshake clientinit, so TS6 servers always received version 3.5.3 and
silently rejected it (never responding with initserver), causing idle timeout.
Fix: monkey-patch handler.sendPacket() to intercept clientinit packets and
upgrade the version to 3.6.2 before they're sent over the wire.
https://claude.ai/code/session_01QzvMLUT3UkhsffShcY1qzD
The script previously checked for a dist/ directory and failed with
"Please run this script from the TSMusicBot source directory after
building" — requiring users to manually build before installing.
Now the script:
- Resolves project root from script location (works from any cwd)
- Installs build tools (build-essential/gcc) for native modules
- Runs npm install + npm run build automatically
- Copies built artifacts to /opt/tsmusicbot
- Creates data directory for runtime files
- Adds journalctl command to the help output
https://claude.ai/code/session_016WhH58avUD9xy2dgADJgTh
- Add dev branch notice and changelog section at the top
- Add TS3/TS6 badges
- Document new protocol modules (protocol-detect, http-query, ts6-compat)
- List all bug fixes in dev branch
- Update architecture diagram with new ts-protocol files
- Add TS6 Server FAQ entry
- Credit NeteaseTSBot for TS6 protocol reference
https://claude.ai/code/session_016WhH58avUD9xy2dgADJgTh
clientinit compatibility:
- Analyzed @honeybbq/teamspeak-client's clientinit: it already sends a
clean 14-field payload without problematic fields (no badges,
integrations, security_hash, etc.)
- The key difference vs NeteaseTSBot is client_version: our library
sends 3.5.3, NeteaseTSBot sends 3.6.2. TS6 servers may reject
older versions.
- Add ts6-compat.ts with CommandMiddleware that patches clientinit
to use version 3.6.2 + matching ECDSA signature when connecting
to detected TS6 servers
- Middleware is automatically applied when detectedProtocol === "ts6"
Pre-existing bug fix:
- Fix playNext() in instance.ts where successful retry still fell
through to player.stop(), killing the just-started playback
https://claude.ai/code/session_016WhH58avUD9xy2dgADJgTh
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
- Fix double-resolve race in probeTS3Query (data event vs connect timer)
by guarding with a resolved flag
- Fix double-resolve in probeTS6HttpQuery similarly
- Support custom query ports in detectServerProtocol via DetectOptions
- Clean up httpQuery and detectedProtocol on disconnect()
- Add res.on("error") handler in HTTP Query client to avoid unhandled errors
- Improve logging: warn with actionable message when protocol is unknown
https://claude.ai/code/session_016WhH58avUD9xy2dgADJgTh
The @honeybbq/teamspeak-client library already handles TS6 license block
type 8 (Ts5Server) in its handshake, so voice connections work with both
TS3 and TS6 servers. This commit adds the surrounding infrastructure:
- protocol-detect.ts: Auto-detect server type by probing TS3 ServerQuery
(port 10011) and TS6 HTTP Query (port 10080) in parallel
- http-query.ts: TS6 HTTP Query client replacing the raw-TCP ServerQuery
that TS6 servers no longer support (ports 10080/10443)
- client.ts: Protocol-aware connection with auto-detection, TS6 HTTP
Query setup, and forced protocol override option
- manager.ts: Pass through serverProtocol and ts6ApiKey config options
- connection.ts: Mark legacy TS3 ServerQuery as deprecated for TS6
https://claude.ai/code/session_016WhH58avUD9xy2dgADJgTh
- Global uncaughtException/unhandledRejection handlers in index.ts
- FFmpeg stdout/stderr stream error handlers in player.ts
- HTTP server and WebSocket server error handlers in server.ts
- Safe WebSocket broadcast with try-catch in websocket.ts
- Catch async errors from textMessage handler in instance.ts
- Reset voiceFramesSent counter on reconnect in client.ts
https://claude.ai/code/session_01EjpEsC2GCsvwbu4n3XC8EE
The pino logger's ThreadStream was throwing unhandled EINTR errors
when FFmpeg child processes were spawned, crashing the entire bot.
Add an error handler to suppress EINTR and prevent process crashes.
https://claude.ai/code/session_01EjpEsC2GCsvwbu4n3XC8EE
The ffmpeg-static bundled binary was crashing immediately (exitCode: null,
no data produced). Now we run `ffmpeg -version` to verify the binary works
before selecting it, with automatic fallback to system ffmpeg.
Also logs ffmpeg binary path at info level and captures signal in close event.
https://claude.ai/code/session_01EjpEsC2GCsvwbu4n3XC8EE
Adds info-level logs at each stage of the audio pipeline to help
diagnose why playback produces no audible output:
- FFmpeg first PCM data received
- FFmpeg process exit code
- FFmpeg stderr (errors/HTTP/stream info at info level)
- First opus frame encoded and emitted
- First voice packet sent to TeamSpeak
- Error catching in frame encoding loop
https://claude.ai/code/session_01EjpEsC2GCsvwbu4n3XC8EE
Avoid repeated filesystem checks on every play() call by resolving
the ffmpeg binary path once at module load. Also verify execute
permission after chmod to handle noexec mounts.
https://claude.ai/code/session_01CqfKgV8GuCmWNpfx86H62X
The bundled ffmpeg-static binary may lose execute permission after npm
install on some platforms, causing EACCES errors during playback. Now
checks and fixes the permission automatically before spawning ffmpeg.
https://claude.ai/code/session_01CqfKgV8GuCmWNpfx86H62X