From ae1a4e14d69a529640dc527031ef37a81aff15ba Mon Sep 17 00:00:00 2001 From: Myx Date: Mon, 17 Feb 2025 03:54:18 +0100 Subject: [PATCH] Add scheduler --- Bot/Handler/ChatCommand/ChatHandler.cs | 113 +++++++------ Bot/Handler/MusicPlayer/Extensions.cs | 6 +- .../MusicPlayer/PlayCommand/PlayHandler.cs | 2 +- .../Scheduler/ProcessMessageCommand.cs | 30 ++++ .../Scheduler/ScheduleMessageCommand.cs | 137 ++++++++++++++++ Bot/Handler/SlashCommandReceivedHandler.cs | 4 + Bot/Lunaris2.csproj | 18 ++- Bot/Notification/DiscordEventListener.cs | 1 - Bot/Program.cs | 48 +++--- Bot/Registration/ChatRegistration.cs | 2 - Bot/Registration/DiscordBotRegistration.cs | 93 ++++++----- Bot/Registration/HangfireRegistration.cs | 19 +++ Bot/Registration/MusicPlayerRegistration.cs | 2 - Bot/Registration/SchedulerRegistration.cs | 26 +++ Bot/Service/VoiceChannelMonitorService.cs | 149 +++++++++--------- Bot/SlashCommand/Command.cs | 35 ++++ Bot/SlashCommand/SlashCommandRegistration.cs | 1 + Bot/appsettings.json | 3 +- Bot/wwwroot/index.html | 55 +++++++ LOGOTYPE.png => Bot/wwwroot/logotype.png | Bin README.md | 2 +- docker-compose.yml | 9 ++ 22 files changed, 535 insertions(+), 220 deletions(-) create mode 100644 Bot/Handler/Scheduler/ProcessMessageCommand.cs create mode 100644 Bot/Handler/Scheduler/ScheduleMessageCommand.cs create mode 100644 Bot/Registration/HangfireRegistration.cs create mode 100644 Bot/Registration/SchedulerRegistration.cs create mode 100644 Bot/wwwroot/index.html rename LOGOTYPE.png => Bot/wwwroot/logotype.png (100%) diff --git a/Bot/Handler/ChatCommand/ChatHandler.cs b/Bot/Handler/ChatCommand/ChatHandler.cs index a016936..63d4bd7 100644 --- a/Bot/Handler/ChatCommand/ChatHandler.cs +++ b/Bot/Handler/ChatCommand/ChatHandler.cs @@ -4,65 +4,64 @@ using MediatR; using Microsoft.Extensions.Options; using OllamaSharp; -namespace Lunaris2.Handler.ChatCommand +namespace Lunaris2.Handler.ChatCommand; + +public record ChatCommand(SocketMessage Message, string FilteredMessage) : IRequest; + +public class ChatHandler : IRequestHandler { - public record ChatCommand(SocketMessage Message, string FilteredMessage) : IRequest; + private readonly OllamaApiClient _ollama; + private readonly Dictionary _chatContexts = new(); + private readonly ChatSettings _chatSettings; - public class ChatHandler : IRequestHandler + public ChatHandler(IOptions chatSettings) { - private readonly OllamaApiClient _ollama; - private readonly Dictionary _chatContexts = new(); - private readonly ChatSettings _chatSettings; - - public ChatHandler(IOptions chatSettings) + _chatSettings = chatSettings.Value; + var uri = new Uri(chatSettings.Value.Url); + + _ollama = new OllamaApiClient(uri) { - _chatSettings = chatSettings.Value; - var uri = new Uri(chatSettings.Value.Url); - - _ollama = new OllamaApiClient(uri) - { - SelectedModel = chatSettings.Value.Model - }; - } - - public async Task Handle(ChatCommand command, CancellationToken cancellationToken) - { - var channelId = command.Message.Channel.Id; - _chatContexts.TryAdd(channelId, null); - - var userMessage = command.FilteredMessage; - - var randomPersonality = _chatSettings.Personalities[new Random().Next(_chatSettings.Personalities.Count)]; - - userMessage = $"{randomPersonality.Instruction} {userMessage}"; - - using var setTyping = command.Message.Channel.EnterTypingState(); - - if (string.IsNullOrWhiteSpace(userMessage)) - { - await command.Message.Channel.SendMessageAsync("Am I expected to read your mind?"); - setTyping.Dispose(); - return; - } - - var response = await GenerateResponse(userMessage, channelId, cancellationToken); - await command.Message.Channel.SendMessageAsync(response); - - setTyping.Dispose(); - } - - private async Task GenerateResponse(string userMessage, ulong channelId, CancellationToken cancellationToken) - { - var response = new StringBuilder(); - - if (_chatContexts[channelId] == null) - { - _chatContexts[channelId] = _ollama.Chat(stream => response.Append(stream.Message?.Content ?? "")); - } - - await _chatContexts[channelId].Send(userMessage, cancellationToken); - - return response.ToString(); - } + SelectedModel = chatSettings.Value.Model + }; } -} + + public async Task Handle(ChatCommand command, CancellationToken cancellationToken) + { + var channelId = command.Message.Channel.Id; + _chatContexts.TryAdd(channelId, null); + + var userMessage = command.FilteredMessage; + + var randomPersonality = _chatSettings.Personalities[new Random().Next(_chatSettings.Personalities.Count)]; + + userMessage = $"{randomPersonality.Instruction} {userMessage}"; + + using var setTyping = command.Message.Channel.EnterTypingState(); + + if (string.IsNullOrWhiteSpace(userMessage)) + { + await command.Message.Channel.SendMessageAsync("Am I expected to read your mind?"); + setTyping.Dispose(); + return; + } + + var response = await GenerateResponse(userMessage, channelId, cancellationToken); + await command.Message.Channel.SendMessageAsync(response); + + setTyping.Dispose(); + } + + private async Task GenerateResponse(string userMessage, ulong channelId, CancellationToken cancellationToken) + { + var response = new StringBuilder(); + + if (_chatContexts[channelId] == null) + { + _chatContexts[channelId] = _ollama.Chat(stream => response.Append(stream.Message?.Content ?? "")); + } + + await _chatContexts[channelId].Send(userMessage, cancellationToken); + + return response.ToString(); + } +} \ No newline at end of file diff --git a/Bot/Handler/MusicPlayer/Extensions.cs b/Bot/Handler/MusicPlayer/Extensions.cs index f7e4fac..f017f8c 100644 --- a/Bot/Handler/MusicPlayer/Extensions.cs +++ b/Bot/Handler/MusicPlayer/Extensions.cs @@ -68,9 +68,9 @@ public static class Extensions await message.RespondAsync(content, ephemeral: true); } - - public static string GetOptionValueByName(this SocketSlashCommand command, string optionName) + public static T? GetOptionValueByName(this SocketSlashCommand command, string optionName) { - return command.Data.Options.FirstOrDefault(option => option.Name == optionName)?.Value.ToString() ?? string.Empty; + return (T?)(command.Data?.Options? + .FirstOrDefault(option => option.Name == optionName)?.Value ?? default(T)); } } \ No newline at end of file diff --git a/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs b/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs index e50422a..e59b70a 100644 --- a/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs +++ b/Bot/Handler/MusicPlayer/PlayCommand/PlayHandler.cs @@ -85,7 +85,7 @@ public class PlayHandler : IRequestHandler return; } - var searchQuery = context.GetOptionValueByName(Option.Input); + var searchQuery = context.GetOptionValueByName(Option.Input); if (string.IsNullOrWhiteSpace(searchQuery)) { diff --git a/Bot/Handler/Scheduler/ProcessMessageCommand.cs b/Bot/Handler/Scheduler/ProcessMessageCommand.cs new file mode 100644 index 0000000..446fc2f --- /dev/null +++ b/Bot/Handler/Scheduler/ProcessMessageCommand.cs @@ -0,0 +1,30 @@ +using Discord.WebSocket; +using MediatR; + +namespace Lunaris2.Handler.Scheduler; + +public class ProcessMessageCommand : IRequest +{ + public ulong? Context { get; set; } + public string Content { get; set; } +} + +public class ProcessMessageHandler(DiscordSocketClient client) : IRequestHandler +{ + public Task Handle(ProcessMessageCommand request, CancellationToken cancellationToken) + { + if (request.Context == null) + return Task.FromResult(Unit.Value); + + var channel = client.GetChannel(request.Context.Value) as ISocketMessageChannel; + + if (channel == null) + return Task.FromResult(Unit.Value); + + using var setTyping = channel.EnterTypingState(); + channel.SendMessageAsync(request.Content); + setTyping.Dispose(); + + return Task.FromResult(Unit.Value); + } +} \ No newline at end of file diff --git a/Bot/Handler/Scheduler/ScheduleMessageCommand.cs b/Bot/Handler/Scheduler/ScheduleMessageCommand.cs new file mode 100644 index 0000000..54d2e6c --- /dev/null +++ b/Bot/Handler/Scheduler/ScheduleMessageCommand.cs @@ -0,0 +1,137 @@ +using System.Globalization; +using System.Text; +using Discord.WebSocket; +using Hangfire; +using Lunaris2.Handler.ChatCommand; +using Lunaris2.Handler.MusicPlayer; +using Lunaris2.SlashCommand; +using MediatR; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using NCrontab; +using OllamaSharp; +using static System.DateTime; + +namespace Lunaris2.Handler.Scheduler; + +public record ScheduleMessageCommand(SocketSlashCommand Message) : IRequest; + +public class ScheduleMessageHandler : IRequestHandler +{ + private readonly ChatSettings _chatSettings; + private readonly OllamaApiClient _ollama; + private readonly ISender _mediator; + + private readonly string _cronInstruction = "You are only able to respond in CRON Format. " + + "Current time is: " + Now.ToString("yyyy-MM-dd HH:mm") + ". and it is " + + Now.DayOfWeek + ". " + + "Please use the a format parsable by ncrontab." + + "The user will describe the CRON format and you can only answer with the CRON format the user describes."; + + private readonly string _dateInstruction = "You are only able to respond in Date Format. " + + "Current time is: " + Now.ToString("dd/MM/yyyy HH:mm:ss") + ". and it is " + + Now.DayOfWeek + ". " + + "Please use the following format: dd/MM/yyyy HH:mm:ss. Convert following to date string with the current time as a context"; + + public ScheduleMessageHandler( + IOptions chatSettings, + ISender mediator) + { + _mediator = mediator; + _chatSettings = chatSettings.Value; + + var uri = new Uri(_chatSettings.Url); + _ollama = new OllamaApiClient(uri) + { + SelectedModel = _chatSettings.Model + }; + } + + public async Task Handle(ScheduleMessageCommand request, CancellationToken cancellationToken) + { + var userDateInput = request.Message.GetOptionValueByName(Option.Time); + var userMessage = request.Message.GetOptionValueByName(Option.Message); + var recurring = request.Message.GetOptionValueByName(Option.IsRecurring); + + if (recurring) + { + await ScheduleRecurringJob(request, userMessage, userDateInput, cancellationToken); + } + else + { + await ScheduleJob(request, userMessage, userDateInput, cancellationToken); + + await request.Message.Channel.SendMessageAsync("Message scheduled successfully."); + } + } + + private async Task ScheduleRecurringJob( + ScheduleMessageCommand request, + string message, + string userDateInput, + CancellationToken cancellationToken) + { + using var setTyping = request.Message.Channel.EnterTypingState(); + var cron = string.Empty; + var jobManager = new RecurringJobManager(); + const int retries = 5; + var userMessage = $"{_cronInstruction}: {userDateInput}"; + + for (var tries = 0; tries < retries; tries++) + { + var textToCronResponse = await GenerateResponse(userMessage, cancellationToken); + var isValid = CrontabSchedule.TryParse(textToCronResponse).ToString().IsNullOrEmpty(); + + if(isValid) + { + await request.Message.Channel.SendMessageAsync("Sorry, I didn't understand that date format. Please try again."); + continue; + } + + cron = textToCronResponse; + + break; + } + var recurringJobId = $"channel_{request.Message.ChannelId}_{request.Message.Id}"; + + jobManager.AddOrUpdate( + recurringJobId, + () => _mediator.Send(new ProcessMessageCommand { Context = request.Message.ChannelId, Content = message}, cancellationToken), + cron + ); + + setTyping.Dispose(); + await request.Message.Channel.SendMessageAsync("Message scheduled successfully."); + } + + private async Task ScheduleJob( + ScheduleMessageCommand request, + string userMessage, + string executeAt, + CancellationToken cancellationToken + ) + { + var dateFormat = $"{_dateInstruction}: {executeAt}"; + + var formattedDate = await GenerateResponse(dateFormat, cancellationToken); + + var date = ParseExact(formattedDate, "dd/MM/yyyy HH:mm:ss", CultureInfo.CurrentCulture); + + BackgroundJob.Schedule( + () => _mediator.Send( + new ProcessMessageCommand { Context = request.Message.ChannelId, Content = userMessage }, + cancellationToken), + date); + } + + private async Task GenerateResponse(string userMessage, CancellationToken cancellationToken) + { + var response = new StringBuilder(); + + var chatContext = _ollama.Chat(stream => response.Append(stream.Message?.Content ?? "")); + + await chatContext.Send(userMessage, cancellationToken); + + return response.ToString(); + } +} \ No newline at end of file diff --git a/Bot/Handler/SlashCommandReceivedHandler.cs b/Bot/Handler/SlashCommandReceivedHandler.cs index 79dd137..87a6e70 100644 --- a/Bot/Handler/SlashCommandReceivedHandler.cs +++ b/Bot/Handler/SlashCommandReceivedHandler.cs @@ -4,6 +4,7 @@ using Lunaris2.Handler.MusicPlayer.PauseCommand; using Lunaris2.Handler.MusicPlayer.PlayCommand; using Lunaris2.Handler.MusicPlayer.ResumeCommand; using Lunaris2.Handler.MusicPlayer.SkipCommand; +using Lunaris2.Handler.Scheduler; using Lunaris2.Notification; using Lunaris2.SlashCommand; using MediatR; @@ -36,6 +37,9 @@ public class SlashCommandReceivedHandler(ISender mediator) : INotificationHandle case Command.Clear.Name: await mediator.Send(new ClearQueueCommand(notification.Message), cancellationToken); break; + case Command.Scheduler.Name: + await mediator.Send(new ScheduleMessageCommand(notification.Message), cancellationToken); + break; } } } \ No newline at end of file diff --git a/Bot/Lunaris2.csproj b/Bot/Lunaris2.csproj index 4bc588a..bdc8f3d 100644 --- a/Bot/Lunaris2.csproj +++ b/Bot/Lunaris2.csproj @@ -1,4 +1,4 @@ - + Exe @@ -16,17 +16,25 @@ + + + + + + - + + + @@ -37,6 +45,12 @@ Always + + Always + + + Always + diff --git a/Bot/Notification/DiscordEventListener.cs b/Bot/Notification/DiscordEventListener.cs index bfb7f99..394ad27 100644 --- a/Bot/Notification/DiscordEventListener.cs +++ b/Bot/Notification/DiscordEventListener.cs @@ -1,6 +1,5 @@ using Discord.WebSocket; using MediatR; -using Microsoft.Extensions.DependencyInjection; namespace Lunaris2.Notification; diff --git a/Bot/Program.cs b/Bot/Program.cs index c0af17c..7d23846 100644 --- a/Bot/Program.cs +++ b/Bot/Program.cs @@ -1,33 +1,27 @@ using Lavalink4NET.Integrations.SponsorBlock.Extensions; using Lunaris2.Registration; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -namespace Lunaris2; +var builder = WebApplication.CreateBuilder(args); -public class Program -{ - public static void Main(string[] args) - { - AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) => - { - Console.WriteLine(eventArgs.ExceptionObject); - }; - var app = CreateHostBuilder(args).Build(); - - app.UseSponsorBlock(); - app.Run(); - } +// Build configuration (using appsettings.json) +var configuration = new ConfigurationBuilder() + .SetBasePath(AppContext.BaseDirectory) + .AddJsonFile("appsettings.json") + .Build(); - private static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureServices((_, services) => - { - var configuration = new ConfigurationBuilder() - .SetBasePath(AppContext.BaseDirectory) - .AddJsonFile("appsettings.json") - .Build(); +// Register your services +builder.Services.AddDiscordBot(configuration); +builder.Services.AddScheduler(configuration); +builder.Services.AddControllers(); - services.AddDiscordBot(configuration); - }); -} \ No newline at end of file +var app = builder.Build(); + +// Call your custom middleware (e.g., for SponsorBlock functionality) +app.UseSponsorBlock(); + +// Serve static files +app.UseDefaultFiles(); +app.UseStaticFiles(); + +app.UseHangfireDashboardAndServer(); +app.Run(); \ No newline at end of file diff --git a/Bot/Registration/ChatRegistration.cs b/Bot/Registration/ChatRegistration.cs index 3954e54..7b70ba6 100644 --- a/Bot/Registration/ChatRegistration.cs +++ b/Bot/Registration/ChatRegistration.cs @@ -1,6 +1,4 @@ using Lunaris2.Handler.ChatCommand; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; namespace Lunaris2.Registration; diff --git a/Bot/Registration/DiscordBotRegistration.cs b/Bot/Registration/DiscordBotRegistration.cs index c0f196f..5276a67 100644 --- a/Bot/Registration/DiscordBotRegistration.cs +++ b/Bot/Registration/DiscordBotRegistration.cs @@ -5,68 +5,65 @@ using Discord.WebSocket; using Lunaris2.Notification; using Lunaris2.Service; using Lunaris2.SlashCommand; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -namespace Lunaris2.Registration +namespace Lunaris2.Registration; + +public static class DiscordBotRegistration { - public static class DiscordBotRegistration + public static IServiceCollection AddDiscordBot(this IServiceCollection services, IConfiguration configuration) { - public static IServiceCollection AddDiscordBot(this IServiceCollection services, IConfiguration configuration) + var config = new DiscordSocketConfig { - var config = new DiscordSocketConfig - { - GatewayIntents = GatewayIntents.All - }; + GatewayIntents = GatewayIntents.All + }; - var client = new DiscordSocketClient(config); + var client = new DiscordSocketClient(config); - services - .AddMediatR(mediatRServiceConfiguration => - mediatRServiceConfiguration.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())) - .AddMusicPlayer(configuration) - .AddSingleton(client) - .AddSingleton() - .AddSingleton(service => new InteractionService(service.GetRequiredService())) - .AddChat(configuration); + services + .AddMediatR(mediatRServiceConfiguration => + mediatRServiceConfiguration.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())) + .AddMusicPlayer(configuration) + .AddSingleton(client) + .AddSingleton() + .AddSingleton(service => new InteractionService(service.GetRequiredService())) + .AddChat(configuration); - client.Ready += () => Client_Ready(client); - client.Log += Log; + client.Ready += () => Client_Ready(client); + client.Log += Log; - client - .LoginAsync(TokenType.Bot, configuration["Token"]) - .GetAwaiter() - .GetResult(); + client + .LoginAsync(TokenType.Bot, configuration["Token"]) + .GetAwaiter() + .GetResult(); - client - .StartAsync() - .GetAwaiter() - .GetResult(); + client + .StartAsync() + .GetAwaiter() + .GetResult(); - var listener = services - .BuildServiceProvider() - .GetRequiredService(); + var listener = services + .BuildServiceProvider() + .GetRequiredService(); - listener - .StartAsync() - .GetAwaiter() - .GetResult(); + listener + .StartAsync() + .GetAwaiter() + .GetResult(); - return services; - } + return services; + } - private static Task Client_Ready(DiscordSocketClient client) - { - client.RegisterCommands(); + private static Task Client_Ready(DiscordSocketClient client) + { + client.RegisterCommands(); - new VoiceChannelMonitorService(client).StartMonitoring(); - return Task.CompletedTask; - } + new VoiceChannelMonitorService(client).StartMonitoring(); + return Task.CompletedTask; + } - private static Task Log(LogMessage arg) - { - Console.WriteLine(arg); - return Task.CompletedTask; - } + private static Task Log(LogMessage arg) + { + Console.WriteLine(arg); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Bot/Registration/HangfireRegistration.cs b/Bot/Registration/HangfireRegistration.cs new file mode 100644 index 0000000..95b136c --- /dev/null +++ b/Bot/Registration/HangfireRegistration.cs @@ -0,0 +1,19 @@ +using Hangfire; + +namespace Lunaris2.Registration; + +public static class HangfireRegistration +{ + public static IApplicationBuilder UseHangfireDashboardAndServer(this IApplicationBuilder app, string dashboardPath = "/hangfire") + { + var dashboardOptions = new DashboardOptions + { + DarkModeEnabled = true, + DashboardTitle = "Lunaris Jobs Dashboard" + }; + + app.UseHangfireDashboard(dashboardPath, dashboardOptions); + + return app; + } +} \ No newline at end of file diff --git a/Bot/Registration/MusicPlayerRegistration.cs b/Bot/Registration/MusicPlayerRegistration.cs index ac0d2f1..8f5f678 100644 --- a/Bot/Registration/MusicPlayerRegistration.cs +++ b/Bot/Registration/MusicPlayerRegistration.cs @@ -1,8 +1,6 @@ using Lavalink4NET.Extensions; using Lunaris2.Handler.MusicPlayer; using Lunaris2.Service; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; namespace Lunaris2.Registration; diff --git a/Bot/Registration/SchedulerRegistration.cs b/Bot/Registration/SchedulerRegistration.cs new file mode 100644 index 0000000..fdfb392 --- /dev/null +++ b/Bot/Registration/SchedulerRegistration.cs @@ -0,0 +1,26 @@ +using Hangfire; +using Hangfire.AspNetCore; +using Lunaris2.Handler.Scheduler; + +namespace Lunaris2.Registration; + +public static class SchedulerRegistration +{ + public static IServiceCollection AddScheduler(this IServiceCollection services, IConfiguration configuration) + { + services.AddHangfire((serviceProvider, config) => + { + config.SetDataCompatibilityLevel(CompatibilityLevel.Version_180) + .UseSimpleAssemblyNameTypeSerializer(); + + config.UseSqlServerStorage(configuration.GetValue("HangfireConnectionString")); + }); + + services.AddHangfireServer(); + + // Register your handler + // services.AddScoped(); + + return services; + } +} \ No newline at end of file diff --git a/Bot/Service/VoiceChannelMonitorService.cs b/Bot/Service/VoiceChannelMonitorService.cs index 7ba695e..7c43732 100644 --- a/Bot/Service/VoiceChannelMonitorService.cs +++ b/Bot/Service/VoiceChannelMonitorService.cs @@ -1,96 +1,95 @@ using Discord; using Discord.WebSocket; -namespace Lunaris2.Service +namespace Lunaris2.Service; + +public class VoiceChannelMonitorService { - public class VoiceChannelMonitorService + private readonly DiscordSocketClient _client; + private readonly Dictionary _timers = new(); + + public VoiceChannelMonitorService(DiscordSocketClient client) { - private readonly DiscordSocketClient _client; - private readonly Dictionary _timers = new(); + _client = client; + } - public VoiceChannelMonitorService(DiscordSocketClient client) + public void StartMonitoring() + { + Task.Run(async () => { - _client = client; - } - - public void StartMonitoring() - { - Task.Run(async () => + while (true) { - while (true) - { - await CheckVoiceChannels(); - await Task.Delay(TimeSpan.FromMinutes(1)); // Monitor every minute - } - }); - } + await CheckVoiceChannels(); + await Task.Delay(TimeSpan.FromMinutes(1)); // Monitor every minute + } + }); + } - private async Task CheckVoiceChannels() - { - SetStatus(); - await LeaveOnAlone(); - } + private async Task CheckVoiceChannels() + { + SetStatus(); + await LeaveOnAlone(); + } - private void SetStatus() - { - var channels = _client.Guilds - .SelectMany(guild => guild.VoiceChannels) - .Count(channel => - channel.ConnectedUsers - .Any(guildUser => guildUser.Id == _client.CurrentUser.Id) && - channel.Users.Count > 1 - ); + private void SetStatus() + { + var channels = _client.Guilds + .SelectMany(guild => guild.VoiceChannels) + .Count(channel => + channel.ConnectedUsers + .Any(guildUser => guildUser.Id == _client.CurrentUser.Id) && + channel.Users.Count > 1 + ); - if (channels == 0) - _client.SetGameAsync(System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version?.ToString(), type: ActivityType.CustomStatus); - else if(channels == 1) - _client.SetGameAsync("in 1 server", type: ActivityType.Playing); - else if(channels > 1) - _client.SetGameAsync($" in {channels} servers", type: ActivityType.Playing); - } + if (channels == 0) + _client.SetGameAsync(System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version?.ToString(), type: ActivityType.CustomStatus); + else if(channels == 1) + _client.SetGameAsync("in 1 server", type: ActivityType.Playing); + else if(channels > 1) + _client.SetGameAsync($" in {channels} servers", type: ActivityType.Playing); + } - private async Task LeaveOnAlone() + private async Task LeaveOnAlone() + { + foreach (var guild in _client.Guilds) { - foreach (var guild in _client.Guilds) - { - // Find voice channels where only the bot is left - var voiceChannel = guild.VoiceChannels.FirstOrDefault(vc => - vc.ConnectedUsers.Count == 1 && - vc.Users.Any(u => u.Id == _client.CurrentUser.Id)); + // Find voice channels where only the bot is left + var voiceChannel = guild.VoiceChannels.FirstOrDefault(vc => + vc.ConnectedUsers.Count == 1 && + vc.Users.Any(u => u.Id == _client.CurrentUser.Id)); - if (voiceChannel != null) + if (voiceChannel != null) + { + // If timer not set for this channel, start one + if (!_timers.ContainsKey(voiceChannel.Id)) { - // If timer not set for this channel, start one - if (!_timers.ContainsKey(voiceChannel.Id)) - { - Console.WriteLine($"Bot is alone in channel {voiceChannel.Name}, starting timer..."); - _timers[voiceChannel.Id] = new Timer(async _ => await LeaveChannel(voiceChannel), null, - TimeSpan.FromMinutes(3), Timeout.InfiniteTimeSpan); // Set delay before leaving - } - } - else - { - // Clean up timer if channel is no longer active - var timersToDispose = _timers.Where(t => guild.VoiceChannels.All(vc => vc.Id != t.Key)).ToList(); - foreach (var timer in timersToDispose) - { - await timer.Value.DisposeAsync(); - _timers.Remove(timer.Key); - Console.WriteLine($"Disposed timer for inactive voice channel ID: {timer.Key}"); - } + Console.WriteLine($"Bot is alone in channel {voiceChannel.Name}, starting timer..."); + _timers[voiceChannel.Id] = new Timer(async _ => await LeaveChannel(voiceChannel), null, + TimeSpan.FromMinutes(3), Timeout.InfiniteTimeSpan); // Set delay before leaving } } - } - - private async Task LeaveChannel(SocketVoiceChannel voiceChannel) - { - if (voiceChannel.ConnectedUsers.Count == 1 && voiceChannel.Users.Any(u => u.Id == _client.CurrentUser.Id)) + else { - Console.WriteLine($"Leaving channel {voiceChannel.Name} due to inactivity..."); - await voiceChannel.DisconnectAsync(); - await _timers[voiceChannel.Id].DisposeAsync(); - _timers.Remove(voiceChannel.Id); // Clean up after leaving + // Clean up timer if channel is no longer active + var timersToDispose = _timers.Where(t => guild.VoiceChannels.All(vc => vc.Id != t.Key)).ToList(); + foreach (var timer in timersToDispose) + { + await timer.Value.DisposeAsync(); + _timers.Remove(timer.Key); + Console.WriteLine($"Disposed timer for inactive voice channel ID: {timer.Key}"); + } } } } -} + + private async Task LeaveChannel(SocketVoiceChannel voiceChannel) + { + if (voiceChannel.ConnectedUsers.Count == 1 && voiceChannel.Users.Any(u => u.Id == _client.CurrentUser.Id)) + { + Console.WriteLine($"Leaving channel {voiceChannel.Name} due to inactivity..."); + await voiceChannel.DisconnectAsync(); + await _timers[voiceChannel.Id].DisposeAsync(); + _timers.Remove(voiceChannel.Id); // Clean up after leaving + } + } +} \ No newline at end of file diff --git a/Bot/SlashCommand/Command.cs b/Bot/SlashCommand/Command.cs index 99d9f04..b270974 100644 --- a/Bot/SlashCommand/Command.cs +++ b/Bot/SlashCommand/Command.cs @@ -5,6 +5,9 @@ namespace Lunaris2.SlashCommand; public static class Option { public const string Input = "input"; + public const string Time = "time"; + public const string IsRecurring = "repeating"; + public const string Message = "message"; } public static class Command @@ -56,6 +59,38 @@ public static class Command }; } + public static class Scheduler + { + public const string Name = "scheduler"; + public const string Description = "Schedule a message"; + + public static readonly List? Options = + [ + new SlashCommandOptionBuilder + { + Name = "message", + Description = "The message you want to schedule", + Type = ApplicationCommandOptionType.String, + IsRequired = true + }, + new SlashCommandOptionBuilder + { + Name = "time", + Description = "The time you want to schedule the message", + Type = ApplicationCommandOptionType.String, + IsRequired = true + }, + new SlashCommandOptionBuilder + { + Name = "repeating", + Description = "Whether the message should repeat", + Type = ApplicationCommandOptionType.Boolean, + IsRequired = false, + IsDefault = false + } + ]; + } + public static string[] GetAllCommands() { return typeof(Command) diff --git a/Bot/SlashCommand/SlashCommandRegistration.cs b/Bot/SlashCommand/SlashCommandRegistration.cs index 2c20b02..0262ef2 100644 --- a/Bot/SlashCommand/SlashCommandRegistration.cs +++ b/Bot/SlashCommand/SlashCommandRegistration.cs @@ -14,6 +14,7 @@ public static class SlashCommandRegistration RegisterCommand(client, Command.Play.Name, Command.Play.Description, Command.Play.Options); RegisterCommand(client, Command.Resume.Name, Command.Resume.Description); RegisterCommand(client, Command.Clear.Name, Command.Clear.Description); + RegisterCommand(client, Command.Scheduler.Name, Command.Scheduler.Description, Command.Scheduler.Options); } private static void RegisterCommand( diff --git a/Bot/appsettings.json b/Bot/appsettings.json index ddb7503..2966a8e 100644 --- a/Bot/appsettings.json +++ b/Bot/appsettings.json @@ -19,5 +19,6 @@ "instruction": "Let's role play. Your name is Lunaris and can be quite rude. You are supposed to answer on following question with a maximum of 2 sentences:" } ] - } + }, + "HangfireConnectionString": "Server=localhost, 1433;Database=Hangfire;User Id=sa;Password=SecretPassword!; TrustServerCertificate=True;" } diff --git a/Bot/wwwroot/index.html b/Bot/wwwroot/index.html new file mode 100644 index 0000000..ed1d1bc --- /dev/null +++ b/Bot/wwwroot/index.html @@ -0,0 +1,55 @@ + + + + + + Logotype Page + + + +
+
+ Logotype +
+ Go to Hangfire +
+ + + + diff --git a/LOGOTYPE.png b/Bot/wwwroot/logotype.png similarity index 100% rename from LOGOTYPE.png rename to Bot/wwwroot/logotype.png diff --git a/README.md b/README.md index 3f19087..c2650e2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Lunaris Logotype](https://github.com/Myxelium/Lunaris2.0/blob/master/LOGOTYPE.png?raw=true) +![Lunaris Logotype](./Bot/wwwroot/logotype.png) # Lunaris - Discord BOT diff --git a/docker-compose.yml b/docker-compose.yml index 0db7e97..58ad462 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,6 +64,15 @@ services: networks: - ollama-docker + mssql: + image: mcr.microsoft.com/mssql/server:2019-latest + container_name: mssql + environment: + SA_PASSWORD: "SecretPassword!" + ACCEPT_EULA: "Y" + ports: + - "1433:1433" + volumes: ollama: {}