Add scheduler

This commit is contained in:
Myx
2025-02-17 03:54:18 +01:00
parent 5726c110a1
commit ae1a4e14d6
22 changed files with 535 additions and 220 deletions

View File

@@ -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<ChatCommand>
{
public record ChatCommand(SocketMessage Message, string FilteredMessage) : IRequest;
private readonly OllamaApiClient _ollama;
private readonly Dictionary<ulong, Chat?> _chatContexts = new();
private readonly ChatSettings _chatSettings;
public class ChatHandler : IRequestHandler<ChatCommand>
public ChatHandler(IOptions<ChatSettings> chatSettings)
{
private readonly OllamaApiClient _ollama;
private readonly Dictionary<ulong, Chat?> _chatContexts = new();
private readonly ChatSettings _chatSettings;
_chatSettings = chatSettings.Value;
var uri = new Uri(chatSettings.Value.Url);
public ChatHandler(IOptions<ChatSettings> chatSettings)
_ollama = new OllamaApiClient(uri)
{
_chatSettings = chatSettings.Value;
var uri = new Uri(chatSettings.Value.Url);
SelectedModel = chatSettings.Value.Model
};
}
_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);
public async Task Handle(ChatCommand command, CancellationToken cancellationToken)
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))
{
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);
await command.Message.Channel.SendMessageAsync("Am I expected to read your mind?");
setTyping.Dispose();
return;
}
private async Task<string> GenerateResponse(string userMessage, ulong channelId, CancellationToken cancellationToken)
var response = await GenerateResponse(userMessage, channelId, cancellationToken);
await command.Message.Channel.SendMessageAsync(response);
setTyping.Dispose();
}
private async Task<string> GenerateResponse(string userMessage, ulong channelId, CancellationToken cancellationToken)
{
var response = new StringBuilder();
if (_chatContexts[channelId] == null)
{
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();
_chatContexts[channelId] = _ollama.Chat(stream => response.Append(stream.Message?.Content ?? ""));
}
await _chatContexts[channelId].Send(userMessage, cancellationToken);
return response.ToString();
}
}

View File

@@ -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<T>(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));
}
}

View File

@@ -85,7 +85,7 @@ public class PlayHandler : IRequestHandler<PlayCommand>
return;
}
var searchQuery = context.GetOptionValueByName(Option.Input);
var searchQuery = context.GetOptionValueByName<string>(Option.Input);
if (string.IsNullOrWhiteSpace(searchQuery))
{

View File

@@ -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<ProcessMessageCommand>
{
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);
}
}

View File

@@ -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<ScheduleMessageCommand>
{
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> 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<string>(Option.Time);
var userMessage = request.Message.GetOptionValueByName<string>(Option.Message);
var recurring = request.Message.GetOptionValueByName<bool>(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<string> 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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
@@ -16,17 +16,25 @@
<PackageReference Include="Discord.Net.Core" Version="3.16.0" />
<PackageReference Include="Discord.Net.Interactions" Version="3.16.0" />
<PackageReference Include="Discord.Net.Rest" Version="3.16.0" />
<PackageReference Include="Hangfire" Version="1.8.17" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.17" />
<PackageReference Include="Hangfire.Core" Version="1.8.18" />
<PackageReference Include="Lavalink4NET" Version="4.0.25" />
<PackageReference Include="Lavalink4NET.Artwork" Version="4.0.25" />
<PackageReference Include="Lavalink4NET.Discord.NET" Version="4.0.25" />
<PackageReference Include="Lavalink4NET.Integrations.Lavasrc" Version="4.0.25" />
<PackageReference Include="Lavalink4NET.Integrations.SponsorBlock" Version="4.0.25" />
<PackageReference Include="MediatR" Version="12.4.1" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.3.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="NCrontab" Version="3.3.3" />
<PackageReference Include="OllamaSharp" Version="1.1.10" />
</ItemGroup>
@@ -37,6 +45,12 @@
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Resource Include="wwwroot\index.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="wwwroot\logotype.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
</ItemGroup>
</Project>

View File

@@ -1,6 +1,5 @@
using Discord.WebSocket;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace Lunaris2.Notification;

View File

@@ -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();
// Build configuration (using appsettings.json)
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json")
.Build();
app.UseSponsorBlock();
app.Run();
}
// Register your services
builder.Services.AddDiscordBot(configuration);
builder.Services.AddScheduler(configuration);
builder.Services.AddControllers();
private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
var configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json")
.Build();
var app = builder.Build();
services.AddDiscordBot(configuration);
});
}
// Call your custom middleware (e.g., for SponsorBlock functionality)
app.UseSponsorBlock();
// Serve static files
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHangfireDashboardAndServer();
app.Run();

View File

@@ -1,6 +1,4 @@
using Lunaris2.Handler.ChatCommand;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Lunaris2.Registration;

View File

@@ -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<DiscordEventListener>()
.AddSingleton(service => new InteractionService(service.GetRequiredService<DiscordSocketClient>()))
.AddChat(configuration);
services
.AddMediatR(mediatRServiceConfiguration =>
mediatRServiceConfiguration.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()))
.AddMusicPlayer(configuration)
.AddSingleton(client)
.AddSingleton<DiscordEventListener>()
.AddSingleton(service => new InteractionService(service.GetRequiredService<DiscordSocketClient>()))
.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<DiscordEventListener>();
var listener = services
.BuildServiceProvider()
.GetRequiredService<DiscordEventListener>();
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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<string>("HangfireConnectionString"));
});
services.AddHangfireServer();
// Register your handler
// services.AddScoped<ScheduleMessageHandler>();
return services;
}
}

View File

@@ -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<ulong, Timer> _timers = new();
public VoiceChannelMonitorService(DiscordSocketClient client)
{
private readonly DiscordSocketClient _client;
private readonly Dictionary<ulong, Timer> _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()
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
);
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()
{
foreach (var guild in _client.Guilds)
{
SetStatus();
await LeaveOnAlone();
}
// 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));
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);
}
private async Task LeaveOnAlone()
{
foreach (var guild in _client.Guilds)
if (voiceChannel != null)
{
// 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 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
}
}
}

View File

@@ -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<SlashCommandOptionBuilder>? 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)

View File

@@ -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(

View File

@@ -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;"
}

55
Bot/wwwroot/index.html Normal file
View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Logotype Page</title>
<style>
body {
margin: 0;
height: 100vh;
background-color: #121212; /* Dark background */
display: flex;
justify-content: center;
align-items: center;
color: #d3d3d3; /* Very light gray text */
font-family: Arial, sans-serif;
text-align: center;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
}
.logotype img {
max-width: 100%;
height: auto;
max-height: 200px;
margin-bottom: 20px;
box-shadow: 0px 0px 20px 20px black;
}
a {
color: #d3d3d3;
text-decoration: none;
font-size: 18px;
}
a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="logotype">
<img src="logotype.png" alt="Logotype">
</div>
<a href="/" id="hangfire-link">Go to Hangfire</a>
</div>
<script>
// Update the link dynamically to include the current URL + /hangfire
const currentUrl = window.location.href;
document.getElementById('hangfire-link').href = currentUrl + 'hangfire';
</script>
</body>
</html>

View File

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 230 KiB

View File

@@ -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

View File

@@ -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: {}