From 117c980240b1f2e722da5754bce8b0e3985ecf08 Mon Sep 17 00:00:00 2001 From: ModZero Date: Fri, 13 Oct 2023 19:50:54 +0200 Subject: [PATCH] Add a scaffold for a command --- .gitignore | 5 +- GodReplacementProduct/GodReplacementBot.cs | 33 +++++ GodReplacementProduct/GodReplacementLogger.cs | 32 +++++ GodReplacementProduct/InteractionHandler.cs | 79 ++++++++++++ GodReplacementProduct/Modules/FactChecker.cs | 13 ++ GodReplacementProduct/Program.cs | 121 +++++++++--------- 6 files changed, 223 insertions(+), 60 deletions(-) create mode 100644 GodReplacementProduct/GodReplacementBot.cs create mode 100644 GodReplacementProduct/GodReplacementLogger.cs create mode 100644 GodReplacementProduct/InteractionHandler.cs create mode 100644 GodReplacementProduct/Modules/FactChecker.cs diff --git a/.gitignore b/.gitignore index 9b809e8..dfe344c 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,7 @@ msbuild.err msbuild.wrn # Visual Studio 2015 -.vs/ \ No newline at end of file +.vs/ + +# Local configuration +appsettings.Development.json \ No newline at end of file diff --git a/GodReplacementProduct/GodReplacementBot.cs b/GodReplacementProduct/GodReplacementBot.cs new file mode 100644 index 0000000..82aacca --- /dev/null +++ b/GodReplacementProduct/GodReplacementBot.cs @@ -0,0 +1,33 @@ +using Discord; +using Discord.WebSocket; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace GodReplacementProduct; + +public sealed class GodReplacementBot : IHostedService +{ + private readonly IConfiguration _config; + private readonly DiscordSocketClient _client; + + public GodReplacementBot( + IConfiguration config, + DiscordSocketClient client + ) + { + _config = config; + _client = client; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + var token = _config.GetValue("GodReplacementProject:DiscordToken"); + await _client.LoginAsync(TokenType.Bot, token); + await _client.StartAsync(); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await _client.StopAsync(); + } +} diff --git a/GodReplacementProduct/GodReplacementLogger.cs b/GodReplacementProduct/GodReplacementLogger.cs new file mode 100644 index 0000000..387366b --- /dev/null +++ b/GodReplacementProduct/GodReplacementLogger.cs @@ -0,0 +1,32 @@ +using Discord; +using Microsoft.Extensions.Logging; + +namespace GodReplacementProduct; + +public class GodReplacementLogger +{ + private readonly ILogger _logger; + + public GodReplacementLogger(ILogger logger) + { + _logger = logger; + } + + public Task LogAsync(LogMessage message) + { + var severity = message.Severity switch + { + LogSeverity.Critical => LogLevel.Critical, + LogSeverity.Error => LogLevel.Error, + LogSeverity.Warning => LogLevel.Warning, + LogSeverity.Info => LogLevel.Information, + LogSeverity.Verbose => LogLevel.Debug, + LogSeverity.Debug => LogLevel.Trace, + _ => LogLevel.Information, + }; + + _logger.Log(severity, message.Exception, "[{Source}] {Message}", message.Source, message.Message); + + return Task.CompletedTask; + } +} diff --git a/GodReplacementProduct/InteractionHandler.cs b/GodReplacementProduct/InteractionHandler.cs new file mode 100644 index 0000000..f9af2fa --- /dev/null +++ b/GodReplacementProduct/InteractionHandler.cs @@ -0,0 +1,79 @@ +using System.Reflection; +using Discord; +using Discord.Interactions; +using Discord.WebSocket; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace GodReplacementProduct; + +public class InteractionHandler : IHostedService { + private readonly DiscordSocketClient _client; + private readonly InteractionService _handler; + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public InteractionHandler(DiscordSocketClient client, InteractionService handler, IServiceProvider services, ILogger logger) + { + _client = client; + _handler = handler; + _services = services; + _logger = logger; + } + + public async Task StartAsync(CancellationToken cancellationToken) { + _client.Ready += ReadyAsync; + await _handler.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + _client.InteractionCreated += HandleInteraction; + } + + private async Task ReadyAsync() + { + var options = _services.GetService>(); + if (options is not null) { + _logger.LogInformation("Development mode, registering commands to guild {0}", options.Value.TestGuildId); + await _handler.RegisterCommandsToGuildAsync(options.Value.TestGuildId, true); + } else { + _logger.LogInformation("Registering commands globally"); + await _handler.RegisterCommandsGloballyAsync(true); + } + } + + private async Task HandleInteraction(SocketInteraction interaction) + { + try + { + // Create an execution context that matches the generic type parameter of your InteractionModuleBase modules. + var context = new SocketInteractionContext(_client, interaction); + + // Execute the incoming command. + var result = await _handler.ExecuteCommandAsync(context, _services); + + if (!result.IsSuccess) + switch (result.Error) + { + case InteractionCommandError.UnmetPrecondition: + break; + default: + break; + } + } + catch + { + // If Slash Command execution fails it is most likely that the original interaction acknowledgement will persist. It is a good idea to delete the original + // response, or at least let the user know that something went wrong during the command execution. + if (interaction.Type is InteractionType.ApplicationCommand) + await interaction.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync()); + } + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _client.Ready -= ReadyAsync; + _client.InteractionCreated -= HandleInteraction; + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/GodReplacementProduct/Modules/FactChecker.cs b/GodReplacementProduct/Modules/FactChecker.cs new file mode 100644 index 0000000..0b6ec1f --- /dev/null +++ b/GodReplacementProduct/Modules/FactChecker.cs @@ -0,0 +1,13 @@ +using Discord.Interactions; + +namespace GodReplacementProduct.Modules; + +public class FactChecker : InteractionModuleBase +{ + + + [SlashCommand("set_fact", "Record a fact for posterity")] + public async Task SetFact(string fact_name, string fact_value) => + await RespondAsync($"{fact_name} - {fact_value}", ephemeral: true); +} + diff --git a/GodReplacementProduct/Program.cs b/GodReplacementProduct/Program.cs index ca4def4..7272cd3 100644 --- a/GodReplacementProduct/Program.cs +++ b/GodReplacementProduct/Program.cs @@ -1,9 +1,8 @@ -using Discord; +using Discord.Interactions; using Discord.WebSocket; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Serilog; using Serilog.Events; @@ -38,67 +37,71 @@ public class Program } public static IHost BuildHost(string[] args) => new HostBuilder() - .ConfigureAppConfiguration((context) => context - .AddUserSecrets() - .Build() - ) - .ConfigureServices(services => services - .AddSingleton() - .AddHostedService() - ) - .UseSerilog((context, services, loggerConfiguration) => loggerConfiguration + .ConfigureDefaults(args) + .ConfigureAppConfiguration((context, builder) => + { + builder + .AddEnvironmentVariables(prefix: "DC_") + .AddEnvironmentVariables("DOTNET_") + .AddJsonFile("appsettings.json", optional: true) + .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true); + + if (context.HostingEnvironment.IsDevelopment()) + { + builder.AddUserSecrets(); + + } + + builder.Build(); + }) + .ConfigureServices((context, services) => + { + services + .AddSingleton() + .AddSingleton(provider => + { + var logger = provider.GetRequiredService(); + var client = new DiscordSocketClient(); + client.Log += logger.LogAsync; + + return client; + }) + .AddSingleton(provider => + { + var logger = provider.GetRequiredService(); + var interactions = new InteractionService(provider.GetService()); + interactions.Log += logger.LogAsync; + + return interactions; + }) + .AddHostedService() + .AddHostedService(); + + if (context.HostingEnvironment.IsDevelopment()) + { + services.Configure(context.Configuration.GetSection(DevelopmentOptions.Development)); + } + }) + .UseSerilog((context, services, loggerConfiguration) => + { + loggerConfiguration + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft", LogEventLevel.Information); + if (context.HostingEnvironment.IsDevelopment()) { + loggerConfiguration.MinimumLevel.Debug(); + } + loggerConfiguration .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext() - .WriteTo.Console()) + .WriteTo.Console(); + + }) .Build(); } -public sealed class GodReplacementBot : IHostedService +public class DevelopmentOptions { - private readonly ILogger _logger; - private readonly IConfiguration _config; - private readonly DiscordSocketClient _client; + public const string Development = "Development"; - public GodReplacementBot( - ILogger logger, - IConfiguration config, - DiscordSocketClient client - ) - { - _config = config; - _client = client; - _logger = logger; - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - var token = _config.GetValue("GodReplacementProject:DiscordToken"); - _client.Log += LogAsync; - - await _client.LoginAsync(TokenType.Bot, token); - await _client.StartAsync(); - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - await _client.StopAsync(); - } - - private Task LogAsync(LogMessage message) { - var severity = message.Severity switch - { - LogSeverity.Critical => LogLevel.Critical, - LogSeverity.Error => LogLevel.Error, - LogSeverity.Warning => LogLevel.Warning, - LogSeverity.Info => LogLevel.Information, - LogSeverity.Verbose => LogLevel.Debug, - LogSeverity.Debug => LogLevel.Trace, - _ => LogLevel.Information, - }; - - _logger.Log(severity, message.Exception, "[{Source}] {Message}", message.Source, message.Message); - - return Task.CompletedTask; - } - -} + public ulong TestGuildId { get; set; } +} \ No newline at end of file