Skip to content

Commit 19e9d1b

Browse files
committedNov 24, 2024
fix: voice
1 parent a9d8417 commit 19e9d1b

File tree

2 files changed

+131
-50
lines changed

2 files changed

+131
-50
lines changed
 

‎agent/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import path from "path";
3434
import { fileURLToPath } from "url";
3535
import { character } from "./character.ts";
3636
import type { DirectClient } from "@ai16z/client-direct";
37+
import blobert from "./blobert.ts";
3738

3839
const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
3940
const __dirname = path.dirname(__filename); // get the name of the directory
@@ -317,7 +318,7 @@ const startAgents = async () => {
317318

318319
let charactersArg = args.characters || args.character;
319320

320-
let characters = [character];
321+
let characters = [blobert];
321322

322323
if (charactersArg) {
323324
characters = await loadCharacters(charactersArg);

‎packages/client-discord/src/voice.ts

+129-49
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ import {
2121
NoSubscriberBehavior,
2222
StreamType,
2323
VoiceConnection,
24+
VoiceConnectionStatus,
2425
createAudioPlayer,
2526
createAudioResource,
2627
getVoiceConnection,
2728
joinVoiceChannel,
29+
entersState,
2830
} from "@discordjs/voice";
2931
import {
3032
BaseGuildVoiceChannel,
@@ -230,6 +232,7 @@ export class VoiceManager extends EventEmitter {
230232
console.error("Error leaving voice channel:", error);
231233
}
232234
}
235+
233236
const connection = joinVoiceChannel({
234237
channelId: channel.id,
235238
guildId: channel.guild.id,
@@ -238,38 +241,103 @@ export class VoiceManager extends EventEmitter {
238241
selfMute: false,
239242
});
240243

241-
const me = channel.guild.members.me;
242-
if (me?.voice && me.permissions.has("DeafenMembers")) {
243-
await me.voice.setDeaf(false);
244-
await me.voice.setMute(false);
245-
} else {
246-
elizaLogger.log("Bot lacks permission to modify voice state");
247-
}
244+
try {
245+
// Wait for either Ready or Signalling state
246+
await Promise.race([
247+
entersState(connection, VoiceConnectionStatus.Ready, 20_000),
248+
entersState(
249+
connection,
250+
VoiceConnectionStatus.Signalling,
251+
20_000
252+
),
253+
]);
254+
255+
// Log connection success
256+
elizaLogger.log(
257+
`Voice connection established in state: ${connection.state.status}`
258+
);
248259

249-
for (const [, member] of channel.members) {
250-
if (!member.user.bot) {
251-
this.monitorMember(member, channel);
252-
}
253-
}
260+
// Set up ongoing state change monitoring
261+
connection.on("stateChange", async (oldState, newState) => {
262+
elizaLogger.log(
263+
`Voice connection state changed from ${oldState.status} to ${newState.status}`
264+
);
254265

255-
connection.on("error", (error) => {
256-
console.error("Voice connection error:", error);
257-
});
266+
if (newState.status === VoiceConnectionStatus.Disconnected) {
267+
elizaLogger.log("Handling disconnection...");
258268

259-
connection.receiver.speaking.on("start", (userId: string) => {
260-
const user = channel.members.get(userId);
261-
if (!user?.user.bot) {
262-
this.monitorMember(user as GuildMember, channel);
263-
this.streams.get(userId)?.emit("speakingStarted");
269+
try {
270+
// Try to reconnect if disconnected
271+
await Promise.race([
272+
entersState(
273+
connection,
274+
VoiceConnectionStatus.Signalling,
275+
5_000
276+
),
277+
entersState(
278+
connection,
279+
VoiceConnectionStatus.Connecting,
280+
5_000
281+
),
282+
]);
283+
// Seems to be reconnecting to a new channel
284+
elizaLogger.log("Reconnecting to channel...");
285+
} catch (e) {
286+
// Seems to be a real disconnect, destroy and cleanup
287+
elizaLogger.log(
288+
"Disconnection confirmed - cleaning up..." + e
289+
);
290+
connection.destroy();
291+
this.connections.delete(channel.id);
292+
}
293+
} else if (
294+
newState.status === VoiceConnectionStatus.Destroyed
295+
) {
296+
this.connections.delete(channel.id);
297+
} else if (
298+
!this.connections.has(channel.id) &&
299+
(newState.status === VoiceConnectionStatus.Ready ||
300+
newState.status === VoiceConnectionStatus.Signalling)
301+
) {
302+
this.connections.set(channel.id, connection);
303+
}
304+
});
305+
306+
connection.on("error", (error) => {
307+
elizaLogger.log("Voice connection error:", error);
308+
// Don't immediately destroy - let the state change handler deal with it
309+
elizaLogger.log(
310+
"Connection error - will attempt to recover..."
311+
);
312+
});
313+
314+
// Store the connection
315+
this.connections.set(channel.id, connection);
316+
317+
// Continue with voice state modifications
318+
const me = channel.guild.members.me;
319+
if (me?.voice && me.permissions.has("DeafenMembers")) {
320+
try {
321+
await me.voice.setDeaf(false);
322+
await me.voice.setMute(false);
323+
} catch (error) {
324+
elizaLogger.log("Failed to modify voice state:", error);
325+
// Continue even if this fails
326+
}
264327
}
265-
});
266328

267-
connection.receiver.speaking.on("end", async (userId: string) => {
268-
const user = channel.members.get(userId);
269-
if (!user?.user.bot) {
270-
this.streams.get(userId)?.emit("speakingStopped");
329+
// Set up member monitoring
330+
for (const [, member] of channel.members) {
331+
if (!member.user.bot) {
332+
await this.monitorMember(member, channel);
333+
}
271334
}
272-
});
335+
} catch (error) {
336+
elizaLogger.log("Failed to establish voice connection:", error);
337+
connection.destroy();
338+
this.connections.delete(channel.id);
339+
throw error;
340+
}
273341
}
274342

275343
private async monitorMember(
@@ -780,7 +848,7 @@ export class VoiceManager extends EventEmitter {
780848

781849
audioPlayer.on(
782850
"stateChange",
783-
(oldState: any, newState: { status: string }) => {
851+
(_oldState: any, newState: { status: string }) => {
784852
if (newState.status == "idle") {
785853
const idleTime = Date.now();
786854
console.log(
@@ -792,34 +860,46 @@ export class VoiceManager extends EventEmitter {
792860
}
793861

794862
async handleJoinChannelCommand(interaction: any) {
795-
const channelId = interaction.options.get("channel")?.value as string;
796-
if (!channelId) {
797-
await interaction.reply("Please provide a voice channel to join.");
798-
return;
799-
}
800-
const guild = interaction.guild;
801-
if (!guild) {
802-
return;
803-
}
804-
const voiceChannel = interaction.guild.channels.cache.find(
805-
(channel: VoiceChannel) =>
806-
channel.id === channelId &&
807-
channel.type === ChannelType.GuildVoice
808-
);
863+
try {
864+
// Defer the reply immediately to prevent interaction timeout
865+
await interaction.deferReply();
866+
867+
const channelId = interaction.options.get("channel")
868+
?.value as string;
869+
if (!channelId) {
870+
await interaction.editReply(
871+
"Please provide a voice channel to join."
872+
);
873+
return;
874+
}
809875

810-
if (!voiceChannel) {
811-
await interaction.reply("Voice channel not found!");
812-
return;
813-
}
876+
const guild = interaction.guild;
877+
if (!guild) {
878+
await interaction.editReply("Could not find guild.");
879+
return;
880+
}
814881

815-
try {
816-
this.joinChannel(voiceChannel as BaseGuildVoiceChannel);
817-
await interaction.reply(
882+
const voiceChannel = interaction.guild.channels.cache.find(
883+
(channel: VoiceChannel) =>
884+
channel.id === channelId &&
885+
channel.type === ChannelType.GuildVoice
886+
);
887+
888+
if (!voiceChannel) {
889+
await interaction.editReply("Voice channel not found!");
890+
return;
891+
}
892+
893+
await this.joinChannel(voiceChannel as BaseGuildVoiceChannel);
894+
await interaction.editReply(
818895
`Joined voice channel: ${voiceChannel.name}`
819896
);
820897
} catch (error) {
821898
console.error("Error joining voice channel:", error);
822-
await interaction.reply("Failed to join the voice channel.");
899+
// Use editReply instead of reply for the error case
900+
await interaction
901+
.editReply("Failed to join the voice channel.")
902+
.catch(console.error);
823903
}
824904
}
825905

0 commit comments

Comments
 (0)