From 0c92f38c6cfee688ffdeaaffd324c50d781759c7 Mon Sep 17 00:00:00 2001 From: FloatingMilkshake Date: Tue, 1 Oct 2024 10:19:12 -0400 Subject: [PATCH] WIP: Update DSharpPlus to 5.0.0-nightly-02379 --- Cliptok.csproj | 5 +- CommandChecks/HomeServerPerms.cs | 14 +- CommandChecks/OwnerChecks.cs | 4 +- CommandChecks/UserRoleChecks.cs | 4 +- Commands/Announcements.cs | 8 +- Commands/Bans.cs | 23 +-- Commands/Debug.cs | 93 ++++++------ Commands/Dehoist.cs | 48 ++++--- Commands/DmRelayBlock.cs | 7 +- Commands/FunCmds.cs | 10 +- Commands/Grant.cs | 7 +- .../AnnouncementInteractions.cs | 85 ++++++----- .../InteractionCommands/BanInteractions.cs | 40 +++--- .../InteractionCommands/ClearInteractions.cs | 60 ++++---- .../InteractionCommands/ContextCommands.cs | 46 +++--- .../InteractionCommands/DebugInteractions.cs | 47 ++++--- .../DehoistInteractions.cs | 31 ++-- .../JoinwatchInteractions.cs | 29 ++-- .../LockdownInteractions.cs | 86 ++++++----- .../InteractionCommands/MuteInteractions.cs | 52 ++++--- .../NicknameLockInteraction.cs | 23 +-- .../RaidmodeInteractions.cs | 29 ++-- .../InteractionCommands/RoleInteractions.cs | 64 +++++---- .../InteractionCommands/RulesInteractions.cs | 21 +-- .../SecurityActionInteractions.cs | 18 ++- .../SlowmodeInteractions.cs | 14 +- .../InteractionCommands/StatusInteractions.cs | 39 ++--- .../TechSupportInteractions.cs | 33 +++-- .../TrackingInteractions.cs | 36 +++-- .../UserNoteInteractions.cs | 111 ++++++++------- .../WarningInteractions.cs | 133 ++++++++++-------- Commands/Kick.cs | 13 +- Commands/Lists.cs | 24 ++-- Commands/Lockdown.cs | 14 +- Commands/Mutes.cs | 13 +- Commands/Raidmode.cs | 21 +-- Commands/Reminders.cs | 7 +- Commands/SecurityActions.cs | 8 +- Commands/TechSupport.cs | 5 +- Commands/Threads.cs | 11 +- Commands/Timestamp.cs | 27 ++-- Commands/UserRoles.cs | 90 +++++++----- Commands/Utility.cs | 16 ++- Commands/Warnings.cs | 56 +++++--- Events/ErrorEvents.cs | 38 ++++- Events/InteractionEvents.cs | 31 ++-- GlobalUsings.cs | 16 ++- Helpers/InteractionHelpers.cs | 14 +- Program.cs | 42 +++--- 49 files changed, 961 insertions(+), 705 deletions(-) diff --git a/Cliptok.csproj b/Cliptok.csproj index 0f93087a..574260b2 100644 --- a/Cliptok.csproj +++ b/Cliptok.csproj @@ -13,9 +13,8 @@ - - - + + diff --git a/CommandChecks/HomeServerPerms.cs b/CommandChecks/HomeServerPerms.cs index 22f3a710..187a5170 100644 --- a/CommandChecks/HomeServerPerms.cs +++ b/CommandChecks/HomeServerPerms.cs @@ -66,7 +66,7 @@ public static async Task GetPermLevelAsync(DiscordMember target } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class RequireHomeserverPermAttribute : CheckBaseAttribute + public class RequireHomeserverPermAttribute : ContextCheckAttribute // TODO(#202): checks changed!! see the checks section of https://dsharpplus.github.io/DSharpPlus/articles/migration/slashcommands_to_commands.html { public ServerPermLevel TargetLvl { get; set; } public bool WorkOutside { get; set; } @@ -80,7 +80,7 @@ public RequireHomeserverPermAttribute(ServerPermLevel targetlvl, bool workOutsid TargetLvl = targetlvl; } - public override async Task ExecuteCheckAsync(CommandContext ctx, bool help) + public async Task ExecuteCheckAsync(CommandContext ctx, bool help) { // If the command is supposed to stay within the server and its being used outside, fail silently if (!WorkOutside && (ctx.Channel.IsPrivate || ctx.Guild.Id != Program.cfgjson.ServerID)) @@ -112,7 +112,7 @@ public override async Task ExecuteCheckAsync(CommandContext ctx, bool help if (level >= TargetLvl) return true; - else if (!help && ctx.Command.QualifiedName != "edit") + else if (!help && ctx.Command.FullName != "edit") { var levelText = level.ToString(); if (level == ServerPermLevel.Nothing && Program.rand.Next(1, 100) == 69) @@ -126,22 +126,22 @@ await ctx.RespondAsync( } } - public class HomeServerAttribute : CheckBaseAttribute + public class HomeServerAttribute : ContextCheckAttribute { - public override async Task ExecuteCheckAsync(CommandContext ctx, bool help) + public async Task ExecuteCheckAsync(CommandContext ctx) { return !ctx.Channel.IsPrivate && ctx.Guild.Id == Program.cfgjson.ServerID; } } - public class SlashRequireHomeserverPermAttribute : SlashCheckBaseAttribute + public class SlashRequireHomeserverPermAttribute : ContextCheckAttribute { public ServerPermLevel TargetLvl; public SlashRequireHomeserverPermAttribute(ServerPermLevel targetlvl) => TargetLvl = targetlvl; - public override async Task ExecuteChecksAsync(InteractionContext ctx) + public async Task ExecuteChecksAsync(CommandContext ctx) { if (ctx.Guild.Id != Program.cfgjson.ServerID) return false; diff --git a/CommandChecks/OwnerChecks.cs b/CommandChecks/OwnerChecks.cs index 66e7a301..cebcadb3 100644 --- a/CommandChecks/OwnerChecks.cs +++ b/CommandChecks/OwnerChecks.cs @@ -1,8 +1,8 @@ namespace Cliptok.CommandChecks { - public class IsBotOwnerAttribute : CheckBaseAttribute + public class IsBotOwnerAttribute : ContextCheckAttribute { - public override async Task ExecuteCheckAsync(CommandContext ctx, bool help) + public async Task ExecuteCheckAsync(CommandContext ctx, bool help) { if (Program.cfgjson.BotOwners.Contains(ctx.User.Id)) { diff --git a/CommandChecks/UserRoleChecks.cs b/CommandChecks/UserRoleChecks.cs index a03a6221..5dd03384 100644 --- a/CommandChecks/UserRoleChecks.cs +++ b/CommandChecks/UserRoleChecks.cs @@ -1,8 +1,8 @@ namespace Cliptok.CommandChecks { - public class UserRolesPresentAttribute : CheckBaseAttribute + public class UserRolesPresentAttribute : ContextCheckAttribute { - public override async Task ExecuteCheckAsync(CommandContext ctx, bool help) + public async Task ExecuteCheckAsync(CommandContext ctx) { return Program.cfgjson.UserRoles is not null; } diff --git a/Commands/Announcements.cs b/Commands/Announcements.cs index 042e200e..f5114215 100644 --- a/Commands/Announcements.cs +++ b/Commands/Announcements.cs @@ -1,13 +1,14 @@ namespace Cliptok.Commands { - internal class Announcements : BaseCommandModule + internal class Announcements { [Command("editannounce")] [Description("Edit an announcement, preserving the ping highlight.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequireHomeserverPerm(ServerPermLevel.Moderator)] public async Task EditAnnounce( - CommandContext ctx, + TextCommandContext ctx, [Description("The ID of the message to edit.")] ulong messageId, [Description("The short name for the role to ping.")] string roleName, [RemainingText, Description("The new message content, excluding the ping.")] string content @@ -40,8 +41,9 @@ public async Task EditAnnounce( [Command("announce")] [Description("Announces something in the current channel, pinging an Insider role in the process.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task AnnounceCmd(CommandContext ctx, [Description("'canary', 'dev', 'beta', 'beta10', 'rp', 'rp10', 'patch', 'rpbeta', 'rpbeta10', 'betadev', 'candev'")] string roleName, [RemainingText, Description("The announcement message to send.")] string announcementMessage) + public async Task AnnounceCmd(TextCommandContext ctx, [Description("'canary', 'dev', 'beta', 'beta10', 'rp', 'rp10', 'patch', 'rpbeta', 'rpbeta10', 'betadev', 'candev'")] string roleName, [RemainingText, Description("The announcement message to send.")] string announcementMessage) { DiscordRole discordRole; diff --git a/Commands/Bans.cs b/Commands/Bans.cs index 4bc5e5c0..c17a5256 100644 --- a/Commands/Bans.cs +++ b/Commands/Bans.cs @@ -2,12 +2,13 @@ namespace Cliptok.Commands { - class Bans : BaseCommandModule + class Bans { [Command("massban")] - [Aliases("bigbonk")] + [TextAlias("bigbonk")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task MassBanCmd(CommandContext ctx, [RemainingText] string input) + public async Task MassBanCmd(TextCommandContext ctx, [RemainingText] string input) { List usersString = input.Replace("\n", " ").Replace("\r", "").Split(' ').ToList(); @@ -21,7 +22,8 @@ public async Task MassBanCmd(CommandContext ctx, [RemainingText] string input) List> taskList = new(); int successes = 0; - var loading = await ctx.RespondAsync("Processing, please wait."); + await ctx.RespondAsync("Processing, please wait."); + var loading = await ctx.GetResponseAsync(); foreach (ulong user in users) { @@ -41,10 +43,11 @@ public async Task MassBanCmd(CommandContext ctx, [RemainingText] string input) } [Command("ban")] - [Aliases("tempban", "bonk", "isekaitruck")] + [TextAlias("tempban", "bonk", "isekaitruck")] [Description("Bans a user that you have permission to ban, deleting all their messages in the process. See also: bankeep.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.BanMembers)] - public async Task BanCmd(CommandContext ctx, + public async Task BanCmd(TextCommandContext ctx, [Description("The user you wish to ban. Accepts many formats")] DiscordUser targetMember, [RemainingText, Description("The time and reason for the ban. e.g. '14d trolling' NOTE: Add 'appeal' to the start of the reason to include an appeal link")] string timeAndReason = "No reason specified.") { @@ -133,9 +136,10 @@ public async Task BanCmd(CommandContext ctx, /// I CANNOT find a way to do this as alias so I made it a separate copy of the command. /// Sue me, I beg you. [Command("bankeep")] - [Aliases("bansave")] + [TextAlias("bansave")] [Description("Bans a user but keeps their messages around."), HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.BanMembers)] - public async Task BankeepCmd(CommandContext ctx, + [AllowedProcessors(typeof(TextCommandProcessor))] + public async Task BankeepCmd(TextCommandContext ctx, [Description("The user you wish to ban. Accepts many formats")] DiscordUser targetMember, [RemainingText, Description("The time and reason for the ban. e.g. '14d trolling' NOTE: Add 'appeal' to the start of the reason to include an appeal link")] string timeAndReason = "No reason specified.") { @@ -216,8 +220,9 @@ public async Task BankeepCmd(CommandContext ctx, [Command("unban")] [Description("Unbans a user who has been previously banned.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.BanMembers)] - public async Task UnbanCmd(CommandContext ctx, [Description("The user to unban, usually a mention or ID")] DiscordUser targetUser, [Description("Used in audit log only currently")] string reason = "No reason specified.") + public async Task UnbanCmd(TextCommandContext ctx, [Description("The user to unban, usually a mention or ID")] DiscordUser targetUser, [Description("Used in audit log only currently")] string reason = "No reason specified.") { if ((await Program.db.HashExistsAsync("bans", targetUser.Id))) { diff --git a/Commands/Debug.cs b/Commands/Debug.cs index 83ff050b..16bd2574 100644 --- a/Commands/Debug.cs +++ b/Commands/Debug.cs @@ -1,17 +1,20 @@ -namespace Cliptok.Commands +using DSharpPlus.Commands.Trees.Metadata; + +namespace Cliptok.Commands { - internal class Debug : BaseCommandModule + internal class Debug { public static Dictionary OverridesPendingAddition = new(); - [Group("debug")] - [Aliases("troubleshoot", "unbug", "bugn't", "helpsomethinghasgoneverywrong")] + [Command("debug")] + [TextAlias("troubleshoot", "unbug", "bugn't", "helpsomethinghasgoneverywrong")] [Description("Commands and things for fixing the bot in the unlikely event that it breaks a bit.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - class DebugCmds : BaseCommandModule + class DebugCmds { [Command("mutestatus")] - public async Task MuteStatus(CommandContext ctx, DiscordUser targetUser = default) + public async Task MuteStatus(TextCommandContext ctx, DiscordUser targetUser = default) { if (targetUser == default) targetUser = ctx.User; @@ -20,9 +23,9 @@ public async Task MuteStatus(CommandContext ctx, DiscordUser targetUser = defaul } [Command("mutes")] - [Aliases("mute")] + [TextAlias("mute")] [Description("Debug the list of mutes.")] - public async Task MuteDebug(CommandContext ctx, DiscordUser targetUser = default) + public async Task MuteDebug(TextCommandContext ctx, DiscordUser targetUser = default) { await DiscordHelpers.SafeTyping(ctx.Channel); @@ -61,9 +64,9 @@ public async Task MuteDebug(CommandContext ctx, DiscordUser targetUser = default } [Command("bans")] - [Aliases("ban")] + [TextAlias("ban")] [Description("Debug the list of bans.")] - public async Task BanDebug(CommandContext ctx, DiscordUser targetUser = default) + public async Task BanDebug(TextCommandContext ctx, DiscordUser targetUser = default) { await DiscordHelpers.SafeTyping(ctx.Channel); @@ -101,7 +104,7 @@ public async Task BanDebug(CommandContext ctx, DiscordUser targetUser = default) [Command("restart")] [RequireHomeserverPerm(ServerPermLevel.Admin, ownerOverride: true), Description("Restart the bot. If not under Docker (Cliptok is, dw) this WILL exit instead.")] - public async Task Restart(CommandContext ctx) + public async Task Restart(TextCommandContext ctx) { await ctx.RespondAsync("Bot is restarting. Please hold."); Environment.Exit(1); @@ -109,7 +112,7 @@ public async Task Restart(CommandContext ctx) [Command("shutdown")] [RequireHomeserverPerm(ServerPermLevel.Admin, ownerOverride: true), Description("Panics and shuts the bot down. Check the arguments for usage.")] - public async Task Shutdown(CommandContext ctx, [Description("This MUST be set to \"I understand what I am doing\" for the command to work."), RemainingText] string verificationArgument) + public async Task Shutdown(TextCommandContext ctx, [Description("This MUST be set to \"I understand what I am doing\" for the command to work."), RemainingText] string verificationArgument) { if (verificationArgument == "I understand what I am doing") { @@ -126,9 +129,10 @@ public async Task Restart(CommandContext ctx) [Command("refresh")] [RequireHomeserverPerm(ServerPermLevel.TrialModerator)] [Description("Manually run all the automatic actions.")] - public async Task Refresh(CommandContext ctx) + public async Task Refresh(TextCommandContext ctx) { - var msg = await ctx.RespondAsync("Checking for pending scheduled tasks..."); + await ctx.RespondAsync("Checking for pending scheduled tasks..."); + var msg = await ctx.GetResponseAsync(); bool bans = await Tasks.PunishmentTasks.CheckBansAsync(); bool mutes = await Tasks.PunishmentTasks.CheckMutesAsync(); bool warns = await Tasks.PunishmentTasks.CheckAutomaticWarningsAsync(); @@ -142,10 +146,10 @@ public async Task Refresh(CommandContext ctx) } [Command("sh")] - [Aliases("cmd")] + [TextAlias("cmd")] [IsBotOwner] [Description("Run shell commands! Bash for Linux/macOS, batch for Windows!")] - public async Task Shell(CommandContext ctx, [RemainingText] string command) + public async Task Shell(TextCommandContext ctx, [RemainingText] string command) { if (string.IsNullOrWhiteSpace(command)) { @@ -153,7 +157,8 @@ public async Task Shell(CommandContext ctx, [RemainingText] string command) return; } - DiscordMessage msg = await ctx.RespondAsync("executing.."); + await ctx.RespondAsync("executing.."); + DiscordMessage msg = await ctx.GetResponseAsync(); ShellResult finishedShell = RunShellCommand(command); string result = Regex.Replace(finishedShell.result, "ghp_[0-9a-zA-Z]{36}", "ghp_REDACTED").Replace(Environment.GetEnvironmentVariable("CLIPTOK_TOKEN"), "REDACTED").Replace(Environment.GetEnvironmentVariable("CLIPTOK_ANTIPHISHING_ENDPOINT") ?? "DUMMYVALUE", "REDACTED"); @@ -166,7 +171,7 @@ public async Task Shell(CommandContext ctx, [RemainingText] string command) } [Command("logs")] - public async Task Logs(CommandContext ctx) + public async Task Logs(TextCommandContext ctx) { if (Program.cfgjson.LogLevel is Level.Verbose) { @@ -189,7 +194,7 @@ public async Task Logs(CommandContext ctx) [Command("dumpwarnings"), Description("Dump all warning data. EXTREMELY computationally expensive, use with caution.")] [IsBotOwner] [RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task MostWarningsCmd(CommandContext ctx) + public async Task MostWarningsCmd(TextCommandContext ctx) { await DiscordHelpers.SafeTyping(ctx.Channel); @@ -221,10 +226,10 @@ public async Task MostWarningsCmd(CommandContext ctx) } [Command("checkpendingchannelevents")] - [Aliases("checkpendingevents", "pendingevents")] + [TextAlias("checkpendingevents", "pendingevents")] [Description("Check pending events to handle in the Channel Update and Channel Delete handlers.")] [IsBotOwner] - public async Task CheckPendingChannelEvents(CommandContext ctx) + public async Task CheckPendingChannelEvents(TextCommandContext ctx) { var pendingUpdateEvents = Tasks.EventTasks.PendingChannelUpdateEvents; var pendingDeleteEvents = Tasks.EventTasks.PendingChannelDeleteEvents; @@ -259,12 +264,12 @@ public async Task CheckPendingChannelEvents(CommandContext ctx) await ctx.RespondAsync(await StringHelpers.CodeOrHasteBinAsync(list)); } - [Group("overrides")] + [Command("overrides")] [Description("Commands for managing stored permission overrides.")] - public class Overrides : BaseCommandModule + public class Overrides { - [GroupCommand] - public async Task ShowOverrides(CommandContext ctx, + [DefaultGroupCommand] + public async Task ShowOverrides(TextCommandContext ctx, [Description("The user whose overrides to show.")] DiscordUser user) { var userOverrides = await Program.db.HashGetAsync("overrides", user.Id.ToString()); @@ -314,7 +319,7 @@ await ctx.RespondAsync(new DiscordMessageBuilder().WithContent(response) [Command("import")] [Description("Import overrides from a channel to the database.")] - public async Task Import(CommandContext ctx, + public async Task Import(TextCommandContext ctx, [Description("The channel to import overrides from.")] DiscordChannel channel) { // Import overrides @@ -330,10 +335,11 @@ await ctx.RespondAsync( [Command("importall")] [Description("Import all overrides from all channels to the database.")] - public async Task ImportAll(CommandContext ctx) + public async Task ImportAll(TextCommandContext ctx) { - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working..."); - + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working..."); + var msg = await ctx.GetResponseAsync(); + // Get all channels var channels = await ctx.Guild.GetChannelsAsync(); @@ -356,7 +362,7 @@ public async Task ImportAll(CommandContext ctx) [Command("add")] [Description("Insert an override into the db. Useful if you want to add an override for a user who has left.")] [IsBotOwner] - public async Task Add(CommandContext ctx, + public async Task Add(TextCommandContext ctx, [Description("The user to add an override for.")] DiscordUser user, [Description("The channel to add the override to.")] DiscordChannel channel, [Description("Allowed permissions. Use a permission integer. See https://discordlookup.com/permissions-calculator.")] int allowedPermissions, @@ -369,11 +375,12 @@ public async Task Add(CommandContext ctx, var confirmButton = new DiscordButtonComponent(DiscordButtonStyle.Success, "debug-overrides-add-confirm-callback", "Yes"); var cancelButton = new DiscordButtonComponent(DiscordButtonStyle.Danger, "debug-overrides-add-cancel-callback", "No"); - var confirmationMessage = await ctx.RespondAsync(new DiscordMessageBuilder().WithContent( + await ctx.RespondAsync(new DiscordMessageBuilder().WithContent( $"{Program.cfgjson.Emoji.ShieldHelp} Just to confirm, you want to add the following override for {user.Mention} to {channel.Mention}?\n" + $"**Allowed:** {parsedAllowedPerms}\n" + $"**Denied:** {parsedDeniedPerms}\n") .AddComponents([confirmButton, cancelButton])); + var confirmationMessage = await ctx.GetResponseAsync(); OverridesPendingAddition.Add(confirmationMessage.Id, new PendingUserOverride { @@ -389,7 +396,7 @@ public async Task Add(CommandContext ctx, [Command("remove")] [Description("Remove a user's overrides for a channel from the database.")] - public async Task Remove(CommandContext ctx, + public async Task Remove(TextCommandContext ctx, [Description("The user whose overrides to remove.")] DiscordUser user, [Description("The channel to remove overrides from.")] DiscordChannel channel) { @@ -421,11 +428,12 @@ await Program.db.HashSetAsync("overrides", user.Id, [Command("apply")] [Description("Apply a user's overrides from the db.")] [IsBotOwner] - public async Task Apply(CommandContext ctx, + public async Task Apply(TextCommandContext ctx, [Description("The user whose overrides to apply.")] DiscordUser user) { - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); - + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); + var msg = await ctx.GetResponseAsync(); + // Try fetching member to determine whether they are in the server. If they are not, we can't apply overrides for them. DiscordMember member; try @@ -485,7 +493,7 @@ public async Task Apply(CommandContext ctx, [Command("dumpchanneloverrides")] [Description("Dump all of a channel's overrides. This pulls from Discord, not the database.")] [IsBotOwner] - public async Task DumpChannelOverrides(CommandContext ctx, + public async Task DumpChannelOverrides(TextCommandContext ctx, [Description("The channel to dump overrides for.")] DiscordChannel channel) { var overwrites = channel.PermissionOverwrites; @@ -502,7 +510,7 @@ public async Task DumpChannelOverrides(CommandContext ctx, [Command("dmchannel")] [Description("Create or find a DM channel ID for a user.")] [IsBotOwner] - public async Task GetDMChannel(CommandContext ctx, DiscordUser user) + public async Task GetDMChannel(TextCommandContext ctx, DiscordUser user) { var dmChannel = await user.CreateDmChannelAsync(); await ctx.RespondAsync(dmChannel.Id.ToString()); @@ -511,7 +519,7 @@ public async Task GetDMChannel(CommandContext ctx, DiscordUser user) [Command("dumpdmchannels")] [Description("Dump all DM channels")] [IsBotOwner] - public async Task DumpDMChannels(CommandContext ctx) + public async Task DumpDMChannels(TextCommandContext ctx) { var dmChannels = ctx.Client.PrivateChannels; @@ -523,11 +531,12 @@ public async Task DumpDMChannels(CommandContext ctx) [Command("searchmembers")] [Description("Search member list with a regex. Restricted to bot owners bc regexes are scary.")] [IsBotOwner] - public async Task SearchMembersCmd(CommandContext ctx, string regex) + public async Task SearchMembersCmd(TextCommandContext ctx, string regex) { var rx = new Regex(regex); - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); + var msg = await ctx.GetResponseAsync(); var discordMembers = await ctx.Guild.GetAllMembersAsync().ToListAsync(); var matchedMembers = discordMembers.Where(discordMember => discordMember.Username is not null && rx.IsMatch(discordMember.Username)).ToList(); @@ -540,9 +549,9 @@ public async Task SearchMembersCmd(CommandContext ctx, string regex) [Command("rawmessage")] [Description("Dumps the raw data for a message.")] - [Aliases("rawmsg")] + [TextAlias("rawmsg")] [IsBotOwner] - public async Task DumpRawMessage(CommandContext ctx, [Description("The message whose raw data to get.")] string msgLinkOrId) + public async Task DumpRawMessage(TextCommandContext ctx, [Description("The message whose raw data to get.")] string msgLinkOrId) { DiscordMessage message; if (Constants.RegexConstants.discord_link_rx.IsMatch(msgLinkOrId)) diff --git a/Commands/Dehoist.cs b/Commands/Dehoist.cs index 3e1a61b5..5fbc3cf9 100644 --- a/Commands/Dehoist.cs +++ b/Commands/Dehoist.cs @@ -1,11 +1,14 @@ -namespace Cliptok.Commands +using DSharpPlus.Commands.Trees.Metadata; + +namespace Cliptok.Commands { - internal class Dehoist : BaseCommandModule + internal class Dehoist { [Command("dehoist")] [Description("Adds an invisible character to someone's nickname that drops them to the bottom of the member list. Accepts multiple members.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task DehoistCmd(CommandContext ctx, [Description("List of server members to dehoist")] params DiscordMember[] discordMembers) + public async Task DehoistCmd(TextCommandContext ctx, [Description("List of server members to dehoist")] params DiscordMember[] discordMembers) { if (discordMembers.Length == 0) { @@ -35,7 +38,8 @@ await discordMembers[0].ModifyAsync(a => return; } - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); + var msg = await ctx.GetResponseAsync(); int failedCount = 0; foreach (DiscordMember discordMember in discordMembers) @@ -67,10 +71,12 @@ await discordMember.ModifyAsync(a => [Command("massdehoist")] [Description("Dehoist everyone on the server who has a bad name. This may take a while and can exhaust rate limits.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task MassDehoist(CommandContext ctx) + public async Task MassDehoist(TextCommandContext ctx) { - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); + var msg = await ctx.GetResponseAsync(); var discordMembers = await ctx.Guild.GetAllMembersAsync().ToListAsync(); int failedCount = 0; @@ -87,8 +93,9 @@ public async Task MassDehoist(CommandContext ctx) [Command("massundehoist")] [Description("Remove the dehoist for users attached via a txt file.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task MassUndhoist(CommandContext ctx) + public async Task MassUndhoist(TextCommandContext ctx) { int failedCount = 0; @@ -106,7 +113,8 @@ public async Task MassUndhoist(CommandContext ctx) var list = strList.Split(' '); - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it. This will take a while."); + var msg = await ctx.GetResponseAsync(); foreach (string strID in list) { @@ -143,14 +151,15 @@ await member.ModifyAsync(a => } } - [Group("permadehoist")] + [Command("permadehoist")] [Description("Permanently/persistently dehoist members.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public class Permadehoist : BaseCommandModule + public class Permadehoist { // Toggle - [GroupCommand] - public async Task PermadehoistToggleCmd(CommandContext ctx, [Description("The member(s) to permadehoist.")] params DiscordUser[] discordUsers) + [DefaultGroupCommand] + public async Task PermadehoistToggleCmd(TextCommandContext ctx, [Description("The member(s) to permadehoist.")] params DiscordUser[] discordUsers) { if (discordUsers.Length == 0) { @@ -200,7 +209,8 @@ await ctx.RespondAsync(new DiscordMessageBuilder() // Toggle permadehoist for multiple members - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); + var msg = await ctx.GetResponseAsync(); int failedCount = 0; foreach (var discordUser in discordUsers) @@ -215,7 +225,7 @@ await ctx.RespondAsync(new DiscordMessageBuilder() [Command("enable")] [Description("Permanently dehoist a member (or members). They will be automatically dehoisted until disabled.")] - public async Task PermadehoistEnableCmd(CommandContext ctx, [Description("The member(s) to permadehoist.")] params DiscordUser[] discordUsers) + public async Task PermadehoistEnableCmd(TextCommandContext ctx, [Description("The member(s) to permadehoist.")] params DiscordUser[] discordUsers) { if (discordUsers.Length == 0) { @@ -249,7 +259,8 @@ await ctx.RespondAsync(new DiscordMessageBuilder() // Permadehoist multiple members - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); + var msg = await ctx.GetResponseAsync(); int failedCount = 0; foreach (var discordUser in discordUsers) @@ -264,7 +275,7 @@ await ctx.RespondAsync(new DiscordMessageBuilder() [Command("disable")] [Description("Disable permadehoist for a member (or members).")] - public async Task PermadehoistDisableCmd(CommandContext ctx, [Description("The member(s) to remove the permadehoist for.")] params DiscordUser[] discordUsers) + public async Task PermadehoistDisableCmd(TextCommandContext ctx, [Description("The member(s) to remove the permadehoist for.")] params DiscordUser[] discordUsers) { if (discordUsers.Length == 0) { @@ -298,7 +309,8 @@ await ctx.RespondAsync(new DiscordMessageBuilder() // Un-permadehoist multiple members - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Working on it..."); + var msg = await ctx.GetResponseAsync(); int failedCount = 0; foreach (var discordUser in discordUsers) @@ -313,7 +325,7 @@ await ctx.RespondAsync(new DiscordMessageBuilder() [Command("status")] [Description("Check the status of permadehoist for a member.")] - public async Task PermadehoistStatus(CommandContext ctx, [Description("The member whose permadehoist status to check.")] DiscordUser discordUser) + public async Task PermadehoistStatus(TextCommandContext ctx, [Description("The member whose permadehoist status to check.")] DiscordUser discordUser) { if (await Program.db.SetContainsAsync("permadehoists", discordUser.Id)) await ctx.RespondAsync(new DiscordMessageBuilder() diff --git a/Commands/DmRelayBlock.cs b/Commands/DmRelayBlock.cs index 69c11e38..60cbe8fd 100644 --- a/Commands/DmRelayBlock.cs +++ b/Commands/DmRelayBlock.cs @@ -1,12 +1,13 @@ namespace Cliptok.Commands { - internal class DmRelayBlock : BaseCommandModule + internal class DmRelayBlock { [Command("dmrelayblock")] [Description("Stop a member's DMs from being relayed to the configured DM relay channel.")] - [Aliases("dmblock")] + [AllowedProcessors(typeof(TextCommandProcessor))] + [TextAlias("dmblock")] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task DmRelayBlockCommand(CommandContext ctx, [Description("The member to stop relaying DMs from.")] DiscordUser user) + public async Task DmRelayBlockCommand(TextCommandContext ctx, [Description("The member to stop relaying DMs from.")] DiscordUser user) { // Only function in configured DM relay channel/thread; do nothing if in wrong channel if (ctx.Channel.Id != Program.cfgjson.DmLogChannelId && Program.cfgjson.LogChannels.All(a => a.Value.ChannelId != ctx.Channel.Id)) return; diff --git a/Commands/FunCmds.cs b/Commands/FunCmds.cs index 4769ed5d..3b717310 100644 --- a/Commands/FunCmds.cs +++ b/Commands/FunCmds.cs @@ -1,11 +1,12 @@ namespace Cliptok.Commands { - internal class FunCmds : BaseCommandModule + internal class FunCmds { [Command("tellraw")] [Description("Nothing of interest.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task TellRaw(CommandContext ctx, [Description("???")] DiscordChannel discordChannel, [RemainingText, Description("???")] string output) + public async Task TellRaw(TextCommandContext ctx, [Description("???")] DiscordChannel discordChannel, [RemainingText, Description("???")] string output) { try { @@ -22,9 +23,10 @@ public async Task TellRaw(CommandContext ctx, [Description("???")] DiscordChanne [Command("no")] [Description("Makes Cliptok choose something for you. Outputs either Yes or No.")] - [Aliases("yes")] + [TextAlias("yes")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Tier5)] - public async Task No(CommandContext ctx) + public async Task No(TextCommandContext ctx) { List noResponses = new() { diff --git a/Commands/Grant.cs b/Commands/Grant.cs index 78207849..a3483028 100644 --- a/Commands/Grant.cs +++ b/Commands/Grant.cs @@ -1,12 +1,13 @@ namespace Cliptok.Commands { - internal class Grant : BaseCommandModule + internal class Grant { [Command("grant")] [Description("Grant a user access to the server, by giving them the Tier 1 role.")] - [Aliases("clipgrant", "verify")] + [TextAlias("grant", "clipgrant", "verify")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task GrantCommand(CommandContext ctx, [Description("The member to grant Tier 1 role to.")] DiscordUser _) + public async Task GrantCommand(TextCommandContext ctx, [Description("The member to grant Tier 1 role to.")] DiscordUser _) { await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works. Please right click (or tap and hold on mobile) the user and click \"Verify Member\" if available."); } diff --git a/Commands/InteractionCommands/AnnouncementInteractions.cs b/Commands/InteractionCommands/AnnouncementInteractions.cs index 2a7907fc..43983327 100644 --- a/Commands/InteractionCommands/AnnouncementInteractions.cs +++ b/Commands/InteractionCommands/AnnouncementInteractions.cs @@ -1,37 +1,32 @@ namespace Cliptok.Commands.InteractionCommands { - internal class AnnouncementInteractions : ApplicationCommandModule + internal class AnnouncementInteractions { - [SlashCommand("announcebuild", "Announce a Windows Insider build in the current channel.", defaultPermission: false)] + [Command("announcebuild")] + [Description("Announce a Windows Insider build in the current channel.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task AnnounceBuildSlashCommand(InteractionContext ctx, - [Choice("Windows 10", 10)] - [Choice("Windows 11", 11)] - [Option("windows_version", "The Windows version to announce a build of. Must be either 10 or 11.")] long windowsVersion, - - [Option("build_number", "Windows build number, including any decimals (Decimals are optional). Do not include the word Build.")] string buildNumber, - - [Option("blog_link", "The link to the Windows blog entry relating to this build.")] string blogLink, - - [Choice("Canary Channel", "Canary")] - [Choice("Dev Channel", "Dev")] - [Choice("Beta Channel", "Beta")] - [Choice("Release Preview Channel", "RP")] - [Option("insider_role1", "The first insider role to ping.")] string insiderChannel1, - - [Choice("Canary Channel", "Canary")] - [Choice("Dev Channel", "Dev")] - [Choice("Beta Channel", "Beta")] - [Choice("Release Preview Channel", "RP")] - [Option("insider_role2", "The second insider role to ping.")] string insiderChannel2 = "", - - [Option("canary_create_new_thread", "Enable this option if you want to create a new Canary thread for some reason")] bool canaryCreateNewThread = false, - [Option("thread", "The thread to mention in the announcement.")] DiscordChannel threadChannel = default, - [Option("flavour_text", "Extra text appended on the end of the main line, replacing :WindowsInsider: or :Windows10:")] string flavourText = "", - [Option("autothread_name", "If no thread is given, create a thread with this name.")] string autothreadName = "Build {0} ({1})", - - [Option("lockdown", "Set 0 to not lock. Lock the channel for a certain period of time after announcing the build.")] string lockdownTime = "auto" + [RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task AnnounceBuildSlashCommand(SlashCommandContext ctx, + [SlashChoiceProvider(typeof(WindowsVersionChoiceProvider))] + [Parameter("windows_version"), Description("The Windows version to announce a build of. Must be either 10 or 11.")] long windowsVersion, + + [Parameter("build_number"), Description("Windows build number, including any decimals (Decimals are optional). Do not include the word Build.")] string buildNumber, + + [Parameter("blog_link"), Description("The link to the Windows blog entry relating to this build.")] string blogLink, + + [SlashChoiceProvider(typeof(WindowsInsiderChannelChoiceProvider))] + [Parameter("insider_role1"), Description("The first insider role to ping.")] string insiderChannel1, // TODO(#202): test choices!!! + + [SlashChoiceProvider(typeof(WindowsInsiderChannelChoiceProvider))] + [Parameter("insider_role2"), Description("The second insider role to ping.")] string insiderChannel2 = "", // TODO(#202): test choices!!! + + [Parameter("canary_create_new_thread"), Description("Enable this option if you want to create a new Canary thread for some reason")] bool canaryCreateNewThread = false, + [Parameter("thread"), Description("The thread to mention in the announcement.")] DiscordChannel threadChannel = default, + [Parameter("flavour_text"), Description("Extra text appended on the end of the main line, replacing :WindowsInsider: or :Windows10:")] string flavourText = "", + [Parameter("autothread_name"), Description("If no thread is given, create a thread with this name.")] string autothreadName = "Build {0} ({1})", + + [Parameter("lockdown"), Description("Set 0 to not lock. Lock the channel for a certain period of time after announcing the build.")] string lockdownTime = "auto" ) { if (Program.cfgjson.InsiderCommandLockedToChannel != 0 && ctx.Channel.Id != Program.cfgjson.InsiderCommandLockedToChannel) @@ -168,7 +163,7 @@ public async Task AnnounceBuildSlashCommand(InteractionContext ctx, await insiderRole2.ModifyAsync(mentionable: true); await ctx.RespondAsync(pingMsgString); - messageSent = await ctx.GetOriginalResponseAsync(); + messageSent = await ctx.GetResponseAsync(); await insiderRole1.ModifyAsync(mentionable: false); if (insiderChannel2 != "") @@ -202,7 +197,7 @@ public async Task AnnounceBuildSlashCommand(InteractionContext ctx, } await ctx.RespondAsync(noPingMsgString); - messageSent = await ctx.GetOriginalResponseAsync(); + messageSent = await ctx.GetResponseAsync(); } if (threadChannel == default) @@ -263,6 +258,32 @@ public async Task AnnounceBuildSlashCommand(InteractionContext ctx, await LockdownHelpers.LockChannelAsync(user: ctx.User, channel: ctx.Channel, duration: lockDuration); } } + + internal class WindowsVersionChoiceProvider : IChoiceProvider + { + public async ValueTask> ProvideAsync(CommandParameter _) + { + return new Dictionary + { + { "Windows 10", "10" }, + { "Windows 11", "11" } + }; + } + } + + internal class WindowsInsiderChannelChoiceProvider : IChoiceProvider + { + public async ValueTask> ProvideAsync(CommandParameter _) + { + return new Dictionary + { + { "Canary Channel", "Canary" }, + { "Dev Channel", "Dev" }, + { "Beta Channel", "Beta" }, + { "Release Preview Channel", "RP" } + }; + } + } } } diff --git a/Commands/InteractionCommands/BanInteractions.cs b/Commands/InteractionCommands/BanInteractions.cs index 64bcfbf8..7d9ce41c 100644 --- a/Commands/InteractionCommands/BanInteractions.cs +++ b/Commands/InteractionCommands/BanInteractions.cs @@ -2,21 +2,23 @@ namespace Cliptok.Commands.InteractionCommands { - internal class BanInteractions : ApplicationCommandModule + internal class BanInteractions { - [SlashCommand("ban", "Bans a user from the server, either permanently or temporarily.", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), SlashCommandPermissions(DiscordPermissions.BanMembers)] - public async Task BanSlashCommand(InteractionContext ctx, - [Option("user", "The user to ban")] DiscordUser user, - [Option("reason", "The reason the user is being banned")] string reason, - [Option("keep_messages", "Whether to keep the users messages when banning")] bool keepMessages = false, - [Option("time", "The length of time the user is banned for")] string time = null, - [Option("appeal_link", "Whether to show the user an appeal URL in the DM")] bool appealable = false + [Command("ban")] + [Description("Bans a user from the server, either permanently or temporarily.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.BanMembers)] + public async Task BanSlashCommand(SlashCommandContext ctx, + [Parameter("user"), Description("The user to ban")] DiscordUser user, + [Parameter("reason"), Description("The reason the user is being banned")] string reason, + [Parameter("keep_messages"), Description("Whether to keep the users messages when banning")] bool keepMessages = false, + [Parameter("time"), Description("The length of time the user is banned for")] string time = null, + [Parameter("appeal_link"), Description("Whether to show the user an appeal URL in the DM")] bool appealable = false ) { // Initial response to avoid the 3 second timeout, will edit later. var eout = new DiscordInteractionResponseBuilder().AsEphemeral(true); - await ctx.CreateResponseAsync(DiscordInteractionResponseType.DeferredChannelMessageWithSource, eout); + await ctx.DeferResponseAsync(); // TODO(#202): ephemeral // Edits need a webhook rather than interaction..? DiscordWebhookBuilder webhookOut = new(); @@ -55,7 +57,7 @@ public async Task BanSlashCommand(InteractionContext ctx, { try { - banDuration = HumanDateParser.HumanDateParser.Parse(time).Subtract(ctx.Interaction.CreationTimestamp.DateTime); + banDuration = HumanDateParser.HumanDateParser.Parse(time).Subtract(DateTime.UtcNow); // TODO(#202): this used InteractionContext#Interaction.CreationTimestamp.LocalDateTime before, please test!! } catch { @@ -112,9 +114,11 @@ public async Task BanSlashCommand(InteractionContext ctx, await ctx.EditResponseAsync(webhookOut); } - [SlashCommand("unban", "Unbans a user who has been previously banned.", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), SlashCommandPermissions(DiscordPermissions.BanMembers)] - public async Task SlashUnbanCommand(InteractionContext ctx, [Option("user", "The ID or mention of the user to unban. Ignore the suggestions, IDs work.")] SnowflakeObject userId, [Option("reason", "Used in audit log only currently")] string reason = "No reason specified.") + [Command("unban")] + [Description("Unbans a user who has been previously banned.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.BanMembers)] + public async Task SlashUnbanCommand(SlashCommandContext ctx, [Parameter("user"), Description("The ID or mention of the user to unban. Ignore the suggestions, IDs work.")] SnowflakeObject userId, [Parameter("reason"), Description("Used in audit log only currently")] string reason = "No reason specified.") { DiscordUser targetUser = default; try @@ -143,9 +147,11 @@ public async Task SlashUnbanCommand(InteractionContext ctx, [Option("user", "The } } - [SlashCommand("kick", "Kicks a user, removing them from the server until they rejoin.", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), SlashCommandPermissions(DiscordPermissions.KickMembers)] - public async Task KickCmd(InteractionContext ctx, [Option("user", "The user you want to kick from the server.")] DiscordUser target, [Option("reason", "The reason for kicking this user.")] string reason = "No reason specified.") + [Command("kick")] + [Description("Kicks a user, removing them from the server until they rejoin.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.KickMembers)] + public async Task KickCmd(SlashCommandContext ctx, [Parameter("user"), Description("The user you want to kick from the server.")] DiscordUser target, [Parameter("reason"), Description("The reason for kicking this user.")] string reason = "No reason specified.") { if (target.IsBot) { diff --git a/Commands/InteractionCommands/ClearInteractions.cs b/Commands/InteractionCommands/ClearInteractions.cs index 1d8c8233..bcc6ec21 100644 --- a/Commands/InteractionCommands/ClearInteractions.cs +++ b/Commands/InteractionCommands/ClearInteractions.cs @@ -1,37 +1,39 @@ namespace Cliptok.Commands.InteractionCommands { - public class ClearInteractions : ApplicationCommandModule + public class ClearInteractions { public static Dictionary> MessagesToClear = new(); - [SlashCommand("clear", "Delete many messages from the current channel.", defaultPermission: false)] - [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequireBotPermissions(DiscordPermissions.ManageMessages), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task ClearSlashCommand(InteractionContext ctx, - [Option("count", "The number of messages to consider for deletion. Required if you don't use the 'up_to' argument.")] long count = 0, - [Option("up_to", "Optionally delete messages up to (not including) this one. Accepts IDs and links.")] string upTo = "", - [Option("user", "Optionally filter the deletion to a specific user.")] DiscordUser user = default, - [Option("ignore_mods", "Optionally filter the deletion to only messages sent by users who are not Moderators.")] bool ignoreMods = false, - [Option("match", "Optionally filter the deletion to only messages containing certain text.")] string match = "", - [Option("bots_only", "Optionally filter the deletion to only bots.")] bool botsOnly = false, - [Option("humans_only", "Optionally filter the deletion to only humans.")] bool humansOnly = false, - [Option("attachments_only", "Optionally filter the deletion to only messages with attachments.")] bool attachmentsOnly = false, - [Option("stickers_only", "Optionally filter the deletion to only messages with stickers.")] bool stickersOnly = false, - [Option("links_only", "Optionally filter the deletion to only messages containing links.")] bool linksOnly = false, - [Option("dry_run", "Don't actually delete the messages, just output what would be deleted.")] bool dryRun = false + [Command("clear")] + [Description("Delete many messages from the current channel.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ManageMessages, DiscordPermissions.ModerateMembers)] + public async Task ClearSlashCommand(SlashCommandContext ctx, + [Parameter("count"), Description("The number of messages to consider for deletion. Required if you don't use the 'up_to' argument.")] long count = 0, + [Parameter("up_to"), Description("Optionally delete messages up to (not including) this one. Accepts IDs and links.")] string upTo = "", + [Parameter("user"), Description("Optionally filter the deletion to a specific user.")] DiscordUser user = default, + [Parameter("ignore_mods"), Description("Optionally filter the deletion to only messages sent by users who are not Moderators.")] bool ignoreMods = false, + [Parameter("match"), Description("Optionally filter the deletion to only messages containing certain text.")] string match = "", + [Parameter("bots_only"), Description("Optionally filter the deletion to only bots.")] bool botsOnly = false, + [Parameter("humans_only"), Description("Optionally filter the deletion to only humans.")] bool humansOnly = false, + [Parameter("attachments_only"), Description("Optionally filter the deletion to only messages with attachments.")] bool attachmentsOnly = false, + [Parameter("stickers_only"), Description("Optionally filter the deletion to only messages with stickers.")] bool stickersOnly = false, + [Parameter("links_only"), Description("Optionally filter the deletion to only messages containing links.")] bool linksOnly = false, + [Parameter("dry_run"), Description("Don't actually delete the messages, just output what would be deleted.")] bool dryRun = false ) { - await ctx.DeferAsync(ephemeral: !dryRun); + await ctx.DeferResponseAsync(ephemeral: !dryRun); // If all args are unset if (count == 0 && upTo == "" && user == default && ignoreMods == false && match == "" && botsOnly == false && humansOnly == false && attachmentsOnly == false && stickersOnly == false && linksOnly == false) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You must provide at least one argument! I need to know which messages to delete.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You must provide at least one argument! I need to know which messages to delete.").AsEphemeral(true)); return; } if (count == 0 && upTo == "") { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I need to know how many messages to delete! Please provide a value for `count` or `up_to`.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I need to know how many messages to delete! Please provide a value for `count` or `up_to`.").AsEphemeral(true)); return; } @@ -39,13 +41,13 @@ public async Task ClearSlashCommand(InteractionContext ctx, if (count < 0) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I can't delete a negative number of messages! Try setting `count` to a positive number.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I can't delete a negative number of messages! Try setting `count` to a positive number.").AsEphemeral(true)); return; } if (count >= 1000) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} Deleting that many messages poses a risk of something disastrous happening, so I'm refusing your request, sorry.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} Deleting that many messages poses a risk of something disastrous happening, so I'm refusing your request, sorry.").AsEphemeral(true)); return; } @@ -53,7 +55,7 @@ public async Task ClearSlashCommand(InteractionContext ctx, if (upTo != "" && count != 0) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You can't provide both a count of messages and a message to delete up to! Please only provide one of the two arguments.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You can't provide both a count of messages and a message to delete up to! Please only provide one of the two arguments.").AsEphemeral(true)); return; } @@ -71,7 +73,7 @@ public async Task ClearSlashCommand(InteractionContext ctx, { if (!ulong.TryParse(upTo, out messageId)) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That doesn't look like a valid message ID or link! Please try again.")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That doesn't look like a valid message ID or link! Please try again.")); return; } } @@ -82,7 +84,7 @@ public async Task ClearSlashCommand(InteractionContext ctx, || !ulong.TryParse(Constants.RegexConstants.discord_link_rx.Match(upTo).Groups[3].Value, out messageId) ) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} Please provide a valid link to a message in this channel!").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} Please provide a valid link to a message in this channel!").AsEphemeral(true)); return; } } @@ -159,7 +161,7 @@ public async Task ClearSlashCommand(InteractionContext ctx, { if (humansOnly) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You can't use `bots_only` and `humans_only` together! Pick one or the other please.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You can't use `bots_only` and `humans_only` together! Pick one or the other please.").AsEphemeral(true)); return; } @@ -234,7 +236,7 @@ public async Task ClearSlashCommand(InteractionContext ctx, if (messagesToClear.Count == 0 && skipped) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} All of the messages to delete are older than 2 weeks, so I can't delete them!").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} All of the messages to delete are older than 2 weeks, so I can't delete them!").AsEphemeral(true)); return; } @@ -245,7 +247,7 @@ public async Task ClearSlashCommand(InteractionContext ctx, var msg = await LogChannelHelper.CreateDumpMessageAsync($"{Program.cfgjson.Emoji.Information} **{messagesToClear.Count}** messages would have been deleted, but are instead logged below.", messagesToClear, ctx.Channel); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent(msg.Content).AddFiles(msg.Files).AddEmbeds(msg.Embeds).AsEphemeral(false)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent(msg.Content).AddFiles(msg.Files).AddEmbeds(msg.Embeds).AsEphemeral(false)); return; } @@ -253,7 +255,7 @@ public async Task ClearSlashCommand(InteractionContext ctx, if (messagesToClear.Count >= 50) { DiscordButtonComponent confirmButton = new(DiscordButtonStyle.Danger, "clear-confirm-callback", "Delete Messages"); - DiscordMessage confirmationMessage = await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Muted} You're about to delete {messagesToClear.Count} messages. Are you sure?").AddComponents(confirmButton).AsEphemeral(true)); + DiscordMessage confirmationMessage = await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Muted} You're about to delete {messagesToClear.Count} messages. Are you sure?").AddComponents(confirmButton).AsEphemeral(true)); MessagesToClear.Add(confirmationMessage.Id, messagesToClear); } @@ -275,11 +277,11 @@ await LogChannelHelper.LogMessageAsync("mod", .WithContent($"{Program.cfgjson.Emoji.Deleted} **{messagesToClear.Count}** messages were cleared in {ctx.Channel.Mention} by {ctx.User.Mention}.") .WithAllowedMentions(Mentions.None) ); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Done!").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Done!").AsEphemeral(true)); } else { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} There were no messages that matched all of the arguments you provided! Nothing to do.")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} There were no messages that matched all of the arguments you provided! Nothing to do.")); } await LogChannelHelper.LogDeletedMessagesAsync( diff --git a/Commands/InteractionCommands/ContextCommands.cs b/Commands/InteractionCommands/ContextCommands.cs index 19cb4de4..6fb48819 100644 --- a/Commands/InteractionCommands/ContextCommands.cs +++ b/Commands/InteractionCommands/ContextCommands.cs @@ -1,47 +1,57 @@ namespace Cliptok.Commands.InteractionCommands { - internal class ContextCommands : ApplicationCommandModule + internal class ContextCommands { - [ContextMenu(DiscordApplicationCommandType.UserContextMenu, "Show Avatar", defaultPermission: true)] - public async Task ContextAvatar(ContextMenuContext ctx) + [Command("Show Avatar")] + [SlashCommandTypes(DiscordApplicationCommandType.UserContextMenu)] + [AllowedProcessors(typeof(UserCommandProcessor))] + public async Task ContextAvatar(CommandContext ctx, DiscordUser targetUser) { - string avatarUrl = await LykosAvatarMethods.UserOrMemberAvatarURL(ctx.TargetUser, ctx.Guild); + string avatarUrl = await LykosAvatarMethods.UserOrMemberAvatarURL(targetUser, ctx.Guild); DiscordEmbedBuilder embed = new DiscordEmbedBuilder() .WithColor(new DiscordColor(0xC63B68)) .WithTimestamp(DateTime.UtcNow) .WithImageUrl(avatarUrl) .WithAuthor( - $"Avatar for {ctx.TargetUser.Username} (Click to open in browser)", + $"Avatar for {targetUser.Username} (Click to open in browser)", avatarUrl ); await ctx.RespondAsync(null, embed, ephemeral: true); } - [ContextMenu(DiscordApplicationCommandType.UserContextMenu, "Show Notes", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task ShowNotes(ContextMenuContext ctx) + [Command("Show Notes")] + [SlashCommandTypes(DiscordApplicationCommandType.UserContextMenu)] + [AllowedProcessors(typeof(UserCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task ShowNotes(CommandContext ctx, DiscordUser targetUser) { - await ctx.RespondAsync(embed: await UserNoteHelpers.GenerateUserNotesEmbedAsync(ctx.TargetUser), ephemeral: true); + await ctx.RespondAsync(embed: await UserNoteHelpers.GenerateUserNotesEmbedAsync(targetUser), ephemeral: true); } - [ContextMenu(DiscordApplicationCommandType.UserContextMenu, "Show Warnings", defaultPermission: true)] - public async Task ContextWarnings(ContextMenuContext ctx) + [Command("Show Warnings")] + [SlashCommandTypes(DiscordApplicationCommandType.UserContextMenu)] + [AllowedProcessors(typeof(UserCommandProcessor))] + public async Task ContextWarnings(CommandContext ctx, DiscordUser targetUser) { - await ctx.RespondAsync(embed: await WarningHelpers.GenerateWarningsEmbedAsync(ctx.TargetUser), ephemeral: true); + await ctx.RespondAsync(embed: await WarningHelpers.GenerateWarningsEmbedAsync(targetUser), ephemeral: true); } - [ContextMenu(DiscordApplicationCommandType.UserContextMenu, "User Information", defaultPermission: true)] - public async Task ContextUserInformation(ContextMenuContext ctx) + [Command("User Information")] + [SlashCommandTypes(DiscordApplicationCommandType.UserContextMenu)] + [AllowedProcessors(typeof(UserCommandProcessor))] + public async Task ContextUserInformation(CommandContext ctx, DiscordUser targetUser) { - await ctx.RespondAsync(embed: await DiscordHelpers.GenerateUserEmbed(ctx.TargetUser, ctx.Guild), ephemeral: true); + await ctx.RespondAsync(embed: await DiscordHelpers.GenerateUserEmbed(targetUser, ctx.Guild), ephemeral: true); } - [ContextMenu(DiscordApplicationCommandType.UserContextMenu, "Hug", defaultPermission: true),] - public async Task Hug(ContextMenuContext ctx) + [Command("Hug")] + [SlashCommandTypes(DiscordApplicationCommandType.UserContextMenu)] + [AllowedProcessors(typeof(UserCommandProcessor))] + public async Task Hug(CommandContext ctx, DiscordUser targetUser) { - var user = ctx.TargetUser; + var user = targetUser; if (user is not null) { diff --git a/Commands/InteractionCommands/DebugInteractions.cs b/Commands/InteractionCommands/DebugInteractions.cs index 64cd5669..5400f357 100644 --- a/Commands/InteractionCommands/DebugInteractions.cs +++ b/Commands/InteractionCommands/DebugInteractions.cs @@ -2,12 +2,13 @@ namespace Cliptok.Commands.InteractionCommands { - internal class DebugInteractions : ApplicationCommandModule + internal class DebugInteractions { - [SlashCommand("scamcheck", "Check if a link or message is known to the anti-phishing API.", defaultPermission: false)] + [Command("scamcheck")] [Description("Check if a link or message is known to the anti-phishing API.")] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task ScamCheck(InteractionContext ctx, [Option("input", "Domain or message content to scan.")] string content) + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task ScamCheck(SlashCommandContext ctx, [Parameter("input"), Description("Domain or message content to scan.")] string content) { var urlMatches = Constants.RegexConstants.url_rx.Matches(content); if (urlMatches.Count > 0 && Environment.GetEnvironmentVariable("CLIPTOK_ANTIPHISHING_ENDPOINT") is not null && Environment.GetEnvironmentVariable("CLIPTOK_ANTIPHISHING_ENDPOINT") != "useyourimagination") @@ -34,9 +35,11 @@ public async Task ScamCheck(InteractionContext ctx, [Option("input", "Domain or } } - [SlashCommand("tellraw", "You know what you're here for.", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task TellRaw(InteractionContext ctx, [Option("input", "???")] string input, [Option("reply_msg_id", "ID of message to use in a reply context.")] string replyID = "0", [Option("pingreply", "Ping pong.")] bool pingreply = true, [Option("channel", "Either mention or ID. Not a name.")] string discordChannel = default) + [Command("tellraw")] + [Description("You know what you're here for.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task TellRaw(SlashCommandContext ctx, [Parameter("input"), Description("???")] string input, [Parameter("reply_msg_id"), Description("ID of message to use in a reply context.")] string replyID = "0", [Parameter("pingreply"), Description("Ping pong.")] bool pingreply = true, [Parameter("channel"), Description("Either mention or ID. Not a name.")] string discordChannel = default) { DiscordChannel channelObj = default; @@ -89,30 +92,36 @@ await LogChannelHelper.LogMessageAsync("secret", ); } - [SlashCommand("userinfo", "Retrieve information about a given user.")] - public async Task UserInfoSlashCommand(InteractionContext ctx, [Option("user", "The user to retrieve information about.")] DiscordUser user, [Option("public", "Whether to show the output publicly.")] bool publicMessage = false) + [Command("userinfo")] + [Description("Retrieve information about a given user.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + public async Task UserInfoSlashCommand(SlashCommandContext ctx, [Parameter("user"), Description("The user to retrieve information about.")] DiscordUser user, [Parameter("public"), Description("Whether to show the output publicly.")] bool publicMessage = false) { await ctx.RespondAsync(embed: await DiscordHelpers.GenerateUserEmbed(user, ctx.Guild), ephemeral: !publicMessage); } - [SlashCommand("muteinfo", "Show information about the mute for a user.")] + [Command("muteinfo")] + [Description("Show information about the mute for a user.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] + [RequirePermissions(DiscordPermissions.ModerateMembers)] public async Task MuteInfoSlashCommand( - InteractionContext ctx, - [Option("user", "The user whose mute information to show.")] DiscordUser targetUser, - [Option("public", "Whether to show the output publicly. Default: false")] bool isPublic = false) + SlashCommandContext ctx, + [Parameter("user"), Description("The user whose mute information to show.")] DiscordUser targetUser, + [Parameter("public"), Description("Whether to show the output publicly. Default: false")] bool isPublic = false) { await ctx.RespondAsync(embed: await MuteHelpers.MuteStatusEmbed(targetUser, ctx.Guild), ephemeral: !isPublic); } - [SlashCommand("baninfo", "Show information about the ban for a user.")] + [Command("baninfo")] + [Description("Show information about the ban for a user.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] + [RequirePermissions(DiscordPermissions.ModerateMembers)] public async Task BanInfoSlashCommand( - InteractionContext ctx, - [Option("user", "The user whose ban information to show.")] DiscordUser targetUser, - [Option("public", "Whether to show the output publicly. Default: false")] bool isPublic = false) + SlashCommandContext ctx, + [Parameter("user"), Description("The user whose ban information to show.")] DiscordUser targetUser, + [Parameter("public"), Description("Whether to show the output publicly. Default: false")] bool isPublic = false) { await ctx.RespondAsync(embed: await BanHelpers.BanStatusEmbed(targetUser, ctx.Guild), ephemeral: !isPublic); } diff --git a/Commands/InteractionCommands/DehoistInteractions.cs b/Commands/InteractionCommands/DehoistInteractions.cs index fec1389c..646e1020 100644 --- a/Commands/InteractionCommands/DehoistInteractions.cs +++ b/Commands/InteractionCommands/DehoistInteractions.cs @@ -1,10 +1,12 @@ namespace Cliptok.Commands.InteractionCommands { - internal class DehoistInteractions : ApplicationCommandModule + internal class DehoistInteractions { - [SlashCommand("dehoist", "Dehoist a member, dropping them to the bottom of the list. Lasts until they change nickname.", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), SlashCommandPermissions(DiscordPermissions.ManageNicknames)] - public async Task DehoistSlashCmd(InteractionContext ctx, [Option("member", "The member to dehoist.")] DiscordUser user) + [Command("dehoist")] + [Description("Dehoist a member, dropping them to the bottom of the list. Lasts until they change nickname.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.ManageNicknames)] + public async Task DehoistSlashCmd(SlashCommandContext ctx, [Parameter("member"), Description("The member to dehoist.")] DiscordUser user) { DiscordMember member; try @@ -38,12 +40,15 @@ await member.ModifyAsync(a => await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfuly dehoisted {member.Mention}!", mentions: false); } - [SlashCommandGroup("permadehoist", "Permanently/persistently dehoist members.", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ManageNicknames)] + [Command("permadehoist")] + [Description("Permanently/persistently dehoist members.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ManageNicknames)] public class PermadehoistSlashCommands { - [SlashCommand("enable", "Permanently dehoist a member. They will be automatically dehoisted until disabled.")] - public async Task PermadehoistEnableSlashCmd(InteractionContext ctx, [Option("member", "The member to permadehoist.")] DiscordUser discordUser) + [Command("enable")] + [Description("Permanently dehoist a member. They will be automatically dehoisted until disabled.")] + public async Task PermadehoistEnableSlashCmd(SlashCommandContext ctx, [Parameter("member"), Description("The member to permadehoist.")] DiscordUser discordUser) { var (success, isPermissionError) = await DehoistHelpers.PermadehoistMember(discordUser, ctx.User, ctx.Guild); @@ -57,8 +62,9 @@ public async Task PermadehoistEnableSlashCmd(InteractionContext ctx, [Option("me await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Failed to permadehoist {discordUser.Mention}!", mentions: false); } - [SlashCommand("disable", "Disable permadehoist for a member.")] - public async Task PermadehoistDisableSlashCmd(InteractionContext ctx, [Option("member", "The member to remove the permadehoist for.")] DiscordUser discordUser) + [Command("disable")] + [Description("Disable permadehoist for a member.")] + public async Task PermadehoistDisableSlashCmd(SlashCommandContext ctx, [Parameter("member"), Description("The member to remove the permadehoist for.")] DiscordUser discordUser) { var (success, isPermissionError) = await DehoistHelpers.UnpermadehoistMember(discordUser, ctx.User, ctx.Guild); @@ -72,8 +78,9 @@ public async Task PermadehoistDisableSlashCmd(InteractionContext ctx, [Option("m await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} Failed to remove the permadehoist for {discordUser.Mention}!", mentions: false); } - [SlashCommand("status", "Check the status of permadehoist for a member.")] - public async Task PermadehoistStatusSlashCmd(InteractionContext ctx, [Option("member", "The member whose permadehoist status to check.")] DiscordUser discordUser) + [Command("status")] + [Description("Check the status of permadehoist for a member.")] + public async Task PermadehoistStatusSlashCmd(SlashCommandContext ctx, [Parameter("member"), Description("The member whose permadehoist status to check.")] DiscordUser discordUser) { if (await Program.db.SetContainsAsync("permadehoists", discordUser.Id)) await ctx.RespondAsync($"{Program.cfgjson.Emoji.On} {discordUser.Mention} is permadehoisted.", mentions: false); diff --git a/Commands/InteractionCommands/JoinwatchInteractions.cs b/Commands/InteractionCommands/JoinwatchInteractions.cs index 2bb23bfd..d112e94f 100644 --- a/Commands/InteractionCommands/JoinwatchInteractions.cs +++ b/Commands/InteractionCommands/JoinwatchInteractions.cs @@ -1,15 +1,18 @@ namespace Cliptok.Commands.InteractionCommands { - internal class JoinwatchInteractions : ApplicationCommandModule + internal class JoinwatchInteractions { - [SlashCommandGroup("joinwatch", "Watch for joins and leaves of a given user. Output goes to #investigations.", defaultPermission: false)] + [Command("joinwatch")] + [Description("Watch for joins and leaves of a given user. Output goes to #investigations.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] public class JoinwatchSlashCmds { - [SlashCommand("add", "Watch for joins and leaves of a given user. Output goes to #investigations.")] - public async Task JoinwatchAdd(InteractionContext ctx, - [Option("user", "The user to watch for joins and leaves of.")] DiscordUser user, - [Option("note", "An optional note for context.")] string note = "") + [Command("add")] + [Description("Watch for joins and leaves of a given user. Output goes to #investigations.")] + public async Task JoinwatchAdd(SlashCommandContext ctx, + [Parameter("user"), Description("The user to watch for joins and leaves of.")] DiscordUser user, + [Parameter("note"), Description("An optional note for context.")] string note = "") { var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers"); @@ -50,9 +53,10 @@ public async Task JoinwatchAdd(InteractionContext ctx, } } - [SlashCommand("remove", "Stop watching for joins and leaves of a user.")] - public async Task JoinwatchRemove(InteractionContext ctx, - [Option("user", "The user to stop watching for joins and leaves of.")] DiscordUser user) + [Command("remove")] + [Description("Stop watching for joins and leaves of a user.")] + public async Task JoinwatchRemove(SlashCommandContext ctx, + [Parameter("user"), Description("The user to stop watching for joins and leaves of.")] DiscordUser user) { var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers"); @@ -68,9 +72,10 @@ public async Task JoinwatchRemove(InteractionContext ctx, await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Successfully unwatched {user.Mention}!"); } - [SlashCommand("status", "Check the joinwatch status for a user.")] - public async Task JoinwatchStatus(InteractionContext ctx, - [Option("user", "The user whose joinwatch status to check.")] DiscordUser user) + [Command("status")] + [Description("Check the joinwatch status for a user.")] + public async Task JoinwatchStatus(SlashCommandContext ctx, + [Parameter("user"), Description("The user whose joinwatch status to check.")] DiscordUser user) { var joinWatchlist = await Program.db.ListRangeAsync("joinWatchedUsers"); diff --git a/Commands/InteractionCommands/LockdownInteractions.cs b/Commands/InteractionCommands/LockdownInteractions.cs index cd132bf8..eda4734b 100644 --- a/Commands/InteractionCommands/LockdownInteractions.cs +++ b/Commands/InteractionCommands/LockdownInteractions.cs @@ -1,27 +1,30 @@ namespace Cliptok.Commands.InteractionCommands { - class LockdownInteractions : ApplicationCommandModule + class LockdownInteractions { public static bool ongoingLockdown = false; - [SlashCommandGroup("lockdown", "Lock the current channel or all channels in the server, preventing new messages. See also: unlock")] - [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequireBotPermissions(DiscordPermissions.ManageChannels)] + [Command("lockdown")] + [Description("Lock the current channel or all channels in the server, preventing new messages. See also: unlock")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.ManageChannels, DiscordPermissions.None)] public class LockdownCmds { - [SlashCommand("channel", "Lock the current channel. See also: unlock channel")] + [Command("channel")] + [Description("Lock the current channel. See also: unlock channel")] public async Task LockdownChannelCommand( - InteractionContext ctx, - [Option("reason", "The reason for the lockdown.")] string reason = "No reason specified.", - [Option("time", "The length of time to lock the channel for.")] string time = null, - [Option("lockthreads", "Whether to lock this channel's threads. Disables sending messages, but does not archive them.")] bool lockThreads = false) + SlashCommandContext ctx, + [Parameter("reason"), Description("The reason for the lockdown.")] string reason = "No reason specified.", + [Parameter("time"), Description("The length of time to lock the channel for.")] string time = null, + [Parameter("lockthreads"), Description("Whether to lock this channel's threads. Disables sending messages, but does not archive them.")] bool lockThreads = false) { - await ctx.DeferAsync(ephemeral: true); + await ctx.DeferResponseAsync(ephemeral: true); if (ctx.Channel.Type is DiscordChannelType.PublicThread or DiscordChannelType.PrivateThread or DiscordChannelType.NewsThread) { if (lockThreads) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Denied} You can't lock this channel!\n`/lockdown` with `lockthreads` cannot be used inside of a thread. If you meant to lock {ctx.Channel.Parent.Mention} and all of its threads, use the command there.\n\nIf you meant to only lock this thread, use `!lock` instead, or use `/lockdown` with `lockthreads` set to False.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Denied} You can't lock this channel!\n`/lockdown` with `lockthreads` cannot be used inside of a thread. If you meant to lock {ctx.Channel.Parent.Mention} and all of its threads, use the command there.\n\nIf you meant to only lock this thread, use `!lock` instead, or use `/lockdown` with `lockthreads` set to False.").AsEphemeral(true)); return; } @@ -33,7 +36,7 @@ await thread.ModifyAsync(a => a.Locked = true; }); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent("Thread locked successfully!").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent("Thread locked successfully!").AsEphemeral(true)); return; } @@ -41,46 +44,47 @@ await thread.ModifyAsync(a => if (!string.IsNullOrWhiteSpace(time)) { - lockDuration = HumanDateParser.HumanDateParser.Parse(time).Subtract(ctx.Interaction.CreationTimestamp.LocalDateTime); + lockDuration = HumanDateParser.HumanDateParser.Parse(time).Subtract(DateTime.Now); // TODO(#202): this used InteractionContext#Interaction.CreationTimestamp.DateTime before, please test!! } var currentChannel = ctx.Channel; if (!Program.cfgjson.LockdownEnabledChannels.Contains(currentChannel.Id)) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Denied} You can't lock or unlock this channel!\nIf this is in error, add its ID (`{currentChannel.Id}`) to the lockdown whitelist.")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Denied} You can't lock or unlock this channel!\nIf this is in error, add its ID (`{currentChannel.Id}`) to the lockdown whitelist.")); return; } if (ongoingLockdown) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} A mass lockdown or unlock is already ongoing. Refusing your request to avoid conflicts, sorry.")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} A mass lockdown or unlock is already ongoing. Refusing your request to avoid conflicts, sorry.")); return; } bool success = await LockdownHelpers.LockChannelAsync(user: ctx.User, channel: currentChannel, duration: lockDuration, reason: reason, lockThreads: lockThreads); if (success) - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent("Channel locked successfully.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent("Channel locked successfully.").AsEphemeral(true)); else - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent("Failed to lock this channel!").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent("Failed to lock this channel!").AsEphemeral(true)); } - [SlashCommand("all", "Lock all lockable channels in the server. See also: unlock all")] + [Command("all")] + [Description("Lock all lockable channels in the server. See also: unlock all")] public async Task LockdownAllCommand( - InteractionContext ctx, - [Option("reason", "The reason for the lockdown.")] string reason = "", - [Option("time", "The length of time to lock the channels for.")] string time = null, - [Option("lockthreads", "Whether to lock threads. Disables sending messages, but does not archive them.")] bool lockThreads = false) + SlashCommandContext ctx, + [Parameter("reason"), Description("The reason for the lockdown.")] string reason = "", + [Parameter("time"), Description("The length of time to lock the channels for.")] string time = null, + [Parameter("lockthreads"), Description("Whether to lock threads. Disables sending messages, but does not archive them.")] bool lockThreads = false) { - await ctx.DeferAsync(); + await ctx.DeferResponseAsync(); ongoingLockdown = true; - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Loading} Working on it, please hold...")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Loading} Working on it, please hold...")); TimeSpan? lockDuration = null; if (!string.IsNullOrWhiteSpace(time)) { - lockDuration = HumanDateParser.HumanDateParser.Parse(time).Subtract(ctx.Interaction.CreationTimestamp.LocalDateTime); + lockDuration = HumanDateParser.HumanDateParser.Parse(time).Subtract(DateTime.Now); // TODO(#202): this used InteractionContext#Interaction.CreationTimestamp.LocalDateTime before, please test!! } foreach (var chanID in Program.cfgjson.LockdownEnabledChannels) @@ -96,47 +100,51 @@ public async Task LockdownAllCommand( } } - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Done!")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Done!")); ongoingLockdown = false; return; } } - [SlashCommandGroup("unlock", "Unlock the current channel or all channels in the server, allowing new messages. See also: lockdown")] - [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequireBotPermissions(DiscordPermissions.ManageChannels)] + [Command("unlock")] + [Description("Unlock the current channel or all channels in the server, allowing new messages. See also: lockdown")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.ManageChannels, DiscordPermissions.None)] public class UnlockCmds { - [SlashCommand("channel", "Unlock the current channel. See also: lockdown")] - public async Task UnlockChannelCommand(InteractionContext ctx, [Option("reason", "The reason for the unlock.")] string reason = "") + [Command("channel")] + [Description("Unlock the current channel. See also: lockdown")] + public async Task UnlockChannelCommand(SlashCommandContext ctx, [Parameter("reason"), Description("The reason for the unlock.")] string reason = "") { - await ctx.DeferAsync(ephemeral: true); + await ctx.DeferResponseAsync(ephemeral: true); var currentChannel = ctx.Channel; if (!Program.cfgjson.LockdownEnabledChannels.Contains(currentChannel.Id)) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Denied} You can't lock or unlock this channel!\nIf this is in error, add its ID (`{currentChannel.Id}`) to the lockdown whitelist.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Denied} You can't lock or unlock this channel!\nIf this is in error, add its ID (`{currentChannel.Id}`) to the lockdown whitelist.").AsEphemeral(true)); return; } if (ongoingLockdown) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} A mass lockdown or unlock is already ongoing. Refusing your request. sorry.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} A mass lockdown or unlock is already ongoing. Refusing your request. sorry.").AsEphemeral(true)); return; } bool success = await LockdownHelpers.UnlockChannel(currentChannel, ctx.Member); if (success) - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent("Channel unlocked successfully.").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent("Channel unlocked successfully.").AsEphemeral(true)); else - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent("Failed to unlock this channel!").AsEphemeral(true)); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent("Failed to unlock this channel!").AsEphemeral(true)); } - [SlashCommand("all", "Unlock all lockable channels in the server. See also: lockdown all")] - public async Task UnlockAllCommand(InteractionContext ctx, [Option("reason", "The reason for the unlock.")] string reason = "") + [Command("all")] + [Description("Unlock all lockable channels in the server. See also: lockdown all")] + public async Task UnlockAllCommand(SlashCommandContext ctx, [Parameter("reason"), Description("The reason for the unlock.")] string reason = "") { - await ctx.CreateResponseAsync(DiscordInteractionResponseType.DeferredChannelMessageWithSource); + await ctx.DeferResponseAsync(); ongoingLockdown = true; - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Loading} Working on it, please hold...")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Loading} Working on it, please hold...")); foreach (var chanID in Program.cfgjson.LockdownEnabledChannels) { try @@ -149,7 +157,7 @@ public class UnlockCmds } } - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Done!")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Done!")); ongoingLockdown = false; return; } diff --git a/Commands/InteractionCommands/MuteInteractions.cs b/Commands/InteractionCommands/MuteInteractions.cs index c9d95f7b..53a305b5 100644 --- a/Commands/InteractionCommands/MuteInteractions.cs +++ b/Commands/InteractionCommands/MuteInteractions.cs @@ -1,18 +1,20 @@ namespace Cliptok.Commands.InteractionCommands { - internal class MuteInteractions : ApplicationCommandModule + internal class MuteInteractions { - [SlashCommand("mute", "Mute a user, temporarily or permanently.")] + [Command("mute")] + [Description("Mute a user, temporarily or permanently.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] + [RequirePermissions(DiscordPermissions.ModerateMembers)] public async Task MuteSlashCommand( - InteractionContext ctx, - [Option("user", "The user you wish to mute.")] DiscordUser targetUser, - [Option("reason", "The reason for the mute.")] string reason, - [Option("time", "The length of time to mute for.")] string time = "" + SlashCommandContext ctx, + [Parameter("user"), Description("The user you wish to mute.")] DiscordUser targetUser, + [Parameter("reason"), Description("The reason for the mute.")] string reason, + [Parameter("time"), Description("The length of time to mute for.")] string time = "" ) { - await ctx.DeferAsync(ephemeral: true); + await ctx.DeferResponseAsync(ephemeral: true); DiscordMember targetMember = default; try { @@ -35,7 +37,7 @@ public async Task MuteSlashCommand( { try { - muteDuration = HumanDateParser.HumanDateParser.Parse(time).Subtract(ctx.Interaction.CreationTimestamp.DateTime); + muteDuration = HumanDateParser.HumanDateParser.Parse(time).Subtract(DateTime.UtcNow); // TODO(#202): this used InteractionContext#Interaction.CreationTimestamp.DateTime before, please test!! } catch { @@ -48,16 +50,18 @@ public async Task MuteSlashCommand( await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Command completed successfully.")); } - [SlashCommand("unmute", "Unmute a user.")] + [Command("unmute")] + [Description("Unmute a user.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] + [RequirePermissions(DiscordPermissions.ModerateMembers)] public async Task UnmuteSlashCommand( - InteractionContext ctx, - [Option("user", "The user you wish to mute.")] DiscordUser targetUser, - [Option("reason", "The reason for the unmute.")] string reason = "No reason specified." + SlashCommandContext ctx, + [Parameter("user"), Description("The user you wish to mute.")] DiscordUser targetUser, + [Parameter("reason"), Description("The reason for the unmute.")] string reason = "No reason specified." ) { - await ctx.DeferAsync(ephemeral: false); + await ctx.DeferResponseAsync(ephemeral: false); reason = $"[Manual unmute by {DiscordHelpers.UniqueUsername(ctx.User)}]: {reason}"; @@ -77,29 +81,31 @@ public async Task UnmuteSlashCommand( if ((await Program.db.HashExistsAsync("mutes", targetUser.Id)) || (member != default && member.Roles.Contains(mutedRole))) { await MuteHelpers.UnmuteUserAsync(targetUser, reason, true, ctx.User); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Information} Successfully unmuted **{DiscordHelpers.UniqueUsername(targetUser)}**.")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Information} Successfully unmuted **{DiscordHelpers.UniqueUsername(targetUser)}**.")); } else try { await MuteHelpers.UnmuteUserAsync(targetUser, reason, true, ctx.User); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Warning} According to Discord that user is not muted, but I tried to unmute them anyway. Hope it works.")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Warning} According to Discord that user is not muted, but I tried to unmute them anyway. Hope it works.")); } catch (Exception e) { Program.discord.Logger.LogError(e, "An error occurred unmuting {user}", targetUser.Id); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That user doesn't appear to be muted, *and* an error occurred while attempting to unmute them anyway. Please contact the bot owner, the error has been logged.")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That user doesn't appear to be muted, *and* an error occurred while attempting to unmute them anyway. Please contact the bot owner, the error has been logged.")); } } - [SlashCommand("tqsmute", "Temporarily mute a user in tech support channels.")] + [Command("tqsmute")] + [Description("Temporarily mute a user in tech support channels.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.TechnicalQueriesSlayer)] public async Task TqsMuteSlashCommand( - InteractionContext ctx, - [Option("user", "The user to mute.")] DiscordUser targetUser, - [Option("reason", "The reason for the mute.")] string reason) + SlashCommandContext ctx, + [Parameter("user"), Description("The user to mute.")] DiscordUser targetUser, + [Parameter("reason"), Description("The reason for the mute.")] string reason) { - await ctx.DeferAsync(ephemeral: true); + await ctx.DeferResponseAsync(ephemeral: true); // only work if TQS mute role is configured if (Program.cfgjson.TqsMutedRole == 0) diff --git a/Commands/InteractionCommands/NicknameLockInteraction.cs b/Commands/InteractionCommands/NicknameLockInteraction.cs index af83509b..f64bf32c 100644 --- a/Commands/InteractionCommands/NicknameLockInteraction.cs +++ b/Commands/InteractionCommands/NicknameLockInteraction.cs @@ -6,14 +6,17 @@ namespace Cliptok.Commands.InteractionCommands { - public class NicknameLockInteraction : ApplicationCommandModule + public class NicknameLockInteraction { - [SlashCommandGroup("nicknamelock", "Prevent a member from changing their nickname.", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ManageNicknames)] + [Command("nicknamelock")] + [Description("Prevent a member from changing their nickname.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ManageNicknames)] public class NicknameLockSlashCommands { - [SlashCommand("enable", "Prevent a member from changing their nickname.")] - public async Task NicknameLockEnableSlashCmd(InteractionContext ctx, [Option("member", "The member to nickname lock.")] DiscordUser discordUser) + [Command("enable")] + [Description("Prevent a member from changing their nickname.")] + public async Task NicknameLockEnableSlashCmd(SlashCommandContext ctx, [Parameter("member"), Description("The member to nickname lock.")] DiscordUser discordUser) { DiscordMember member = default; @@ -40,8 +43,9 @@ public async Task NicknameLockEnableSlashCmd(InteractionContext ctx, [Option("me } } - [SlashCommand("disable", "Allow a member to change their nickname again.")] - public async Task NicknameLockDisableSlashCmd(InteractionContext ctx, [Option("member", "The member to remove the nickname lock for.")] DiscordUser discordUser) + [Command("disable")] + [Description("Allow a member to change their nickname again.")] + public async Task NicknameLockDisableSlashCmd(SlashCommandContext ctx, [Parameter("member"), Description("The member to remove the nickname lock for.")] DiscordUser discordUser) { DiscordMember member = default; @@ -70,8 +74,9 @@ public async Task NicknameLockEnableSlashCmd(InteractionContext ctx, [Option("me } } - [SlashCommand("status", "Check the status of nickname lock for a member.")] - public async Task NicknameLockStatusSlashCmd(InteractionContext ctx, [Option("member", "The member whose nickname lock status to check.")] DiscordUser discordUser) + [Command("status")] + [Description("Check the status of nickname lock for a member.")] + public async Task NicknameLockStatusSlashCmd(SlashCommandContext ctx, [Parameter("member"), Description("The member whose nickname lock status to check.")] DiscordUser discordUser) { if ((await Program.db.HashGetAsync("nicknamelock", discordUser.Id)).HasValue) await ctx.RespondAsync($"{Program.cfgjson.Emoji.On} {discordUser.Mention} is nickname locked.", mentions: false); diff --git a/Commands/InteractionCommands/RaidmodeInteractions.cs b/Commands/InteractionCommands/RaidmodeInteractions.cs index 6998389d..7fa671c0 100644 --- a/Commands/InteractionCommands/RaidmodeInteractions.cs +++ b/Commands/InteractionCommands/RaidmodeInteractions.cs @@ -1,14 +1,17 @@ namespace Cliptok.Commands.InteractionCommands { - internal class RaidmodeInteractions : ApplicationCommandModule + internal class RaidmodeInteractions { - [SlashCommandGroup("raidmode", "Commands relating to Raidmode", defaultPermission: false)] + [Command("raidmode")] + [Description("Commands relating to Raidmode")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.Moderator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public class RaidmodeSlashCommands : ApplicationCommandModule + [RequirePermissions(DiscordPermissions.ModerateMembers)] + public class RaidmodeSlashCommands { - [SlashCommand("status", "Check the current state of raidmode.")] - public async Task RaidmodeStatus(InteractionContext ctx) + [Command("status")] + [Description("Check the current state of raidmode.")] + public async Task RaidmodeStatus(SlashCommandContext ctx) { if (Program.db.HashExists("raidmode", ctx.Guild.Id)) { @@ -30,10 +33,11 @@ public async Task RaidmodeStatus(InteractionContext ctx) } - [SlashCommand("on", "Enable raidmode. Defaults to 3 hour length if not specified.")] - public async Task RaidmodeOnSlash(InteractionContext ctx, - [Option("duration", "How long to keep raidmode enabled for.")] string duration = default, - [Option("allowed_account_age", "How old an account can be to be allowed to bypass raidmode. Relative to right now.")] string allowedAccountAge = "" + [Command("on")] + [Description("Enable raidmode. Defaults to 3 hour length if not specified.")] + public async Task RaidmodeOnSlash(SlashCommandContext ctx, + [Parameter("duration"), Description("How long to keep raidmode enabled for.")] string duration = default, + [Parameter("allowed_account_age"), Description("How old an account can be to be allowed to bypass raidmode. Relative to right now.")] string allowedAccountAge = "" ) { if (Program.db.HashExists("raidmode", ctx.Guild.Id)) @@ -96,8 +100,9 @@ public async Task RaidmodeOnSlash(InteractionContext ctx, } } - [SlashCommand("off", "Disable raidmode immediately.")] - public async Task RaidmodeOffSlash(InteractionContext ctx) + [Command("off")] + [Description("Disable raidmode immediately.")] + public async Task RaidmodeOffSlash(SlashCommandContext ctx) { if (Program.db.HashExists("raidmode", ctx.Guild.Id)) { diff --git a/Commands/InteractionCommands/RoleInteractions.cs b/Commands/InteractionCommands/RoleInteractions.cs index de51601b..5eccada4 100644 --- a/Commands/InteractionCommands/RoleInteractions.cs +++ b/Commands/InteractionCommands/RoleInteractions.cs @@ -1,30 +1,28 @@ namespace Cliptok.Commands.InteractionCommands { - internal class RoleInteractions : ApplicationCommandModule + internal class RoleInteractions { - [SlashCommand("grant", "Grant a user Tier 1, bypassing any verification requirements.", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task SlashGrant(InteractionContext ctx, [Option("user", "The user to grant Tier 1 to.")] DiscordUser _) + [Command("grant")] + [Description("Grant a user Tier 1, bypassing any verification requirements.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task SlashGrant(SlashCommandContext ctx, [Parameter("user"), Description("The user to grant Tier 1 to.")] DiscordUser _) { await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} This command is deprecated and no longer works. Please right click (or tap and hold on mobile) the user and click \"Verify Member\" if available."); } [HomeServer] - [SlashCommandGroup("roles", "Opt in/out of roles.")] + [Command("roles")] + [Description("Opt in/out of roles.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] internal class RoleSlashCommands { - [SlashCommand("grant", "Opt into a role.")] + [Command("grant")] + [Description("Opt into a role.")] public async Task GrantRole( - InteractionContext ctx, - [Choice("Windows 11 Insiders (Canary)", "insiderCanary")] - [Choice("Windows 11 Insiders (Dev)", "insiderDev")] - [Choice("Windows 11 Insiders (Beta)", "insiderBeta")] - [Choice("Windows 11 Insiders (Release Preview)", "insiderRP")] - [Choice("Windows 10 Insiders (Release Preview)", "insider10RP")] - [Choice("Windows 10 Insiders (Beta)", "insider10Beta")] - [Choice("Patch Tuesday", "patchTuesday")] - [Choice("Giveaways", "giveaways")] - [Option("role", "The role to opt into.")] string role) + SlashCommandContext ctx, + [SlashChoiceProvider(typeof(RoleCommandChoiceProvider))] + [Parameter("role"), Description("The role to opt into.")] string role) // TODO(#202): test choices!!! { DiscordMember member = ctx.Member; @@ -47,18 +45,12 @@ public async Task GrantRole( await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} The role {roleData.Mention} has been successfully granted!", ephemeral: true, mentions: false); } - [SlashCommand("remove", "Opt out of a role.")] + [Command("remove")] + [Description("Opt out of a role.")] public async Task RemoveRole( - InteractionContext ctx, - [Choice("Windows 11 Insiders (Canary)", "insiderCanary")] - [Choice("Windows 11 Insiders (Dev)", "insiderDev")] - [Choice("Windows 11 Insiders (Beta)", "insiderBeta")] - [Choice("Windows 11 Insiders (Release Preview)", "insiderRP")] - [Choice("Windows 10 Insiders (Release Preview)", "insider10RP")] - [Choice("Windows 10 Insiders (Beta)", "insider10Beta")] - [Choice("Patch Tuesday", "patchTuesday")] - [Choice("Giveaways", "giveaways")] - [Option("role", "The role to opt out of.")] string role) + SlashCommandContext ctx, + [SlashChoiceProvider(typeof(RoleCommandChoiceProvider))] + [Parameter("role"), Description("The role to opt out of.")] string role) // TODO(#202): test choices!!! { DiscordMember member = ctx.Member; @@ -81,5 +73,23 @@ public async Task RemoveRole( await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} The role {roleData.Mention} has been successfully removed!", ephemeral: true, mentions: false); } } + + internal class RoleCommandChoiceProvider : IChoiceProvider + { + public async ValueTask> ProvideAsync(CommandParameter _) + { + return new Dictionary + { + { "Windows 11 Insiders (Canary)", "insiderCanary" }, + { "Windows 11 Insiders (Dev)", "insiderDev" }, + { "Windows 11 Insiders (Beta)", "insiderBeta" }, + { "Windows 11 Insiders (Release Preview)", "insiderRP" }, + { "Windows 10 Insiders (Release Preview)", "insider10RP" }, + { "Windows 10 Insiders (Beta)", "insider10Beta" }, + { "Patch Tuesday", "patchTuesday" }, + { "Giveaways", "giveaways" } + }; + } + } } } diff --git a/Commands/InteractionCommands/RulesInteractions.cs b/Commands/InteractionCommands/RulesInteractions.cs index edb6d938..71bada1f 100644 --- a/Commands/InteractionCommands/RulesInteractions.cs +++ b/Commands/InteractionCommands/RulesInteractions.cs @@ -1,13 +1,16 @@ namespace Cliptok.Commands.InteractionCommands { - public class RulesInteractions : ApplicationCommandModule + public class RulesInteractions { [HomeServer] - [SlashCommandGroup("rules", "Misc. commands related to server rules", defaultPermission: true)] + [Command("rules")] + [Description("Misc. commands related to server rules")] + [AllowedProcessors(typeof(SlashCommandProcessor))] internal class RulesSlashCommands { - [SlashCommand("all", "Shows all of the community rules.", defaultPermission: true)] - public async Task RulesAllCommand(InteractionContext ctx) + [Command("all")] + [Description("Shows all of the community rules.")] + public async Task RulesAllCommand(SlashCommandContext ctx) { List rules = default; @@ -34,8 +37,9 @@ public async Task RulesAllCommand(InteractionContext ctx) } - [SlashCommand("rule", "Shows a specific rule.", defaultPermission: true)] - public async Task RuleCommand(InteractionContext ctx, [Option("rule_number", "The rule number to show.")] long ruleNumber) + [Command("rule")] + [Description("Shows a specific rule.")] + public async Task RuleCommand(SlashCommandContext ctx, [Parameter("rule_number"), Description("The rule number to show.")] long ruleNumber) { IReadOnlyList rules = default; @@ -62,8 +66,9 @@ public async Task RuleCommand(InteractionContext ctx, [Option("rule_number", "Th await ctx.RespondAsync(embed: embed); } - [SlashCommand("search", "Search for a rule by keyword.", defaultPermission: true)] - public async Task RuleSearchCommand(InteractionContext ctx, [Option("keyword", "The keyword to search for.")] string keyword) + [Command("search")] + [Description("Search for a rule by keyword.")] + public async Task RuleSearchCommand(SlashCommandContext ctx, [Parameter("keyword"), Description("The keyword to search for.")] string keyword) { List rules = default; diff --git a/Commands/InteractionCommands/SecurityActionInteractions.cs b/Commands/InteractionCommands/SecurityActionInteractions.cs index 93fc18d7..6dfb6bb5 100644 --- a/Commands/InteractionCommands/SecurityActionInteractions.cs +++ b/Commands/InteractionCommands/SecurityActionInteractions.cs @@ -1,10 +1,12 @@ namespace Cliptok.Commands.InteractionCommands { - public class SecurityActionInteractions : ApplicationCommandModule + public class SecurityActionInteractions { - [SlashCommand("pausedms", "Temporarily pause DMs between server members.", defaultPermission: false)] - [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task SlashPauseDMs(InteractionContext ctx, [Option("time", "The amount of time to pause DMs for. Cannot be greater than 24 hours.")] string time) + [Command("pausedms")] + [Description("Temporarily pause DMs between server members.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task SlashPauseDMs(SlashCommandContext ctx, [Parameter("time"), Description("The amount of time to pause DMs for. Cannot be greater than 24 hours.")] string time) { // need to make our own api calls because D#+ can't do this natively? @@ -50,9 +52,11 @@ public async Task SlashPauseDMs(InteractionContext ctx, [Option("time", "The amo } } - [SlashCommand("unpausedms", "Unpause DMs between server members.", defaultPermission: false)] - [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task SlashUnpauseDMs(InteractionContext ctx) + [Command("unpausedms")] + [Description("Unpause DMs between server members.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [HomeServer, SlashRequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task SlashUnpauseDMs(SlashCommandContext ctx) { // need to make our own api calls because D#+ can't do this natively? diff --git a/Commands/InteractionCommands/SlowmodeInteractions.cs b/Commands/InteractionCommands/SlowmodeInteractions.cs index 2d1fa948..227ea4ba 100644 --- a/Commands/InteractionCommands/SlowmodeInteractions.cs +++ b/Commands/InteractionCommands/SlowmodeInteractions.cs @@ -1,14 +1,16 @@ namespace Cliptok.Commands.InteractionCommands { - internal class SlowmodeInteractions : ApplicationCommandModule + internal class SlowmodeInteractions { - [SlashCommand("slowmode", "Slow down the channel...", defaultPermission: false)] + [Command("slowmode")] + [Description("Slow down the channel...")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] + [RequirePermissions(DiscordPermissions.ModerateMembers)] public async Task SlowmodeSlashCommand( - InteractionContext ctx, - [Option("slow_time", "Allowed time between each users messages. 0 for off. A number of seconds or a parseable time.")] string timeToParse, - [Option("channel", "The channel to slow down, if not the current one.")] DiscordChannel channel = default + SlashCommandContext ctx, + [Parameter("slow_time"), Description("Allowed time between each users messages. 0 for off. A number of seconds or a parseable time.")] string timeToParse, + [Parameter("channel"), Description("The channel to slow down, if not the current one.")] DiscordChannel channel = default ) { if (channel == default) diff --git a/Commands/InteractionCommands/StatusInteractions.cs b/Commands/InteractionCommands/StatusInteractions.cs index c6eb0ca9..b757ee3f 100644 --- a/Commands/InteractionCommands/StatusInteractions.cs +++ b/Commands/InteractionCommands/StatusInteractions.cs @@ -1,25 +1,24 @@ -namespace Cliptok.Commands.InteractionCommands +using DSharpPlus.Commands.Processors.SlashCommands.ArgumentModifiers; + +namespace Cliptok.Commands.InteractionCommands { - internal class StatusInteractions : ApplicationCommandModule + internal class StatusInteractions { - [SlashCommandGroup("status", "Status commands")] + [Command("status")] + [Description("Status commands")] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] + [RequirePermissions(DiscordPermissions.ModerateMembers)] public class StatusSlashCommands { - [SlashCommand("set", "Set Cliptoks status.", defaultPermission: false)] + [Command("set")] + [Description("Set Cliptoks status.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] public async Task StatusSetCommand( - InteractionContext ctx, - [Option("text", "The text to use for the status.")] string statusText, - [Choice("Custom", (long)DiscordActivityType.Custom)] - [Choice("Playing", (long)DiscordActivityType.Playing)] - [Choice("Streaming", (long)DiscordActivityType.Streaming)] - [Choice("Listening to", (long)DiscordActivityType.ListeningTo)] - [Choice("Watching", (long)DiscordActivityType.Watching)] - [Choice("Competing", (long)DiscordActivityType.Competing)] - [Option("type", "Defaults to custom. The type of status to use.")] long statusType = (long)DiscordActivityType.Custom + SlashCommandContext ctx, + [Parameter("text"), Description("The text to use for the status.")] string statusText, + [Parameter("type"), Description("Defaults to custom. The type of status to use.")] DiscordActivityType statusType = DiscordActivityType.Custom // TODO(#202): test this!!!! ) { if (statusText.Length > 128) @@ -28,15 +27,17 @@ public async Task StatusSetCommand( } await Program.db.StringSetAsync("config:status", statusText); - await Program.db.StringSetAsync("config:status_type", statusType); + await Program.db.StringSetAsync("config:status_type", (long)statusType); - await ctx.Client.UpdateStatusAsync(new DiscordActivity(statusText, (DiscordActivityType)statusType)); + await ctx.Client.UpdateStatusAsync(new DiscordActivity(statusText, statusType)); - await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Status has been updated!\nType: `{((DiscordActivityType)statusType).ToString()}`\nText: `{statusText}`"); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Success} Status has been updated!\nType: `{statusType.ToString()}`\nText: `{statusText}`"); } - [SlashCommand("clear", "Clear Cliptoks status.", defaultPermission: false)] - public async Task StatusClearCommand(InteractionContext ctx) + [Command("clear")] + [Description("Clear Cliptoks status.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + public async Task StatusClearCommand(SlashCommandContext ctx) { await Program.db.KeyDeleteAsync("config:status"); await Program.db.KeyDeleteAsync("config:status_type"); diff --git a/Commands/InteractionCommands/TechSupportInteractions.cs b/Commands/InteractionCommands/TechSupportInteractions.cs index 2f1dd4a2..102d0105 100644 --- a/Commands/InteractionCommands/TechSupportInteractions.cs +++ b/Commands/InteractionCommands/TechSupportInteractions.cs @@ -2,19 +2,16 @@ namespace Cliptok.Commands.InteractionCommands { - public class TechSupportInteractions : ApplicationCommandModule + public class TechSupportInteractions { - [SlashCommand("vcredist", "Outputs download URLs for the specified Visual C++ Redistributables version")] + [Command("vcredist")] + [Description("Outputs download URLs for the specified Visual C++ Redistributables version")] + [AllowedProcessors(typeof(SlashCommandProcessor))] public async Task RedistsCommand( - InteractionContext ctx, + SlashCommandContext ctx, - [Choice("Visual Studio 2015+ - v140", 140)] - [Choice("Visual Studio 2013 - v120", 120)] - [Choice("Visual Studio 2012 - v110", 110)] - [Choice("Visual Studio 2010 - v100", 100)] - [Choice("Visual Studio 2008 - v90", 90)] - [Choice("Visual Studio 2005 - v80", 80)] - [Option("version", "Visual Studio version number or year")] long version + [SlashChoiceProvider(typeof(VcRedistChoiceProvider))] + [Parameter("version"), Description("Visual Studio version number or year")] long version // TODO(#202): test choices!!! ) { VcRedist redist = VcRedistConstants.VcRedists @@ -36,4 +33,20 @@ public async Task RedistsCommand( await ctx.RespondAsync(null, embed.Build(), false); } } + + internal class VcRedistChoiceProvider : IChoiceProvider + { + public async ValueTask> ProvideAsync(CommandParameter _) + { + return new Dictionary + { + { "Visual Studio 2015+ - v140", "140" }, + { "Visual Studio 2013 - v120", "120" }, + { "Visual Studio 2012 - v110", "110" }, + { "Visual Studio 2010 - v100", "100" }, + { "Visual Studio 2008 - v90", "90" }, + { "Visual Studio 2005 - v80", "80" } + }; + } + } } diff --git a/Commands/InteractionCommands/TrackingInteractions.cs b/Commands/InteractionCommands/TrackingInteractions.cs index c7078da8..e616076b 100644 --- a/Commands/InteractionCommands/TrackingInteractions.cs +++ b/Commands/InteractionCommands/TrackingInteractions.cs @@ -1,19 +1,24 @@ -namespace Cliptok.Commands.InteractionCommands +using System.Runtime.CompilerServices; + +namespace Cliptok.Commands.InteractionCommands { - internal class TrackingInteractions : ApplicationCommandModule + internal class TrackingInteractions { - [SlashCommandGroup("tracking", "Commands to manage message tracking of users", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] + [Command("tracking")] + [Description("Commands to manage message tracking of users")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ModerateMembers)] public class PermadehoistSlashCommands { - [SlashCommand("add", "Track a users messages.")] - public async Task TrackingAddSlashCmd(InteractionContext ctx, [Option("member", "The member to track.")] DiscordUser discordUser) + [Command("add")] + [Description("Track a users messages.")] + public async Task TrackingAddSlashCmd(SlashCommandContext ctx, [Parameter("member"), Description("The member to track.")] DiscordUser discordUser) { - await ctx.DeferAsync(ephemeral: false); + await ctx.DeferResponseAsync(ephemeral: false); if (Program.db.SetContains("trackedUsers", discordUser.Id)) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} This user is already tracked!")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} This user is already tracked!")); return; } @@ -26,7 +31,7 @@ public async Task TrackingAddSlashCmd(InteractionContext ctx, [Option("member", await thread.SendMessageAsync($"{Program.cfgjson.Emoji.On} Now tracking {discordUser.Mention} in this thread! :eyes:"); thread.AddThreadMemberAsync(ctx.Member); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.On} Now tracking {discordUser.Mention} in {thread.Mention}!")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.On} Now tracking {discordUser.Mention} in {thread.Mention}!")); } else @@ -35,18 +40,19 @@ public async Task TrackingAddSlashCmd(InteractionContext ctx, [Option("member", await Program.db.HashSetAsync("trackingThreads", discordUser.Id, thread.Id); await thread.SendMessageAsync($"{Program.cfgjson.Emoji.On} Now tracking {discordUser.Mention} in this thread! :eyes:"); await thread.AddThreadMemberAsync(ctx.Member); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.On} Now tracking {discordUser.Mention} in {thread.Mention}!")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.On} Now tracking {discordUser.Mention} in {thread.Mention}!")); } } - [SlashCommand("remove", "Stop tracking a users messages.")] - public async Task TrackingRemoveSlashCmd(InteractionContext ctx, [Option("member", "The member to track.")] DiscordUser discordUser) + [Command("remove")] + [Description("Stop tracking a users messages.")] + public async Task TrackingRemoveSlashCmd(SlashCommandContext ctx, [Parameter("member"), Description("The member to track.")] DiscordUser discordUser) { - await ctx.DeferAsync(ephemeral: false); + await ctx.DeferResponseAsync(ephemeral: false); if (!Program.db.SetContains("trackedUsers", discordUser.Id)) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} This user is not being tracked.")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} This user is not being tracked.")); return; } @@ -61,7 +67,7 @@ await thread.ModifyAsync(thread => thread.IsArchived = true; }); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Off} No longer tracking {discordUser.Mention}! Thread has been archived for now.")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Off} No longer tracking {discordUser.Mention}! Thread has been archived for now.")); } } diff --git a/Commands/InteractionCommands/UserNoteInteractions.cs b/Commands/InteractionCommands/UserNoteInteractions.cs index 763ebd5b..eaab5274 100644 --- a/Commands/InteractionCommands/UserNoteInteractions.cs +++ b/Commands/InteractionCommands/UserNoteInteractions.cs @@ -2,22 +2,25 @@ namespace Cliptok.Commands.InteractionCommands { - internal class UserNoteInteractions : ApplicationCommandModule + internal class UserNoteInteractions { - [SlashCommandGroup("note", "Manage user notes", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] + [Command("note")] + [Description("Manage user notes")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ModerateMembers)] public class UserNoteSlashCommands { - [SlashCommand("add", "Add a note to a user. Only visible to mods.")] - public async Task AddUserNoteAsync(InteractionContext ctx, - [Option("user", "The user to add a note for.")] DiscordUser user, - [Option("note", "The note to add.")] string noteText, - [Option("show_on_modmail", "Whether to show the note when the user opens a modmail thread. Default: true")] bool showOnModmail = true, - [Option("show_on_warn", "Whether to show the note when the user is warned. Default: true")] bool showOnWarn = true, - [Option("show_all_mods", "Whether to show this note to all mods, versus just yourself. Default: true")] bool showAllMods = true, - [Option("show_once", "Whether to show this note once and then discard it. Default: false")] bool showOnce = false) + [Command("add")] + [Description("Add a note to a user. Only visible to mods.")] + public async Task AddUserNoteAsync(SlashCommandContext ctx, + [Parameter("user"), Description("The user to add a note for.")] DiscordUser user, + [Parameter("note"), Description("The note to add.")] string noteText, + [Parameter("show_on_modmail"), Description("Whether to show the note when the user opens a modmail thread. Default: true")] bool showOnModmail = true, + [Parameter("show_on_warn"), Description("Whether to show the note when the user is warned. Default: true")] bool showOnWarn = true, + [Parameter("show_all_mods"), Description("Whether to show this note to all mods, versus just yourself. Default: true")] bool showAllMods = true, + [Parameter("show_once"), Description("Whether to show this note once and then discard it. Default: false")] bool showOnce = false) { - await ctx.DeferAsync(); + await ctx.DeferResponseAsync(); // Assemble new note long noteId = Program.db.StringIncrement("totalWarnings"); @@ -42,13 +45,14 @@ public async Task AddUserNoteAsync(InteractionContext ctx, await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Information} New note for {user.Mention}!", embed); // Respond - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully added note!").AsEphemeral()); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully added note!").AsEphemeral()); } - [SlashCommand("delete", "Delete a note.")] - public async Task RemoveUserNoteAsync(InteractionContext ctx, - [Option("user", "The user whose note to delete.")] DiscordUser user, - [Autocomplete(typeof(NotesAutocompleteProvider))][Option("note", "The note to delete.")] string targetNote) + [Command("delete")] + [Description("Delete a note.")] + public async Task RemoveUserNoteAsync(SlashCommandContext ctx, + [Parameter("user"), Description("The user whose note to delete.")] DiscordUser user, + [SlashAutoCompleteProvider(typeof(NotesAutocompleteProvider))][Parameter("note"), Description("The note to delete.")] string targetNote) { // Get note UserNote note; @@ -58,14 +62,14 @@ public async Task RemoveUserNoteAsync(InteractionContext ctx, } catch { - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't find that note! Make sure you've got the right ID.").AsEphemeral()); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't find that note! Make sure you've got the right ID.").AsEphemeral()); return; } // If user manually provided an ID of a warning, refuse the request and suggest /delwarn instead if (note.Type == WarningType.Warning) { - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That's a warning, not a note! Try using `/delwarn` instead, or make sure you've got the right note ID.").AsEphemeral()); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That's a warning, not a note! Try using `/delwarn` instead, or make sure you've got the right note ID.").AsEphemeral()); return; } @@ -77,18 +81,19 @@ public async Task RemoveUserNoteAsync(InteractionContext ctx, await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Deleted} Note deleted: `{note.NoteId}` (belonging to {user.Mention}, deleted by {ctx.User.Mention})", embed); // Respond - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully deleted note!").AsEphemeral()); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully deleted note!").AsEphemeral()); } - [SlashCommand("edit", "Edit a note for a user.")] - public async Task EditUserNoteAsync(InteractionContext ctx, - [Option("user", "The user to edit a note for.")] DiscordUser user, - [Autocomplete(typeof(NotesAutocompleteProvider))][Option("note", "The note to edit.")] string targetNote, - [Option("new_text", "The new note text. Leave empty to not change.")] string newNoteText = default, - [Option("show_on_modmail", "Whether to show the note when the user opens a modmail thread.")] bool? showOnModmail = null, - [Option("show_on_warn", "Whether to show the note when the user is warned.")] bool? showOnWarn = null, - [Option("show_all_mods", "Whether to show this note to all mods, versus just yourself.")] bool? showAllMods = null, - [Option("show_once", "Whether to show this note once and then discard it.")] bool? showOnce = null) + [Command("edit")] + [Description("Edit a note for a user.")] + public async Task EditUserNoteAsync(SlashCommandContext ctx, + [Parameter("user"), Description("The user to edit a note for.")] DiscordUser user, + [SlashAutoCompleteProvider(typeof(NotesAutocompleteProvider))][Parameter("note"), Description("The note to edit.")] string targetNote, + [Parameter("new_text"), Description("The new note text. Leave empty to not change.")] string newNoteText = default, + [Parameter("show_on_modmail"), Description("Whether to show the note when the user opens a modmail thread.")] bool? showOnModmail = null, + [Parameter("show_on_warn"), Description("Whether to show the note when the user is warned.")] bool? showOnWarn = null, + [Parameter("show_all_mods"), Description("Whether to show this note to all mods, versus just yourself.")] bool? showAllMods = null, + [Parameter("show_once"), Description("Whether to show this note once and then discard it.")] bool? showOnce = null) { // Get note UserNote note; @@ -98,7 +103,7 @@ public async Task EditUserNoteAsync(InteractionContext ctx, } catch { - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't find that note! Make sure you've got the right ID.").AsEphemeral()); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't find that note! Make sure you've got the right ID.").AsEphemeral()); return; } @@ -109,14 +114,14 @@ public async Task EditUserNoteAsync(InteractionContext ctx, // If no changes are made, refuse the request if (note.NoteText == newNoteText && showOnModmail is null && showOnWarn is null && showAllMods is null && showOnce is null) { - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You didn't change anything about the note!").AsEphemeral()); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} You didn't change anything about the note!").AsEphemeral()); return; } // If user manually provided an ID of a warning, refuse the request and suggest /editwarn instead if (note.Type == WarningType.Warning) { - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That's a warning, not a note! Try using `/editwarn` instead, or make sure you've got the right note ID.").AsEphemeral()); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That's a warning, not a note! Try using `/editwarn` instead, or make sure you've got the right note ID.").AsEphemeral()); return; } @@ -146,22 +151,24 @@ public async Task EditUserNoteAsync(InteractionContext ctx, await LogChannelHelper.LogMessageAsync("mod", $"{Program.cfgjson.Emoji.Information} Note edited: `{note.NoteId}` (belonging to {user.Mention})", embed); // Respond - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully edited note!").AsEphemeral()); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully edited note!").AsEphemeral()); } - [SlashCommand("list", "List all notes for a user.")] - public async Task ListUserNotesAsync(InteractionContext ctx, - [Option("user", "The user whose notes to list.")] DiscordUser user, - [Option("public", "Whether to show the notes in public chat. Default: false")] bool showPublicly = false) + [Command("list")] + [Description("List all notes for a user.")] + public async Task ListUserNotesAsync(SlashCommandContext ctx, + [Parameter("user"), Description("The user whose notes to list.")] DiscordUser user, + [Parameter("public"), Description("Whether to show the notes in public chat. Default: false")] bool showPublicly = false) { - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().AddEmbed(await GenerateUserNotesEmbedAsync(user)).AsEphemeral(!showPublicly)); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().AddEmbed(await GenerateUserNotesEmbedAsync(user)).AsEphemeral(!showPublicly)); } - [SlashCommand("details", "Show the details of a specific note for a user.")] - public async Task ShowUserNoteAsync(InteractionContext ctx, - [Option("user", "The user whose note to show details for.")] DiscordUser user, - [Autocomplete(typeof(NotesAutocompleteProvider))][Option("note", "The note to show.")] string targetNote, - [Option("public", "Whether to show the note in public chat. Default: false")] bool showPublicly = false) + [Command("details")] + [Description("Show the details of a specific note for a user.")] + public async Task ShowUserNoteAsync(SlashCommandContext ctx, + [Parameter("user"), Description("The user whose note to show details for.")] DiscordUser user, + [SlashAutoCompleteProvider(typeof(NotesAutocompleteProvider))][Parameter("note"), Description("The note to show.")] string targetNote, + [Parameter("public"), Description("Whether to show the note in public chat. Default: false")] bool showPublicly = false) { // Get note UserNote note; @@ -171,26 +178,26 @@ public async Task ShowUserNoteAsync(InteractionContext ctx, } catch { - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't find that note! Make sure you've got the right ID.").AsEphemeral()); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} I couldn't find that note! Make sure you've got the right ID.").AsEphemeral()); return; } // If user manually provided an ID of a warning, refuse the request and suggest /warndetails instead if (note.Type == WarningType.Warning) { - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That's a warning, not a note! Try using `/warndetails` instead, or make sure you've got the right note ID.").AsEphemeral()); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().WithContent($"{Program.cfgjson.Emoji.Error} That's a warning, not a note! Try using `/warndetails` instead, or make sure you've got the right note ID.").AsEphemeral()); return; } // Respond - await ctx.CreateResponseAsync(new DiscordInteractionResponseBuilder().AddEmbed(await GenerateUserNoteDetailEmbedAsync(note, user)).AsEphemeral(!showPublicly)); + await ctx.RespondAsync(new DiscordInteractionResponseBuilder().AddEmbed(await GenerateUserNoteDetailEmbedAsync(note, user)).AsEphemeral(!showPublicly)); } - private class NotesAutocompleteProvider : IAutocompleteProvider + private class NotesAutocompleteProvider : IAutoCompleteProvider { - public async Task> Provider(AutocompleteContext ctx) + public async ValueTask> AutoCompleteAsync(AutoCompleteContext ctx) { - var list = new List(); + var list = new Dictionary(); var useroption = ctx.Options.FirstOrDefault(x => x.Name == "user"); if (useroption == default) @@ -213,8 +220,10 @@ public async Task> Provider(AutocompleteC string noteString = $"{StringHelpers.Pad(note.Value.NoteId)} - {StringHelpers.Truncate(note.Value.NoteText, 29, true)} - {TimeHelpers.TimeToPrettyFormat(DateTime.Now - note.Value.Timestamp, true)}"; - if (ctx.FocusedOption.Value.ToString() == "" || note.Value.NoteText.Contains((string)ctx.FocusedOption.Value) || noteString.ToLower().Contains(ctx.FocusedOption.Value.ToString().ToLower())) - list.Add(new DiscordAutoCompleteChoice(noteString, StringHelpers.Pad(note.Value.NoteId))); + var focusedOption = ctx.Options.FirstOrDefault(option => option.Focused); + if (focusedOption is not null) // TODO(#202): is this right? + if (note.Value.NoteText.Contains((string)focusedOption.Value) || noteString.ToLower().Contains(focusedOption.Value.ToString().ToLower())) + list.Add(noteString, StringHelpers.Pad(note.Value.NoteId)); } return list; diff --git a/Commands/InteractionCommands/WarningInteractions.cs b/Commands/InteractionCommands/WarningInteractions.cs index d8a674f8..f1621d68 100644 --- a/Commands/InteractionCommands/WarningInteractions.cs +++ b/Commands/InteractionCommands/WarningInteractions.cs @@ -1,22 +1,26 @@ -using static Cliptok.Helpers.WarningHelpers; +using DSharpPlus.Commands.Processors.SlashCommands; +using DSharpPlus.Commands.Processors.SlashCommands.ArgumentModifiers; +using static Cliptok.Helpers.WarningHelpers; namespace Cliptok.Commands.InteractionCommands { - internal class WarningInteractions : ApplicationCommandModule + internal class WarningInteractions { - [SlashCommand("warn", "Formally warn a user, usually for breaking the server rules.", defaultPermission: false)] + [Command("warn")] + [Description("Formally warn a user, usually for breaking the server rules.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task WarnSlashCommand(InteractionContext ctx, - [Option("user", "The user to warn.")] DiscordUser user, - [Option("reason", "The reason they're being warned.")] string reason, - [Option("reply_msg_id", "The ID of a message to reply to, must be in the same channel.")] string replyMsgId = "0", - [Option("channel", "The channel to warn the user in, implied if not supplied.")] DiscordChannel channel = null + [RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task WarnSlashCommand(SlashCommandContext ctx, + [Parameter("user"), Description("The user to warn.")] DiscordUser user, + [Parameter("reason"), Description("The reason they're being warned.")] string reason, + [Parameter("reply_msg_id"), Description("The ID of a message to reply to, must be in the same channel.")] string replyMsgId = "0", + [Parameter("channel"), Description("The channel to warn the user in, implied if not supplied.")] DiscordChannel channel = null ) { // Initial response to avoid the 3 second timeout, will edit later. var eout = new DiscordInteractionResponseBuilder().AsEphemeral(true); - await ctx.CreateResponseAsync(DiscordInteractionResponseType.DeferredChannelMessageWithSource, eout); + await ctx.RespondAsync(eout); // Edits need a webhook rather than interaction..? DiscordWebhookBuilder webhookOut; @@ -41,9 +45,6 @@ public async Task WarnSlashCommand(InteractionContext ctx, if (channel is null) channel = ctx.Channel; - if (channel is null) - channel = await ctx.Client.GetChannelAsync(ctx.Interaction.ChannelId); - var messageBuild = new DiscordMessageBuilder() .WithContent($"{Program.cfgjson.Emoji.Warning} {user.Mention} was warned: **{reason.Replace("`", "\\`").Replace("*", "\\*")}**"); @@ -57,34 +58,38 @@ public async Task WarnSlashCommand(InteractionContext ctx, await ctx.EditResponseAsync(webhookOut); } - [SlashCommand("warnings", "Fetch the warnings for a user.")] - public async Task WarningsSlashCommand(InteractionContext ctx, - [Option("user", "The user to find the warnings for.")] DiscordUser user, - [Option("public", "Whether to show the warnings in public chat. Do not disrupt chat with this.")] bool publicWarnings = false + [Command("warnings")] + [Description("Fetch the warnings for a user.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + public async Task WarningsSlashCommand(SlashCommandContext ctx, + [Parameter("user"), Description("The user to find the warnings for.")] DiscordUser user, + [Parameter("public"), Description("Whether to show the warnings in public chat. Do not disrupt chat with this.")] bool publicWarnings = false ) { var eout = new DiscordInteractionResponseBuilder().AddEmbed(await WarningHelpers.GenerateWarningsEmbedAsync(user)); if (!publicWarnings) eout.AsEphemeral(true); - await ctx.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, eout); + await ctx.RespondAsync(eout); } - [SlashCommand("transfer_warnings", "Transfer warnings from one user to another.", defaultPermission: false)] + [Command("transfer_warnings")] + [Description("Transfer warnings from one user to another.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] [SlashRequireHomeserverPerm(ServerPermLevel.Moderator)] - [SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task TransferWarningsSlashCommand(InteractionContext ctx, - [Option("source_user", "The user currently holding the warnings.")] DiscordUser sourceUser, - [Option("target_user", "The user receiving the warnings.")] DiscordUser targetUser, - [Option("merge", "Whether to merge the source user's warnings and the target user's warnings.")] bool merge = false, - [Option("force_override", "DESTRUCTIVE OPERATION: Whether to OVERRIDE and DELETE the target users existing warnings.")] bool forceOverride = false + [RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task TransferWarningsSlashCommand(SlashCommandContext ctx, + [Parameter("source_user"), Description("The user currently holding the warnings.")] DiscordUser sourceUser, + [Parameter("target_user"), Description("The user receiving the warnings.")] DiscordUser targetUser, + [Parameter("merge"), Description("Whether to merge the source user's warnings and the target user's warnings.")] bool merge = false, + [Parameter("force_override"), Description("DESTRUCTIVE OPERATION: Whether to OVERRIDE and DELETE the target users existing warnings.")] bool forceOverride = false ) { - await ctx.DeferAsync(ephemeral: false); + await ctx.DeferResponseAsync(); // TODO(#202): how do you make this ephemeral? if (sourceUser == targetUser) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} The source and target users cannot be the same!")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} The source and target users cannot be the same!")); return; } @@ -93,7 +98,7 @@ public async Task TransferWarningsSlashCommand(InteractionContext ctx, if (sourceWarnings.Length == 0) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} The source user has no warnings to transfer.").AddEmbed(await GenerateWarningsEmbedAsync(sourceUser))); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Error} The source user has no warnings to transfer.").AddEmbed(await GenerateWarningsEmbedAsync(sourceUser))); return; } else if (merge) @@ -106,7 +111,7 @@ public async Task TransferWarningsSlashCommand(InteractionContext ctx, } else if (targetWarnings.Length > 0 && !forceOverride) { - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Warning} **CAUTION**: The target user has warnings.\n\n" + + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Warning} **CAUTION**: The target user has warnings.\n\n" + $"If you are sure you want to **OVERRIDE** and **DELETE** these warnings, please consider the consequences before adding `force_override: True` to the command.\nIf you wish to **NOT** override the target's warnings, please use `merge: True` instead.") .AddEmbed(await GenerateWarningsEmbedAsync(targetUser))); return; @@ -131,14 +136,14 @@ await LogChannelHelper.LogMessageAsync("mod", .WithContent($"{Program.cfgjson.Emoji.Information} Warnings from {sourceUser.Mention} were {operationText}transferred to {targetUser.Mention} by `{DiscordHelpers.UniqueUsername(ctx.User)}`") .AddEmbed(await GenerateWarningsEmbedAsync(targetUser)) ); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully {operationText}transferred warnings from {sourceUser.Mention} to {targetUser.Mention}!")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Success} Successfully {operationText}transferred warnings from {sourceUser.Mention} to {targetUser.Mention}!")); } - internal partial class WarningsAutocompleteProvider : IAutocompleteProvider + internal partial class WarningsAutocompleteProvider : IAutoCompleteProvider { - public async Task> Provider(AutocompleteContext ctx) + public async ValueTask> AutoCompleteAsync(AutoCompleteContext ctx) { - var list = new List(); + var list = new Dictionary(); var useroption = ctx.Options.FirstOrDefault(x => x.Name == "user"); if (useroption == default) @@ -161,8 +166,10 @@ public async Task> Provider(AutocompleteC string warningString = $"{StringHelpers.Pad(warning.Value.WarningId)} - {StringHelpers.Truncate(warning.Value.WarnReason, 29, true)} - {TimeHelpers.TimeToPrettyFormat(DateTime.Now - warning.Value.WarnTimestamp, true)}"; - if (ctx.FocusedOption.Value.ToString() == "" || warning.Value.WarnReason.Contains((string)ctx.FocusedOption.Value) || warningString.ToLower().Contains(ctx.FocusedOption.Value.ToString().ToLower())) - list.Add(new DiscordAutoCompleteChoice(warningString, StringHelpers.Pad(warning.Value.WarningId))); + var focusedOption = ctx.Options.FirstOrDefault(option => option.Focused); + if (focusedOption is not null) // TODO(#202): is this right? + if (warning.Value.WarnReason.Contains((string)focusedOption.Value) || warningString.ToLower().Contains(focusedOption.Value.ToString().ToLower())) + list.Add(warningString, StringHelpers.Pad(warning.Value.WarningId)); } return list; @@ -170,12 +177,14 @@ public async Task> Provider(AutocompleteC } } - [SlashCommand("warndetails", "Search for a warning and return its details.", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task WarndetailsSlashCommand(InteractionContext ctx, - [Option("user", "The user to fetch a warning for.")] DiscordUser user, - [Autocomplete(typeof(WarningsAutocompleteProvider)), Option("warning", "Type to search! Find the warning you want to fetch.")] string warning, - [Option("public", "Whether to show the output publicly.")] bool publicWarnings = false + [Command("warndetails")] + [Description("Search for a warning and return its details.")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task WarndetailsSlashCommand(SlashCommandContext ctx, + [Parameter("user"), Description("The user to fetch a warning for.")] DiscordUser user, + [SlashAutoCompleteProvider(typeof(WarningsAutocompleteProvider)), Parameter("warning"), Description("Type to search! Find the warning you want to fetch.")] string warning, + [Parameter("public"), Description("Whether to show the output publicly.")] bool publicWarnings = false ) { if (warning.Contains(' ')) @@ -202,17 +211,19 @@ public async Task WarndetailsSlashCommand(InteractionContext ctx, await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} That's a note, not a warning! Try using `/note details` instead, or make sure you've got the right warning ID.", ephemeral: true); else { - await ctx.DeferAsync(ephemeral: !publicWarnings); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().AddEmbed(await FancyWarnEmbedAsync(warningObject, true, userID: user.Id))); + await ctx.DeferResponseAsync(ephemeral: !publicWarnings); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().AddEmbed(await FancyWarnEmbedAsync(warningObject, true, userID: user.Id))); } } - [SlashCommand("delwarn", "Search for a warning and delete it!", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task DelwarnSlashCommand(InteractionContext ctx, - [Option("user", "The user to delete a warning for.")] DiscordUser targetUser, - [Autocomplete(typeof(WarningsAutocompleteProvider))][Option("warning", "Type to search! Find the warning you want to delete.")] string warningId, - [Option("public", "Whether to show the output publicly. Default: false")] bool showPublic = false + [Command("delwarn")] + [Description("Search for a warning and delete it!")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task DelwarnSlashCommand(SlashCommandContext ctx, + [Parameter("user"), Description("The user to delete a warning for.")] DiscordUser targetUser, + [SlashAutoCompleteProvider(typeof(WarningsAutocompleteProvider))][Parameter("warning"), Description("Type to search! Find the warning you want to delete.")] string warningId, + [Parameter("public"), Description("Whether to show the output publicly. Default: false")] bool showPublic = false ) { if (warningId.Contains(' ')) @@ -245,7 +256,7 @@ public async Task DelwarnSlashCommand(InteractionContext ctx, } else { - await ctx.DeferAsync(ephemeral: !showPublic); + await ctx.DeferResponseAsync(ephemeral: !showPublic); bool success = await DelWarningAsync(warning, targetUser.Id); if (success) @@ -258,7 +269,7 @@ await LogChannelHelper.LogMessageAsync("mod", .WithAllowedMentions(Mentions.None) ); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Deleted} Successfully deleted warning `{StringHelpers.Pad(warnId)}` (belonging to {targetUser.Mention})")); + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Deleted} Successfully deleted warning `{StringHelpers.Pad(warnId)}` (belonging to {targetUser.Mention})")); } @@ -269,13 +280,15 @@ await LogChannelHelper.LogMessageAsync("mod", } } - [SlashCommand("editwarn", "Search for a warning and edit it!", defaultPermission: false)] - [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), SlashCommandPermissions(DiscordPermissions.ModerateMembers)] - public async Task EditWarnSlashCommand(InteractionContext ctx, - [Option("user", "The user to fetch a warning for.")] DiscordUser user, - [Autocomplete(typeof(WarningsAutocompleteProvider))][Option("warning", "Type to search! Find the warning you want to edit.")] string warning, - [Option("new_reason", "The new reason for the warning")] string reason, - [Option("public", "Whether to show the output publicly. Default: false")] bool showPublic = false) + [Command("editwarn")] + [Description("Search for a warning and edit it!")] + [AllowedProcessors(typeof(SlashCommandProcessor))] + [SlashRequireHomeserverPerm(ServerPermLevel.TrialModerator), RequirePermissions(DiscordPermissions.ModerateMembers)] + public async Task EditWarnSlashCommand(SlashCommandContext ctx, + [Parameter("user"), Description("The user to fetch a warning for.")] DiscordUser user, + [SlashAutoCompleteProvider(typeof(WarningsAutocompleteProvider))][Parameter("warning"), Description("Type to search! Find the warning you want to edit.")] string warning, + [Parameter("new_reason"), Description("The new reason for the warning")] string reason, + [Parameter("public"), Description("Whether to show the output publicly. Default: false")] bool showPublic = false) { if (warning.Contains(' ')) { @@ -313,7 +326,7 @@ public async Task EditWarnSlashCommand(InteractionContext ctx, } else { - await ctx.DeferAsync(ephemeral: !showPublic); + await ctx.DeferResponseAsync(ephemeral: !showPublic); await LogChannelHelper.LogMessageAsync("mod", new DiscordMessageBuilder() @@ -323,7 +336,7 @@ await LogChannelHelper.LogMessageAsync("mod", ); await EditWarning(user, warnId, ctx.User, reason); - await ctx.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Information} Successfully edited warning `{StringHelpers.Pad(warnId)}` (belonging to {user.Mention})") + await ctx.FollowupAsync(new DiscordFollowupMessageBuilder().WithContent($"{Program.cfgjson.Emoji.Information} Successfully edited warning `{StringHelpers.Pad(warnId)}` (belonging to {user.Mention})") .AddEmbed(await FancyWarnEmbedAsync(GetWarning(user.Id, warnId), userID: user.Id))); } } diff --git a/Commands/Kick.cs b/Commands/Kick.cs index 2f969519..87b49b58 100644 --- a/Commands/Kick.cs +++ b/Commands/Kick.cs @@ -1,12 +1,13 @@ namespace Cliptok.Commands { - internal class Kick : BaseCommandModule + internal class Kick { [Command("kick")] - [Aliases("yeet", "shoo", "goaway", "defenestrate")] + [TextAlias("yeet", "shoo", "goaway", "defenestrate")] [Description("Kicks a user, removing them from the server until they rejoin. Generally not very useful.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequirePermissions(DiscordPermissions.KickMembers), HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task KickCmd(CommandContext ctx, DiscordUser target, [RemainingText] string reason = "No reason specified.") + public async Task KickCmd(TextCommandContext ctx, DiscordUser target, [RemainingText] string reason = "No reason specified.") { if (target.IsBot) { @@ -50,8 +51,9 @@ public async Task KickCmd(CommandContext ctx, DiscordUser target, [RemainingText } [Command("masskick")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task MassKickCmd(CommandContext ctx, [RemainingText] string input) + public async Task MassKickCmd(TextCommandContext ctx, [RemainingText] string input) { List usersString = input.Replace("\n", " ").Replace("\r", "").Split(' ').ToList(); @@ -65,7 +67,8 @@ public async Task MassKickCmd(CommandContext ctx, [RemainingText] string input) List> taskList = new(); int successes = 0; - var loading = await ctx.RespondAsync("Processing, please wait."); + await ctx.RespondAsync("Processing, please wait."); + var loading = await ctx.GetResponseAsync(); foreach (ulong user in users) { diff --git a/Commands/Lists.cs b/Commands/Lists.cs index 61190cb4..76f0fe01 100644 --- a/Commands/Lists.cs +++ b/Commands/Lists.cs @@ -1,6 +1,6 @@ namespace Cliptok.Commands { - internal class Lists : BaseCommandModule + internal class Lists { public class GitHubDispatchBody { @@ -25,8 +25,9 @@ public class GitHubDispatchInputs [Command("listupdate")] [Description("Updates the private lists from the GitHub repository, then reloads them into memory.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task ListUpdate(CommandContext ctx) + public async Task ListUpdate(TextCommandContext ctx) { if (Program.cfgjson.GitListDirectory is null || Program.cfgjson.GitListDirectory == "") { @@ -35,7 +36,8 @@ public async Task ListUpdate(CommandContext ctx) } string command = $"cd Lists/{Program.cfgjson.GitListDirectory} && git pull"; - DiscordMessage msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Updating private lists.."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Loading} Updating private lists.."); + DiscordMessage msg = await ctx.GetResponseAsync(); ShellResult finishedShell = RunShellCommand(command); @@ -55,9 +57,10 @@ public async Task ListUpdate(CommandContext ctx) [Command("listadd")] [Description("Add a piece of text to a public list.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] public async Task ListAdd( - CommandContext ctx, + TextCommandContext ctx, [Description("The filename of the public list to add to. For example scams.txt")] string fileName, [RemainingText, Description("The text to add the list. Can be in a codeblock and across multiple line.")] string content ) @@ -134,8 +137,9 @@ await ctx.RespondAsync($"{Program.cfgjson.Emoji.Error} An error with code `{resp [Command("scamcheck")] [Description("Check if a link or message is known to the anti-phishing API.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task ScamCheck(CommandContext ctx, [RemainingText, Description("Domain or message content to scan.")] string content) + public async Task ScamCheck(TextCommandContext ctx, [RemainingText, Description("Domain or message content to scan.")] string content) { var urlMatches = Constants.RegexConstants.url_rx.Matches(content); if (urlMatches.Count > 0 && Environment.GetEnvironmentVariable("CLIPTOK_ANTIPHISHING_ENDPOINT") is not null && Environment.GetEnvironmentVariable("CLIPTOK_ANTIPHISHING_ENDPOINT") != "useyourimagination") @@ -164,11 +168,12 @@ public async Task ScamCheck(CommandContext ctx, [RemainingText, Description("Dom } [Command("joinwatch")] - [Aliases("joinnotify", "leavewatch", "leavenotify")] + [TextAlias("joinnotify", "leavewatch", "leavenotify")] [Description("Watch for joins and leaves of a given user. Output goes to #investigations.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] public async Task JoinWatch( - CommandContext ctx, + TextCommandContext ctx, [Description("The user to watch for joins and leaves of.")] DiscordUser user, [Description("An optional note for context."), RemainingText] string note = "" ) @@ -212,11 +217,12 @@ public async Task JoinWatch( } [Command("appealblock")] - [Aliases("superduperban", "ablock")] + [TextAlias("superduperban", "ablock")] [Description("Prevents a user from submitting ban appeals.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] public async Task AppealBlock( - CommandContext ctx, + TextCommandContext ctx, [Description("The user to block from ban appeals.")] DiscordUser user ) { diff --git a/Commands/Lockdown.cs b/Commands/Lockdown.cs index c55df3a4..68af2cf8 100644 --- a/Commands/Lockdown.cs +++ b/Commands/Lockdown.cs @@ -1,15 +1,16 @@ namespace Cliptok.Commands { - class Lockdown : BaseCommandModule + class Lockdown { public bool ongoingLockdown = false; [Command("lockdown")] - [Aliases("lock")] + [TextAlias("lock")] [Description("Locks the current channel, preventing any new messages. See also: unlock")] - [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator), RequireBotPermissions(DiscordPermissions.ManageChannels)] + [AllowedProcessors(typeof(TextCommandProcessor))] + [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.ManageChannels, DiscordPermissions.None)] public async Task LockdownCommand( - CommandContext ctx, + TextCommandContext ctx, [RemainingText, Description("The time and reason for the lockdown. For example '3h' or '3h spam'. Default is permanent with no reason.")] string timeAndReason = "" ) { @@ -98,8 +99,9 @@ await thread.ModifyAsync(a => [Command("unlock")] [Description("Unlocks a previously locked channel. See also: lockdown")] - [Aliases("unlockdown"), HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator), RequireBotPermissions(DiscordPermissions.ManageChannels)] - public async Task UnlockCommand(CommandContext ctx, [RemainingText] string reason = "") + [AllowedProcessors(typeof(TextCommandProcessor))] + [TextAlias("unlockdown"), HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator), RequirePermissions(DiscordPermissions.ManageChannels, DiscordPermissions.None)] + public async Task UnlockCommand(TextCommandContext ctx, [RemainingText] string reason = "") { var currentChannel = ctx.Channel; if (!Program.cfgjson.LockdownEnabledChannels.Contains(currentChannel.Id)) diff --git a/Commands/Mutes.cs b/Commands/Mutes.cs index 9f8116d0..85c89e7b 100644 --- a/Commands/Mutes.cs +++ b/Commands/Mutes.cs @@ -1,12 +1,13 @@ namespace Cliptok.Commands { - internal class Mutes : BaseCommandModule + internal class Mutes { [Command("unmute")] - [Aliases("umute")] + [TextAlias("umute")] [Description("Unmutes a previously muted user, typically ahead of the standard expiration time. See also: mute")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task UnmuteCmd(CommandContext ctx, [Description("The user you're trying to unmute.")] DiscordUser targetUser, string reason = "No reason provided.") + public async Task UnmuteCmd(TextCommandContext ctx, [Description("The user you're trying to unmute.")] DiscordUser targetUser, string reason = "No reason provided.") { reason = $"[Manual unmute by {DiscordHelpers.UniqueUsername(ctx.User)}]: {reason}"; @@ -46,9 +47,10 @@ public async Task UnmuteCmd(CommandContext ctx, [Description("The user you're tr [Command("mute")] [Description("Mutes a user, preventing them from sending messages until they're unmuted. See also: unmute")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] public async Task MuteCmd( - CommandContext ctx, [Description("The user you're trying to mute")] DiscordUser targetUser, + TextCommandContext ctx, [Description("The user you're trying to mute")] DiscordUser targetUser, [RemainingText, Description("Combined argument for the time and reason for the mute. For example '1h rule 7' or 'rule 10'")] string timeAndReason = "No reason specified." ) { @@ -100,9 +102,10 @@ public async Task MuteCmd( [Command("tqsmute")] [Description( "Temporarily mutes a user, preventing them from sending messages in #tech-support and related channels until they're unmuted.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TechnicalQueriesSlayer)] public async Task TqsMuteCmd( - CommandContext ctx, [Description("The user to mute")] DiscordUser targetUser, + TextCommandContext ctx, [Description("The user to mute")] DiscordUser targetUser, [RemainingText, Description("The reason for the mute")] string reason = "No reason specified.") { if (Program.cfgjson.TqsMutedRole == 0) diff --git a/Commands/Raidmode.cs b/Commands/Raidmode.cs index 0da42988..365dcb62 100644 --- a/Commands/Raidmode.cs +++ b/Commands/Raidmode.cs @@ -1,16 +1,19 @@ -namespace Cliptok.Commands +using DSharpPlus.Commands.Trees.Metadata; + +namespace Cliptok.Commands { - internal class Raidmode : BaseCommandModule + internal class Raidmode { - [Group("clipraidmode")] + [Command("clipraidmode")] [Description("Manage the server's raidmode, preventing joins while on.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequireHomeserverPerm(ServerPermLevel.Moderator)] - class RaidmodeCommands : BaseCommandModule + class RaidmodeCommands { - [GroupCommand] + [DefaultGroupCommand] [Description("Check whether raidmode is enabled or not, and when it ends.")] - [Aliases("status")] - public async Task RaidmodeStatus(CommandContext ctx) + [TextAlias("status")] + public async Task RaidmodeStatus(TextCommandContext ctx) { if (Program.db.HashExists("raidmode", ctx.Guild.Id)) { @@ -27,7 +30,7 @@ public async Task RaidmodeStatus(CommandContext ctx) [Command("on")] [Description("Enable raidmode.")] - public async Task RaidmodeOn(CommandContext ctx, [Description("The amount of time to keep raidmode enabled for. Default is 3 hours.")] string duration = default) + public async Task RaidmodeOn(TextCommandContext ctx, [Description("The amount of time to keep raidmode enabled for. Default is 3 hours.")] string duration = default) { if (Program.db.HashExists("raidmode", ctx.Guild.Id)) { @@ -60,7 +63,7 @@ await LogChannelHelper.LogMessageAsync("mod", [Command("off")] [Description("Disable raidmode.")] - public async Task RaidmdodeOff(CommandContext ctx) + public async Task RaidmdodeOff(TextCommandContext ctx) { if (Program.db.HashExists("raidmode", ctx.Guild.Id)) { diff --git a/Commands/Reminders.cs b/Commands/Reminders.cs index c152e95a..9e49af29 100644 --- a/Commands/Reminders.cs +++ b/Commands/Reminders.cs @@ -1,6 +1,6 @@ namespace Cliptok.Commands { - public class Reminders : BaseCommandModule + public class Reminders { public class Reminder { @@ -28,10 +28,11 @@ public class Reminder [Command("remindme")] [Description("Set a reminder for yourself. Example: !reminder 1h do the thing")] - [Aliases("reminder", "rember", "wemember", "remember", "remind")] + [TextAlias("reminder", "rember", "wemember", "remember", "remind")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequireHomeserverPerm(ServerPermLevel.Tier4, WorkOutside = true)] public async Task RemindMe( - CommandContext ctx, + TextCommandContext ctx, [Description("The amount of time to wait before reminding you. For example: 2s, 5m, 1h, 1d")] string timetoParse, [RemainingText, Description("The text to send when the reminder triggers.")] string reminder ) diff --git a/Commands/SecurityActions.cs b/Commands/SecurityActions.cs index b1c7851a..2ea5b5e5 100644 --- a/Commands/SecurityActions.cs +++ b/Commands/SecurityActions.cs @@ -1,11 +1,12 @@ namespace Cliptok.Commands { - public class SecurityActions : BaseCommandModule + public class SecurityActions { [Command("pausedms")] [Description("Temporarily pause DMs between server members.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task PauseDMs(CommandContext ctx, [Description("The amount of time to pause DMs for."), RemainingText] string time) + public async Task PauseDMs(TextCommandContext ctx, [Description("The amount of time to pause DMs for."), RemainingText] string time) { if (string.IsNullOrWhiteSpace(time)) { @@ -68,8 +69,9 @@ public async Task PauseDMs(CommandContext ctx, [Description("The amount of time [Command("unpausedms")] [Description("Unpause DMs between server members.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.Moderator)] - public async Task UnpauseDMs(CommandContext ctx) + public async Task UnpauseDMs(TextCommandContext ctx) { // need to make our own api calls because D#+ can't do this natively? diff --git a/Commands/TechSupport.cs b/Commands/TechSupport.cs index 6a73ceb9..f1728bb7 100644 --- a/Commands/TechSupport.cs +++ b/Commands/TechSupport.cs @@ -1,11 +1,12 @@ namespace Cliptok.Commands { - internal class TechSupport : BaseCommandModule + internal class TechSupport { [Command("ask")] [Description("Outputs information on how and where to ask tech support questions. Replying to a message while triggering the command will mirror the reply in the response.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer] - public async Task AskCmd(CommandContext ctx, [Description("Optional, a user to ping with the information")] DiscordUser user = default) + public async Task AskCmd(TextCommandContext ctx, [Description("Optional, a user to ping with the information")] DiscordUser user = default) { await ctx.Message.DeleteAsync(); DiscordEmbedBuilder embed = new DiscordEmbedBuilder() diff --git a/Commands/Threads.cs b/Commands/Threads.cs index dd7fa4ea..c412876a 100644 --- a/Commands/Threads.cs +++ b/Commands/Threads.cs @@ -1,11 +1,12 @@ namespace Cliptok.Commands { - internal class Threads : BaseCommandModule + internal class Threads { [Command("archive")] [Description("Archive the current thread or another thread.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task ArchiveCommand(CommandContext ctx, DiscordChannel channel = default) + public async Task ArchiveCommand(TextCommandContext ctx, DiscordChannel channel = default) { if (channel == default) channel = ctx.Channel; @@ -27,8 +28,9 @@ await thread.ModifyAsync(a => [Command("lockthread")] [Description("Lock the current thread or another thread.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task LockThreadCommand(CommandContext ctx, DiscordChannel channel = default) + public async Task LockThreadCommand(TextCommandContext ctx, DiscordChannel channel = default) { if (channel == default) channel = ctx.Channel; @@ -50,8 +52,9 @@ await thread.ModifyAsync(a => [Command("unarchive")] [Description("Unarchive a thread")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task UnarchiveCommand(CommandContext ctx, DiscordChannel channel = default) + public async Task UnarchiveCommand(TextCommandContext ctx, DiscordChannel channel = default) { if (channel == default) channel = ctx.Channel; diff --git a/Commands/Timestamp.cs b/Commands/Timestamp.cs index 47c07e5e..af532575 100644 --- a/Commands/Timestamp.cs +++ b/Commands/Timestamp.cs @@ -1,17 +1,20 @@ -namespace Cliptok.Commands +using DSharpPlus.Commands.Trees.Metadata; + +namespace Cliptok.Commands { - internal class Timestamp : BaseCommandModule + internal class Timestamp { - [Group("timestamp")] - [Aliases("ts", "time")] + [Command("timestamp")] + [TextAlias("ts", "time")] [Description("Returns various timestamps for a given Discord ID/snowflake")] + [AllowedProcessors(typeof(TextCommandProcessor))] [HomeServer] - class TimestampCmds : BaseCommandModule + class TimestampCmds { - [GroupCommand] - [Aliases("u", "unix", "epoch")] + [DefaultGroupCommand] + [TextAlias("u", "unix", "epoch")] [Description("Returns the Unix timestamp of a given Discord ID/snowflake")] - public async Task TimestampUnixCmd(CommandContext ctx, [Description("The ID/snowflake to fetch the Unix timestamp for")] ulong snowflake) + public async Task TimestampUnixCmd(TextCommandContext ctx, [Description("The ID/snowflake to fetch the Unix timestamp for")] ulong snowflake) { var msSinceEpoch = snowflake >> 22; var msUnix = msSinceEpoch + 1420070400000; @@ -19,9 +22,9 @@ public async Task TimestampUnixCmd(CommandContext ctx, [Description("The ID/snow } [Command("relative")] - [Aliases("r")] + [TextAlias("r")] [Description("Returns the amount of time between now and a given Discord ID/snowflake")] - public async Task TimestampRelativeCmd(CommandContext ctx, [Description("The ID/snowflake to fetch the relative timestamp for")] ulong snowflake) + public async Task TimestampRelativeCmd(TextCommandContext ctx, [Description("The ID/snowflake to fetch the relative timestamp for")] ulong snowflake) { var msSinceEpoch = snowflake >> 22; var msUnix = msSinceEpoch + 1420070400000; @@ -29,9 +32,9 @@ public async Task TimestampRelativeCmd(CommandContext ctx, [Description("The ID/ } [Command("fulldate")] - [Aliases("f", "datetime")] + [TextAlias("f", "datetime")] [Description("Returns the fully-formatted date and time of a given Discord ID/snowflake")] - public async Task TimestampFullCmd(CommandContext ctx, [Description("The ID/snowflake to fetch the full timestamp for")] ulong snowflake) + public async Task TimestampFullCmd(TextCommandContext ctx, [Description("The ID/snowflake to fetch the full timestamp for")] ulong snowflake) { var msSinceEpoch = snowflake >> 22; var msUnix = msSinceEpoch + 1420070400000; diff --git a/Commands/UserRoles.cs b/Commands/UserRoles.cs index 0b874ec7..45c27f89 100644 --- a/Commands/UserRoles.cs +++ b/Commands/UserRoles.cs @@ -1,14 +1,14 @@ namespace Cliptok.Commands { [UserRolesPresent] - public class UserRoleCmds : BaseCommandModule + public class UserRoleCmds { - public static async Task GiveUserRoleAsync(CommandContext ctx, ulong role) + public static async Task GiveUserRoleAsync(TextCommandContext ctx, ulong role) { await GiveUserRolesAsync(ctx, x => (ulong)x.GetValue(Program.cfgjson.UserRoles, null) == role); } - public static async Task GiveUserRolesAsync(CommandContext ctx, Func predicate) + public static async Task GiveUserRolesAsync(TextCommandContext ctx, Func predicate) { if (Program.cfgjson.UserRoles is null) { @@ -42,13 +42,13 @@ public static async Task GiveUserRolesAsync(CommandContext ctx, Func (ulong)x.GetValue(Program.cfgjson.UserRoles, null) == role); } - public static async Task RemoveUserRolesAsync(CommandContext ctx, Func predicate) + public static async Task RemoveUserRolesAsync(TextCommandContext ctx, Func predicate) { if (Program.cfgjson.UserRoles is null) { @@ -73,11 +73,12 @@ public static async Task RemoveUserRolesAsync(CommandContext ctx, Func true); } [ Command("leave-insiders"), - Aliases("leave-insider"), + TextAlias("leave-insider"), Description("Removes you from Insider roles"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] - public async Task LeaveInsiders(CommandContext ctx) + public async Task LeaveInsiders(TextCommandContext ctx) { foreach (ulong roleId in new ulong[] { Program.cfgjson.UserRoles.InsiderDev, Program.cfgjson.UserRoles.InsiderBeta, Program.cfgjson.UserRoles.InsiderRP, Program.cfgjson.UserRoles.InsiderCanary, Program.cfgjson.UserRoles.InsiderDev }) { await RemoveUserRoleAsync(ctx, roleId); } - var msg = await ctx.RespondAsync($"{Program.cfgjson.Emoji.Insider} You are no longer receiving Windows Insider notifications. If you ever wish to receive Insider notifications again, you can check the <#740272437719072808> description for the commands."); + await ctx.RespondAsync($"{Program.cfgjson.Emoji.Insider} You are no longer receiving Windows Insider notifications. If you ever wish to receive Insider notifications again, you can check the <#740272437719072808> description for the commands."); + var msg = await ctx.GetResponseAsync(); await Task.Delay(10000); await msg.DeleteAsync(); } @@ -193,64 +204,70 @@ public async Task LeaveInsiders(CommandContext ctx) [ Command("dont-keep-me-updated"), Description("Takes away from you all opt-in roles"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] - public async Task DontKeepMeUpdated(CommandContext ctx) + public async Task DontKeepMeUpdated(TextCommandContext ctx) { await RemoveUserRolesAsync(ctx, x => true); } [ Command("leave-insider-dev"), - Aliases("leave-insiders-dev"), + TextAlias("leave-insiders-dev"), Description("Removes the Windows 11 Insiders (Dev) role"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] - public async Task LeaveInsiderDevCmd(CommandContext ctx) + public async Task LeaveInsiderDevCmd(TextCommandContext ctx) { await RemoveUserRoleAsync(ctx, Program.cfgjson.UserRoles.InsiderDev); } [ Command("leave-insider-canary"), - Aliases("leave-insiders-canary", "leave-insider-can", "leave-insiders-can"), + TextAlias("leave-insiders-canary", "leave-insider-can", "leave-insiders-can"), Description("Removes the Windows 11 Insiders (Canary) role"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] - public async Task LeaveInsiderCanaryCmd(CommandContext ctx) + public async Task LeaveInsiderCanaryCmd(TextCommandContext ctx) { await RemoveUserRoleAsync(ctx, Program.cfgjson.UserRoles.InsiderCanary); } [ Command("leave-insider-beta"), - Aliases("leave-insiders-beta"), + TextAlias("leave-insiders-beta"), Description("Removes the Windows 11 Insiders (Beta) role"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] - public async Task LeaveInsiderBetaCmd(CommandContext ctx) + public async Task LeaveInsiderBetaCmd(TextCommandContext ctx) { await RemoveUserRoleAsync(ctx, Program.cfgjson.UserRoles.InsiderBeta); } [ Command("leave-insider-10"), - Aliases("leave-insiders-10"), + TextAlias("leave-insiders-10"), Description("Removes the Windows 10 Insiders (Release Preview) role"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] - public async Task LeaveInsiderRPCmd(CommandContext ctx) + public async Task LeaveInsiderRPCmd(TextCommandContext ctx) { await RemoveUserRoleAsync(ctx, Program.cfgjson.UserRoles.Insider10RP); } [ Command("leave-insider-rp"), - Aliases("leave-insiders-rp", "leave-insiders-11-rp", "leave-insider-11-rp"), + TextAlias("leave-insiders-rp", "leave-insiders-11-rp", "leave-insider-11-rp"), Description("Removes the Windows 11 Insiders (Release Preview) role"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] - public async Task LeaveInsider10RPCmd(CommandContext ctx) + public async Task LeaveInsider10RPCmd(TextCommandContext ctx) { await RemoveUserRoleAsync(ctx, Program.cfgjson.UserRoles.InsiderRP); } @@ -258,9 +275,10 @@ public async Task LeaveInsider10RPCmd(CommandContext ctx) [ Command("leave-patch-tuesday"), Description("Removes the 💻 Patch Tuesday role"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] - public async Task LeavePatchTuesday(CommandContext ctx) + public async Task LeavePatchTuesday(TextCommandContext ctx) { await RemoveUserRoleAsync(ctx, Program.cfgjson.UserRoles.PatchTuesday); } diff --git a/Commands/Utility.cs b/Commands/Utility.cs index fff1edc6..1a557a1d 100644 --- a/Commands/Utility.cs +++ b/Commands/Utility.cs @@ -1,10 +1,11 @@ namespace Cliptok.Commands { - internal class Utility : BaseCommandModule + internal class Utility { [Command("ping")] [Description("Pong? This command lets you know whether I'm working well.")] - public async Task Ping(CommandContext ctx) + [AllowedProcessors(typeof(TextCommandProcessor))] + public async Task Ping(TextCommandContext ctx) { ctx.Client.Logger.LogDebug(ctx.Client.GetConnectionLatency(Program.cfgjson.ServerID).ToString()); DiscordMessage return_message = await ctx.Message.RespondAsync("Pinging..."); @@ -18,9 +19,10 @@ await return_message.ModifyAsync($"P{letter}ng! 🏓\n" + [Command("edit")] [Description("Edit a message.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequireHomeserverPerm(ServerPermLevel.Moderator)] public async Task Edit( - CommandContext ctx, + TextCommandContext ctx, [Description("The ID of the message to edit.")] ulong messageId, [RemainingText, Description("New message content.")] string content ) @@ -37,9 +39,10 @@ public async Task Edit( [Command("editappend")] [Description("Append content to an existing bot message with a newline.")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequireHomeserverPerm(ServerPermLevel.Moderator)] public async Task EditAppend( - CommandContext ctx, + TextCommandContext ctx, [Description("The ID of the message to edit")] ulong messageId, [RemainingText, Description("Content to append on the end of the message.")] string content ) @@ -63,9 +66,10 @@ public async Task EditAppend( [Command("userinfo")] [Description("Show info about a user.")] - [Aliases("user-info", "whois")] + [TextAlias("userinfo", "user-info", "whois")] + [AllowedProcessors(typeof(TextCommandProcessor))] public async Task UserInfoCommand( - CommandContext ctx, + TextCommandContext ctx, DiscordUser user = null) { if (user is null) diff --git a/Commands/Warnings.cs b/Commands/Warnings.cs index 196dc199..01958be0 100644 --- a/Commands/Warnings.cs +++ b/Commands/Warnings.cs @@ -3,16 +3,17 @@ namespace Cliptok.Commands { - public class Warnings : BaseCommandModule + public class Warnings { [ - Command("warn"), + Command("6158e255-e8b3-4467-8d1a-79f89829"), Description("Issues a formal warning to a user."), - Aliases("wam", "warm"), + TextAlias("warn", "wam", "warm"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator) ] public async Task WarnCmd( - CommandContext ctx, + TextCommandContext ctx, [Description("The user you are warning. Accepts many formats.")] DiscordUser targetUser, [RemainingText, Description("The reason for giving this warning.")] string reason = null ) @@ -54,11 +55,12 @@ public async Task WarnCmd( [ Command("anonwarn"), Description("Issues a formal warning to a user from a private channel."), - Aliases("anonwam", "anonwarm"), + TextAlias("anonwam", "anonwarm"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator) ] public async Task AnonWarnCmd( - CommandContext ctx, + TextCommandContext ctx, [Description("The channel you wish for the warning message to appear in.")] DiscordChannel targetChannel, [Description("The user you are warning. Accepts many formats.")] DiscordUser targetUser, [RemainingText, Description("The reason for giving this warning.")] string reason = null @@ -91,13 +93,14 @@ public async Task AnonWarnCmd( } [ - Command("warnings"), + Command("6158e255-e8b3-4467-8d1a-79f89810"), Description("Shows a list of warnings that a user has been given. For more in-depth information, use the 'warnlookup' command."), - Aliases("infractions", "warnfractions", "wammings", "wamfractions"), + TextAlias("warnings", "infractions", "warnfractions", "wammings", "wamfractions"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] public async Task WarningCmd( - CommandContext ctx, + TextCommandContext ctx, [Description("The user you want to look up warnings for. Accepts many formats.")] DiscordUser targetUser = null ) { @@ -108,13 +111,14 @@ public async Task WarningCmd( } [ - Command("delwarn"), + Command("6158e255-e8b3-4467-8d1a-79f89811"), Description("Delete a warning that was issued by mistake or later became invalid."), - Aliases("delwarm", "delwam", "deletewarn", "delwarning", "deletewarning"), + TextAlias("delwarn", "delwarm", "delwam", "deletewarn", "delwarning", "deletewarning"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator) ] public async Task DelwarnCmd( - CommandContext ctx, + TextCommandContext ctx, [Description("The user you're removing a warning from. Accepts many formats.")] DiscordUser targetUser, [Description("The ID of the warning you want to delete.")] long warnId ) @@ -155,11 +159,12 @@ await LogChannelHelper.LogMessageAsync("mod", [ Command("warnlookup"), Description("Looks up information about a warning. Shows only publicly available information."), - Aliases("warning", "warming", "waming", "wamming", "lookup", "lookylooky", "peek", "investigate", "what-did-i-do-wrong-there", "incident"), + TextAlias("warning", "warming", "waming", "wamming", "lookup", "lookylooky", "peek", "investigate", "what-did-i-do-wrong-there", "incident"), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer ] public async Task WarnlookupCmd( - CommandContext ctx, + TextCommandContext ctx, [Description("The user you're looking at a warning for. Accepts many formats.")] DiscordUser targetUser, [Description("The ID of the warning you want to see")] long warnId ) @@ -172,14 +177,15 @@ public async Task WarnlookupCmd( } [ - Command("warndetails"), - Aliases("warninfo", "waminfo", "wamdetails", "warndetail", "wamdetail"), + Command("6158e255-e8b3-4467-8d1a-79f89822"), + TextAlias("warndetails", "warninfo", "waminfo", "wamdetails", "warndetail", "wamdetail"), Description("Check the details of a warning in depth. Shows extra information (Such as responsible Mod) that may not be wanted to be public."), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator) ] public async Task WarnDetailsCmd( - CommandContext ctx, + TextCommandContext ctx, [Description("The user you're looking up detailed warn information for. Accepts many formats.")] DiscordUser targetUser, [Description("The ID of the warning you're looking at in detail.")] long warnId ) @@ -198,15 +204,16 @@ public async Task WarnDetailsCmd( } [ - Command("editwarn"), - Aliases("warnedit", "editwarning"), + Command("6158e255-e8b3-4467-8d1a-79f89812"), + TextAlias("editwarn", "warnedit", "editwarning"), Description("Edit the reason of an existing warning.\n" + "The Moderator who is editing the reason will become responsible for the case."), + AllowedProcessors(typeof(TextCommandProcessor)), HomeServer, RequireHomeserverPerm(ServerPermLevel.TrialModerator) ] public async Task EditwarnCmd( - CommandContext ctx, + TextCommandContext ctx, [Description("The user you're editing a warning for. Accepts many formats.")] DiscordUser targetUser, [Description("The ID of the warning you want to edit.")] long warnId, [RemainingText, Description("The new reason for the warning.")] string newReason) @@ -217,7 +224,8 @@ public async Task EditwarnCmd( return; } - var msg = await ctx.RespondAsync("Processing your request..."); + await ctx.RespondAsync("Processing your request..."); + var msg = await ctx.GetResponseAsync(); var warning = GetWarning(targetUser.Id, warnId); if (warning is null) await msg.ModifyAsync($"{Program.cfgjson.Emoji.Error} I couldn't find a warning for that user with that ID! Please check again."); @@ -245,8 +253,9 @@ await LogChannelHelper.LogMessageAsync("mod", } [Command("mostwarnings"), Description("Who has the most warnings???")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task MostWarningsCmd(CommandContext ctx) + public async Task MostWarningsCmd(TextCommandContext ctx) { await DiscordHelpers.SafeTyping(ctx.Channel); @@ -276,8 +285,9 @@ public async Task MostWarningsCmd(CommandContext ctx) } [Command("mostwarningsday"), Description("Which day has the most warnings???")] + [AllowedProcessors(typeof(TextCommandProcessor))] [RequireHomeserverPerm(ServerPermLevel.TrialModerator)] - public async Task MostWarningsDayCmd(CommandContext ctx) + public async Task MostWarningsDayCmd(TextCommandContext ctx) { await DiscordHelpers.SafeTyping(ctx.Channel); diff --git a/Events/ErrorEvents.cs b/Events/ErrorEvents.cs index 7f8bb503..c84360a9 100644 --- a/Events/ErrorEvents.cs +++ b/Events/ErrorEvents.cs @@ -4,16 +4,40 @@ namespace Cliptok.Events { public class ErrorEvents { - public static async Task CommandsNextService_CommandErrored(CommandsNextExtension _, CommandErrorEventArgs e) + public static async Task CommandErrored(CommandsExtension _, CommandErroredEventArgs e) { - if (e.Exception is CommandNotFoundException && (e.Command is null || e.Command.QualifiedName != "help")) + // Because we no longer have DSharpPlus.CommandsNext or DSharpPlus.SlashCommands (only DSharpPlus.Commands), we can't point to different + // error handlers based on command type in our command handler configuration. Instead, we can start here, and jump to the correct + // handler based on the command type. TODO(#202): hopefully. + + // This is a lazy approach that just takes error type and points to the error handlers we already had. + // Maybe it can be improved later? + + if (e.Context is TextCommandContext) + { + // Text command error + await TextCommandErrored(e); + } + else if (e.Context is SlashCommandContext) + { + // Interaction command error (slash, user ctx, message ctx) + } + else + { + // Maybe left as CommandContext... TODO(#202): how to handle? + } + } + + public static async Task TextCommandErrored(CommandErroredEventArgs e) + { + if (e.Exception is CommandNotFoundException && (e.Context.Command is null || e.Context.Command.FullName != "help")) return; // avoid conflicts with modmail - if (e.Command.QualifiedName == "edit" || e.Command.QualifiedName == "timestamp") + if (e.Context.Command.FullName == "edit" || e.Context.Command.FullName == "timestamp") return; - e.Context.Client.Logger.LogError(CliptokEventID, e.Exception, "Exception occurred during {user}s invocation of {command}", e.Context.User.Username, e.Context.Command.QualifiedName); + e.Context.Client.Logger.LogError(CliptokEventID, e.Exception, "Exception occurred during {user}s invocation of {command}", e.Context.User.Username, e.Context.Command.FullName); var exs = new List(); if (e.Exception is AggregateException ae) @@ -23,17 +47,17 @@ public static async Task CommandsNextService_CommandErrored(CommandsNextExtensio foreach (var ex in exs) { - if (ex is CommandNotFoundException && (e.Command is null || e.Command.QualifiedName != "help")) + if (ex is CommandNotFoundException && (e.Context.Command is null || e.Context.Command.FullName != "help")) return; - if (ex is ChecksFailedException && (e.Command.Name != "help")) + if (ex is ChecksFailedException && (e.Context.Command.Name != "help")) return; var embed = new DiscordEmbedBuilder { Color = new DiscordColor("#FF0000"), Title = "An exception occurred when executing a command", - Description = $"{cfgjson.Emoji.BSOD} `{e.Exception.GetType()}` occurred when executing `{e.Command.QualifiedName}`.", + Description = $"{cfgjson.Emoji.BSOD} `{e.Exception.GetType()}` occurred when executing `{e.Context.Command.FullName}`.", Timestamp = DateTime.UtcNow }; embed.WithFooter(discord.CurrentUser.Username, discord.CurrentUser.AvatarUrl) diff --git a/Events/InteractionEvents.cs b/Events/InteractionEvents.cs index ec478095..92159032 100644 --- a/Events/InteractionEvents.cs +++ b/Events/InteractionEvents.cs @@ -207,54 +207,51 @@ await LogChannelHelper.LogDeletedMessagesAsync( } - public static async Task SlashCommandErrorEvent(SlashCommandsExtension _, DSharpPlus.SlashCommands.EventArgs.SlashCommandErrorEventArgs e) + public static async Task SlashCommandErrored(CommandErroredEventArgs e) { - if (e.Exception is SlashExecutionChecksFailedException slex) + if (e.Exception is ChecksFailedException slex) { - foreach (var check in slex.FailedChecks) - if (check is SlashRequireHomeserverPermAttribute att && e.Context.CommandName != "edit") + foreach (var check in slex.Errors) // TODO(#202): test this!!! + if (check.ContextCheckAttribute is SlashRequireHomeserverPermAttribute att && e.Context.Command.Name != "edit") { var level = (await GetPermLevelAsync(e.Context.Member)); var levelText = level.ToString(); if (level == ServerPermLevel.Nothing && rand.Next(1, 100) == 69) levelText = $"naught but a thing, my dear human. Congratulations, you win {rand.Next(1, 10)} bonus points."; - await e.Context.CreateResponseAsync( - DiscordInteractionResponseType.ChannelMessageWithSource, - new DiscordInteractionResponseBuilder().WithContent( - $"{cfgjson.Emoji.NoPermissions} Invalid permission level to use command **{e.Context.CommandName}**!\n" + + await e.Context.RespondAsync(new DiscordInteractionResponseBuilder().WithContent( + $"{cfgjson.Emoji.NoPermissions} Invalid permission level to use command **{e.Context.Command.Name}**!\n" + $"Required: `{att.TargetLvl}`\n" + $"You have: `{levelText}`") .AsEphemeral(true) ); } } - e.Context.Client.Logger.LogError(CliptokEventID, e.Exception, "Error during invocation of interaction command {command} by {user}", e.Context.CommandName, $"{DiscordHelpers.UniqueUsername(e.Context.User)}"); + e.Context.Client.Logger.LogError(CliptokEventID, e.Exception, "Error during invocation of interaction command {command} by {user}", e.Context.Command.Name, $"{DiscordHelpers.UniqueUsername(e.Context.User)}"); } - public static async Task ContextCommandErrorEvent(SlashCommandsExtension _, DSharpPlus.SlashCommands.EventArgs.ContextMenuErrorEventArgs e) + public static async Task ContextCommandErrored(CommandErroredEventArgs e) { - if (e.Exception is SlashExecutionChecksFailedException slex) + if (e.Exception is ChecksFailedException slex) { - foreach (var check in slex.FailedChecks) - if (check is SlashRequireHomeserverPermAttribute att && e.Context.CommandName != "edit") + foreach (var check in slex.Errors) // TODO(#202): test this!!! + if (check.ContextCheckAttribute is SlashRequireHomeserverPermAttribute att && e.Context.Command.Name != "edit") { var level = (await GetPermLevelAsync(e.Context.Member)); var levelText = level.ToString(); if (level == ServerPermLevel.Nothing && rand.Next(1, 100) == 69) levelText = $"naught but a thing, my dear human. Congratulations, you win {rand.Next(1, 10)} bonus points."; - await e.Context.CreateResponseAsync( - DiscordInteractionResponseType.ChannelMessageWithSource, + await e.Context.RespondAsync( new DiscordInteractionResponseBuilder().WithContent( - $"{cfgjson.Emoji.NoPermissions} Invalid permission level to use command **{e.Context.CommandName}**!\n" + + $"{cfgjson.Emoji.NoPermissions} Invalid permission level to use command **{e.Context.Command.Name}**!\n" + $"Required: `{att.TargetLvl}`\n" + $"You have: `{levelText}`") .AsEphemeral(true) ); } } - e.Context.Client.Logger.LogError(CliptokEventID, e.Exception, "Error during invocation of context command {command} by {user}", e.Context.CommandName, $"{DiscordHelpers.UniqueUsername(e.Context.User)}"); + e.Context.Client.Logger.LogError(CliptokEventID, e.Exception, "Error during invocation of context command {command} by {user}", e.Context.Command.Name, $"{DiscordHelpers.UniqueUsername(e.Context.User)}"); } } diff --git a/GlobalUsings.cs b/GlobalUsings.cs index 3b26ef20..e936a843 100644 --- a/GlobalUsings.cs +++ b/GlobalUsings.cs @@ -3,12 +3,19 @@ global using Cliptok.Events; global using Cliptok.Helpers; global using DSharpPlus; -global using DSharpPlus.CommandsNext; -global using DSharpPlus.CommandsNext.Attributes; -global using DSharpPlus.CommandsNext.Exceptions; +global using DSharpPlus.Commands; +global using DSharpPlus.Commands.ArgumentModifiers; +global using DSharpPlus.Commands.ContextChecks; +global using DSharpPlus.Commands.EventArgs; +global using DSharpPlus.Commands.Exceptions; +global using DSharpPlus.Commands.Processors.SlashCommands; +global using DSharpPlus.Commands.Processors.SlashCommands.ArgumentModifiers; +global using DSharpPlus.Commands.Processors.TextCommands; +global using DSharpPlus.Commands.Processors.UserCommands; +global using DSharpPlus.Commands.Trees; +global using DSharpPlus.Commands.Trees.Metadata; global using DSharpPlus.Entities; global using DSharpPlus.EventArgs; -global using DSharpPlus.SlashCommands; global using Microsoft.Extensions.Logging; global using Newtonsoft.Json; global using Newtonsoft.Json.Linq; @@ -17,6 +24,7 @@ global using StackExchange.Redis; global using System; global using System.Collections.Generic; +global using System.ComponentModel; global using System.Diagnostics; global using System.IO; global using System.Linq; diff --git a/Helpers/InteractionHelpers.cs b/Helpers/InteractionHelpers.cs index e69c112a..70b42ad6 100644 --- a/Helpers/InteractionHelpers.cs +++ b/Helpers/InteractionHelpers.cs @@ -2,12 +2,12 @@ { public static class BaseContextExtensions { - public static async Task PrepareResponseAsync(this BaseContext ctx) + public static async Task PrepareResponseAsync(this CommandContext ctx) { - await ctx.CreateResponseAsync(DiscordInteractionResponseType.DeferredChannelMessageWithSource); + await ctx.DeferResponseAsync(); } - public static async Task RespondAsync(this BaseContext ctx, string text = null, DiscordEmbed embed = null, bool ephemeral = false, bool mentions = true, params DiscordComponent[] components) + public static async Task RespondAsync(this CommandContext ctx, string text = null, DiscordEmbed embed = null, bool ephemeral = false, bool mentions = true, params DiscordComponent[] components) { DiscordInteractionResponseBuilder response = new(); @@ -18,10 +18,10 @@ public static async Task RespondAsync(this BaseContext ctx, string text = null, response.AsEphemeral(ephemeral); response.AddMentions(mentions ? Mentions.All : Mentions.None); - await ctx.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, response); + await ctx.RespondAsync(response); } - public static async Task EditAsync(this BaseContext ctx, string text = null, DiscordEmbed embed = null, params DiscordComponent[] components) + public static async Task EditAsync(this CommandContext ctx, string text = null, DiscordEmbed embed = null, params DiscordComponent[] components) { DiscordWebhookBuilder response = new(); @@ -32,7 +32,7 @@ public static async Task EditAsync(this BaseContext ctx, string text = null, Dis await ctx.EditResponseAsync(response); } - public static async Task FollowAsync(this BaseContext ctx, string text = null, DiscordEmbed embed = null, bool ephemeral = false, params DiscordComponent[] components) + public static async Task FollowAsync(this CommandContext ctx, string text = null, DiscordEmbed embed = null, bool ephemeral = false, params DiscordComponent[] components) { DiscordFollowupMessageBuilder response = new(); @@ -44,7 +44,7 @@ public static async Task FollowAsync(this BaseContext ctx, string text = null, D response.AsEphemeral(ephemeral); - await ctx.FollowUpAsync(response); + await ctx.FollowupAsync(response); } } } diff --git a/Program.cs b/Program.cs index 340b9877..ed1cd7c1 100644 --- a/Program.cs +++ b/Program.cs @@ -2,6 +2,7 @@ using DSharpPlus.Net.Gateway; using Serilog.Sinks.Grafana.Loki; using System.Reflection; +using DSharpPlus.Commands.EventArgs; namespace Cliptok { @@ -36,10 +37,9 @@ public async Task SessionInvalidatedAsync(IGatewayClient _) { } } - class Program : BaseCommandModule + class Program { public static DiscordClient discord; - static CommandsNextExtension commands; public static Random rnd = new(); public static ConfigJson cfgjson; public static ConnectionMultiplexer redis; @@ -179,6 +179,20 @@ static async Task Main(string[] _) discordBuilder.ConfigureServices(services => { services.Replace(); + services.AddCommandsExtension(builder => + { + builder.CommandErrored += ErrorEvents.CommandErrored; + + // Interaction commands + var slashCommandClasses = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsClass && t.Namespace == "Cliptok.Commands.InteractionCommands" && !t.IsNested); + foreach (var type in slashCommandClasses) + builder.AddCommands(type, cfgjson.ServerID); + + // Text commands TODO(#202): [Error] Failed to build command '"editwarn"' System.ArgumentException: An item with the same key has already been added. Key: editwarn + var commandClasses = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsClass && t.Namespace == "Cliptok.Commands" && !t.IsNested); + foreach (var type in commandClasses) + builder.AddCommands(type); + }); }); discordBuilder.ConfigureExtraFeatures(clientConfig => @@ -211,30 +225,6 @@ static async Task Main(string[] _) .HandleAutoModerationRuleExecuted(AutoModEvents.AutoModerationRuleExecuted) ); -#pragma warning disable CS0618 // Type or member is obsolete - discordBuilder.UseSlashCommands(slash => - { - slash.SlashCommandErrored += InteractionEvents.SlashCommandErrorEvent; - slash.ContextMenuErrored += InteractionEvents.ContextCommandErrorEvent; - - var slashCommandClasses = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsClass && t.Namespace == "Cliptok.Commands.InteractionCommands" && !t.IsNested); - foreach (var type in slashCommandClasses) - slash.RegisterCommands(type, cfgjson.ServerID); ; - }); -#pragma warning restore CS0618 // Type or member is obsolete - - discordBuilder.UseCommandsNext(commands => - { - var commandClasses = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsClass && t.Namespace == "Cliptok.Commands" && !t.IsNested); - foreach (var type in commandClasses) - commands.RegisterCommands(type); - - commands.CommandErrored += ErrorEvents.CommandsNextService_CommandErrored; - }, new CommandsNextConfiguration - { - StringPrefixes = cfgjson.Core.Prefixes - }); - // TODO(erisa): At some point we might be forced to ConnectAsync() the builder directly // and then we will need to rework some other pieces that rely on Program.discord discord = discordBuilder.Build();