debug: add voice adapter wrapper to trace gateway communication

Logs sendPayload calls (op code, result), onVoiceServerUpdate
and onVoiceStateUpdate to identify why connection stays at signalling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-06 01:27:50 +01:00
parent 5faf5139ef
commit cc5e21fe1c

View file

@ -238,7 +238,7 @@ async function ensureConnectionReady(connection: VoiceConnection, channelId: str
try { connection.destroy(); } catch {}
guildAudioState.delete(guildId);
console.log(`${SB} Creating fresh connection (attempt 3)...`);
const newConn = joinVoiceChannel({ channelId, guildId, adapterCreator: guild.voiceAdapterCreator as any, selfMute: false, selfDeaf: false });
const newConn = joinVoiceChannel({ channelId, guildId, adapterCreator: debugAdapterCreator(guild), selfMute: false, selfDeaf: false });
try { await entersState(newConn, VoiceConnectionStatus.Ready, 15_000); console.log(`${SB} Connection ready (fresh)`); return newConn; }
catch (e) { console.error(`${SB} All 3 connection attempts failed: ${(e as Error)?.message ?? e}`); try { newConn.destroy(); } catch {} guildAudioState.delete(guildId); throw new Error('Voice connection failed after 3 attempts'); }
}
@ -267,13 +267,13 @@ function attachVoiceLifecycle(state: GuildAudioState, guild: any) {
catch {
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { reconnectAttempts++; console.log(`${SB} Rejoin attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS}`); connection.rejoin({ channelId: state.channelId, selfDeaf: false, selfMute: false }); }
else { reconnectAttempts = 0; console.log(`${SB} Max reconnect attempts reached, creating fresh connection`); try { connection.destroy(); } catch {}
const nc = joinVoiceChannel({ channelId: state.channelId, guildId: state.guildId, adapterCreator: guild.voiceAdapterCreator as any, selfMute: false, selfDeaf: false });
const nc = joinVoiceChannel({ channelId: state.channelId, guildId: state.guildId, adapterCreator: debugAdapterCreator(guild), selfMute: false, selfDeaf: false });
state.connection = nc; nc.subscribe(state.player); attachVoiceLifecycle(state, guild); }
}
} else if (newS.status === VoiceConnectionStatus.Destroyed) {
console.warn(`${SB} Connection destroyed, recreating...`);
connectedSince.delete(state.guildId);
const nc = joinVoiceChannel({ channelId: state.channelId, guildId: state.guildId, adapterCreator: guild.voiceAdapterCreator as any, selfMute: false, selfDeaf: false });
const nc = joinVoiceChannel({ channelId: state.channelId, guildId: state.guildId, adapterCreator: debugAdapterCreator(guild), selfMute: false, selfDeaf: false });
state.connection = nc; nc.subscribe(state.player); attachVoiceLifecycle(state, guild);
} else if (newS.status === VoiceConnectionStatus.Connecting || newS.status === VoiceConnectionStatus.Signalling) {
isReconnecting = true;
@ -284,7 +284,7 @@ function attachVoiceLifecycle(state: GuildAudioState, guild: any) {
console.warn(`${SB} Timeout waiting for Ready from ${newS.status} (attempt ${reconnectAttempts}): ${(e as Error)?.message ?? e}`);
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { await new Promise(r => setTimeout(r, reconnectAttempts * 2000)); isReconnecting = false; connection.rejoin({ channelId: state.channelId, selfDeaf: false, selfMute: false }); }
else { reconnectAttempts = 0; isReconnecting = false; console.error(`${SB} Max attempts from ${newS.status}, fresh connection`); try { connection.destroy(); } catch {}
const nc = joinVoiceChannel({ channelId: state.channelId, guildId: state.guildId, adapterCreator: guild.voiceAdapterCreator as any, selfMute: false, selfDeaf: false });
const nc = joinVoiceChannel({ channelId: state.channelId, guildId: state.guildId, adapterCreator: debugAdapterCreator(guild), selfMute: false, selfDeaf: false });
state.connection = nc; nc.subscribe(state.player); attachVoiceLifecycle(state, guild); }
}
}
@ -293,6 +293,33 @@ function attachVoiceLifecycle(state: GuildAudioState, guild: any) {
(connection as any).__lifecycleAttached = true;
}
// ── Debug adapter wrapper ──
function debugAdapterCreator(guild: any): any {
const original = guild.voiceAdapterCreator;
return (methods: any) => {
const wrappedMethods = {
...methods,
sendPayload(payload: any) {
const result = methods.sendPayload(payload);
console.log(`${SB} adapter.sendPayload op=${payload?.op ?? '?'} d.channel_id=${payload?.d?.channel_id ?? '?'}${result}`);
return result;
},
};
const adapter = original(wrappedMethods);
const origVSU = adapter.onVoiceServerUpdate;
const origVStU = adapter.onVoiceStateUpdate;
adapter.onVoiceServerUpdate = (data: any) => {
console.log(`${SB} adapter.onVoiceServerUpdate: token=${data?.token ? 'yes' : 'no'} endpoint=${data?.endpoint ?? 'none'}`);
return origVSU(data);
};
adapter.onVoiceStateUpdate = (data: any) => {
console.log(`${SB} adapter.onVoiceStateUpdate: session_id=${data?.session_id ? 'yes' : 'no'} channel_id=${data?.channel_id ?? 'none'}`);
return origVStU(data);
};
return adapter;
};
}
// ── Playback ──
let _pluginCtx: PluginContext | null = null;
@ -305,7 +332,7 @@ async function playFilePath(guildId: string, channelId: string, filePath: string
let state = guildAudioState.get(guildId);
if (!state) {
console.log(`${SB} No existing audio state, creating new connection...`);
const connection = joinVoiceChannel({ channelId, guildId, adapterCreator: guild.voiceAdapterCreator as any, selfMute: false, selfDeaf: false });
const connection = joinVoiceChannel({ channelId, guildId, adapterCreator: debugAdapterCreator(guild), selfMute: false, selfDeaf: false });
const player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play } });
connection.subscribe(player);
state = { connection, player, guildId, channelId, currentVolume: getPersistedVolume(guildId) };
@ -322,7 +349,7 @@ async function playFilePath(guildId: string, channelId: string, filePath: string
const current = getVoiceConnection(guildId);
if (current && current.joinConfig?.channelId !== channelId) {
current.destroy();
const connection = joinVoiceChannel({ channelId, guildId, adapterCreator: guild.voiceAdapterCreator as any, selfMute: false, selfDeaf: false });
const connection = joinVoiceChannel({ channelId, guildId, adapterCreator: debugAdapterCreator(guild), selfMute: false, selfDeaf: false });
const player = state.player ?? createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play } });
connection.subscribe(player);
state = { connection, player, guildId, channelId, currentVolume: state.currentVolume ?? getPersistedVolume(guildId) };
@ -333,7 +360,7 @@ async function playFilePath(guildId: string, channelId: string, filePath: string
} catch {}
if (!getVoiceConnection(guildId)) {
const connection = joinVoiceChannel({ channelId, guildId, adapterCreator: guild.voiceAdapterCreator as any, selfMute: false, selfDeaf: false });
const connection = joinVoiceChannel({ channelId, guildId, adapterCreator: debugAdapterCreator(guild), selfMute: false, selfDeaf: false });
const player = state?.player ?? createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play } });
connection.subscribe(player);
state = { connection, player, guildId, channelId, currentVolume: state?.currentVolume ?? getPersistedVolume(guildId) };