From 4223fa8ff1c1beeeaa0666f4ebe91e8c7754a999 Mon Sep 17 00:00:00 2001 From: Cosma George Date: Sun, 14 Feb 2021 16:20:49 +0200 Subject: [PATCH] Implemented basic parameter recognition and passing them to the method. Details: Subcommands and Subcommand groups not yet implemented, they will require for some parts of the code to be re-done. More attributes can and should be implemented, such as [Required] and [Choice(... , ...)]. Breakdown: * Rectified line endings to LF, as per the settings of the project. * Added a new command to SlashCommandService and SlashCommandServiceHelper to register the found commands to discord. * Implemented CommandRegistrationOptions that can be used to configure the behaviour on registration - what to do with old commands, and with commands that already exist with the same name. A default version exists and can be accessed with CommandRegistrationOptions.Default * Modified the sample program to reflect the changes made to the SlashCommandService and to also register a new command that tests all 6 types of CommandOptions (except subcommand and subcommand group) * At the moment all commands are registered in my test guild, because the update for global commands is not instant. See SlashCommandServiceHelper.RegisterCommands(...) or line 221. * Modified SlashCommandInfo to parse arguments given from Interaction, unde in ExecuteAsync, and added method BuilDcommand that returns SlashCommandCreationProperties - which can be registered to Discord. * Renamed in the sample project PingCommand.cs to DevModule.cs * Added custom attribute Description for the command method's parameters. * Implemented SlashParameterInfo - and extension of the OptionBuilder that implements a method name Parse - takes DataOptions and gives out a cast object to be passed to the command Delegate. Planning on doing more with it. * Moved SlashCommandBuilder.cs to the same directory structure * Moved SlashCommandModule.cs and ISlashCommandModule.cs to its own folder. --- SlashCommandsExample/DiscordClient.cs | 1 + SlashCommandsExample/Modules/DevModule.cs | 53 ++++++++ SlashCommandsExample/Modules/PingCommand.cs | 33 ----- .../SlashCommands/Attributes/Description.cs | 31 +++++ .../Builders/SlashCommandBuilder.cs | 0 .../CommandRegistrationOptions.cs | 39 ++++++ .../ExistingCommandOptions.cs | 14 ++ .../CommandRegistration/OldCommandOptions.cs | 24 ++++ .../SlashCommands/Info/SlashCommandInfo.cs | 82 +++++++++++- .../SlashCommands/Info/SlashParameterInfo.cs | 37 ++++++ .../SlashCommands/SlashCommandService.cs | 39 ++++-- .../SlashCommandServiceHelper.cs | 124 +++++++++++++++++- .../ISlashCommandModule.cs | 0 .../{ => CommandModule}/SlashCommandModule.cs | 13 ++ .../SocketInteractionDataOption.cs | 13 +- 15 files changed, 444 insertions(+), 59 deletions(-) create mode 100644 SlashCommandsExample/Modules/DevModule.cs delete mode 100644 SlashCommandsExample/Modules/PingCommand.cs create mode 100644 src/Discord.Net.Commands/SlashCommands/Attributes/Description.cs rename src/Discord.Net.Commands/{ => SlashCommands}/Builders/SlashCommandBuilder.cs (100%) create mode 100644 src/Discord.Net.Commands/SlashCommands/CommandRegistration/CommandRegistrationOptions.cs create mode 100644 src/Discord.Net.Commands/SlashCommands/CommandRegistration/ExistingCommandOptions.cs create mode 100644 src/Discord.Net.Commands/SlashCommands/CommandRegistration/OldCommandOptions.cs create mode 100644 src/Discord.Net.Commands/SlashCommands/Info/SlashParameterInfo.cs rename src/Discord.Net.Commands/SlashCommands/Types/{ => CommandModule}/ISlashCommandModule.cs (100%) rename src/Discord.Net.Commands/SlashCommands/Types/{ => CommandModule}/SlashCommandModule.cs (58%) diff --git a/SlashCommandsExample/DiscordClient.cs b/SlashCommandsExample/DiscordClient.cs index 7137da37d..408c3870b 100644 --- a/SlashCommandsExample/DiscordClient.cs +++ b/SlashCommandsExample/DiscordClient.cs @@ -51,6 +51,7 @@ namespace SlashCommandsExample await socketClient.StartAsync(); await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + await _commands.RegisterCommandsAsync(socketClient, CommandRegistrationOptions.Default); await Task.Delay(-1); } diff --git a/SlashCommandsExample/Modules/DevModule.cs b/SlashCommandsExample/Modules/DevModule.cs new file mode 100644 index 000000000..37ef108ec --- /dev/null +++ b/SlashCommandsExample/Modules/DevModule.cs @@ -0,0 +1,53 @@ +using Discord.Commands; +using Discord.Commands.SlashCommands.Types; +using Discord.SlashCommands; +using Discord.WebSocket; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace SlashCommandsExample.Modules +{ + public class DevModule : SlashCommandModule + { + [SlashCommand("ping", "Ping the bot to see if it's alive!")] + public async Task PingAsync() + { + await Reply(":white_check_mark: **Bot Online**"); + } + + [SlashCommand("echo", "I'll repeate everything you said to me, word for word.")] + public async Task EchoAsync([Description("The message you want repetead")]string message) + { + await Reply($"{Interaction.Member?.Nickname ?? Interaction.Member?.Username} told me to say this: \r\n{message}"); + } + + [SlashCommand("overload","Just hit me with every type of data you got, man!")] + public async Task OverloadAsync( + bool boolean, + int integer, + string myString, + SocketGuildChannel channel, + SocketGuildUser user, + SocketRole role + ) + { + await Reply($"You gave me:\r\n {boolean}, {integer}, {myString}, <#{channel?.Id}>, {user?.Mention}, {role?.Mention}"); + + } + } +} +/* +The base way of defining a command using the regular command service: + +public class PingModule : ModuleBase +{ + [Command("ping")] + [Summary("Pong! Check if the bot is alive.")] + public async Task PingAsync() + { + await ReplyAsync(":white_check_mark: **Bot Online**"); + } +} +*/ diff --git a/SlashCommandsExample/Modules/PingCommand.cs b/SlashCommandsExample/Modules/PingCommand.cs deleted file mode 100644 index 7df135afd..000000000 --- a/SlashCommandsExample/Modules/PingCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Discord.Commands; -using Discord.Commands.SlashCommands.Types; -using Discord.SlashCommands; -using Discord.WebSocket; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace SlashCommandsExample.Modules -{ - public class PingCommand : SlashCommandModule - { - [SlashCommand("johnny-test", "Ping the bot to see if it is alive!")] - public async Task PingAsync() - { - await Interaction.FollowupAsync(":white_check_mark: **Bot Online**"); - } - } -} -/* -The base way of defining a command using the regular command service: - -public class PingModule : ModuleBase -{ - [Command("ping")] - [Summary("Pong! Check if the bot is alive.")] - public async Task PingAsync() - { - await ReplyAsync(":white_check_mark: **Bot Online**"); - } -} -*/ diff --git a/src/Discord.Net.Commands/SlashCommands/Attributes/Description.cs b/src/Discord.Net.Commands/SlashCommands/Attributes/Description.cs new file mode 100644 index 000000000..e6fffc1cd --- /dev/null +++ b/src/Discord.Net.Commands/SlashCommands/Attributes/Description.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.SlashCommands +{ + /// + /// An Attribute that gives the command parameter a description. + /// + [AttributeUsage(AttributeTargets.Parameter , AllowMultiple = false)] + public class Description : Attribute + { + public static string DefaultDescription = "No description."; + /// + /// The description of this slash command parameter. + /// + public string description; + + /// + /// Tells the that this parameter has a description. + /// + /// The name of this slash command. + public Description(string description) + { + this.description = description; + } + } + +} diff --git a/src/Discord.Net.Commands/Builders/SlashCommandBuilder.cs b/src/Discord.Net.Commands/SlashCommands/Builders/SlashCommandBuilder.cs similarity index 100% rename from src/Discord.Net.Commands/Builders/SlashCommandBuilder.cs rename to src/Discord.Net.Commands/SlashCommands/Builders/SlashCommandBuilder.cs diff --git a/src/Discord.Net.Commands/SlashCommands/CommandRegistration/CommandRegistrationOptions.cs b/src/Discord.Net.Commands/SlashCommands/CommandRegistration/CommandRegistrationOptions.cs new file mode 100644 index 000000000..a5f61237c --- /dev/null +++ b/src/Discord.Net.Commands/SlashCommands/CommandRegistration/CommandRegistrationOptions.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.SlashCommands +{ + /// + /// The options that should be kept in mind when registering the slash commands to discord. + /// + public class CommandRegistrationOptions + { + /// + /// The options that should be kept in mind when registering the slash commands to discord. + /// + /// What to do with the old commands that are already registered with discord + /// What to do with the old commands (if they weren't wiped) that we re-define. + public CommandRegistrationOptions(OldCommandOptions oldCommands, ExistingCommandOptions existingCommands) + { + OldCommands = oldCommands; + ExistingCommands = existingCommands; + } + /// + /// What to do with the old commands that are already registered with discord + /// + public OldCommandOptions OldCommands { get; set; } + /// + /// What to do with the old commands (if they weren't wiped) that we re-define. + /// + public ExistingCommandOptions ExistingCommands { get; set; } + + /// + /// The default, and reccomended options - Keep the old commands, and overwrite existing commands we re-defined. + /// + public static CommandRegistrationOptions Default => + new CommandRegistrationOptions(OldCommandOptions.KEEP, ExistingCommandOptions.OVERWRITE); + } +} diff --git a/src/Discord.Net.Commands/SlashCommands/CommandRegistration/ExistingCommandOptions.cs b/src/Discord.Net.Commands/SlashCommands/CommandRegistration/ExistingCommandOptions.cs new file mode 100644 index 000000000..fd6b6c522 --- /dev/null +++ b/src/Discord.Net.Commands/SlashCommands/CommandRegistration/ExistingCommandOptions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.SlashCommands +{ + public enum ExistingCommandOptions + { + OVERWRITE, + KEEP_EXISTING + } +} diff --git a/src/Discord.Net.Commands/SlashCommands/CommandRegistration/OldCommandOptions.cs b/src/Discord.Net.Commands/SlashCommands/CommandRegistration/OldCommandOptions.cs new file mode 100644 index 000000000..603e0a288 --- /dev/null +++ b/src/Discord.Net.Commands/SlashCommands/CommandRegistration/OldCommandOptions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.SlashCommands +{ + public enum OldCommandOptions + { + /// + /// Keep the old commands intact - do nothing to them. + /// + KEEP, + /// + /// Delete the old commands that we won't be re-defined this time around. + /// + DELETE_UNUSED, + /// + /// Delete everything discord has. + /// + WIPE + } +} diff --git a/src/Discord.Net.Commands/SlashCommands/Info/SlashCommandInfo.cs b/src/Discord.Net.Commands/SlashCommands/Info/SlashCommandInfo.cs index 710d85d41..eee54d32b 100644 --- a/src/Discord.Net.Commands/SlashCommands/Info/SlashCommandInfo.cs +++ b/src/Discord.Net.Commands/SlashCommands/Info/SlashCommandInfo.cs @@ -1,4 +1,6 @@ using Discord.Commands; +using Discord.Commands.Builders; +using Discord.WebSocket; using System; using System.Collections.Generic; using System.Linq; @@ -21,6 +23,10 @@ namespace Discord.SlashCommands /// Gets the name of the command. /// public string Description { get; } + /// + /// The parameters we are expecting - an extension of SlashCommandOptionBuilder + /// + public List Parameters { get; } /// /// The user method as a delegate. We need to use Delegate because there is an unknown number of parameters @@ -31,11 +37,12 @@ namespace Discord.SlashCommands /// public Func> callback; - public SlashCommandInfo(SlashModuleInfo module, string name, string description, Delegate userMethod) + public SlashCommandInfo(SlashModuleInfo module, string name, string description,List parameters , Delegate userMethod) { Module = module; Name = name; Description = description; + Parameters = parameters; this.userMethod = userMethod; this.callback = new Func>(async (args) => { @@ -56,9 +63,78 @@ namespace Discord.SlashCommands }); } - public async Task ExecuteAsync(object[] args) + /// + /// Execute the function based on the interaction data we get. + /// + /// Interaction data from interaction + public async Task ExecuteAsync(SocketInteractionData data) { - return await callback.Invoke(args).ConfigureAwait(false); + // List of arguments to be passed to the Delegate + List args = new List(); + try + { + foreach (var parameter in Parameters) + { + // For each parameter to try find its coresponding DataOption based on the name. + + // !!! names from `data` will always be lowercase regardless if we defined the command with any + // number of upercase letters !!! + if (TryGetInteractionDataOption(data, parameter.Name, out SocketInteractionDataOption dataOption)) + { + // Parse the dataOption to one corresponding argument type, and then add it to the list of arguments + args.Add(parameter.Parse(dataOption)); + } + else + { + // There was no input from the user on this field. + args.Add(null); + } + } + } + catch(Exception e) + { + return ExecuteResult.FromError(e); + } + + return await callback.Invoke(args.ToArray()).ConfigureAwait(false); + } + /// + /// Get the interaction data from the name of the parameter we want to fill in. + /// + private bool TryGetInteractionDataOption(SocketInteractionData data, string name, out SocketInteractionDataOption dataOption) + { + if (data.Options == null) + { + dataOption = null; + return false; + } + foreach (var option in data.Options) + { + if (option.Name == name.ToLower()) + { + dataOption = option; + return true; + } + } + dataOption = null; + return false; + } + + /// + /// Build the command and put it in a state in which we can use to define it to Discord. + /// + public SlashCommandCreationProperties BuildCommand() + { + SlashCommandBuilder builder = new SlashCommandBuilder(); + builder.WithName(Name); + builder.WithDescription(Description); + builder.Options = new List(); + foreach (var parameter in Parameters) + { + builder.AddOptions(parameter); + } + + return builder.Build(); } } } diff --git a/src/Discord.Net.Commands/SlashCommands/Info/SlashParameterInfo.cs b/src/Discord.Net.Commands/SlashCommands/Info/SlashParameterInfo.cs new file mode 100644 index 000000000..0b43ac058 --- /dev/null +++ b/src/Discord.Net.Commands/SlashCommands/Info/SlashParameterInfo.cs @@ -0,0 +1,37 @@ +using Discord.Commands.Builders; +using Discord.WebSocket; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.SlashCommands +{ + public class SlashParameterInfo : SlashCommandOptionBuilder + { + public object Parse(SocketInteractionDataOption dataOption) + { + switch (Type) + { + case ApplicationCommandOptionType.Boolean: + return (bool)dataOption; + case ApplicationCommandOptionType.Integer: + return (int)dataOption; + case ApplicationCommandOptionType.String: + return (string)dataOption; + case ApplicationCommandOptionType.Channel: + return (SocketGuildChannel)dataOption; + case ApplicationCommandOptionType.Role: + return (SocketRole)dataOption; + case ApplicationCommandOptionType.User: + return (SocketGuildUser)dataOption; + case ApplicationCommandOptionType.SubCommandGroup: + throw new NotImplementedException(); + case ApplicationCommandOptionType.SubCommand: + throw new NotImplementedException(); + } + throw new NotImplementedException($"There is no such type of data... unless we missed it. Please report this error on the Discord.Net github page! Type: {Type}"); + } + } +} diff --git a/src/Discord.Net.Commands/SlashCommands/SlashCommandService.cs b/src/Discord.Net.Commands/SlashCommands/SlashCommandService.cs index 2db136243..cddc2350e 100644 --- a/src/Discord.Net.Commands/SlashCommands/SlashCommandService.cs +++ b/src/Discord.Net.Commands/SlashCommands/SlashCommandService.cs @@ -34,11 +34,6 @@ namespace Discord.SlashCommands _logger = new Logger(_logManager, "SlshCommand"); } - public void AddAssembly() - { - - } - /// /// Execute a slash command. /// @@ -50,19 +45,39 @@ namespace Discord.SlashCommands SlashCommandInfo commandInfo; if (commandDefs.TryGetValue(interaction.Data.Name, out commandInfo)) { - // TODO: implement everything that has to do with parameters :) - // Then, set the context in which the command will be executed commandInfo.Module.userCommandModule.SetContext(interaction); - // Then run the command (with no parameters) - return await commandInfo.ExecuteAsync(new object[] { }).ConfigureAwait(false); + // Then run the command and pass the interaction data over to the CommandInfo class + return await commandInfo.ExecuteAsync(interaction.Data).ConfigureAwait(false); } else { return SearchResult.FromError(CommandError.UnknownCommand, $"There is no registered slash command with the name {interaction.Data.Name}"); } } - + + /// + /// Registers all previously scanned commands. + /// + public async Task RegisterCommandsAsync(DiscordSocketClient socketClient, CommandRegistrationOptions registrationOptions) + { + // First take a hold of the module lock, as to make sure we aren't editing stuff while we do our business + await _moduleLock.WaitAsync().ConfigureAwait(false); + + try + { + await SlashCommandServiceHelper.RegisterCommands(socketClient, commandDefs, this,registrationOptions).ConfigureAwait(false); + } + finally + { + _moduleLock.Release(); + } + await _logger.InfoAsync("All commands have been registered!").ConfigureAwait(false); + } + + /// + /// Scans the program for Attribute-based SlashCommandModules + /// public async Task AddModulesAsync(Assembly assembly, IServiceProvider services) { // First take a hold of the module lock, as to make sure we aren't editing stuff while we do our business @@ -75,9 +90,7 @@ namespace Discord.SlashCommands // Then, based on that, make an instance out of each of them, and get the resulting SlashModuleInfo s moduleDefs = await SlashCommandServiceHelper.InstantiateModules(types, this).ConfigureAwait(false); // After that, internally register all of the commands into SlashCommandInfo - commandDefs = await SlashCommandServiceHelper.PrepareAsync(types,moduleDefs,this).ConfigureAwait(false); - // TODO: And finally, register the commands with discord. - await SlashCommandServiceHelper.RegisterCommands(commandDefs, this, services).ConfigureAwait(false); + commandDefs = await SlashCommandServiceHelper.CreateCommandInfos(types,moduleDefs,this).ConfigureAwait(false); } finally { diff --git a/src/Discord.Net.Commands/SlashCommands/SlashCommandServiceHelper.cs b/src/Discord.Net.Commands/SlashCommands/SlashCommandServiceHelper.cs index d76c61a55..2d40185f3 100644 --- a/src/Discord.Net.Commands/SlashCommands/SlashCommandServiceHelper.cs +++ b/src/Discord.Net.Commands/SlashCommands/SlashCommandServiceHelper.cs @@ -1,3 +1,5 @@ +using Discord.Commands.Builders; +using Discord.WebSocket; using System; using System.Collections.Generic; using System.Linq; @@ -63,17 +65,19 @@ namespace Discord.SlashCommands /// /// Prepare all of the commands and register them internally. /// - public static async Task> PrepareAsync(IReadOnlyList types, Dictionary moduleDefs, SlashCommandService slashCommandService) + public static async Task> CreateCommandInfos(IReadOnlyList types, Dictionary moduleDefs, SlashCommandService slashCommandService) { + // Create the resulting dictionary ahead of time var result = new Dictionary(); - // fore each user-defined module + // For each user-defined module ... foreach (var userModule in types) { - // Get its associated information + // Get its associated information. If there isn't any it means something went wrong, but it's not a critical error. SlashModuleInfo moduleInfo; if (moduleDefs.TryGetValue(userModule, out moduleInfo)) { - // and get all of its method, and check if they are valid, and if so create a new SlashCommandInfo for them. + // TODO: handle sub-command groups + // And get all of its method, and check if they are valid, and if so create a new SlashCommandInfo for them. var commandMethods = userModule.GetMethods(); List commandInfos = new List(); foreach (var commandMethod in commandMethods) @@ -81,13 +85,19 @@ namespace Discord.SlashCommands SlashCommand slashCommand; if (IsValidSlashCommand(commandMethod, out slashCommand)) { + // Create the delegate for the method we want to call once the user interacts with the bot. + // We use a delegate because of the unknown number and type of parameters we will have. Delegate delegateMethod = CreateDelegate(commandMethod, moduleInfo.userCommandModule); + SlashCommandInfo commandInfo = new SlashCommandInfo( module: moduleInfo, name: slashCommand.commandName, description: slashCommand.description, + // Generate the parameters. Due to it's complicated way the algorithm has been moved to its own function. + parameters: ConstructCommandParameters(commandMethod), userMethod: delegateMethod ); + result.Add(slashCommand.commandName, commandInfo); commandInfos.Add(commandInfo); } @@ -97,6 +107,9 @@ namespace Discord.SlashCommands } return result; } + /// + /// Determines wheater a method can be clasified as a slash command + /// private static bool IsValidSlashCommand(MethodInfo method, out SlashCommand slashCommand) { // Verify that we only have one [SlashCommand(...)] attribute @@ -115,6 +128,66 @@ namespace Discord.SlashCommands slashCommand = slashCommandAttributes.First() as SlashCommand; return true; } + private static List ConstructCommandParameters(MethodInfo method) + { + // Prepare the final list of parameters + List finalParameters = new List(); + + // For each mehod parameter ... + // ex: ... MyCommand(string abc, int myInt) + // `abc` and `myInt` are parameters + foreach (var methodParameter in method.GetParameters()) + { + SlashParameterInfo newParameter = new SlashParameterInfo(); + + // Set the parameter name to that of the method + // TODO: Implement an annotation that lets the user choose a custom name + newParameter.Name = methodParameter.Name; + + // Get to see if it has a Description Attribute. + // If it has + // 0 -> then use the default description + // 1 -> Use the value from that attribute + // 2+ -> Throw an error. This shouldn't normaly happen, but we check for sake of sanity + var descriptions = methodParameter.GetCustomAttributes(typeof(Description)); + if (descriptions.Count() == 0) + newParameter.Description = Description.DefaultDescription; + else if (descriptions.Count() > 1) + throw new Exception($"Too many Description attributes on a single parameter ({method.Name} -> {methodParameter.Name}). It can only contain one!"); + else + newParameter.Description = (descriptions.First() as Description).description; + + // And get the parameter type + newParameter.Type = TypeFromMethodParameter(methodParameter); + + // TODO: implement more attributes, such as [Required] + + finalParameters.Add(newParameter); + } + return finalParameters; + } + /// + /// Get the type of command option from a method parameter info. + /// + private static ApplicationCommandOptionType TypeFromMethodParameter(ParameterInfo methodParameter) + { + // Can't do switch -- who knows why? + if (methodParameter.ParameterType == typeof(int)) + return ApplicationCommandOptionType.Integer; + if (methodParameter.ParameterType == typeof(string)) + return ApplicationCommandOptionType.String; + if (methodParameter.ParameterType == typeof(bool)) + return ApplicationCommandOptionType.Boolean; + if (methodParameter.ParameterType == typeof(SocketGuildChannel)) + return ApplicationCommandOptionType.Channel; + if (methodParameter.ParameterType == typeof(SocketRole)) + return ApplicationCommandOptionType.Role; + if (methodParameter.ParameterType == typeof(SocketGuildUser)) + return ApplicationCommandOptionType.User; + + throw new Exception($"Got parameter type other than int, string, bool, guild, role, or user. {methodParameter.Name}"); + } + /// /// Creae a delegate from methodInfo. Taken from /// https://stackoverflow.com/a/40579063/8455128 @@ -143,8 +216,49 @@ namespace Discord.SlashCommands return Delegate.CreateDelegate(getType(types.ToArray()), target, methodInfo.Name); } - public static async Task RegisterCommands(Dictionary commandDefs, SlashCommandService slashCommandService, IServiceProvider services) + public static async Task RegisterCommands(DiscordSocketClient socketClient, Dictionary commandDefs, SlashCommandService slashCommandService, CommandRegistrationOptions options) { + // Get existing commmands + ulong devGuild = 386658607338618891; + var existingCommands = await socketClient.Rest.GetGuildApplicationCommands(devGuild).ConfigureAwait(false); + List existingCommandNames = new List(); + foreach (var existingCommand in existingCommands) + { + existingCommandNames.Add(existingCommand.Name); + } + + // Delete old ones that we want to re-implement + if (options.OldCommands == OldCommandOptions.DELETE_UNUSED || + options.OldCommands == OldCommandOptions.WIPE) + { + foreach (var existingCommand in existingCommands) + { + // If we want to wipe all commands + // or if the existing command isn't re-defined (probably code deleted by user) + // remove it from discord. + if(options.OldCommands == OldCommandOptions.WIPE || + !commandDefs.ContainsKey(existingCommand.Name)) + { + await existingCommand.DeleteAsync(); + } + } + } + foreach (var entry in commandDefs) + { + if (existingCommandNames.Contains(entry.Value.Name) && + options.ExistingCommands == ExistingCommandOptions.KEEP_EXISTING) + { + continue; + } + // If it's a new command or we want to overwrite an old one... + else + { + SlashCommandInfo slashCommandInfo = entry.Value; + SlashCommandCreationProperties command = slashCommandInfo.BuildCommand(); + // TODO: Implement Global and Guild Commands. + await socketClient.Rest.CreateGuildCommand(command, devGuild).ConfigureAwait(false); + } + } return; } } diff --git a/src/Discord.Net.Commands/SlashCommands/Types/ISlashCommandModule.cs b/src/Discord.Net.Commands/SlashCommands/Types/CommandModule/ISlashCommandModule.cs similarity index 100% rename from src/Discord.Net.Commands/SlashCommands/Types/ISlashCommandModule.cs rename to src/Discord.Net.Commands/SlashCommands/Types/CommandModule/ISlashCommandModule.cs diff --git a/src/Discord.Net.Commands/SlashCommands/Types/SlashCommandModule.cs b/src/Discord.Net.Commands/SlashCommands/Types/CommandModule/SlashCommandModule.cs similarity index 58% rename from src/Discord.Net.Commands/SlashCommands/Types/SlashCommandModule.cs rename to src/Discord.Net.Commands/SlashCommands/Types/CommandModule/SlashCommandModule.cs index 2249ff08d..b0dd34a90 100644 --- a/src/Discord.Net.Commands/SlashCommands/Types/SlashCommandModule.cs +++ b/src/Discord.Net.Commands/SlashCommands/Types/CommandModule/SlashCommandModule.cs @@ -1,5 +1,7 @@ using Discord.Commands.SlashCommands.Types; +using Discord.WebSocket; using System; +using System.Threading.Tasks; namespace Discord.SlashCommands { @@ -17,5 +19,16 @@ namespace Discord.SlashCommands var newValue = interaction as T; Interaction = newValue ?? throw new InvalidOperationException($"Invalid interaction type. Expected {typeof(T).Name}, got {interaction.GetType().Name}."); } + + + public async Task Reply(string text = null, bool isTTS = false, Embed embed = null, InteractionResponseType Type = InteractionResponseType.ChannelMessageWithSource, + AllowedMentions allowedMentions = null, RequestOptions options = null) + { + if(Interaction is SocketInteraction) + { + return await (Interaction as SocketInteraction).FollowupAsync(text, isTTS, embed, Type, allowedMentions, options); + } + return null; + } } } diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs index 683998272..bd8324eb7 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs @@ -30,7 +30,7 @@ namespace Discord.WebSocket internal SocketInteractionDataOption() { } internal SocketInteractionDataOption(Model model, DiscordSocketClient discord, ulong guild) { - this.Name = Name; + this.Name = model.Name; this.Value = model.Value.IsSpecified ? model.Value.Value : null; this.discord = discord; this.guild = guild; @@ -44,14 +44,17 @@ namespace Discord.WebSocket // Converters public static explicit operator bool(SocketInteractionDataOption option) => (bool)option.Value; + // The default value is of type long, so an implementaiton of of the long option is trivial public static explicit operator int(SocketInteractionDataOption option) - => (int)option.Value; + => unchecked( + (int)( (long)option.Value ) + ); public static explicit operator string(SocketInteractionDataOption option) => option.Value.ToString(); public static explicit operator SocketGuildChannel(SocketInteractionDataOption option) { - if (option.Value is ulong id) + if (ulong.TryParse((string)option.Value, out ulong id)) { var guild = option.discord.GetGuild(option.guild); @@ -66,7 +69,7 @@ namespace Discord.WebSocket public static explicit operator SocketRole(SocketInteractionDataOption option) { - if (option.Value is ulong id) + if (ulong.TryParse((string)option.Value, out ulong id)) { var guild = option.discord.GetGuild(option.guild); @@ -81,7 +84,7 @@ namespace Discord.WebSocket public static explicit operator SocketGuildUser(SocketInteractionDataOption option) { - if(option.Value is ulong id) + if (ulong.TryParse((string)option.Value, out ulong id)) { var guild = option.discord.GetGuild(option.guild);