From 8dcd4b334d4e15c56379a4714e1196731adad52b Mon Sep 17 00:00:00 2001 From: SocksOnHead Date: Mon, 12 Aug 2024 02:02:10 +0200 Subject: [PATCH] Fix deadlock and auto leave voice channel after 3 min (#4) * Fix for thread being busy handling discord commands blocking audioplayer to timeout * Auto leave if alone in voice channel after 3 min Co-authored-by: Myx --- .../MusicPlayer/PlayCommand/PlayHandler.cs | 82 +++++++++---------- Bot/Program.cs | 10 ++- Bot/Service/VoiceChannelMonitorService.cs | 59 +++++++++++++ Bot/SlashCommand/Command.cs | 12 --- 4 files changed, 106 insertions(+), 57 deletions(-) create mode 100644 Bot/Service/VoiceChannelMonitorService.cs diff --git a/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs b/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs index 90e68c4..7d6c890 100644 --- a/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs +++ b/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs @@ -5,6 +5,7 @@ using Lavalink4NET; using Lavalink4NET.Events.Players; using Lavalink4NET.Players.Queued; using Lavalink4NET.Rest.Entities.Tracks; +using System.Threading; namespace Lunaris2.Handler.MusicPlayer.PlayCommand; @@ -37,57 +38,54 @@ public class PlayHandler : IRequestHandler await _musicEmbed.NowPlayingEmbed(track, _context, _client); } - public async Task Handle(PlayCommand command, CancellationToken cancellationToken) + public Task Handle(PlayCommand command, CancellationToken cancellationToken) { - await _audioService.StartAsync(cancellationToken); - var context = command.Message; - _context = context; - - var searchQuery = context.GetOptionValueByName(Option.Input); + new Thread(PlayMusic).Start(); + return Task.CompletedTask; - if (string.IsNullOrWhiteSpace(searchQuery)) { - await context.SendMessageAsync("Please provide search terms.", _client); - return; - } - - var player = await _audioService.GetPlayerAsync(_client, context, connectToVoiceChannel: true); - - if (player is null) - return; - - var trackLoadOptions = new TrackLoadOptions + async void PlayMusic() { - SearchMode = TrackSearchMode.YouTube, - }; + await _audioService.StartAsync(cancellationToken); + var context = command.Message; + _context = context; - var track = await _audioService.Tracks - .LoadTrackAsync( - searchQuery, - trackLoadOptions, - cancellationToken: cancellationToken); - - if (track is null) - await context.SendMessageAsync("😖 No results.", _client); + var searchQuery = context.GetOptionValueByName(Option.Input); - if (player.CurrentTrack is null) - { - await player - .PlayAsync(track, cancellationToken: cancellationToken) - .ConfigureAwait(false); - - await _musicEmbed.NowPlayingEmbed(track, context, _client); - } - else - { - if (track != null) + if (string.IsNullOrWhiteSpace(searchQuery)) { - var queueTracks = new[] { new TrackQueueItem(track) }; - await player.Queue.AddRangeAsync(queueTracks, cancellationToken); - await context.SendMessageAsync($"🔈 Added to queue: {track.Title}", _client); + await context.SendMessageAsync("Please provide search terms.", _client); + return; + } + + var player = await _audioService.GetPlayerAsync(_client, context, connectToVoiceChannel: true); + + if (player is null) return; + + var trackLoadOptions = new TrackLoadOptions { SearchMode = TrackSearchMode.YouTube, }; + + var track = await _audioService.Tracks.LoadTrackAsync(searchQuery, trackLoadOptions, cancellationToken: cancellationToken); + + if (track is null) await context.SendMessageAsync("😖 No results.", _client); + + if (player.CurrentTrack is null) + { + await player.PlayAsync(track, cancellationToken: cancellationToken) + .ConfigureAwait(false); + + await _musicEmbed.NowPlayingEmbed(track, context, _client); } else { - await context.SendMessageAsync($"Couldn't read song information", _client); + if (track != null) + { + var queueTracks = new[] { new TrackQueueItem(track) }; + await player.Queue.AddRangeAsync(queueTracks, cancellationToken); + await context.SendMessageAsync($"🔈 Added to queue: {track.Title}", _client); + } + else + { + await context.SendMessageAsync($"Couldn't read song information", _client); + } } } } diff --git a/Bot/Program.cs b/Bot/Program.cs index cac2d54..79d1f77 100644 --- a/Bot/Program.cs +++ b/Bot/Program.cs @@ -6,6 +6,7 @@ using Lunaris2.Handler.ChatCommand; using Lavalink4NET.Extensions; using Lunaris2.Handler.MusicPlayer; using Lunaris2.Notification; +using Lunaris2.Service; using Lunaris2.SlashCommand; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -41,10 +42,7 @@ public class Program .Build(); services - .AddSingleton(client) .AddMediatR(mediatRServiceConfiguration => mediatRServiceConfiguration.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())) - .AddSingleton() - .AddSingleton(service => new InteractionService(service.GetRequiredService())) .AddLavalink() .ConfigureLavalink(options => { @@ -58,6 +56,10 @@ public class Program .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton(client) + .AddSingleton() + .AddSingleton() + .AddSingleton(service => new InteractionService(service.GetRequiredService())) .Configure(configuration.GetSection("LLM")); client.Ready += () => Client_Ready(client); @@ -86,6 +88,8 @@ public class Program private static Task Client_Ready(DiscordSocketClient client) { client.RegisterCommands(); + + new VoiceChannelMonitorService(client).StartMonitoring(); return Task.CompletedTask; } diff --git a/Bot/Service/VoiceChannelMonitorService.cs b/Bot/Service/VoiceChannelMonitorService.cs new file mode 100644 index 0000000..9bacaee --- /dev/null +++ b/Bot/Service/VoiceChannelMonitorService.cs @@ -0,0 +1,59 @@ +using Discord.WebSocket; + +namespace Lunaris2.Service; + +public class VoiceChannelMonitorService +{ + private readonly DiscordSocketClient _client; + private readonly Dictionary _timers = new(); + + public VoiceChannelMonitorService(DiscordSocketClient client) + { + _client = client; + } + + public void StartMonitoring() + { + Task.Run(async () => + { + while (true) + { + await CheckVoiceChannels(); + await Task.Delay(TimeSpan.FromMinutes(1)); + } + }); + } + + private async Task CheckVoiceChannels() + { + foreach (var guild in _client.Guilds) + { + var voiceChannel = guild.VoiceChannels.FirstOrDefault(vc => vc.ConnectedUsers.Count == 1); + if (voiceChannel != null) + { + if (!_timers.ContainsKey(voiceChannel.Id)) + { + _timers[voiceChannel.Id] = new Timer(async _ => await LeaveChannel(voiceChannel), null, TimeSpan.FromMinutes(3), Timeout.InfiniteTimeSpan); + } + } + else + { + if (voiceChannel == null || !_timers.ContainsKey(voiceChannel.Id)) + continue; + + await _timers[voiceChannel.Id].DisposeAsync(); + _timers.Remove(voiceChannel.Id); + } + } + } + + private async Task LeaveChannel(SocketVoiceChannel voiceChannel) + { + if (voiceChannel.ConnectedUsers.Count == 1 && voiceChannel.Users.Any(u => u.Id == _client.CurrentUser.Id)) + { + await voiceChannel.DisconnectAsync(); + await _timers[voiceChannel.Id].DisposeAsync(); + _timers.Remove(voiceChannel.Id); + } + } +} \ No newline at end of file diff --git a/Bot/SlashCommand/Command.cs b/Bot/SlashCommand/Command.cs index b289bb4..979385f 100644 --- a/Bot/SlashCommand/Command.cs +++ b/Bot/SlashCommand/Command.cs @@ -9,18 +9,6 @@ public static class Option public static class Command { - // public static class Hello - // { - // public const string Name = "hello"; - // public const string Description = "Say hello to the bot!"; - // } - // - // public static class Goodbye - // { - // public const string Name = "goodbye"; - // public const string Description = "Say goodbye to the bot!"; - // } - public static class Disconnect { public const string Name = "disconnect";