diff --git a/ModularAssistentForDiscordServer/Commands/ContextMenu/StealEmojiMessage.cs b/ModularAssistentForDiscordServer/Commands/ContextMenu/StealEmojiMessage.cs index 1368c9e..c0c5f5e 100644 --- a/ModularAssistentForDiscordServer/Commands/ContextMenu/StealEmojiMessage.cs +++ b/ModularAssistentForDiscordServer/Commands/ContextMenu/StealEmojiMessage.cs @@ -13,11 +13,15 @@ // limitations under the License. using System.Text.RegularExpressions; +using DSharpPlus.Commands; +using DSharpPlus.Commands.ContextChecks; +using DSharpPlus.Commands.Processors.SlashCommands; using DSharpPlus.Entities; +using MADS.Extensions; namespace MADS.Commands.ContextMenu; -public partial class StealEmojiMessage : MadsBaseApplicationCommand +public partial class StealEmojiMessage { private readonly HttpClient _httpClient; @@ -26,19 +30,19 @@ public StealEmojiMessage(HttpClient httpClient) _httpClient = httpClient; } - [ContextMenu(DiscordApplicationCommandType.MessageContextMenu, "Steal emoji(s)"), - SlashRequirePermissions(DiscordPermissions.ManageEmojis)] - public async Task YoinkAsync(ContextMenuContext ctx) + [SlashCommandTypes(DiscordApplicationCommandType.MessageContextMenu),Command("Steal emoji(s)"), + RequirePermissions(DiscordPermissions.ManageEmojis)] + public async Task YoinkAsync(SlashCommandContext ctx, DiscordMessage targetMessage) { await ctx.DeferAsync(true); - if (ctx.TargetMessage.Content is null) + if (string.IsNullOrWhiteSpace(targetMessage.Content)) { await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("⚠️ Message has not content!")); return; } - MatchCollection matches = EmojiRegex().Matches(ctx.TargetMessage.Content.Replace("><", "> <")); + MatchCollection matches = EmojiRegex().Matches(targetMessage.Content.Replace("><", "> <")); if (matches.Count < 1) { @@ -73,7 +77,7 @@ await ctx.EditResponseAsync( // ignored } - await IntendedWait(500); + await Task.Delay(500); } string message = newEmojis.Aggregate("✅ Yoink! These emoji(s) have been added to your server: ", @@ -86,7 +90,7 @@ await ctx.EditResponseAsync( await ctx.EditResponseAsync(discordWebhook); } - private async Task CopyEmoji(ContextMenuContext ctx, string name, ulong id, bool animated) + private async Task CopyEmoji(SlashCommandContext ctx, string name, ulong id, bool animated) { Stream downloadedEmoji = await _httpClient.GetStreamAsync($"https://cdn.discordapp.com/emojis/{id}.{(animated ? "gif" : "png")}"); @@ -96,7 +100,7 @@ private async Task CopyEmoji(ContextMenuContext ctx, string name, await downloadedEmoji.CopyToAsync(memory); await downloadedEmoji.DisposeAsync(); - DiscordGuildEmoji newEmoji = await ctx.Guild.CreateEmojiAsync(name, memory); + DiscordGuildEmoji newEmoji = await ctx.Guild!.CreateEmojiAsync(name, memory); return newEmoji; } diff --git a/ModularAssistentForDiscordServer/Commands/ContextMenu/TranslateMessage.cs b/ModularAssistentForDiscordServer/Commands/ContextMenu/TranslateMessage.cs index 67cd89a..3cbb4bf 100644 --- a/ModularAssistentForDiscordServer/Commands/ContextMenu/TranslateMessage.cs +++ b/ModularAssistentForDiscordServer/Commands/ContextMenu/TranslateMessage.cs @@ -14,14 +14,17 @@ using DeepL; using DeepL.Model; +using DSharpPlus.Commands; +using DSharpPlus.Commands.Processors.SlashCommands; using DSharpPlus.Entities; using MADS.CustomComponents; +using MADS.Extensions; using MADS.Services; using Quartz.Util; namespace MADS.Commands.ContextMenu; -public class TranslateMessage : MadsBaseApplicationCommand +public class TranslateMessage { private readonly TranslateInformationService _translateInformationService; private readonly Translator _translator; @@ -32,8 +35,8 @@ public TranslateMessage(TranslateInformationService translateInformationService, _translator = translator; } - [ContextMenu(DiscordApplicationCommandType.MessageContextMenu, "Translate message")] - public async Task TranslateAsync(ContextMenuContext ctx) + [SlashCommandTypes(DiscordApplicationCommandType.MessageContextMenu), Command("Translate message")] + public async Task TranslateAsync(SlashCommandContext ctx, DiscordMessage targetMessage) { await ctx.DeferAsync(true); @@ -45,19 +48,19 @@ public async Task TranslateAsync(ContextMenuContext ctx) preferredLanguage = "en-US"; } - ulong messageId = ctx.TargetMessage.Id; + ulong messageId = targetMessage.Id; DiscordMessage message = await ctx.Channel.GetMessageAsync(messageId); string? messageContent = message.Content; if (messageContent.IsNullOrWhiteSpace() || messageContent is null) { - await ctx.CreateResponseAsync("⚠️ Message is empty!"); + await ctx.CreateResponse_Error("⚠️ Message is empty!"); return; } if (preferredLanguage is null) { - await ctx.CreateResponseAsync("⚠️ No language set!"); + await ctx.CreateResponse_Error("⚠️ No language set!"); return; } @@ -72,7 +75,7 @@ public async Task TranslateAsync(ContextMenuContext ctx) .WithFooter($"Translated from {translatedMessage.DetectedSourceLanguageCode} to {preferredLanguage}") .WithTimestamp(DateTime.Now); - await ctx.CreateResponseAsync(embed); + await ctx.RespondAsync(embed); if (isPreferredLanguageSet) { @@ -86,6 +89,6 @@ public async Task TranslateAsync(ContextMenuContext ctx) .AsEphemeral(); - await ctx.FollowUpAsync(followUpMessage); + await ctx.FollowupAsync(followUpMessage); } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Commands/ContextMenu/UserInfoUser.cs b/ModularAssistentForDiscordServer/Commands/ContextMenu/UserInfoUser.cs index 0147063..7a1def3 100644 --- a/ModularAssistentForDiscordServer/Commands/ContextMenu/UserInfoUser.cs +++ b/ModularAssistentForDiscordServer/Commands/ContextMenu/UserInfoUser.cs @@ -13,18 +13,20 @@ // limitations under the License. using DSharpPlus; +using DSharpPlus.Commands; +using DSharpPlus.Commands.Processors.SlashCommands; using DSharpPlus.Entities; using DSharpPlus.Exceptions; using Humanizer; namespace MADS.Commands.ContextMenu; -public class UserInfoUser : MadsBaseApplicationCommand +public class UserInfoUser { - [ContextMenu(DiscordApplicationCommandType.UserContextMenu, "Info")] - public async Task GetUserInfo(ContextMenuContext ctx) + [SlashCommandTypes(DiscordApplicationCommandType.UserContextMenu), Command("Info")] + public async Task GetUserInfo(SlashCommandContext ctx, DiscordUser targetUser) { - DiscordUser user = ctx.TargetUser; + DiscordUser user = targetUser; DiscordMember? member = null; @@ -77,6 +79,6 @@ public async Task GetUserInfo(ContextMenuContext ctx) } } - await ctx.CreateResponseAsync(embed.Build(), true); + await ctx.RespondAsync(embed.Build(), true); } } \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/Commands/Text/Base/Eval.cs b/ModularAssistentForDiscordServer/Commands/Text/Base/Eval.cs index 38417b4..a0b13f8 100644 --- a/ModularAssistentForDiscordServer/Commands/Text/Base/Eval.cs +++ b/ModularAssistentForDiscordServer/Commands/Text/Base/Eval.cs @@ -18,6 +18,7 @@ using DSharpPlus.Commands.ArgumentModifiers; using DSharpPlus.Commands.Processors.TextCommands; using DSharpPlus.Entities; +using MADS.CommandsChecks; using MADS.Services; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; @@ -35,18 +36,9 @@ public Eval(DiscordClientService service) } [Command("eval"), Description("Evaluate the result of c# code")] - public async Task EvalCommand(CommandContext ctx, [RemainingText] string code) + public async Task EvalCommand(TextCommandContext ctx, [FromCode] string code) { - int codeStart = code.IndexOf("```", StringComparison.Ordinal) + 3; - codeStart = code.IndexOf('\n', codeStart) + 1; - int codeEnd = code.LastIndexOf("```", StringComparison.Ordinal); - - if (codeStart == -1 || codeEnd == -1) - { - throw new ArgumentException("⚠️ You need to wrap the code into a code block."); - } - - string csCode = code[codeStart..codeEnd]; + string csCode = code; await ctx.RespondAsync(new DiscordEmbedBuilder() .WithColor(new DiscordColor("#FF007F")) diff --git a/ModularAssistentForDiscordServer/Commands/Text/Base/ExitGuild.cs b/ModularAssistentForDiscordServer/Commands/Text/Base/ExitGuild.cs index 8467df5..54aeba4 100644 --- a/ModularAssistentForDiscordServer/Commands/Text/Base/ExitGuild.cs +++ b/ModularAssistentForDiscordServer/Commands/Text/Base/ExitGuild.cs @@ -15,21 +15,23 @@ using System.ComponentModel; using DSharpPlus.Commands; using DSharpPlus.Commands.ContextChecks; +using DSharpPlus.Commands.Processors.TextCommands; using DSharpPlus.Entities; +using MADS.CommandsChecks; namespace MADS.Commands.Text.Base; public class ExitGuild { [Command("exit"), Description("Exit the bot"), RequirePermissions(DiscordPermissions.ManageGuild), RequireGuild] - public async Task ExitGuildCommand(CommandContext ctx) + public async Task ExitGuildCommand(TextCommandContext ctx) { await ctx.RespondAsync("Leaving server..."); await ctx.Guild.LeaveAsync(); } - [Command("leave"), Description("Leave given server"), RequireGuild, Hidden, RequireOwner] - public async Task LeaveGuildOwner(CommandContext ctx) + [Command("leave"), Description("Leave given server"), RequireGuild, RequireOwner] + public async Task LeaveGuildOwner(TextCommandContext ctx) { await ctx.Message.DeleteAsync(); await ctx.Guild.LeaveAsync(); diff --git a/ModularAssistentForDiscordServer/CommandsChecks/EnsureDBEntitiesCheck.cs b/ModularAssistentForDiscordServer/CommandsChecks/EnsureDBEntitiesCheck.cs new file mode 100644 index 0000000..a36a84d --- /dev/null +++ b/ModularAssistentForDiscordServer/CommandsChecks/EnsureDBEntitiesCheck.cs @@ -0,0 +1,67 @@ +// Copyright 2023 Plerx2493 +// +// Licensed under the Apache License, Version 2.0 (the "License") +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using DSharpPlus.Commands; +using DSharpPlus.Commands.ContextChecks; +using DSharpPlus.Entities; +using MADS.Entities; +using Microsoft.EntityFrameworkCore; + +namespace MADS.CommandsChecks; + +public class EnsureDBEntitiesCheck : IContextCheck +{ + private IDbContextFactory _contextFactory; + + public EnsureDBEntitiesCheck(IDbContextFactory dbContextFactory) + { + _contextFactory = dbContextFactory; + } + + public async ValueTask ExecuteCheckAsync(UnconditionalCheckAttribute _, CommandContext context) + { + DiscordUser user = context.User; + + await using MadsContext dbContext = await _contextFactory.CreateDbContextAsync(); + + UserDbEntity userdbEntity = new() + { + Id = user.Id, + Username = user.Username, + PreferedLanguage = "en-US" + }; + + await dbContext.Users.Upsert(userdbEntity) + .On(x => x.Id) + .NoUpdate() + .RunAsync(); + + if (context.Guild is null) + { + return null; + } + + GuildDbEntity guildDbEntity = new() + { + DiscordId = context.Guild.Id + }; + + await dbContext.Guilds.Upsert(guildDbEntity) + .On(x => x.DiscordId) + .NoUpdate() + .RunAsync(); + + return null; + } +} \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/CommandsChecks/RequireOwnerAttribute.cs b/ModularAssistentForDiscordServer/CommandsChecks/RequireOwnerAttribute.cs new file mode 100644 index 0000000..ae505e2 --- /dev/null +++ b/ModularAssistentForDiscordServer/CommandsChecks/RequireOwnerAttribute.cs @@ -0,0 +1,23 @@ +// Copyright 2023 Plerx2493 +// +// Licensed under the Apache License, Version 2.0 (the "License") +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using DSharpPlus.Commands.ContextChecks; + +namespace MADS.CommandsChecks; + +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class), ] +public class RequireOwnerAttribute : ContextCheckAttribute +{ + +} \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/CommandsChecks/RequireOwnerCheck.cs b/ModularAssistentForDiscordServer/CommandsChecks/RequireOwnerCheck.cs new file mode 100644 index 0000000..22c6c60 --- /dev/null +++ b/ModularAssistentForDiscordServer/CommandsChecks/RequireOwnerCheck.cs @@ -0,0 +1,37 @@ +// Copyright 2023 Plerx2493 +// +// Licensed under the Apache License, Version 2.0 (the "License") +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using DSharpPlus.Commands; +using DSharpPlus.Commands.ContextChecks; +using DSharpPlus.Entities; + +namespace MADS.CommandsChecks; + +public class RequireOwnerCheck : IContextCheck +{ + public ValueTask ExecuteCheckAsync(RequireOwnerAttribute attribute, CommandContext context) + { + DiscordApplication app = context.Client.CurrentApplication; + DiscordUser me = context.Client.CurrentUser; + + bool isOwner = app is not null ? app!.Owners!.Any(x => x.Id == context.User.Id) : context.User.Id == me.Id; + + if (!isOwner) + { + return ValueTask.FromResult("User must be on of the application owner"); + } + + return ValueTask.FromResult(null); + } +} \ No newline at end of file diff --git a/ModularAssistentForDiscordServer/EventListeners/EventListener.Errors.cs b/ModularAssistentForDiscordServer/EventListeners/EventListener.Errors.cs index 04b5bce..fa6f09f 100644 --- a/ModularAssistentForDiscordServer/EventListeners/EventListener.Errors.cs +++ b/ModularAssistentForDiscordServer/EventListeners/EventListener.Errors.cs @@ -26,11 +26,11 @@ namespace MADS.EventListeners; internal static partial class EventListener { - internal static async Task OnSlashCommandErrored(SlashCommandsExtension sender, SlashCommandErrorEventArgs e) + internal static async Task OnCommandErrored(CommandsExtension sender, CommandErroredEventArgs e) { Type typeOfException = e.Exception.GetType(); if (typeOfException == typeof(ArgumentException) - || typeOfException == typeof(SlashExecutionChecksFailedException)) + || typeOfException == typeof(ChecksFailedException)) { return; } @@ -48,8 +48,7 @@ internal static async Task OnSlashCommandErrored(SlashCommandsExtension sender, try { - await e.Context.Interaction.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, - new DiscordInteractionResponseBuilder().AddEmbed(discordEmbed).AsEphemeral()); + await e.Context.RespondAsync(new DiscordInteractionResponseBuilder().AddEmbed(discordEmbed).AsEphemeral()); return; } catch (BadRequestException) @@ -58,7 +57,7 @@ await e.Context.Interaction.CreateResponseAsync(DiscordInteractionResponseType.C try { - await e.Context.Interaction.EditOriginalResponseAsync( + await e.Context.EditResponseAsync( new DiscordWebhookBuilder(new DiscordInteractionResponseBuilder().AddEmbed(discordEmbed) .AsEphemeral())); return; @@ -111,21 +110,6 @@ internal static async Task OnClientErrored(DiscordClient sender, ClientErrorEven await MainProgram.WebhookClient.BroadcastMessageAsync(webhookBuilder); } - - internal static async Task OnAutocompleteError(SlashCommandsExtension sender, AutocompleteErrorEventArgs e) - { - await e.Context.Channel.SendMessageAsync($"OOPS your command just errored... \n {e.Exception.Message}"); - await e.Context.Channel.SendMessageAsync(e.Exception.InnerException?.Message ?? "no inner exception"); - string? reallyLongString = e.Exception.StackTrace; - - if (reallyLongString != null) - { - IEnumerable pages = InteractivityExtension.GeneratePagesInEmbed(reallyLongString); - - await e.Context.Channel.SendPaginatedMessageAsync(e.Context.Member, pages, PaginationBehaviour.Ignore, - ButtonPaginationBehavior.DeleteButtons); - } - } internal static async Task OnSocketErrored(DiscordClient client, SocketErrorEventArgs e) { diff --git a/ModularAssistentForDiscordServer/Services/DiscordClientService.cs b/ModularAssistentForDiscordServer/Services/DiscordClientService.cs index fca3f25..a7a2694 100644 --- a/ModularAssistentForDiscordServer/Services/DiscordClientService.cs +++ b/ModularAssistentForDiscordServer/Services/DiscordClientService.cs @@ -21,6 +21,7 @@ using DSharpPlus.Interactivity; using DSharpPlus.Interactivity.Enums; using DSharpPlus.Interactivity.Extensions; +using MADS.CommandsChecks; using MADS.Entities; using MADS.EventListeners; using Microsoft.EntityFrameworkCore; @@ -76,19 +77,21 @@ LoggingService loggingService Assembly asm = Assembly.GetExecutingAssembly(); - CommandsConfiguration cnextConfig = new() + CommandsConfiguration commandsConfiguration = new() { ServiceProvider = ModularDiscordBot.Services, #if !RELEASE DebugGuildId = 938120155974750288 #endif }; - Commands = DiscordClient.UseCommands(cnextConfig); + Commands = DiscordClient.UseCommands(commandsConfiguration); Commands.AddCommands(asm); + Commands.AddCheck(); + Commands.AddCheck(); Commands.CommandErrored += EventListener.OnCommandsErrored; - SlashCommands.SlashCommandErrored += EventListener.OnSlashCommandErrored; - SlashCommands.AutocompleteErrored += EventListener.OnAutocompleteError; + //C.SlashCommandErrored += EventListener.OnCommandErrored; + //SlashCommands.AutocompleteErrored += EventListener.OnAutocompleteError; //Interactivity InteractivityConfiguration interactivityConfig = new() diff --git a/ModularAssistentForDiscordServer/Services/LoggingService.cs b/ModularAssistentForDiscordServer/Services/LoggingService.cs index a1834eb..ab7e837 100644 --- a/ModularAssistentForDiscordServer/Services/LoggingService.cs +++ b/ModularAssistentForDiscordServer/Services/LoggingService.cs @@ -192,31 +192,13 @@ private void SetupWebhookLogging() Uri webhookUrl = new(config.DiscordWebhook); _discordWebhookClient.AddWebhookAsync(webhookUrl).GetAwaiter().GetResult(); } - + public async Task LogEvent(string message, string sender, LogLevel lvl) { string log = $"[{DateTime.Now:yyyy'-'MM'-'dd'T'HH':'mm':'ss}] [{lvl}] [{sender}] {message}"; await File.AppendAllTextAsync(_logPath, log + "\n", Encoding.UTF8); } - - public async Task LogCommandExecutionAsync(CommandContext ctx, TimeSpan timespan) - { - await LogInfo( - $"[{ctx.User.Username}#{ctx.User.Discriminator} : {ctx.User.Id}] [{ctx.Command?.Name}] {timespan.TotalMilliseconds} milliseconds to execute"); - } - - public async Task LogCommandExecutionAsync(InteractionContext ctx, TimeSpan timespan) - { - await LogInfo( - $"[{ctx.User.Username}#{ctx.User.Discriminator} : {ctx.User.Id}] [/{ctx.CommandName}] {timespan.TotalMilliseconds} milliseconds to execute"); - } - - public async Task LogCommandExecutionAsync(ContextMenuContext ctx, TimeSpan timespan) - { - await LogInfo( - $"[{ctx.User.Username}#{ctx.User.Discriminator} : {ctx.User.Id}] [CM-{ctx.CommandName}] {timespan.TotalMilliseconds} milliseconds to execute"); - } - + private async Task LogInfo(string input) { string logEntry =