From c1e8a797cd4f3a0e6add15f19cfebcc8531a79a7 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 7 Nov 2024 22:37:28 +0900 Subject: [PATCH 1/7] Add godbolt command --- src/Modix.Bot/Modules/GodboltModule.cs | 75 +++++++++++++++++++ src/Modix.Services/Godbolt/AssemblyFilters.cs | 15 ++++ src/Modix.Services/Godbolt/CompileRequest.cs | 9 +++ src/Modix.Services/Godbolt/CompilerOptions.cs | 9 +++ src/Modix.Services/Godbolt/GodboltService.cs | 51 +++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 2 + 6 files changed, 161 insertions(+) create mode 100644 src/Modix.Bot/Modules/GodboltModule.cs create mode 100644 src/Modix.Services/Godbolt/AssemblyFilters.cs create mode 100644 src/Modix.Services/Godbolt/CompileRequest.cs create mode 100644 src/Modix.Services/Godbolt/CompilerOptions.cs create mode 100644 src/Modix.Services/Godbolt/GodboltService.cs diff --git a/src/Modix.Bot/Modules/GodboltModule.cs b/src/Modix.Bot/Modules/GodboltModule.cs new file mode 100644 index 000000000..fb0cca07d --- /dev/null +++ b/src/Modix.Bot/Modules/GodboltModule.cs @@ -0,0 +1,75 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using Discord; +using Discord.Interactions; +using Modix.Services.AutoRemoveMessage; +using Modix.Services.CommandHelp; +using Modix.Services.Godbolt; + +namespace Modix.Bot.Modules +{ + [ModuleHelp("Godbolt", "Commands for working with Godbolt.")] + public class GodboltModule : InteractionModuleBase + { + private readonly IAutoRemoveMessageService _autoRemoveMessageService; + private readonly GodboltService _godboltService; + + public GodboltModule(IAutoRemoveMessageService autoRemoveMessageService, GodboltService godboltService) + { + _autoRemoveMessageService = autoRemoveMessageService; + _godboltService = godboltService; + } + + [SlashCommand("godbolt", "Compile and disassemble the JIT code.")] + public async Task DisasmAsync( + [Summary(description: "The code to compiler.")] string code, + [Summary(description: "The language of code.")] Language language = Language.CSharp, + [Summary(description: "Arguments to pass to the compiler.")] string arguments = "", + [Summary(description: "Execute the code.")] bool execute = false) + { + var langId = language switch + { + Language.CSharp => "csharp", + Language.FSharp => "fsharp", + Language.VisualBasic => "vb", + Language.IL => "il", + _ => throw new ArgumentOutOfRangeException(nameof(language)) + }; + + try + { + var response = $""" + ```asm + {await _godboltService.CompileAsync(code, langId, arguments, execute)} + ``` + """; + + if (response.Length > DiscordConfig.MaxMessageSize) + { + await FollowupAsync("Error: The disassembly code is too long to be converted to a message."); + return; + } + + await FollowupAsync(text: response); + } + catch (Exception ex) + { + await FollowupAsync($"Error: {ex}"); + return; + } + } + + public enum Language + { + [ChoiceDisplay("C#")] + CSharp, + [ChoiceDisplay("F#")] + FSharp, + [ChoiceDisplay("VB.NET")] + VisualBasic, + [ChoiceDisplay("IL")] + IL + } + } +} diff --git a/src/Modix.Services/Godbolt/AssemblyFilters.cs b/src/Modix.Services/Godbolt/AssemblyFilters.cs new file mode 100644 index 000000000..df05fb91d --- /dev/null +++ b/src/Modix.Services/Godbolt/AssemblyFilters.cs @@ -0,0 +1,15 @@ +#nullable enable +namespace Modix.Services.Godbolt +{ + internal record class AssemblyFilters + { + public bool Binary { get; set; } = true; + public bool CommentOnly { get; set; } = true; + public bool Directives { get; set; } = true; + public bool Labels { get; set; } = true; + public bool Trim { get; set; } = true; + public bool Demangle { get; set; } = true; + public bool Intel { get; set; } = true; + public bool Execute { get; set; } = false; + } +} diff --git a/src/Modix.Services/Godbolt/CompileRequest.cs b/src/Modix.Services/Godbolt/CompileRequest.cs new file mode 100644 index 000000000..21642c6a1 --- /dev/null +++ b/src/Modix.Services/Godbolt/CompileRequest.cs @@ -0,0 +1,9 @@ +#nullable enable +namespace Modix.Services.Godbolt +{ + internal record class CompileRequest(string Lang, string Source) + { + public string Compiler { get; } = $"dotnettrunk{Lang}coreclr"; + public CompilerOptions Options { get; } = new(); + } +} diff --git a/src/Modix.Services/Godbolt/CompilerOptions.cs b/src/Modix.Services/Godbolt/CompilerOptions.cs new file mode 100644 index 000000000..624d7b755 --- /dev/null +++ b/src/Modix.Services/Godbolt/CompilerOptions.cs @@ -0,0 +1,9 @@ +#nullable enable +namespace Modix.Services.Godbolt +{ + internal record class CompilerOptions + { + public string UserArguments { get; set; } = ""; + public AssemblyFilters Filters { get; } = new(); + } +} diff --git a/src/Modix.Services/Godbolt/GodboltService.cs b/src/Modix.Services/Godbolt/GodboltService.cs new file mode 100644 index 000000000..e20896a78 --- /dev/null +++ b/src/Modix.Services/Godbolt/GodboltService.cs @@ -0,0 +1,51 @@ +#nullable enable +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Modix.Services.Godbolt +{ + public class GodboltService + { + private static readonly JsonSerializerOptions _jsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + private const string GodboltApiScheme = "https://godbolt.org/api/compiler/dotnettrunk{0}coreclr/compile"; + + public GodboltService(IHttpClientFactory httpClientFactory) + { + HttpClientFactory = httpClientFactory; + } + + public async Task CompileAsync(string code, string lang, string arguments, bool execute) + { + var request = new CompileRequest(lang, code); + + if (!string.IsNullOrWhiteSpace(arguments)) + { + request.Options.UserArguments = arguments; + } + + if (execute) + { + request.Options.Filters.Execute = true; + } + + var requestJson = JsonSerializer.Serialize(request, _jsonOptions); + + var client = HttpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Add("Accept", "text/plain"); + + var response = await client.PostAsync(string.Format(GodboltApiScheme, lang), new StringContent(requestJson, Encoding.UTF8, "application/json")); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Godbolt returns '{response.StatusCode}' for the request."); + } + + return await response.Content.ReadAsStringAsync(); + } + + protected IHttpClientFactory HttpClientFactory { get; } + } + +} diff --git a/src/Modix/Extensions/ServiceCollectionExtensions.cs b/src/Modix/Extensions/ServiceCollectionExtensions.cs index 0ad55a812..aca28ef51 100644 --- a/src/Modix/Extensions/ServiceCollectionExtensions.cs +++ b/src/Modix/Extensions/ServiceCollectionExtensions.cs @@ -27,6 +27,7 @@ using Modix.Services.Core; using Modix.Services.Csharp; using Modix.Services.EmojiStats; +using Modix.Services.Godbolt; using Modix.Services.GuildStats; using Modix.Services.Images; using Modix.Services.Moderation; @@ -174,6 +175,7 @@ public static IServiceCollection AddModix( services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped, PromotionLoggingHandler>(); From 78236474d27d535b2465125b60e648fa4b7e3b8c Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 7 Nov 2024 22:42:18 +0900 Subject: [PATCH 2/7] Remove unused service --- src/Modix.Bot/Modules/GodboltModule.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Modix.Bot/Modules/GodboltModule.cs b/src/Modix.Bot/Modules/GodboltModule.cs index fb0cca07d..4725c9390 100644 --- a/src/Modix.Bot/Modules/GodboltModule.cs +++ b/src/Modix.Bot/Modules/GodboltModule.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Discord; using Discord.Interactions; -using Modix.Services.AutoRemoveMessage; using Modix.Services.CommandHelp; using Modix.Services.Godbolt; @@ -12,12 +11,10 @@ namespace Modix.Bot.Modules [ModuleHelp("Godbolt", "Commands for working with Godbolt.")] public class GodboltModule : InteractionModuleBase { - private readonly IAutoRemoveMessageService _autoRemoveMessageService; private readonly GodboltService _godboltService; - public GodboltModule(IAutoRemoveMessageService autoRemoveMessageService, GodboltService godboltService) + public GodboltModule(GodboltService godboltService) { - _autoRemoveMessageService = autoRemoveMessageService; _godboltService = godboltService; } From c41a6c59c3a6919f472309db45e75ddfe2e20097 Mon Sep 17 00:00:00 2001 From: Steven He Date: Thu, 7 Nov 2024 22:49:43 +0900 Subject: [PATCH 3/7] Nit --- src/Modix.Services/Godbolt/GodboltService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Modix.Services/Godbolt/GodboltService.cs b/src/Modix.Services/Godbolt/GodboltService.cs index e20896a78..7d6872f6e 100644 --- a/src/Modix.Services/Godbolt/GodboltService.cs +++ b/src/Modix.Services/Godbolt/GodboltService.cs @@ -47,5 +47,4 @@ public async Task CompileAsync(string code, string lang, string argument protected IHttpClientFactory HttpClientFactory { get; } } - } From c08612e6de4f1c6fe915261f26c45cbc08c9d530 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sat, 9 Nov 2024 03:36:25 +0900 Subject: [PATCH 4/7] Use modal instead of inline parameters --- src/Modix.Bot/Modules/GodboltDisasmModal.cs | 25 ++++++++++ src/Modix.Bot/Modules/GodboltModule.cs | 46 ++++++------------- src/Modix.Services/Godbolt/AssemblyFilters.cs | 2 +- src/Modix.Services/Godbolt/GodboltService.cs | 7 +-- 4 files changed, 42 insertions(+), 38 deletions(-) create mode 100644 src/Modix.Bot/Modules/GodboltDisasmModal.cs diff --git a/src/Modix.Bot/Modules/GodboltDisasmModal.cs b/src/Modix.Bot/Modules/GodboltDisasmModal.cs new file mode 100644 index 000000000..682865974 --- /dev/null +++ b/src/Modix.Bot/Modules/GodboltDisasmModal.cs @@ -0,0 +1,25 @@ +#nullable enable +using Discord; +using Discord.Interactions; + +namespace Modix.Bot.Modules +{ + public class GodboltDisasmModal : IModal + { + public string Title => "Godbolt"; + + [InputLabel("Code")] + [ModalTextInput("code", TextInputStyle.Paragraph, placeholder: "System.Console.WriteLine(42);")] + public required string Code { get; set; } + + [InputLabel("Language: csharp, fsharp, vb or il")] + [ModalTextInput("language", initValue: "csharp")] + [RequiredInput(false)] + public required string Language { get; set; } + + [InputLabel("Arguments")] + [ModalTextInput("arguments", TextInputStyle.Short)] + [RequiredInput(false)] + public string Arguments { get; set; } = ""; + } +} diff --git a/src/Modix.Bot/Modules/GodboltModule.cs b/src/Modix.Bot/Modules/GodboltModule.cs index 4725c9390..2290571e8 100644 --- a/src/Modix.Bot/Modules/GodboltModule.cs +++ b/src/Modix.Bot/Modules/GodboltModule.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Discord; using Discord.Interactions; +using Modix.Bot.Attributes; using Modix.Services.CommandHelp; using Modix.Services.Godbolt; @@ -18,55 +19,38 @@ public GodboltModule(GodboltService godboltService) _godboltService = godboltService; } - [SlashCommand("godbolt", "Compile and disassemble the JIT code.")] - public async Task DisasmAsync( - [Summary(description: "The code to compiler.")] string code, - [Summary(description: "The language of code.")] Language language = Language.CSharp, - [Summary(description: "Arguments to pass to the compiler.")] string arguments = "", - [Summary(description: "Execute the code.")] bool execute = false) - { - var langId = language switch - { - Language.CSharp => "csharp", - Language.FSharp => "fsharp", - Language.VisualBasic => "vb", - Language.IL => "il", - _ => throw new ArgumentOutOfRangeException(nameof(language)) - }; + [SlashCommand("godbolt", description: "Compile and disassemble JIT code.")] + [DoNotDefer] + public Task DisasmAsync() => Context.Interaction.RespondWithModalAsync("godbolt_disasm"); + [ModalInteraction("godbolt_disasm")] + public async Task ProcessDisasmAsync(GodboltDisasmModal modal) + { try { var response = $""" + ```{modal.Language} + {modal.Code} + ``` + Disassembly {(string.IsNullOrWhiteSpace(modal.Arguments) ? "" : $"with ``{modal.Arguments}``")}: ```asm - {await _godboltService.CompileAsync(code, langId, arguments, execute)} + {await _godboltService.CompileAsync(modal.Code, modal.Language, modal.Arguments)} ``` """; if (response.Length > DiscordConfig.MaxMessageSize) { - await FollowupAsync("Error: The disassembly code is too long to be converted to a message."); + await FollowupAsync("Error: The code is too long to be converted to a message.", allowedMentions: AllowedMentions.None); return; } - await FollowupAsync(text: response); + await FollowupAsync(response, allowedMentions: AllowedMentions.None); } catch (Exception ex) { - await FollowupAsync($"Error: {ex}"); + await FollowupAsync($"Error: {ex}", allowedMentions: AllowedMentions.None); return; } } - - public enum Language - { - [ChoiceDisplay("C#")] - CSharp, - [ChoiceDisplay("F#")] - FSharp, - [ChoiceDisplay("VB.NET")] - VisualBasic, - [ChoiceDisplay("IL")] - IL - } } } diff --git a/src/Modix.Services/Godbolt/AssemblyFilters.cs b/src/Modix.Services/Godbolt/AssemblyFilters.cs index df05fb91d..1adc3332d 100644 --- a/src/Modix.Services/Godbolt/AssemblyFilters.cs +++ b/src/Modix.Services/Godbolt/AssemblyFilters.cs @@ -3,7 +3,7 @@ namespace Modix.Services.Godbolt { internal record class AssemblyFilters { - public bool Binary { get; set; } = true; + public bool Binary { get; set; } = false; public bool CommentOnly { get; set; } = true; public bool Directives { get; set; } = true; public bool Labels { get; set; } = true; diff --git a/src/Modix.Services/Godbolt/GodboltService.cs b/src/Modix.Services/Godbolt/GodboltService.cs index 7d6872f6e..097055353 100644 --- a/src/Modix.Services/Godbolt/GodboltService.cs +++ b/src/Modix.Services/Godbolt/GodboltService.cs @@ -16,7 +16,7 @@ public GodboltService(IHttpClientFactory httpClientFactory) HttpClientFactory = httpClientFactory; } - public async Task CompileAsync(string code, string lang, string arguments, bool execute) + public async Task CompileAsync(string code, string lang, string arguments) { var request = new CompileRequest(lang, code); @@ -25,11 +25,6 @@ public async Task CompileAsync(string code, string lang, string argument request.Options.UserArguments = arguments; } - if (execute) - { - request.Options.Filters.Execute = true; - } - var requestJson = JsonSerializer.Serialize(request, _jsonOptions); var client = HttpClientFactory.CreateClient(); From 2f5cc4620b60fe536960a4113a8dd76f17d52605 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sat, 9 Nov 2024 03:41:26 +0900 Subject: [PATCH 5/7] Update snippets --- src/Modix.Bot/Modules/GodboltDisasmModal.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Modix.Bot/Modules/GodboltDisasmModal.cs b/src/Modix.Bot/Modules/GodboltDisasmModal.cs index 682865974..df5cd4434 100644 --- a/src/Modix.Bot/Modules/GodboltDisasmModal.cs +++ b/src/Modix.Bot/Modules/GodboltDisasmModal.cs @@ -6,16 +6,30 @@ namespace Modix.Bot.Modules { public class GodboltDisasmModal : IModal { + private string _language = "csharp"; + public string Title => "Godbolt"; [InputLabel("Code")] - [ModalTextInput("code", TextInputStyle.Paragraph, placeholder: "System.Console.WriteLine(42);")] + [ModalTextInput("code", TextInputStyle.Paragraph, initValue: """ + using System; + + class Program + { + static int Square(int num) => num * num; + static void Main() => Console.WriteLine(Square(42)); + } + """)] public required string Code { get; set; } [InputLabel("Language: csharp, fsharp, vb or il")] [ModalTextInput("language", initValue: "csharp")] [RequiredInput(false)] - public required string Language { get; set; } + public required string Language + { + get => _language; + set => _language = value.ToLowerInvariant(); + } [InputLabel("Arguments")] [ModalTextInput("arguments", TextInputStyle.Short)] From bba525c56425c8164e65eed69db9ab0569d2c415 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sat, 9 Nov 2024 03:44:58 +0900 Subject: [PATCH 6/7] Minor fixes to format --- src/Modix.Bot/Modules/GodboltModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modix.Bot/Modules/GodboltModule.cs b/src/Modix.Bot/Modules/GodboltModule.cs index 2290571e8..4270ccc08 100644 --- a/src/Modix.Bot/Modules/GodboltModule.cs +++ b/src/Modix.Bot/Modules/GodboltModule.cs @@ -32,8 +32,8 @@ public async Task ProcessDisasmAsync(GodboltDisasmModal modal) ```{modal.Language} {modal.Code} ``` - Disassembly {(string.IsNullOrWhiteSpace(modal.Arguments) ? "" : $"with ``{modal.Arguments}``")}: - ```asm + Disassembly{(string.IsNullOrWhiteSpace(modal.Arguments) ? "" : $" with ``{modal.Arguments}``")}: + ``` {await _godboltService.CompileAsync(modal.Code, modal.Language, modal.Arguments)} ``` """; From 3cfda88f73de621d51c3b144268a41c39b9095e7 Mon Sep 17 00:00:00 2001 From: Steven He Date: Sat, 9 Nov 2024 03:46:21 +0900 Subject: [PATCH 7/7] Language is required --- src/Modix.Bot/Modules/GodboltDisasmModal.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modix.Bot/Modules/GodboltDisasmModal.cs b/src/Modix.Bot/Modules/GodboltDisasmModal.cs index df5cd4434..f02cacd30 100644 --- a/src/Modix.Bot/Modules/GodboltDisasmModal.cs +++ b/src/Modix.Bot/Modules/GodboltDisasmModal.cs @@ -24,7 +24,7 @@ class Program [InputLabel("Language: csharp, fsharp, vb or il")] [ModalTextInput("language", initValue: "csharp")] - [RequiredInput(false)] + [RequiredInput(true)] public required string Language { get => _language;