Browse Source

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.
pull/1733/head^2^2
Cosma George 4 years ago
parent
commit
4223fa8ff1
15 changed files with 444 additions and 59 deletions
  1. +1
    -0
      SlashCommandsExample/DiscordClient.cs
  2. +53
    -0
      SlashCommandsExample/Modules/DevModule.cs
  3. +0
    -33
      SlashCommandsExample/Modules/PingCommand.cs
  4. +31
    -0
      src/Discord.Net.Commands/SlashCommands/Attributes/Description.cs
  5. +0
    -0
      src/Discord.Net.Commands/SlashCommands/Builders/SlashCommandBuilder.cs
  6. +39
    -0
      src/Discord.Net.Commands/SlashCommands/CommandRegistration/CommandRegistrationOptions.cs
  7. +14
    -0
      src/Discord.Net.Commands/SlashCommands/CommandRegistration/ExistingCommandOptions.cs
  8. +24
    -0
      src/Discord.Net.Commands/SlashCommands/CommandRegistration/OldCommandOptions.cs
  9. +79
    -3
      src/Discord.Net.Commands/SlashCommands/Info/SlashCommandInfo.cs
  10. +37
    -0
      src/Discord.Net.Commands/SlashCommands/Info/SlashParameterInfo.cs
  11. +26
    -13
      src/Discord.Net.Commands/SlashCommands/SlashCommandService.cs
  12. +119
    -5
      src/Discord.Net.Commands/SlashCommands/SlashCommandServiceHelper.cs
  13. +0
    -0
      src/Discord.Net.Commands/SlashCommands/Types/CommandModule/ISlashCommandModule.cs
  14. +13
    -0
      src/Discord.Net.Commands/SlashCommands/Types/CommandModule/SlashCommandModule.cs
  15. +8
    -5
      src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs

+ 1
- 0
SlashCommandsExample/DiscordClient.cs View File

@@ -51,6 +51,7 @@ namespace SlashCommandsExample
await socketClient.StartAsync(); await socketClient.StartAsync();


await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
await _commands.RegisterCommandsAsync(socketClient, CommandRegistrationOptions.Default);


await Task.Delay(-1); await Task.Delay(-1);
} }


+ 53
- 0
SlashCommandsExample/Modules/DevModule.cs View File

@@ -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<SocketInteraction>
{
[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<SocketCommandContext>
{
[Command("ping")]
[Summary("Pong! Check if the bot is alive.")]
public async Task PingAsync()
{
await ReplyAsync(":white_check_mark: **Bot Online**");
}
}
*/

+ 0
- 33
SlashCommandsExample/Modules/PingCommand.cs View File

@@ -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<SocketInteraction>
{
[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<SocketCommandContext>
{
[Command("ping")]
[Summary("Pong! Check if the bot is alive.")]
public async Task PingAsync()
{
await ReplyAsync(":white_check_mark: **Bot Online**");
}
}
*/

+ 31
- 0
src/Discord.Net.Commands/SlashCommands/Attributes/Description.cs View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.SlashCommands
{
/// <summary>
/// An Attribute that gives the command parameter a description.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter , AllowMultiple = false)]
public class Description : Attribute
{
public static string DefaultDescription = "No description.";
/// <summary>
/// The description of this slash command parameter.
/// </summary>
public string description;

/// <summary>
/// Tells the <see cref="SlashCommandService"/> that this parameter has a description.
/// </summary>
/// <param name="commandName">The name of this slash command.</param>
public Description(string description)
{
this.description = description;
}
}

}

src/Discord.Net.Commands/Builders/SlashCommandBuilder.cs → src/Discord.Net.Commands/SlashCommands/Builders/SlashCommandBuilder.cs View File


+ 39
- 0
src/Discord.Net.Commands/SlashCommands/CommandRegistration/CommandRegistrationOptions.cs View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.SlashCommands
{
/// <summary>
/// The options that should be kept in mind when registering the slash commands to discord.
/// </summary>
public class CommandRegistrationOptions
{
/// <summary>
/// The options that should be kept in mind when registering the slash commands to discord.
/// </summary>
/// <param name="oldCommands">What to do with the old commands that are already registered with discord</param>
/// <param name="existingCommands"> What to do with the old commands (if they weren't wiped) that we re-define.</param>
public CommandRegistrationOptions(OldCommandOptions oldCommands, ExistingCommandOptions existingCommands)
{
OldCommands = oldCommands;
ExistingCommands = existingCommands;
}
/// <summary>
/// What to do with the old commands that are already registered with discord
/// </summary>
public OldCommandOptions OldCommands { get; set; }
/// <summary>
/// What to do with the old commands (if they weren't wiped) that we re-define.
/// </summary>
public ExistingCommandOptions ExistingCommands { get; set; }

/// <summary>
/// The default, and reccomended options - Keep the old commands, and overwrite existing commands we re-defined.
/// </summary>
public static CommandRegistrationOptions Default =>
new CommandRegistrationOptions(OldCommandOptions.KEEP, ExistingCommandOptions.OVERWRITE);
}
}

+ 14
- 0
src/Discord.Net.Commands/SlashCommands/CommandRegistration/ExistingCommandOptions.cs View File

@@ -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
}
}

+ 24
- 0
src/Discord.Net.Commands/SlashCommands/CommandRegistration/OldCommandOptions.cs View File

@@ -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
{
/// <summary>
/// Keep the old commands intact - do nothing to them.
/// </summary>
KEEP,
/// <summary>
/// Delete the old commands that we won't be re-defined this time around.
/// </summary>
DELETE_UNUSED,
/// <summary>
/// Delete everything discord has.
/// </summary>
WIPE
}
}

+ 79
- 3
src/Discord.Net.Commands/SlashCommands/Info/SlashCommandInfo.cs View File

@@ -1,4 +1,6 @@
using Discord.Commands; using Discord.Commands;
using Discord.Commands.Builders;
using Discord.WebSocket;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -21,6 +23,10 @@ namespace Discord.SlashCommands
/// Gets the name of the command. /// Gets the name of the command.
/// </summary> /// </summary>
public string Description { get; } public string Description { get; }
/// <summary>
/// The parameters we are expecting - an extension of SlashCommandOptionBuilder
/// </summary>
public List<SlashParameterInfo> Parameters { get; }


/// <summary> /// <summary>
/// The user method as a delegate. We need to use Delegate because there is an unknown number of parameters /// 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
/// </summary> /// </summary>
public Func<object[], Task<IResult>> callback; public Func<object[], Task<IResult>> callback;


public SlashCommandInfo(SlashModuleInfo module, string name, string description, Delegate userMethod)
public SlashCommandInfo(SlashModuleInfo module, string name, string description,List<SlashParameterInfo> parameters , Delegate userMethod)
{ {
Module = module; Module = module;
Name = name; Name = name;
Description = description; Description = description;
Parameters = parameters;
this.userMethod = userMethod; this.userMethod = userMethod;
this.callback = new Func<object[], Task<IResult>>(async (args) => this.callback = new Func<object[], Task<IResult>>(async (args) =>
{ {
@@ -56,9 +63,78 @@ namespace Discord.SlashCommands
}); });
} }


public async Task<IResult> ExecuteAsync(object[] args)
/// <summary>
/// Execute the function based on the interaction data we get.
/// </summary>
/// <param name="data">Interaction data from interaction</param>
public async Task<IResult> ExecuteAsync(SocketInteractionData data)
{ {
return await callback.Invoke(args).ConfigureAwait(false);
// List of arguments to be passed to the Delegate
List<object> args = new List<object>();
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);
}
/// <summary>
/// Get the interaction data from the name of the parameter we want to fill in.
/// </summary>
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;
}

/// <summary>
/// Build the command and put it in a state in which we can use to define it to Discord.
/// </summary>
public SlashCommandCreationProperties BuildCommand()
{
SlashCommandBuilder builder = new SlashCommandBuilder();
builder.WithName(Name);
builder.WithDescription(Description);
builder.Options = new List<SlashCommandOptionBuilder>();
foreach (var parameter in Parameters)
{
builder.AddOptions(parameter);
}

return builder.Build();
} }
} }
} }

+ 37
- 0
src/Discord.Net.Commands/SlashCommands/Info/SlashParameterInfo.cs View File

@@ -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}");
}
}
}

+ 26
- 13
src/Discord.Net.Commands/SlashCommands/SlashCommandService.cs View File

@@ -34,11 +34,6 @@ namespace Discord.SlashCommands
_logger = new Logger(_logManager, "SlshCommand"); _logger = new Logger(_logManager, "SlshCommand");
} }


public void AddAssembly()
{

}

/// <summary> /// <summary>
/// Execute a slash command. /// Execute a slash command.
/// </summary> /// </summary>
@@ -50,19 +45,39 @@ namespace Discord.SlashCommands
SlashCommandInfo commandInfo; SlashCommandInfo commandInfo;
if (commandDefs.TryGetValue(interaction.Data.Name, out 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 // Then, set the context in which the command will be executed
commandInfo.Module.userCommandModule.SetContext(interaction); 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 else
{ {
return SearchResult.FromError(CommandError.UnknownCommand, $"There is no registered slash command with the name {interaction.Data.Name}"); return SearchResult.FromError(CommandError.UnknownCommand, $"There is no registered slash command with the name {interaction.Data.Name}");
} }
} }

/// <summary>
/// Registers all previously scanned commands.
/// </summary>
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);
}

/// <summary>
/// Scans the program for Attribute-based SlashCommandModules
/// </summary>
public async Task AddModulesAsync(Assembly assembly, IServiceProvider services) 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 // 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 // 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); moduleDefs = await SlashCommandServiceHelper.InstantiateModules(types, this).ConfigureAwait(false);
// After that, internally register all of the commands into SlashCommandInfo // 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 finally
{ {


+ 119
- 5
src/Discord.Net.Commands/SlashCommands/SlashCommandServiceHelper.cs View File

@@ -1,3 +1,5 @@
using Discord.Commands.Builders;
using Discord.WebSocket;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -63,17 +65,19 @@ namespace Discord.SlashCommands
/// <summary> /// <summary>
/// Prepare all of the commands and register them internally. /// Prepare all of the commands and register them internally.
/// </summary> /// </summary>
public static async Task<Dictionary<string, SlashCommandInfo>> PrepareAsync(IReadOnlyList<TypeInfo> types, Dictionary<Type, SlashModuleInfo> moduleDefs, SlashCommandService slashCommandService)
public static async Task<Dictionary<string, SlashCommandInfo>> CreateCommandInfos(IReadOnlyList<TypeInfo> types, Dictionary<Type, SlashModuleInfo> moduleDefs, SlashCommandService slashCommandService)
{ {
// Create the resulting dictionary ahead of time
var result = new Dictionary<string, SlashCommandInfo>(); var result = new Dictionary<string, SlashCommandInfo>();
// fore each user-defined module
// For each user-defined module ...
foreach (var userModule in types) 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; SlashModuleInfo moduleInfo;
if (moduleDefs.TryGetValue(userModule, out 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(); var commandMethods = userModule.GetMethods();
List<SlashCommandInfo> commandInfos = new List<SlashCommandInfo>(); List<SlashCommandInfo> commandInfos = new List<SlashCommandInfo>();
foreach (var commandMethod in commandMethods) foreach (var commandMethod in commandMethods)
@@ -81,13 +85,19 @@ namespace Discord.SlashCommands
SlashCommand slashCommand; SlashCommand slashCommand;
if (IsValidSlashCommand(commandMethod, out 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); Delegate delegateMethod = CreateDelegate(commandMethod, moduleInfo.userCommandModule);

SlashCommandInfo commandInfo = new SlashCommandInfo( SlashCommandInfo commandInfo = new SlashCommandInfo(
module: moduleInfo, module: moduleInfo,
name: slashCommand.commandName, name: slashCommand.commandName,
description: slashCommand.description, 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 userMethod: delegateMethod
); );

result.Add(slashCommand.commandName, commandInfo); result.Add(slashCommand.commandName, commandInfo);
commandInfos.Add(commandInfo); commandInfos.Add(commandInfo);
} }
@@ -97,6 +107,9 @@ namespace Discord.SlashCommands
} }
return result; return result;
} }
/// <summary>
/// Determines wheater a method can be clasified as a slash command
/// </summary>
private static bool IsValidSlashCommand(MethodInfo method, out SlashCommand slashCommand) private static bool IsValidSlashCommand(MethodInfo method, out SlashCommand slashCommand)
{ {
// Verify that we only have one [SlashCommand(...)] attribute // Verify that we only have one [SlashCommand(...)] attribute
@@ -115,6 +128,66 @@ namespace Discord.SlashCommands
slashCommand = slashCommandAttributes.First() as SlashCommand; slashCommand = slashCommandAttributes.First() as SlashCommand;
return true; return true;
} }
private static List<SlashParameterInfo> ConstructCommandParameters(MethodInfo method)
{
// Prepare the final list of parameters
List<SlashParameterInfo> finalParameters = new List<SlashParameterInfo>();

// 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;
}
/// <summary>
/// Get the type of command option from a method parameter info.
/// </summary>
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}");
}

/// <summary> /// <summary>
/// Creae a delegate from methodInfo. Taken from /// Creae a delegate from methodInfo. Taken from
/// https://stackoverflow.com/a/40579063/8455128 /// https://stackoverflow.com/a/40579063/8455128
@@ -143,8 +216,49 @@ namespace Discord.SlashCommands
return Delegate.CreateDelegate(getType(types.ToArray()), target, methodInfo.Name); return Delegate.CreateDelegate(getType(types.ToArray()), target, methodInfo.Name);
} }


public static async Task RegisterCommands(Dictionary<string, SlashCommandInfo> commandDefs, SlashCommandService slashCommandService, IServiceProvider services)
public static async Task RegisterCommands(DiscordSocketClient socketClient, Dictionary<string, SlashCommandInfo> commandDefs, SlashCommandService slashCommandService, CommandRegistrationOptions options)
{ {
// Get existing commmands
ulong devGuild = 386658607338618891;
var existingCommands = await socketClient.Rest.GetGuildApplicationCommands(devGuild).ConfigureAwait(false);
List<string> existingCommandNames = new List<string>();
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; return;
} }
} }


src/Discord.Net.Commands/SlashCommands/Types/ISlashCommandModule.cs → src/Discord.Net.Commands/SlashCommands/Types/CommandModule/ISlashCommandModule.cs View File


src/Discord.Net.Commands/SlashCommands/Types/SlashCommandModule.cs → src/Discord.Net.Commands/SlashCommands/Types/CommandModule/SlashCommandModule.cs View File

@@ -1,5 +1,7 @@
using Discord.Commands.SlashCommands.Types; using Discord.Commands.SlashCommands.Types;
using Discord.WebSocket;
using System; using System;
using System.Threading.Tasks;


namespace Discord.SlashCommands namespace Discord.SlashCommands
{ {
@@ -17,5 +19,16 @@ namespace Discord.SlashCommands
var newValue = interaction as T; var newValue = interaction as T;
Interaction = newValue ?? throw new InvalidOperationException($"Invalid interaction type. Expected {typeof(T).Name}, got {interaction.GetType().Name}."); Interaction = newValue ?? throw new InvalidOperationException($"Invalid interaction type. Expected {typeof(T).Name}, got {interaction.GetType().Name}.");
} }


public async Task<IMessage> 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;
}
} }
} }

+ 8
- 5
src/Discord.Net.WebSocket/Entities/Interaction/SocketInteractionDataOption.cs View File

@@ -30,7 +30,7 @@ namespace Discord.WebSocket
internal SocketInteractionDataOption() { } internal SocketInteractionDataOption() { }
internal SocketInteractionDataOption(Model model, DiscordSocketClient discord, ulong guild) 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.Value = model.Value.IsSpecified ? model.Value.Value : null;
this.discord = discord; this.discord = discord;
this.guild = guild; this.guild = guild;
@@ -44,14 +44,17 @@ namespace Discord.WebSocket
// Converters // Converters
public static explicit operator bool(SocketInteractionDataOption option) public static explicit operator bool(SocketInteractionDataOption option)
=> (bool)option.Value; => (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) public static explicit operator int(SocketInteractionDataOption option)
=> (int)option.Value;
=> unchecked(
(int)( (long)option.Value )
);
public static explicit operator string(SocketInteractionDataOption option) public static explicit operator string(SocketInteractionDataOption option)
=> option.Value.ToString(); => option.Value.ToString();


public static explicit operator SocketGuildChannel(SocketInteractionDataOption option) 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); var guild = option.discord.GetGuild(option.guild);


@@ -66,7 +69,7 @@ namespace Discord.WebSocket


public static explicit operator SocketRole(SocketInteractionDataOption option) 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); var guild = option.discord.GetGuild(option.guild);


@@ -81,7 +84,7 @@ namespace Discord.WebSocket


public static explicit operator SocketGuildUser(SocketInteractionDataOption option) 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); var guild = option.discord.GetGuild(option.guild);




Loading…
Cancel
Save