Basic bot

This commit is contained in:
Myx
2024-04-11 04:22:49 +02:00
commit 5b996ecd1d
13 changed files with 332 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
obj
.idea
.vs
bin

View File

@@ -0,0 +1,15 @@
using Discord.WebSocket;
using MediatR;
namespace Lunaris2.Handler.GoodByeCommand
{
public record GoodbyeCommand(SocketSlashCommand Message) : IRequest;
public class GoodbyeHandler : IRequestHandler<GoodbyeCommand>
{
public async Task Handle(GoodbyeCommand message, CancellationToken cancellationToken)
{
await message.Message.RespondAsync($"Goodbye, {message.Message.User.Username}! :c");
}
}
}

View File

@@ -0,0 +1,19 @@
using Discord.WebSocket;
using Lunaris2.SlashCommand;
using MediatR;
using Newtonsoft.Json;
namespace Lunaris2.Handler.HelloCommand
{
public record HelloCommand(SocketSlashCommand Message) : IRequest;
public class HelloHandler : IRequestHandler<HelloCommand>
{
public async Task Handle(HelloCommand message, CancellationToken cancellationToken)
{
Console.WriteLine(JsonConvert.SerializeObject(Command.GetAllCommands()));
await message.Message.RespondAsync($"Hello, {message.Message.User.Username}!");
}
}
}

View File

@@ -0,0 +1,24 @@
using Lunaris2.Handler.GoodByeCommand;
using Lunaris2.Notification;
using Lunaris2.SlashCommand;
using MediatR;
namespace Lunaris2.Handler;
public class MessageReceivedHandler(ISender mediator) : INotificationHandler<MessageReceivedNotification>
{
public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken)
{
switch (notification.Message.CommandName)
{
case Command.Hello.Name:
await mediator.Send(new HelloCommand.HelloCommand(notification.Message), cancellationToken);
break;
case Command.Goodbye.Name:
await mediator.Send(new GoodbyeCommand(notification.Message), cancellationToken);
break;
default:
break;
}
}
}

28
Lunaris2.csproj Normal file
View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.14.1" />
<PackageReference Include="Discord.Net.Commands" Version="3.14.1" />
<PackageReference Include="Discord.Net.Core" Version="3.14.1" />
<PackageReference Include="Discord.Net.Interactions" Version="3.14.1" />
<PackageReference Include="Discord.Net.Rest" Version="3.14.1" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

25
Lunaris2.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34701.34
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lunaris2", "Lunaris2.csproj", "{3A16FD4C-EE50-4B05-9A75-0DDCD93AA615}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3A16FD4C-EE50-4B05-9A75-0DDCD93AA615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A16FD4C-EE50-4B05-9A75-0DDCD93AA615}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A16FD4C-EE50-4B05-9A75-0DDCD93AA615}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A16FD4C-EE50-4B05-9A75-0DDCD93AA615}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4E738D71-9DB0-456E-9BF2-0CE3FED1DE44}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,31 @@
using Discord.WebSocket;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace Lunaris2.Notification;
public class DiscordEventListener(DiscordSocketClient client, IServiceScopeFactory serviceScope)
{
private readonly CancellationToken _cancellationToken = new CancellationTokenSource().Token;
private IMediator Mediator
{
get
{
var scope = serviceScope.CreateScope();
return scope.ServiceProvider.GetRequiredService<IMediator>();
}
}
public async Task StartAsync()
{
client.SlashCommandExecuted += OnMessageReceivedAsync;
await Task.CompletedTask;
}
private Task OnMessageReceivedAsync(SocketSlashCommand arg)
{
return Mediator.Publish(new MessageReceivedNotification(arg), _cancellationToken);
}
}

View File

@@ -0,0 +1,9 @@
using Discord.WebSocket;
using MediatR;
namespace Lunaris2.Notification;
public class MessageReceivedNotification(SocketSlashCommand message) : INotification
{
public SocketSlashCommand Message { get; } = message ?? throw new ArgumentNullException(nameof(message));
}

68
Program.cs Normal file
View File

@@ -0,0 +1,68 @@
using System.Reflection;
using Discord;
using Discord.Commands;
using Discord.Interactions;
using Discord.WebSocket;
using Lunaris2.Notification;
using Lunaris2.SlashCommand;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Lunaris2;
public class Program
{
private DiscordSocketClient? _client;
private CommandService? _commands;
private IServiceProvider? _services;
private IConfiguration? _config;
private static void Main(string[] args) => new Program()
.RunBotAsync()
.GetAwaiter()
.GetResult();
private async Task RunBotAsync()
{
var config = new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.All
};
_client = new DiscordSocketClient(config);
_client.Ready += Client_Ready;
_commands = new CommandService();
_services = new ServiceCollection()
.AddSingleton(_client)
.AddSingleton(_commands)
.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()))
.AddSingleton<DiscordEventListener>()
.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>()))
.BuildServiceProvider();
_client.Log += Log;
_config = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json")
.Build();
await _client.LoginAsync(TokenType.Bot, _config["Token"]);
await _client.StartAsync();
var listener = _services.GetRequiredService<DiscordEventListener>();
await listener.StartAsync();
await Task.Delay(Timeout.Infinite);
}
private async Task Client_Ready()
{
_client.RegisterCommands();
}
private Task Log(LogMessage arg)
{
Console.WriteLine(arg);
return Task.CompletedTask;
}
}

24
SlashCommand/Command.cs Normal file
View File

@@ -0,0 +1,24 @@
namespace Lunaris2.SlashCommand;
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 string[] GetAllCommands()
{
return typeof(Command)
.GetNestedTypes()
.Select(command => command.GetField("Name")?.GetValue(null)?.ToString())
.ToArray()!;
}
}

View File

@@ -0,0 +1,57 @@
using Discord.Net;
using Discord.WebSocket;
using Newtonsoft.Json;
namespace Lunaris2.SlashCommand;
public class SlashCommandBuilder(string commandName, string commandDescription)
{
private string CommandName { get; set; } = commandName;
private string CommandDescription { get; set; } = commandDescription;
public async Task CreateSlashCommand(DiscordSocketClient client)
{
var registeredCommands = await client.GetGlobalApplicationCommandsAsync();
await RemoveUnusedCommands(Command.GetAllCommands(), registeredCommands);
if (await CommandExists(registeredCommands))
return;
var globalCommand = new Discord.SlashCommandBuilder();
globalCommand.WithName(CommandName);
globalCommand.WithDescription(CommandDescription);
try
{
await client.CreateGlobalApplicationCommandAsync(globalCommand.Build());
Console.WriteLine($"Command {CommandName} has been registered.");
}
catch(HttpException exception)
{
var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
Console.WriteLine(json);
}
}
private static async Task RemoveUnusedCommands(string[] commands, IEnumerable<SocketApplicationCommand> registeredCommands)
{
// Remove commands from Discord(registeredCommands) that are not in the list of commands
foreach(var command in registeredCommands)
{
if (commands.Contains(command.Name))
continue;
await command.DeleteAsync();
Console.WriteLine($"Command {command.Name} has been removed.");
}
}
private async Task<bool> CommandExists(IEnumerable<SocketApplicationCommand> registeredCommands)
{
if (!registeredCommands.Any(command => command.Name == CommandName && command.Description == CommandDescription))
return false;
Console.WriteLine($"Command {CommandName} already exists.");
return true;
}
}

View File

@@ -0,0 +1,18 @@
using Discord.WebSocket;
namespace Lunaris2.SlashCommand;
public static class SlashCommandRegistration
{
public static void RegisterCommands(this DiscordSocketClient client)
{
RegisterCommand(client, Command.Hello.Name, Command.Hello.Description);
RegisterCommand(client, Command.Goodbye.Name, Command.Goodbye.Description);
}
private static void RegisterCommand(DiscordSocketClient client, string commandName, string commandDescription)
{
var command = new SlashCommandBuilder(commandName, commandDescription);
_ = command.CreateSlashCommand(client);
}
}

10
appsettings.json Normal file
View File

@@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"Token": "YOUR_BOT_TOKEN"
}