mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
fix(auto-pause): auto-resume on a listener's return via clientEnter event
Follow-up to the auto-pause fix: resume never fired when someone came back. Root cause (verified live against a TS3 server): the full-client library's command/response channel is dead whenever >=2 clients are connected anywhere on the server — clientlist, channellist and channelclientlist ALL time out (confirmed even with the two clients in different channels). So the moment a listener returns is exactly the moment occupancy can no longer be queried, and the query-based refreshOccupancy() can never observe the return -> no resume. Event channelID is also unusable (library reads notify `cid` but enter-view carries `ctid`, so it's always 0), so per-channel membership can't be derived from events either. Fix (minimal, asymmetric): keep PAUSE on the authoritative clientlist path (reliable precisely because it only succeeds when the bot is alone on the server — the only state pause should fire), and arm RESUME directly from the clientEnter push event. Because the bot only auto-pauses while alone, the sole way occupancy can return while autoPaused is set is a fresh connection, which arrives reliably as clientEnter. New pure predicate shouldResumeOnReturn() + _resumeIfReturning() resume iff autoPaused && paused; the resume branch routes through handleOccupancy(1) and NEVER pauses (userCount>0), so a spurious enter can only harmlessly resume. The bot's own enter at connect is a no-op (autoPaused is already false). This deliberately does NOT adopt a full event-tracked peer set: events don't reliably seed clients already present when the bot joins, so a count-from-events ==0 would reintroduce the false-pause bug we just fixed, and reconcile can't heal it (clientlist only works when alone). Pause must trust only the authoritative query; resume can trust the event. Net semantics: pause when the server is empty (bot alone), resume when someone connects. Channel granularity is impossible with this library. UI copy updated to say "服务器" instead of "频道", and the Settings toggle default corrected to false to match the backend default. cmdVote intentionally left as-is. Verified live: auto-paused bot + a real client connecting -> resume fires with no clientlist call in the path; bot's own enter and not-auto-paused enters do not resume. 311 unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ba11519fdb
commit
3a34c01abb
5 files changed
+128
-8
No files matched your search
+33
-2
@@ -18,7 +18,11 @@ import type { BotDatabase, ProfileConfig } from "../data/database.js";
|
||||
import type { BotConfig } from "../data/config.js";
|
||||
import { BotProfileManager } from "./profile.js";
|
||||
import type { AvatarStore } from "../data/avatars.js";
|
||||
import { decideOccupancyAction, occupancyFromClientList } from "./auto-pause.js";
|
||||
import {
|
||||
decideOccupancyAction,
|
||||
occupancyFromClientList,
|
||||
shouldResumeOnReturn,
|
||||
} from "./auto-pause.js";
|
||||
|
||||
export interface BotInstanceOptions {
|
||||
id: string;
|
||||
@@ -166,11 +170,38 @@ export class BotInstance extends EventEmitter {
|
||||
|
||||
// React near-instantly to channel membership changes. The 30s idle
|
||||
// poller remains the fallback if any of these events are missed.
|
||||
this.tsClient.on("clientEnter", () => void this.refreshOccupancy());
|
||||
//
|
||||
// clientEnter additionally arms auto-RESUME directly from the event,
|
||||
// because the occupancy query (clientlist) times out whenever another
|
||||
// client is present — i.e. exactly when a listener returns — so it cannot
|
||||
// be used to confirm the return. See _resumeIfReturning().
|
||||
this.tsClient.on("clientEnter", () => {
|
||||
this._resumeIfReturning();
|
||||
void this.refreshOccupancy();
|
||||
});
|
||||
this.tsClient.on("clientLeave", () => void this.refreshOccupancy());
|
||||
this.tsClient.on("clientMoved", () => void this.refreshOccupancy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume playback when a listener returns after an auto-pause, driven by the
|
||||
* clientEnter push event rather than a (timing-out) occupancy query.
|
||||
*
|
||||
* We only auto-pause while alone on the server, so `autoPaused` is a reliable
|
||||
* "paused because empty" flag; any client appearing while it's set means a
|
||||
* listener returned. Delegating to handleOccupancy(1) routes through
|
||||
* decideOccupancyAction (resume iff autoPaused && paused) and also cancels the
|
||||
* idle-disconnect timer. This path NEVER pauses — userCount is always > 0 —
|
||||
* so a spurious or unrelated enter can only (harmlessly) resume, never stop
|
||||
* playback. Pause remains exclusively on the authoritative clientlist path.
|
||||
*/
|
||||
private _resumeIfReturning(): void {
|
||||
if (!this.connected) return;
|
||||
if (shouldResumeOnReturn(this.autoPaused, this.player.getState())) {
|
||||
this.handleOccupancy(1);
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshOccupancy(): Promise<void> {
|
||||
if (!this.connected) return;
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user