Browse Source

Implemented Global Attribute and added a way to register all commands in particualr guilds.

Details:
There's nothing much to say, just added a variable to track with modules/commands/command groups have the global attribute and depending on that I register them globally or locally. There is currently no way to implement two commands with the same name, but one on the guild level and one on the global level. There is also currently no implemented way to register only some commands to only some guilds - this could be done through attributes, but another solution for users who want to do complex stuff would be to just give them the built commands and let them maually register them as they see fit.
pull/1733/head^2^2
Cosma George 4 years ago
parent
commit
c76fdec8d6
8 changed files with 137 additions and 48 deletions
  1. +5
    -1
      SlashCommandsExample/DiscordClient.cs
  2. +4
    -0
      SlashCommandsExample/Modules/DevModule.cs
  3. +16
    -0
      src/Discord.Net.Commands/SlashCommands/Attributes/Global.cs
  4. +3
    -1
      src/Discord.Net.Commands/SlashCommands/Info/SlashCommandInfo.cs
  5. +13
    -2
      src/Discord.Net.Commands/SlashCommands/Info/SlashModuleInfo.cs
  6. +2
    -2
      src/Discord.Net.Commands/SlashCommands/SlashCommandService.cs
  7. +90
    -42
      src/Discord.Net.Commands/SlashCommands/SlashCommandServiceHelper.cs
  8. +4
    -0
      src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs

+ 5
- 1
SlashCommandsExample/DiscordClient.cs View File

@@ -120,7 +120,11 @@ namespace SlashCommandsExample
await socketClient.StartAsync();

await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
await _commands.RegisterCommandsAsync(socketClient, CommandRegistrationOptions.Default);
await _commands.RegisterCommandsAsync(socketClient, new List<ulong>()
{
386658607338618891
},
new CommandRegistrationOptions(OldCommandOptions.DELETE_UNUSED,ExistingCommandOptions.OVERWRITE));

await Task.Delay(-1);
}


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

@@ -9,9 +9,12 @@ using System.Threading.Tasks;

namespace SlashCommandsExample.Modules
{
// You can make the whole module Global
//[Global]
public class DevModule : SlashCommandModule<SocketInteraction>
{
[SlashCommand("ping", "Ping the bot to see if it's alive!")]
[Global]
public async Task PingAsync()
{
await Reply(":white_check_mark: **Bot Online**");
@@ -38,6 +41,7 @@ namespace SlashCommandsExample.Modules
}

[CommandGroup("root")]
//[Global]
public class DevModule_Root : SlashCommandModule<SocketInteraction>
{
[SlashCommand("rng", "Gives you a random number from this \"machine\"")]


+ 16
- 0
src/Discord.Net.Commands/SlashCommands/Attributes/Global.cs View File

@@ -0,0 +1,16 @@
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.Method | AttributeTargets.Class, AllowMultiple = false)]
public class Global : Attribute
{
}
}

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

@@ -28,6 +28,7 @@ namespace Discord.SlashCommands
/// </summary>
public List<SlashParameterInfo> Parameters { get; }

public bool isGlobal { get; }
/// <summary>
/// The user method as a delegate. We need to use Delegate because there is an unknown number of parameters
/// </summary>
@@ -37,13 +38,14 @@ namespace Discord.SlashCommands
/// </summary>
public Func<object[], Task<IResult>> callback;

public SlashCommandInfo(SlashModuleInfo module, string name, string description,List<SlashParameterInfo> parameters , Delegate userMethod)
public SlashCommandInfo(SlashModuleInfo module, string name, string description,List<SlashParameterInfo> parameters , Delegate userMethod , bool isGlobal = false)
{
Module = module;
Name = name;
Description = description;
Parameters = parameters;
this.userMethod = userMethod;
this.isGlobal = isGlobal;
this.callback = new Func<object[], Task<IResult>>(async (args) =>
{
// Try-catch it and see what we get - error or success


+ 13
- 2
src/Discord.Net.Commands/SlashCommands/Info/SlashModuleInfo.cs View File

@@ -28,6 +28,7 @@ namespace Discord.SlashCommands
public List<SlashModuleInfo> commandGroups { get; set; }
public string Path { get; set; } = RootModuleName;

public bool isGlobal { get; set; } = false;
/// <summary>
/// Gets the command service associated with this module.
/// </summary>
@@ -85,11 +86,21 @@ namespace Discord.SlashCommands
List<SlashCommandCreationProperties> builtCommands = new List<SlashCommandCreationProperties>();
foreach (var command in Commands)
{
builtCommands.Add(command.BuildCommand());
var builtCommand = command.BuildCommand();
if (isGlobal || command.isGlobal)
{
builtCommand.Global = true;
}
builtCommands.Add(builtCommand);
}
foreach(var commandGroup in commandGroups)
{
builtCommands.Add(commandGroup.BuildTopLevelCommandGroup());
var builtCommand = commandGroup.BuildTopLevelCommandGroup();
if (isGlobal || commandGroup.isGlobal)
{
builtCommand.Global = true;
}
builtCommands.Add(builtCommand);
}
return builtCommands;
}


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

@@ -103,14 +103,14 @@ namespace Discord.SlashCommands
/// <summary>
/// Registers all previously scanned commands.
/// </summary>
public async Task RegisterCommandsAsync(DiscordSocketClient socketClient, CommandRegistrationOptions registrationOptions)
public async Task RegisterCommandsAsync(DiscordSocketClient socketClient, List<ulong> guildIDs, 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, moduleDefs, commandDefs, this,registrationOptions).ConfigureAwait(false);
await SlashCommandServiceHelper.RegisterCommands(socketClient, moduleDefs, commandDefs, this, guildIDs, registrationOptions).ConfigureAwait(false);
}
finally
{


+ 90
- 42
src/Discord.Net.Commands/SlashCommands/SlashCommandServiceHelper.cs View File

@@ -59,10 +59,9 @@ namespace Discord.SlashCommands
// 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.isGlobal = IsCommandModuleGlobal(userModuleType);

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

result.Add(userModuleType, moduleInfo);
}
return result;
@@ -82,9 +81,9 @@ namespace Discord.SlashCommands

groupInfo.MakeCommandGroup(commandGroup,rootModuleInfo);
groupInfo.MakePath();
groupInfo.isGlobal = IsCommandModuleGlobal(commandGroupType);

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

commandGroups.Add(groupInfo);
}
}
@@ -114,7 +113,21 @@ namespace Discord.SlashCommands
return true;
}
}

public static bool IsCommandModuleGlobal(Type userModuleType)
{
// Verify that we only have one [Global] attribute
IEnumerable<Attribute> slashCommandAttributes = userModuleType.GetCustomAttributes(typeof(Global));
if (slashCommandAttributes.Count() > 1)
{
throw new Exception("Too many Global attributes on a single method. It can only contain one!");
}
// And at least one
if (slashCommandAttributes.Count() == 0)
{
return false;
}
return true;
}
/// <summary>
/// Prepare all of the commands and register them internally.
/// </summary>
@@ -129,7 +142,7 @@ namespace Discord.SlashCommands
SlashModuleInfo moduleInfo;
if (moduleDefs.TryGetValue(userModule, out moduleInfo))
{
var commandInfos = RegisterSameLevelCommands(result, userModule, moduleInfo);
var commandInfos = CreateSameLevelCommands(result, userModule, moduleInfo);
moduleInfo.SetCommands(commandInfos);
CreateSubCommandInfos(result, moduleInfo.commandGroups, slashCommandService);
}
@@ -140,12 +153,12 @@ namespace Discord.SlashCommands
{
foreach (var subCommandGroup in subCommandGroups)
{
var commandInfos = RegisterSameLevelCommands(result, subCommandGroup.moduleType.GetTypeInfo(), subCommandGroup);
var commandInfos = CreateSameLevelCommands(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)
private static List<SlashCommandInfo> CreateSameLevelCommands(Dictionary<string, SlashCommandInfo> result, TypeInfo userModule, SlashModuleInfo moduleInfo)
{
var commandMethods = userModule.GetMethods();
List<SlashCommandInfo> commandInfos = new List<SlashCommandInfo>();
@@ -164,7 +177,8 @@ namespace Discord.SlashCommands
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,
isGlobal: IsCommandGlobal(commandMethod)
);

result.Add(commandInfo.Module.Path + SlashModuleInfo.PathSeperator + commandInfo.Name, commandInfo);
@@ -196,6 +210,21 @@ namespace Discord.SlashCommands
slashCommand = slashCommandAttributes.First() as SlashCommand;
return true;
}
private static bool IsCommandGlobal(MethodInfo method)
{
// Verify that we only have one [Global] attribute
IEnumerable<Attribute> slashCommandAttributes = method.GetCustomAttributes(typeof(Global));
if (slashCommandAttributes.Count() > 1)
{
throw new Exception("Too many Global attributes on a single method. It can only contain one!");
}
// And at least one
if (slashCommandAttributes.Count() == 0)
{
return false;
}
return true;
}
private static List<SlashParameterInfo> ConstructCommandParameters(MethodInfo method)
{
// Prepare the final list of parameters
@@ -283,57 +312,76 @@ namespace Discord.SlashCommands
return Delegate.CreateDelegate(getType(types.ToArray()), target, methodInfo.Name);
}

public static async Task RegisterCommands(DiscordSocketClient socketClient, Dictionary<Type, SlashModuleInfo> rootModuleInfos, 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, List<ulong> guildIDs,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)
// TODO: see how we should handle if user wants to register two commands with the same name, one global and one not.
List<SlashCommandCreationProperties> builtCommands = new List<SlashCommandCreationProperties>();
foreach (var pair in rootModuleInfos)
{
existingCommandNames.Add(existingCommand.Name);
var rootModuleInfo = pair.Value;
builtCommands.AddRange(rootModuleInfo.BuildCommands());
}

List<Rest.RestGuildCommand> existingGuildCommands = new List<Rest.RestGuildCommand>();
List<Rest.RestGlobalCommand> existingGlobalCommands = new List<Rest.RestGlobalCommand>();
existingGlobalCommands.AddRange(await socketClient.Rest.GetGlobalApplicationCommands().ConfigureAwait(false));
foreach (ulong guildID in guildIDs)
{
existingGuildCommands.AddRange(await socketClient.Rest.GetGuildApplicationCommands(guildID).ConfigureAwait(false));
}
if (options.ExistingCommands == ExistingCommandOptions.KEEP_EXISTING)
{
foreach (var existingCommand in existingGuildCommands)
{
builtCommands.RemoveAll(x => (!x.Global && x.Name == existingCommand.Name));
}
foreach (var existingCommand in existingGlobalCommands)
{
builtCommands.RemoveAll(x => (x.Global && x.Name == 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)
foreach (var existingCommand in existingGuildCommands)
{
// If we want to wipe all commands
// or if the existing command isn't re-defined (probably code deleted by user)
// or if the existing command isn't re-defined and re-built
// remove it from discord.
if(options.OldCommands == OldCommandOptions.WIPE ||
!commandDefs.ContainsKey(SlashModuleInfo.RootCommandPrefix + existingCommand.Name))
if (options.OldCommands == OldCommandOptions.WIPE ||
// There are no commands which contain this existing command.
!builtCommands.Any( x => !x.Global && x.Name.Contains(SlashModuleInfo.PathSeperator + existingCommand.Name)))
{
await existingCommand.DeleteAsync();
}
}
foreach (var existingCommand in existingGlobalCommands)
{
// If we want to wipe all commands
// or if the existing command isn't re-defined and re-built
// remove it from discord.
if (options.OldCommands == OldCommandOptions.WIPE ||
// There are no commands which contain this existing command.
!builtCommands.Any(x => x.Global && x.Name.Contains(SlashModuleInfo.PathSeperator + 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();
//
// await socketClient.Rest.CreateGuildCommand(command, devGuild).ConfigureAwait(false);
// }
//}
foreach (var pair in rootModuleInfos)

foreach (var builtCommand in builtCommands)
{
var rootModuleInfo = pair.Value;
List<SlashCommandCreationProperties> builtCommands = rootModuleInfo.BuildCommands();
foreach (var builtCommand in builtCommands)
if (builtCommand.Global)
{
await socketClient.Rest.CreateGlobalCommand(builtCommand).ConfigureAwait(false);
}
else
{
// TODO: Implement Global and Guild Commands.
await socketClient.Rest.CreateGuildCommand(builtCommand, devGuild).ConfigureAwait(false);
foreach (ulong guildID in guildIDs)
{
await socketClient.Rest.CreateGuildCommand(builtCommand, guildID).ConfigureAwait(false);
}
}
}



+ 4
- 0
src/Discord.Net.Core/Entities/Interactions/SlashCommandCreationProperties.cs View File

@@ -21,6 +21,10 @@ namespace Discord
/// </summary>
public string Description { get; set; }

/// <summary>
/// If the command should be defined as a global command.
/// </summary>
public bool Global { get; set; } = false;

/// <summary>
/// Gets or sets the options for this command.


Loading…
Cancel
Save