Browse Source

Implemented SubCommands and SubCommandGroups properly.

Details:
To implement them I had to get creative. First thing i did was manually register a command that uses sub commands and sub command groups. Two things I noticed immediately:
1) I can create a subcommand on a "root" command - where no SubCommandGroup is used
2) The current implementation of the Interactions doesn't know what type of value an option is. Good thing is that there is only 1 option when querying subcommands and subcommand groups, so I can find out what the "path" of the subcommand is.
TOP/root/rng
TOP/root/usr/zero
TOP/root/usr/johnny (i misspelled it in the source files, woops)
[See SlashCommandsExample/DiscordClient.cs]

Next I wanted to make command groups (I'll use this term as to mean a slash command with subcommands and regular slash command groups) to be implemented in code in a sort of hierarchical manner - so I made them classes with attributes. Unfortunately to make this work I had to make them re-inherit the same things as the base module - UGLY but I see no other option to do this other than making them inherit from another class that remembers the instance of the upper class and implements the same methods aka a whole mess that I decided I won't want to partake in.
[See SlashCommandsExample/Modules/DevModule.cs]

Next-up is to search for these sub-groups. I decided that the most intuitive way of implementing these was to make SlashModuleInfo have children and parent of the same type -- from which arose different problems, but we'll get to that.
So I gave them some children and a parent and a reference to the CommandGroup attribute they have on themselves. The boolean isCommandGroup is unused, but could be useful in the future... maybe. Also I've added a path variable to internally store structure.
I wanted (after the whole reflections business) for commands to be easly accessed and deal WITH NO REFLECTION because those are slow, so I changed the final string - SlashCommandInfo dictionary to containt paths instead of command infos, something like what I exemplefied above.

In any case, I edited the service helper (the search for modules method) to ignore command groups and only store top level commands. After that I made a command to instantiate command groups, and the command creation and registration were changed as to be recursive - because recurion is the simpest way to do this and it's efficient enough for what we want - we only run this once anyway.

The biggest change was with command building - commands no longer build themselves, but now we command each module to build itself. There are 3 cases:
Top-Level commands
Top-Level subcommands (or level 1 command group)
subcommands within slash command groups

The code is uncommented, untidy and I'll fix that in a future commit. One last thing to note is that SlashCommands can have 0 options! - fixed that bug. Also SlashCommandBuilder.WithName() for some reason was implemented wrongly - I pressume a copy-paste error,
Also I implemented 0 types of enforcing rules - I'm going to leave this to other people to do.
pull/1733/head^2^2
Cosma George 4 years ago
parent
commit
50a88e09cd
10 changed files with 413 additions and 68 deletions
  1. +69
    -0
      SlashCommandsExample/DiscordClient.cs
  2. +26
    -0
      SlashCommandsExample/Modules/DevModule.cs
  3. +35
    -0
      src/Discord.Net.Commands/SlashCommands/Attributes/CommandGroup.cs
  4. +2
    -2
      src/Discord.Net.Commands/SlashCommands/Attributes/SlashCommand.cs
  5. +4
    -15
      src/Discord.Net.Commands/SlashCommands/Builders/SlashCommandBuilder.cs
  6. +22
    -4
      src/Discord.Net.Commands/SlashCommands/Info/SlashCommandInfo.cs
  7. +85
    -1
      src/Discord.Net.Commands/SlashCommands/Info/SlashModuleInfo.cs
  8. +48
    -4
      src/Discord.Net.Commands/SlashCommands/SlashCommandService.cs
  9. +119
    -41
      src/Discord.Net.Commands/SlashCommands/SlashCommandServiceHelper.cs
  10. +3
    -1
      src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs

+ 69
- 0
SlashCommandsExample/DiscordClient.cs View File

@@ -4,6 +4,7 @@ using Discord.SlashCommands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;

@@ -28,6 +29,8 @@ namespace SlashCommandsExample
socketClient.Log += SocketClient_Log;
_commands.Log += SocketClient_Log;
socketClient.InteractionCreated += InteractionHandler;
socketClient.Ready += RegisterCommand;

// This is for dev purposes.
// To avoid the situation in which you accidentally push your bot token to upstream, you can use
// EnviromentVariables to store your key.
@@ -45,6 +48,72 @@ namespace SlashCommandsExample
// EnvironmentVariableTarget.User);
}

public async Task RegisterCommand()
{
// Use this to manually register a command for testing.
return;
await socketClient.Rest.CreateGuildCommand(new SlashCommandCreationProperties()
{
Name = "root",
Description = "Root Command",
Options = new List<ApplicationCommandOptionProperties>()
{
new ApplicationCommandOptionProperties()
{
Name = "usr",
Description = "User Folder",
Type = ApplicationCommandOptionType.SubCommandGroup,
Options = new List<ApplicationCommandOptionProperties>()
{
// This doesn't work. This is good!
//new ApplicationCommandOptionProperties()
//{
// Name = "strstr",
// Description = "Some random string I guess.",
// Type = ApplicationCommandOptionType.String,
//},
new ApplicationCommandOptionProperties()
{
Name = "zero",
Description = "Zero's Home Folder - COMMAND",
Type = ApplicationCommandOptionType.SubCommand,
Options = new List<ApplicationCommandOptionProperties>()
{
new ApplicationCommandOptionProperties()
{
Name = "file",
Description = "the file you want accessed.",
Type = ApplicationCommandOptionType.String
}
}
},
new ApplicationCommandOptionProperties()
{
Name = "johhny",
Description = "Johnny Test's Home Folder - COMMAND",
Type = ApplicationCommandOptionType.SubCommand,
Options = new List<ApplicationCommandOptionProperties>()
{
new ApplicationCommandOptionProperties()
{
Name = "file",
Description = "the file you want accessed.",
Type = ApplicationCommandOptionType.String
}
}
}
}
},
new ApplicationCommandOptionProperties()
{
Name = "random",
Description = "Random things",
Type = ApplicationCommandOptionType.SubCommand
}
}
}, 386658607338618891) ;
}

public async Task RunAsync()
{
await socketClient.LoginAsync(TokenType.Bot, botToken);


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

@@ -36,6 +36,32 @@ namespace SlashCommandsExample.Modules
await Reply($"You gave me:\r\n {boolean}, {integer}, {myString}, <#{channel?.Id}>, {user?.Mention}, {role?.Mention}");

}

[CommandGroup("root")]
public class DevModule_Root : SlashCommandModule<SocketInteraction>
{
[SlashCommand("rng", "Gives you a random number from this \"machine\"")]
public async Task RNGAsync()
{
var rand = new Random();
await Reply(rand.Next(0, 101).ToString());
}

[CommandGroup("usr")]
public class DevModule_Root_Usr : SlashCommandModule<SocketInteraction>
{
[SlashCommand("zero", "Gives you a file from user zero from this \"machine\"")]
public async Task ZeroAsync([Description("The file you want.")] string file)
{
await Reply($"You don't have permissiont to access {file} from user \"zero\".");
}
[SlashCommand("johnny", "Gives you a file from user Johnny Test from this \"machine\"")]
public async Task JohnnyAsync([Description("The file you want.")] string file)
{
await Reply($"You don't have permissiont to access {file} from user \"johnny\".");
}
}
}
}
}
/*


+ 35
- 0
src/Discord.Net.Commands/SlashCommands/Attributes/CommandGroup.cs View File

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

namespace Discord.SlashCommands
{
/// <summary>
/// Defines the current as being a group of slash commands.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CommandGroup : Attribute
{
/// <summary>
/// The name of this slash command.
/// </summary>
public string groupName;

/// <summary>
/// The description of this slash command.
/// </summary>
public string description;

/// <summary>
/// Tells the <see cref="SlashCommandService"/> that this class/function is a slash command.
/// </summary>
/// <param name="commandName">The name of this slash command.</param>
public CommandGroup(string groupName, string description = "No description.")
{
this.groupName = groupName.ToLower();
this.description = description;
}
}
}

+ 2
- 2
src/Discord.Net.Commands/SlashCommands/Attributes/SlashCommand.cs View File

@@ -9,7 +9,7 @@ namespace Discord.SlashCommands
/// <summary>
/// Defines the current class or function as a slash command.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class SlashCommand : Attribute
{
/// <summary>
@@ -28,7 +28,7 @@ namespace Discord.SlashCommands
/// <param name="commandName">The name of this slash command.</param>
public SlashCommand(string commandName, string description = "No description.")
{
this.commandName = commandName;
this.commandName = commandName.ToLower();
this.description = description;
}
}


+ 4
- 15
src/Discord.Net.Commands/SlashCommands/Builders/SlashCommandBuilder.cs View File

@@ -377,8 +377,8 @@ namespace Discord.Commands.Builders
{
bool isSubType = this.Type == ApplicationCommandOptionType.SubCommand || this.Type == ApplicationCommandOptionType.SubCommandGroup;

if (isSubType && (Options == null || !Options.Any()))
throw new ArgumentException(nameof(Options), "SubCommands/SubCommandGroups must have at least one option");
if (this.Type == ApplicationCommandOptionType.SubCommandGroup && (Options == null || !Options.Any()))
throw new ArgumentException(nameof(Options), "SubCommandGroups must have at least one option");

if (!isSubType && (Options != null && Options.Any()))
throw new ArgumentException(nameof(Options), $"Cannot have options on {Type} type");
@@ -448,20 +448,9 @@ namespace Discord.Commands.Builders
return this;
}

public SlashCommandOptionBuilder WithName(string Name, int Value)
public SlashCommandOptionBuilder WithName(string Name)
{
if (Choices == null)
Choices = new List<ApplicationCommandOptionChoiceProperties>();

if (Choices.Count >= MaxChoiceCount)
throw new ArgumentOutOfRangeException(nameof(Choices), $"Cannot add more than {MaxChoiceCount} choices!");

Choices.Add(new ApplicationCommandOptionChoiceProperties()
{
Name = Name,
Value = Value
});

this.Name = Name;
return this;
}



+ 22
- 4
src/Discord.Net.Commands/SlashCommands/Info/SlashCommandInfo.cs View File

@@ -67,7 +67,7 @@ namespace Discord.SlashCommands
/// 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)
public async Task<IResult> ExecuteAsync(IReadOnlyCollection<SocketInteractionDataOption> data)
{
// List of arguments to be passed to the Delegate
List<object> args = new List<object>();
@@ -101,14 +101,14 @@ namespace Discord.SlashCommands
/// <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)
private bool TryGetInteractionDataOption(IReadOnlyCollection<SocketInteractionDataOption> data, string name, out SocketInteractionDataOption dataOption)
{
if (data.Options == null)
if (data == null)
{
dataOption = null;
return false;
}
foreach (var option in data.Options)
foreach (var option in data)
{
if (option.Name == name.ToLower())
{
@@ -136,5 +136,23 @@ namespace Discord.SlashCommands

return builder.Build();
}

/// <summary>
/// Build the command AS A SUBCOMMAND and put it in a state in which we can use to define it to Discord.
/// </summary>
public SlashCommandOptionBuilder BuildSubCommand()
{
SlashCommandOptionBuilder builder = new SlashCommandOptionBuilder();
builder.WithName(Name);
builder.WithDescription(Description);
builder.WithType(ApplicationCommandOptionType.SubCommand);
builder.Options = new List<SlashCommandOptionBuilder>();
foreach (var parameter in Parameters)
{
builder.AddOption(parameter);
}

return builder;
}
}
}

+ 85
- 1
src/Discord.Net.Commands/SlashCommands/Info/SlashModuleInfo.cs View File

@@ -1,6 +1,10 @@
using Discord.Commands;
using Discord.Commands.Builders;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

@@ -8,11 +12,22 @@ namespace Discord.SlashCommands
{
public class SlashModuleInfo
{
public const string PathSeperator = "//";
public const string RootModuleName = "TOP";
public const string RootCommandPrefix = RootModuleName + PathSeperator;

public SlashModuleInfo(SlashCommandService service)
{
Service = service;
}

public bool isCommandGroup { get; set; } = false;
public CommandGroup commandGroupInfo { get; set; }

public SlashModuleInfo parent { get; set; }
public List<SlashModuleInfo> commandGroups { get; set; }
public string Path { get; set; } = RootModuleName;

/// <summary>
/// Gets the command service associated with this module.
/// </summary>
@@ -27,7 +42,7 @@ namespace Discord.SlashCommands
/// Used to set context.
/// </summary>
public ISlashCommandModule userCommandModule;
public Type moduleType;

public void SetCommands(List<SlashCommandInfo> commands)
{
@@ -43,5 +58,74 @@ namespace Discord.SlashCommands
this.userCommandModule = userCommandModule as ISlashCommandModule;
}
}
public void SetType(Type type)
{
moduleType = type;
}

public void MakeCommandGroup(CommandGroup commandGroupInfo, SlashModuleInfo parent)
{
isCommandGroup = true;
this.commandGroupInfo = commandGroupInfo;
this.parent = parent;
}
public void SetSubCommandGroups(List<SlashModuleInfo> subCommandGroups)
{
// this.commandGroups = new List<SlashModuleInfo>(subCommandGroups);
this.commandGroups = subCommandGroups;
}

public void MakePath()
{
Path = parent.Path + SlashModuleInfo.PathSeperator + commandGroupInfo.groupName;
}

public List<SlashCommandCreationProperties> BuildCommands()
{
List<SlashCommandCreationProperties> builtCommands = new List<SlashCommandCreationProperties>();
foreach (var command in Commands)
{
builtCommands.Add(command.BuildCommand());
}
foreach(var commandGroup in commandGroups)
{
builtCommands.Add(commandGroup.BuildTopLevelCommandGroup());
}
return builtCommands;
}

public SlashCommandCreationProperties BuildTopLevelCommandGroup()
{
SlashCommandBuilder builder = new SlashCommandBuilder();
builder.WithName(commandGroupInfo.groupName);
builder.WithDescription(commandGroupInfo.description);
foreach (var command in Commands)
{
builder.AddOption(command.BuildSubCommand());
}
foreach (var commandGroup in commandGroups)
{
builder.AddOption(commandGroup.BuildNestedCommandGroup());
}
return builder.Build();
}

private SlashCommandOptionBuilder BuildNestedCommandGroup()
{
SlashCommandOptionBuilder builder = new SlashCommandOptionBuilder();
builder.WithName(commandGroupInfo.groupName);
builder.WithDescription(commandGroupInfo.description);
builder.WithType(ApplicationCommandOptionType.SubCommandGroup);
foreach (var command in Commands)
{
builder.AddOption(command.BuildSubCommand());
}
foreach (var commandGroup in commandGroups)
{
builder.AddOption(commandGroup.BuildNestedCommandGroup());
}

return builder;
}
}
}

+ 48
- 4
src/Discord.Net.Commands/SlashCommands/SlashCommandService.cs View File

@@ -41,20 +41,64 @@ namespace Discord.SlashCommands
/// <returns></returns>
public async Task<IResult> ExecuteAsync(SocketInteraction interaction)
{
// First, get the info about this command, if it exists
SlashCommandInfo commandInfo;
if (commandDefs.TryGetValue(interaction.Data.Name, out commandInfo))
// Get the name of the actual command - be it a normal slash command or subcommand, and return the options we can give it.
string name = GetSearchName(interaction.Data, out var resultingOptions);
if (commandDefs.TryGetValue(name, out commandInfo))
{
// Then, set the context in which the command will be executed
commandInfo.Module.userCommandModule.SetContext(interaction);
// Then run the command and pass the interaction data over to the CommandInfo class
return await commandInfo.ExecuteAsync(interaction.Data).ConfigureAwait(false);
return await commandInfo.ExecuteAsync(resultingOptions).ConfigureAwait(false);
}
else
{
return SearchResult.FromError(CommandError.UnknownCommand, $"There is no registered slash command with the name {interaction.Data.Name}");
}
}
/// <summary>
/// Get the name of the command we want to search for - be it a normal slash command or a sub command. Returns as out the options to be given to the method.
/// /// </summary>
/// <param name="interactionData"></param>
/// <param name="resultingOptions"></param>
/// <returns></returns>
private string GetSearchName(SocketInteractionData interactionData, out IReadOnlyCollection<SocketInteractionDataOption> resultingOptions)
{
string nameToSearch = SlashModuleInfo.RootCommandPrefix + interactionData.Name;
var options = interactionData.Options;
while(options != null && options.Count == 1)
{
string newName = nameToSearch + SlashModuleInfo.PathSeperator + GetFirstOption(options).Name;
if (AnyKeyContains(commandDefs,newName))
{
nameToSearch = newName;
options = GetFirstOption(options).Options;
}
else
{
break;
}
}
resultingOptions = options;
return nameToSearch;
}

private bool AnyKeyContains(Dictionary<string, SlashCommandInfo> commandDefs, string newName)
{
foreach (var pair in commandDefs)
{
if (pair.Key.Contains(newName))
return true;
}
return false;
}

private SocketInteractionDataOption GetFirstOption(IReadOnlyCollection<SocketInteractionDataOption> options)
{
var it = options.GetEnumerator();
it.MoveNext();
return it.Current;
}

/// <summary>
/// Registers all previously scanned commands.
@@ -66,7 +110,7 @@ namespace Discord.SlashCommands

try
{
await SlashCommandServiceHelper.RegisterCommands(socketClient, commandDefs, this,registrationOptions).ConfigureAwait(false);
await SlashCommandServiceHelper.RegisterCommands(socketClient, moduleDefs, commandDefs, this,registrationOptions).ConfigureAwait(false);
}
finally
{


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

@@ -40,7 +40,8 @@ namespace Discord.SlashCommands
{
// See if the base type (SlashCommandInfo<T>) implements interface ISlashCommandModule
return typeInfo.BaseType.GetInterfaces()
.Any(n => n == typeof(ISlashCommandModule));
.Any(n => n == typeof(ISlashCommandModule)) &&
typeInfo.GetCustomAttributes(typeof(CommandGroup)).Count() == 0;
}

/// <summary>
@@ -49,18 +50,70 @@ namespace Discord.SlashCommands
public static async Task<Dictionary<Type, SlashModuleInfo>> InstantiateModules(IReadOnlyList<TypeInfo> types, SlashCommandService slashCommandService)
{
var result = new Dictionary<Type, SlashModuleInfo>();
// Here we get all modules thate are NOT sub command groups
foreach (Type userModuleType in types)
{
SlashModuleInfo moduleInfo = new SlashModuleInfo(slashCommandService);
moduleInfo.SetType(userModuleType);

// If they want a constructor with different parameters, this is the place to add them.
object instance = userModuleType.GetConstructor(Type.EmptyTypes).Invoke(null);
moduleInfo.SetCommandModule(instance);

// ,,
moduleInfo.SetSubCommandGroups(InstantiateSubCommands(userModuleType, moduleInfo, slashCommandService));

result.Add(userModuleType, moduleInfo);
}
return result;
}
public static List<SlashModuleInfo> InstantiateSubCommands(Type rootModule,SlashModuleInfo rootModuleInfo, SlashCommandService slashCommandService)
{
List<SlashModuleInfo> commandGroups = new List<SlashModuleInfo>();
foreach(Type commandGroupType in rootModule.GetNestedTypes())
{
if(TryGetCommandGroupAttribute(commandGroupType, out CommandGroup commandGroup))
{
SlashModuleInfo groupInfo = new SlashModuleInfo(slashCommandService);
groupInfo.SetType(commandGroupType);

object instance = commandGroupType.GetConstructor(Type.EmptyTypes).Invoke(null);
groupInfo.SetCommandModule(instance);

groupInfo.MakeCommandGroup(commandGroup,rootModuleInfo);
groupInfo.MakePath();

groupInfo.SetSubCommandGroups(InstantiateSubCommands(commandGroupType, groupInfo, slashCommandService));

commandGroups.Add(groupInfo);
}
}
return commandGroups;
}
public static bool TryGetCommandGroupAttribute(Type module, out CommandGroup commandGroup)
{
if(!module.IsPublic && !module.IsNestedPublic)
{
commandGroup = null;
return false;
}

var commandGroupAttributes = module.GetCustomAttributes(typeof(CommandGroup));
if( commandGroupAttributes.Count() == 0)
{
commandGroup = null;
return false;
}
else if(commandGroupAttributes.Count() > 1)
{
throw new Exception($"Too many CommandGroup attributes on a single class ({module.FullName}). It can only contain one!");
}
else
{
commandGroup = commandGroupAttributes.First() as CommandGroup;
return true;
}
}

/// <summary>
/// Prepare all of the commands and register them internally.
@@ -76,37 +129,52 @@ namespace Discord.SlashCommands
SlashModuleInfo moduleInfo;
if (moduleDefs.TryGetValue(userModule, out moduleInfo))
{
// 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<SlashCommandInfo> commandInfos = new List<SlashCommandInfo>();
foreach (var commandMethod in commandMethods)
{
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);
}
}
var commandInfos = RegisterSameLevelCommands(result, userModule, moduleInfo);
moduleInfo.SetCommands(commandInfos);
CreateSubCommandInfos(result, moduleInfo.commandGroups, slashCommandService);
}
}
return result;
}
public static void CreateSubCommandInfos(Dictionary<string, SlashCommandInfo> result, List<SlashModuleInfo> subCommandGroups, SlashCommandService slashCommandService)
{
foreach (var subCommandGroup in subCommandGroups)
{
var commandInfos = RegisterSameLevelCommands(result, subCommandGroup.moduleType.GetTypeInfo(), subCommandGroup);
subCommandGroup.SetCommands(commandInfos);
CreateSubCommandInfos(result, subCommandGroup.commandGroups, slashCommandService);
}
}
private static List<SlashCommandInfo> RegisterSameLevelCommands(Dictionary<string, SlashCommandInfo> result, TypeInfo userModule, SlashModuleInfo moduleInfo)
{
var commandMethods = userModule.GetMethods();
List<SlashCommandInfo> commandInfos = new List<SlashCommandInfo>();
foreach (var commandMethod in commandMethods)
{
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(commandInfo.Module.Path + SlashModuleInfo.PathSeperator + commandInfo.Name, commandInfo);
commandInfos.Add(commandInfo);
}
}

return commandInfos;
}

/// <summary>
/// Determines wheater a method can be clasified as a slash command
/// </summary>
@@ -187,7 +255,6 @@ namespace Discord.SlashCommands

throw new Exception($"Got parameter type other than int, string, bool, guild, role, or user. {methodParameter.Name}");
}

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

public static async Task RegisterCommands(DiscordSocketClient socketClient, Dictionary<string, SlashCommandInfo> commandDefs, SlashCommandService slashCommandService, CommandRegistrationOptions options)
public static async Task RegisterCommands(DiscordSocketClient socketClient, Dictionary<Type, SlashModuleInfo> rootModuleInfos, Dictionary<string, SlashCommandInfo> commandDefs, SlashCommandService slashCommandService, CommandRegistrationOptions options)
{
// Get existing commmands
ulong devGuild = 386658607338618891;
@@ -237,28 +304,39 @@ namespace Discord.SlashCommands
// 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))
!commandDefs.ContainsKey(SlashModuleInfo.RootCommandPrefix + existingCommand.Name))
{
await existingCommand.DeleteAsync();
}
}
}
foreach (var entry in commandDefs)
//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();
//
// await socketClient.Rest.CreateGuildCommand(command, devGuild).ConfigureAwait(false);
// }
//}
foreach (var pair in rootModuleInfos)
{
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
var rootModuleInfo = pair.Value;
List<SlashCommandCreationProperties> builtCommands = rootModuleInfo.BuildCommands();
foreach (var builtCommand in builtCommands)
{
SlashCommandInfo slashCommandInfo = entry.Value;
SlashCommandCreationProperties command = slashCommandInfo.BuildCommand();
// TODO: Implement Global and Guild Commands.
await socketClient.Rest.CreateGuildCommand(command, devGuild).ConfigureAwait(false);
await socketClient.Rest.CreateGuildCommand(builtCommand, devGuild).ConfigureAwait(false);
}
}

return;
}
}


+ 3
- 1
src/Discord.Net.Rest/API/Common/ApplicationCommandOption.cs View File

@@ -67,7 +67,9 @@ namespace Discord.API
? option.Options.Select(x => new ApplicationCommandOption(x)).ToArray()
: Optional<ApplicationCommandOption[]>.Unspecified;

this.Required = option.Required.Value;
this.Required = option.Required.HasValue
? option.Required.Value
: Optional<bool>.Unspecified;
this.Default = option.Default.HasValue
? option.Default.Value
: Optional<bool>.Unspecified;


Loading…
Cancel
Save