@@ -21,10 +21,12 @@ import {
21
21
NoSubscriberBehavior ,
22
22
StreamType ,
23
23
VoiceConnection ,
24
+ VoiceConnectionStatus ,
24
25
createAudioPlayer ,
25
26
createAudioResource ,
26
27
getVoiceConnection ,
27
28
joinVoiceChannel ,
29
+ entersState ,
28
30
} from "@discordjs/voice" ;
29
31
import {
30
32
BaseGuildVoiceChannel ,
@@ -230,6 +232,7 @@ export class VoiceManager extends EventEmitter {
230
232
console . error ( "Error leaving voice channel:" , error ) ;
231
233
}
232
234
}
235
+
233
236
const connection = joinVoiceChannel ( {
234
237
channelId : channel . id ,
235
238
guildId : channel . guild . id ,
@@ -238,38 +241,103 @@ export class VoiceManager extends EventEmitter {
238
241
selfMute : false ,
239
242
} ) ;
240
243
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
+ ) ;
248
259
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
+ ) ;
254
265
255
- connection . on ( "error" , ( error ) => {
256
- console . error ( "Voice connection error:" , error ) ;
257
- } ) ;
266
+ if ( newState . status === VoiceConnectionStatus . Disconnected ) {
267
+ elizaLogger . log ( "Handling disconnection..." ) ;
258
268
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
+ }
264
327
}
265
- } ) ;
266
328
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
+ }
271
334
}
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
+ }
273
341
}
274
342
275
343
private async monitorMember (
@@ -780,7 +848,7 @@ export class VoiceManager extends EventEmitter {
780
848
781
849
audioPlayer . on (
782
850
"stateChange" ,
783
- ( oldState : any , newState : { status : string } ) => {
851
+ ( _oldState : any , newState : { status : string } ) => {
784
852
if ( newState . status == "idle" ) {
785
853
const idleTime = Date . now ( ) ;
786
854
console . log (
@@ -792,34 +860,46 @@ export class VoiceManager extends EventEmitter {
792
860
}
793
861
794
862
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
+ }
809
875
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
+ }
814
881
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 (
818
895
`Joined voice channel: ${ voiceChannel . name } `
819
896
) ;
820
897
} catch ( error ) {
821
898
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 ) ;
823
903
}
824
904
}
825
905
0 commit comments