mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
fix: resolve critical race conditions and high-severity bugs
- 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>
This commit is contained in:
1 parent
4c71a0a37f
commit
17991b936a
8 files changed
+150
-57
No files matched your search
+30
-17
@@ -54,6 +54,7 @@ export class BotInstance extends EventEmitter {
|
||||
private logger: Logger;
|
||||
private connected = false;
|
||||
private voteSkipUsers = new Set<string>();
|
||||
private isAdvancing = false;
|
||||
|
||||
constructor(options: BotInstanceOptions) {
|
||||
super();
|
||||
@@ -142,9 +143,13 @@ export class BotInstance extends EventEmitter {
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error({ err, command: parsed.name }, "Command execution error");
|
||||
await this.tsClient.sendTextMessage(
|
||||
`Error: ${(err as Error).message}`
|
||||
);
|
||||
try {
|
||||
await this.tsClient.sendTextMessage(
|
||||
`Error: ${(err as Error).message}`
|
||||
);
|
||||
} catch (sendErr) {
|
||||
this.logger.error({ err: sendErr }, "Failed to send error message to chat");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +426,9 @@ export class BotInstance extends EventEmitter {
|
||||
|
||||
if (votes >= needed) {
|
||||
this.voteSkipUsers.clear();
|
||||
this.playNext();
|
||||
this.playNext().catch((err) => {
|
||||
this.logger.error({ err }, "playNext failed after vote skip");
|
||||
});
|
||||
return `Vote passed (${votes}/${needed}). Skipping to next song.`;
|
||||
}
|
||||
return `Vote to skip: ${votes}/${needed} (need ${needed - votes} more)`;
|
||||
@@ -472,23 +479,29 @@ export class BotInstance extends EventEmitter {
|
||||
}
|
||||
|
||||
private async playNext(): Promise<void> {
|
||||
this.voteSkipUsers.clear();
|
||||
const next = this.queue.next();
|
||||
if (next) {
|
||||
const ok = await this.resolveAndPlay(next);
|
||||
if (!ok) {
|
||||
// Skip to next if URL resolve fails (up to 3 retries)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const retry = this.queue.next();
|
||||
if (!retry) break;
|
||||
if (await this.resolveAndPlay(retry)) return;
|
||||
if (this.isAdvancing) return;
|
||||
this.isAdvancing = true;
|
||||
try {
|
||||
this.voteSkipUsers.clear();
|
||||
const next = this.queue.next();
|
||||
if (next) {
|
||||
const ok = await this.resolveAndPlay(next);
|
||||
if (!ok) {
|
||||
// Skip to next if URL resolve fails (up to 3 retries)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const retry = this.queue.next();
|
||||
if (!retry) break;
|
||||
if (await this.resolveAndPlay(retry)) return;
|
||||
}
|
||||
this.player.stop();
|
||||
}
|
||||
} else {
|
||||
this.player.stop();
|
||||
}
|
||||
} else {
|
||||
this.player.stop();
|
||||
this.emit("stateChange");
|
||||
} finally {
|
||||
this.isAdvancing = false;
|
||||
}
|
||||
this.emit("stateChange");
|
||||
}
|
||||
|
||||
private extractId(input: string): string {
|
||||
|
||||
Reference in new issue
Block a user