The previous implementation pointed at endpoints and response shapes that
don't match the current @sansenjian/qq-music-api. As a result the bot was
happily saving a user-supplied cookie to disk and sending it on every
request, but every downstream login check returned "not logged in".
- getAuthStatus:
/getUserAvatar is not a registered route (the real one is
/user/getUserAvatar), and even then that handler only builds a static
avatar URL from a uin and never talks to QQ, so it can't validate a
cookie. Parse uin from the cookie and round-trip it through
/user/getUserPlaylists, which actually hits QQ Music with the cookie,
then derive the avatar URL from the uin.
- getQrCode:
checkQQLoginQr requires BOTH qrsig and ptqrtoken, but only qrsig was
being returned as the poll key. Encode both into the opaque key.
- checkQrCodeStatus:
/checkQQLoginQr is POST, not GET, and the response is
{ isOk, refresh, session: { cookie, ... } } rather than { code, cookie }.
Switch to POST, pass both params, and read session.cookie on success.
https://claude.ai/code/session_01LH83VXwxPY8f9RQ5HgfPby
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
- 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
Store queue and timing state per-bot instead of globally, move bot
selector from Home page into Navbar dropdown, show bot name badge
in the player bar, and route WebSocket stateChange queues per-bot.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement BiliBiliProvider for video-as-audio playback using direct
BiliBili API calls (search, video info, DASH audio URL extraction).
Add QR code login support and cookie persistence. Update FFmpeg to
send Referer header for BiliBili CDN URLs. Extend platform union type
to "netease" | "qq" | "bilibili" across all interfaces. Add -b flag
for chat commands and B站 badge in web UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bugs fixed:
- play-at: stop current playback before jumping to queue index
- playNext retry: use break instead of return to ensure stateChange emits
- sendVoiceData: skip if disconnecting to avoid errors during teardown
UI: player-left and lyrics button now toggle lyrics page (open/close)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add isAdvancing guard to playNext() to prevent concurrent calls from
skipping songs (trackEnd + user !next race condition)
- Replace removeAllListeners in WebSocket setup with tracked named
listeners, attach only once per bot instead of every 5 seconds
- Add .catch() to unhandled playNext() in cmdVote
- Add sessionId counter to AudioPlayer to discard stale setTimeout
callbacks after stop()+play() transitions
- Defer nulling TS3 client until disconnect() promise resolves
- Add null checks for providers in play-by-id and add-by-id endpoints
- Add PCM buffer backpressure (pause FFmpeg at 960KB, resume at 480KB)
- Change volume slider from @input to @change to avoid flooding server
- Wrap error-reporting sendTextMessage in try/catch to prevent
unhandled rejection in error handler
- Clamp elapsed getter to song duration
- Log FFmpeg stderr at debug level instead of swallowing
- Return null from prev() in Sequential mode instead of wrapping
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs prevented auto-advance:
1. FFmpeg close handler was empty — ChildProcess object persisted after
close, so frame loop check `!this.ffmpeg` never triggered trackEnd
2. playNext() is async (resolves URL) but trackEnd handler didn't catch
rejected promises, causing silent failures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>