diff --git a/DisCatSharp.ApplicationCommands/ApplicationCommandsConfiguration.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsConfiguration.cs
index d05bbc96c..09c277ac7 100644
--- a/DisCatSharp.ApplicationCommands/ApplicationCommandsConfiguration.cs
+++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsConfiguration.cs
@@ -1,115 +1,114 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using Microsoft.Extensions.DependencyInjection;
-namespace DisCatSharp.ApplicationCommands
+namespace DisCatSharp.ApplicationCommands;
+
+///
+/// A configuration for a
+///
+public class ApplicationCommandsConfiguration
{
///
- /// A configuration for a
+ /// Sets the service provider.
+ /// Objects in this provider are used when instantiating application command modules.
+ /// This allows passing data around without resorting to static members.
+ /// Defaults to .
///
- public class ApplicationCommandsConfiguration
- {
- ///
- /// Sets the service provider.
- /// Objects in this provider are used when instantiating application command modules.
- /// This allows passing data around without resorting to static members.
- /// Defaults to .
- ///
- public IServiceProvider ServiceProvider { internal get; set; }
+ public IServiceProvider ServiceProvider { internal get; set; }
- ///
- /// This option enables the default help command.
- /// Disabling this will allow you to make your own help command.
- /// Defaults to .
- ///
- public bool EnableDefaultHelp { internal get; set; } = true;
+ ///
+ /// This option enables the default help command.
+ /// Disabling this will allow you to make your own help command.
+ /// Defaults to .
+ ///
+ public bool EnableDefaultHelp { internal get; set; } = true;
- ///
- /// This option enables the localization feature.
- /// Defaults to .
- ///
- public bool EnableLocalization { internal get; set; } = false;
+ ///
+ /// This option enables the localization feature.
+ /// Defaults to .
+ ///
+ public bool EnableLocalization { internal get; set; } = false;
- ///
- /// Automatically defer all responses.
- /// If you enable this, you can't use CreateResponse. Use EditResponse instead.
- /// Defaults to .
- ///
- public bool AutoDefer { internal get; set; } = false;
+ ///
+ /// Automatically defer all responses.
+ /// If you enable this, you can't use CreateResponse. Use EditResponse instead.
+ /// Defaults to .
+ ///
+ public bool AutoDefer { internal get; set; } = false;
- ///
- /// This option informs the module to check through all guilds whether the
- /// application.commands scope is set.
- /// This will take quite a while, when the bot is on more than 1k guilds.
- /// Defaults to .
- ///
- public bool CheckAllGuilds { internal get; set; } = false;
+ ///
+ /// This option informs the module to check through all guilds whether the
+ /// application.commands scope is set.
+ /// This will take quite a while, when the bot is on more than 1k guilds.
+ /// Defaults to .
+ ///
+ public bool CheckAllGuilds { internal get; set; } = false;
- ///
- /// This option can override the default registration behavior of the module.
- ///
- /// It can lead to unexpected behavior of the application commands module.
- /// Enable this option only if DisCatSharp support advises you to do so.
- ///
- /// Defaults to .
- ///
- public bool ManualOverride { internal get; set; } = false;
+ ///
+ /// This option can override the default registration behavior of the module.
+ ///
+ /// It can lead to unexpected behavior of the application commands module.
+ /// Enable this option only if DisCatSharp support advises you to do so.
+ ///
+ /// Defaults to .
+ ///
+ public bool ManualOverride { internal get; set; } = false;
- ///
- /// This option increases the debug output of the module.
- ///
- /// This is not recommended for production use.
- /// Enable this option only if DisCatSharp support advises you to do so.
- ///
- /// Defaults to .
- ///
- public bool DebugStartup { internal get; set; } = false;
+ ///
+ /// This option increases the debug output of the module.
+ ///
+ /// This is not recommended for production use.
+ /// Enable this option only if DisCatSharp support advises you to do so.
+ ///
+ /// Defaults to .
+ ///
+ public bool DebugStartup { internal get; set; } = false;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The service provider.
- [ActivatorUtilitiesConstructor]
- public ApplicationCommandsConfiguration(IServiceProvider provider = null)
- {
- this.ServiceProvider = provider;
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The service provider.
+ [ActivatorUtilitiesConstructor]
+ public ApplicationCommandsConfiguration(IServiceProvider provider = null)
+ {
+ this.ServiceProvider = provider;
+ }
- ///
- /// Creates a new instance of , copying the properties of another configuration.
- ///
- /// Configuration the properties of which are to be copied.
- public ApplicationCommandsConfiguration(ApplicationCommandsConfiguration acc)
- {
- this.EnableDefaultHelp = acc.EnableDefaultHelp;
- this.ServiceProvider = acc.ServiceProvider;
- this.DebugStartup = acc.DebugStartup;
- this.CheckAllGuilds = acc.CheckAllGuilds;
- this.ManualOverride = acc.ManualOverride;
- this.AutoDefer = acc.AutoDefer;
- this.EnableLocalization = acc.EnableLocalization;
- }
+ ///
+ /// Creates a new instance of , copying the properties of another configuration.
+ ///
+ /// Configuration the properties of which are to be copied.
+ public ApplicationCommandsConfiguration(ApplicationCommandsConfiguration acc)
+ {
+ this.EnableDefaultHelp = acc.EnableDefaultHelp;
+ this.ServiceProvider = acc.ServiceProvider;
+ this.DebugStartup = acc.DebugStartup;
+ this.CheckAllGuilds = acc.CheckAllGuilds;
+ this.ManualOverride = acc.ManualOverride;
+ this.AutoDefer = acc.AutoDefer;
+ this.EnableLocalization = acc.EnableLocalization;
}
}
diff --git a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
index ba2d4aff1..735e78a50 100644
--- a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
+++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
@@ -1,1820 +1,1819 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Attributes;
using DisCatSharp.ApplicationCommands.EventArgs;
using DisCatSharp.Common;
using DisCatSharp.Common.Utilities;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Exceptions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
-namespace DisCatSharp.ApplicationCommands
+namespace DisCatSharp.ApplicationCommands;
+
+///
+/// A class that handles slash commands for a client.
+///
+public sealed class ApplicationCommandsExtension : BaseExtension
{
///
- /// A class that handles slash commands for a client.
+ /// A list of methods for top level commands.
+ ///
+ private static List s_commandMethods { get; set; } = new();
+
+ ///
+ /// List of groups.
+ ///
+ private static List s_groupCommands { get; set; } = new();
+
+ ///
+ /// List of groups with subgroups.
+ ///
+ private static List s_subGroupCommands { get; set; } = new();
+
+ ///
+ /// List of context menus.
+ ///
+ private static List s_contextMenuCommands { get; set; } = new();
+
+ ///
+ /// List of global commands on discords backend.
+ ///
+ internal static List GlobalDiscordCommands { get; set; }
+
+ ///
+ /// List of guild commands on discords backend.
+ ///
+ internal static Dictionary> GuildDiscordCommands { get; set; }
+
+ ///
+ /// Singleton modules.
+ ///
+ private static List s_singletonModules { get; set; } = new();
+
+ ///
+ /// List of modules to register.
+ ///
+ private readonly List> _updateList = new();
+
+ ///
+ /// Configuration for Discord.
+ ///
+ internal static ApplicationCommandsConfiguration Configuration;
+
+ ///
+ /// Discord client.
+ ///
+ internal static DiscordClient ClientInternal;
+
+ ///
+ /// Set to true if anything fails when registering.
+ ///
+ private static bool s_errored { get; set; }
+
+ ///
+ /// Gets a list of registered commands. The key is the guild id (null if global).
+ ///
+ public IReadOnlyList>> RegisteredCommands
+ => s_registeredCommands;
+ private static readonly List>> s_registeredCommands = new();
+
+ ///
+ /// Gets a list of registered global commands.
+ ///
+ public IReadOnlyList GlobalCommands
+ => GlobalCommandsInternal;
+ internal static readonly List GlobalCommandsInternal = new();
+
+ ///
+ /// Gets a list of registered guild commands mapped by guild id.
+ ///
+ public IReadOnlyDictionary> GuildCommands
+ => GuildCommandsInternal;
+ internal static readonly Dictionary> GuildCommandsInternal = new();
+
+ ///
+ /// Gets the registration count.
+ ///
+ private static int s_registrationCount { get; set; }
+
+ ///
+ /// Gets the expected count.
+ ///
+ private static int s_expectedCount { get; set; }
+
+ ///
+ /// Gets the guild ids where the applications.commands scope is missing.
+ ///
+ private IReadOnlyList _missingScopeGuildIds;
+
+ ///
+ /// Gets whether debug is enabled.
+ ///
+ internal static bool DebugEnabled { get; set; }
+
+ internal static LogLevel ApplicationCommandsLogLevel
+ => DebugEnabled ? LogLevel.Debug : LogLevel.Trace;
+
+ ///
+ /// Gets whether check through all guilds is enabled.
+ ///
+ internal static bool CheckAllGuilds { get; set; }
+
+ ///
+ /// Gets whether the registration check should be manually overridden.
+ ///
+ internal static bool ManOr { get; set; }
+
+ ///
+ /// Gets whether interactions should be automatically deffered.
///
- public sealed class ApplicationCommandsExtension : BaseExtension
+ internal static bool AutoDeferEnabled { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ internal ApplicationCommandsExtension(ApplicationCommandsConfiguration configuration = null)
{
- ///
- /// A list of methods for top level commands.
- ///
- private static List s_commandMethods { get; set; } = new();
-
- ///
- /// List of groups.
- ///
- private static List s_groupCommands { get; set; } = new();
-
- ///
- /// List of groups with subgroups.
- ///
- private static List s_subGroupCommands { get; set; } = new();
-
- ///
- /// List of context menus.
- ///
- private static List s_contextMenuCommands { get; set; } = new();
-
- ///
- /// List of global commands on discords backend.
- ///
- internal static List GlobalDiscordCommands { get; set; }
-
- ///
- /// List of guild commands on discords backend.
- ///
- internal static Dictionary> GuildDiscordCommands { get; set; }
-
- ///
- /// Singleton modules.
- ///
- private static List s_singletonModules { get; set; } = new();
-
- ///
- /// List of modules to register.
- ///
- private readonly List> _updateList = new();
-
- ///
- /// Configuration for Discord.
- ///
- internal static ApplicationCommandsConfiguration Configuration;
-
- ///
- /// Discord client.
- ///
- internal static DiscordClient ClientInternal;
-
- ///
- /// Set to true if anything fails when registering.
- ///
- private static bool s_errored { get; set; }
-
- ///
- /// Gets a list of registered commands. The key is the guild id (null if global).
- ///
- public IReadOnlyList>> RegisteredCommands
- => s_registeredCommands;
- private static readonly List>> s_registeredCommands = new();
-
- ///
- /// Gets a list of registered global commands.
- ///
- public IReadOnlyList GlobalCommands
- => GlobalCommandsInternal;
- internal static readonly List GlobalCommandsInternal = new();
-
- ///
- /// Gets a list of registered guild commands mapped by guild id.
- ///
- public IReadOnlyDictionary> GuildCommands
- => GuildCommandsInternal;
- internal static readonly Dictionary> GuildCommandsInternal = new();
-
- ///
- /// Gets the registration count.
- ///
- private static int s_registrationCount { get; set; }
-
- ///
- /// Gets the expected count.
- ///
- private static int s_expectedCount { get; set; }
-
- ///
- /// Gets the guild ids where the applications.commands scope is missing.
- ///
- private IReadOnlyList _missingScopeGuildIds;
-
- ///
- /// Gets whether debug is enabled.
- ///
- internal static bool DebugEnabled { get; set; }
-
- internal static LogLevel ApplicationCommandsLogLevel
- => DebugEnabled ? LogLevel.Debug : LogLevel.Trace;
-
- ///
- /// Gets whether check through all guilds is enabled.
- ///
- internal static bool CheckAllGuilds { get; set; }
-
- ///
- /// Gets whether the registration check should be manually overridden.
- ///
- internal static bool ManOr { get; set; }
-
- ///
- /// Gets whether interactions should be automatically deffered.
- ///
- internal static bool AutoDeferEnabled { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The configuration.
- internal ApplicationCommandsExtension(ApplicationCommandsConfiguration configuration = null)
- {
- Configuration = configuration;
- DebugEnabled = configuration?.DebugStartup ?? false;
- CheckAllGuilds = configuration?.CheckAllGuilds ?? false;
- ManOr = configuration?.ManualOverride ?? false;
- AutoDeferEnabled = configuration?.AutoDefer ?? false;
- }
+ Configuration = configuration;
+ DebugEnabled = configuration?.DebugStartup ?? false;
+ CheckAllGuilds = configuration?.CheckAllGuilds ?? false;
+ ManOr = configuration?.ManualOverride ?? false;
+ AutoDeferEnabled = configuration?.AutoDefer ?? false;
+ }
- ///
- /// Runs setup.
- /// DO NOT RUN THIS MANUALLY. DO NOT DO ANYTHING WITH THIS.
- ///
- /// The client to setup on.
- protected internal override void Setup(DiscordClient client)
- {
- if (this.Client != null)
- throw new InvalidOperationException("What did I tell you?");
-
- this.Client = client;
- ClientInternal = client;
-
- this._slashError = new AsyncEvent("SLASHCOMMAND_ERRORED", TimeSpan.Zero, null);
- this._slashExecuted = new AsyncEvent("SLASHCOMMAND_EXECUTED", TimeSpan.Zero, null);
- this._contextMenuErrored = new AsyncEvent("CONTEXTMENU_ERRORED", TimeSpan.Zero, null);
- this._contextMenuExecuted = new AsyncEvent("CONTEXTMENU_EXECUTED", TimeSpan.Zero, null);
- this._applicationCommandsModuleReady = new AsyncEvent("APPLICATION_COMMANDS_MODULE_READY", TimeSpan.Zero, null);
- this._applicationCommandsModuleStartupFinished = new AsyncEvent("APPLICATION_COMMANDS_MODULE_STARTUP_FINISHED", TimeSpan.Zero, null);
- this._globalApplicationCommandsRegistered = new AsyncEvent("GLOBAL_COMMANDS_REGISTERED", TimeSpan.Zero, null);
- this._guildApplicationCommandsRegistered = new AsyncEvent("GUILD_COMMANDS_REGISTERED", TimeSpan.Zero, null);
-
- this.Client.GuildDownloadCompleted += async (c, e) => await this.UpdateAsync();
- this.Client.InteractionCreated += this.CatchInteractionsOnStartup;
- this.Client.ContextMenuInteractionCreated += this.CatchContextMenuInteractionsOnStartup;
- }
+ ///
+ /// Runs setup.
+ /// DO NOT RUN THIS MANUALLY. DO NOT DO ANYTHING WITH THIS.
+ ///
+ /// The client to setup on.
+ protected internal override void Setup(DiscordClient client)
+ {
+ if (this.Client != null)
+ throw new InvalidOperationException("What did I tell you?");
+
+ this.Client = client;
+ ClientInternal = client;
+
+ this._slashError = new AsyncEvent("SLASHCOMMAND_ERRORED", TimeSpan.Zero, null);
+ this._slashExecuted = new AsyncEvent("SLASHCOMMAND_EXECUTED", TimeSpan.Zero, null);
+ this._contextMenuErrored = new AsyncEvent("CONTEXTMENU_ERRORED", TimeSpan.Zero, null);
+ this._contextMenuExecuted = new AsyncEvent("CONTEXTMENU_EXECUTED", TimeSpan.Zero, null);
+ this._applicationCommandsModuleReady = new AsyncEvent("APPLICATION_COMMANDS_MODULE_READY", TimeSpan.Zero, null);
+ this._applicationCommandsModuleStartupFinished = new AsyncEvent("APPLICATION_COMMANDS_MODULE_STARTUP_FINISHED", TimeSpan.Zero, null);
+ this._globalApplicationCommandsRegistered = new AsyncEvent("GLOBAL_COMMANDS_REGISTERED", TimeSpan.Zero, null);
+ this._guildApplicationCommandsRegistered = new AsyncEvent("GUILD_COMMANDS_REGISTERED", TimeSpan.Zero, null);
+
+ this.Client.GuildDownloadCompleted += async (c, e) => await this.UpdateAsync();
+ this.Client.InteractionCreated += this.CatchInteractionsOnStartup;
+ this.Client.ContextMenuInteractionCreated += this.CatchContextMenuInteractionsOnStartup;
+ }
- private async Task CatchInteractionsOnStartup(DiscordClient sender, InteractionCreateEventArgs e)
- => await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral(true).WithContent("Attention: This application is still starting up. Application commands are unavailable for now."));
+ private async Task CatchInteractionsOnStartup(DiscordClient sender, InteractionCreateEventArgs e)
+ => await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral(true).WithContent("Attention: This application is still starting up. Application commands are unavailable for now."));
- private async Task CatchContextMenuInteractionsOnStartup(DiscordClient sender, ContextMenuInteractionCreateEventArgs e)
- => await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral(true).WithContent("Attention: This application is still starting up. Context menu commands are unavailable for now."));
+ private async Task CatchContextMenuInteractionsOnStartup(DiscordClient sender, ContextMenuInteractionCreateEventArgs e)
+ => await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral(true).WithContent("Attention: This application is still starting up. Context menu commands are unavailable for now."));
- private void FinishedRegistration()
- {
- this.Client.InteractionCreated -= this.CatchInteractionsOnStartup;
- this.Client.ContextMenuInteractionCreated -= this.CatchContextMenuInteractionsOnStartup;
+ private void FinishedRegistration()
+ {
+ this.Client.InteractionCreated -= this.CatchInteractionsOnStartup;
+ this.Client.ContextMenuInteractionCreated -= this.CatchContextMenuInteractionsOnStartup;
- this.Client.InteractionCreated += this.InteractionHandler;
- this.Client.ContextMenuInteractionCreated += this.ContextMenuHandler;
- }
- ///
- /// Cleans the module for a new start of the bot.
- /// DO NOT USE IF YOU DON'T KNOW WHAT IT DOES.
- ///
- public void CleanModule()
- {
- this._updateList.Clear();
- s_singletonModules.Clear();
- s_errored = false;
- s_expectedCount = 0;
- s_registrationCount = 0;
- s_commandMethods.Clear();
- s_groupCommands.Clear();
- s_contextMenuCommands.Clear();
- s_subGroupCommands.Clear();
- s_singletonModules.Clear();
- s_registeredCommands.Clear();
- GlobalCommandsInternal.Clear();
- GuildCommandsInternal.Clear();
- }
+ this.Client.InteractionCreated += this.InteractionHandler;
+ this.Client.ContextMenuInteractionCreated += this.ContextMenuHandler;
+ }
+ ///
+ /// Cleans the module for a new start of the bot.
+ /// DO NOT USE IF YOU DON'T KNOW WHAT IT DOES.
+ ///
+ public void CleanModule()
+ {
+ this._updateList.Clear();
+ s_singletonModules.Clear();
+ s_errored = false;
+ s_expectedCount = 0;
+ s_registrationCount = 0;
+ s_commandMethods.Clear();
+ s_groupCommands.Clear();
+ s_contextMenuCommands.Clear();
+ s_subGroupCommands.Clear();
+ s_singletonModules.Clear();
+ s_registeredCommands.Clear();
+ GlobalCommandsInternal.Clear();
+ GuildCommandsInternal.Clear();
+ }
- ///
- /// Registers a command class.
- ///
- /// The command class to register.
- public void RegisterGlobalCommands() where T : ApplicationCommandsModule
- {
- if (this.Client.ShardId == 0)
- this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(typeof(T))));
- }
- ///
- /// Registers a command class.
- ///
- /// The of the command class to register.
- public void RegisterGlobalCommands(Type type)
- {
- if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
- throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
- //If sharding, only register for shard 0
- if (this.Client.ShardId == 0)
- this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(type)));
- }
+ ///
+ /// Registers a command class.
+ ///
+ /// The command class to register.
+ public void RegisterGlobalCommands() where T : ApplicationCommandsModule
+ {
+ if (this.Client.ShardId == 0)
+ this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(typeof(T))));
+ }
+ ///
+ /// Registers a command class.
+ ///
+ /// The of the command class to register.
+ public void RegisterGlobalCommands(Type type)
+ {
+ if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
+ throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
+ //If sharding, only register for shard 0
+ if (this.Client.ShardId == 0)
+ this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(type)));
+ }
- ///
- /// Cleans all guild application commands.
- /// You normally don't need to execute it.
- ///
- public async Task CleanGuildCommandsAsync()
+ ///
+ /// Cleans all guild application commands.
+ /// You normally don't need to execute it.
+ ///
+ public async Task CleanGuildCommandsAsync()
+ {
+ foreach (var guild in this.Client.Guilds.Values)
{
- foreach (var guild in this.Client.Guilds.Values)
- {
- await this.Client.BulkOverwriteGuildApplicationCommandsAsync(guild.Id, Array.Empty());
- }
+ await this.Client.BulkOverwriteGuildApplicationCommandsAsync(guild.Id, Array.Empty());
}
+ }
- ///
- /// Cleans the global application commands.
- /// You normally don't need to execute it.
- ///
- public async Task CleanGlobalCommandsAsync()
- => await this.Client.BulkOverwriteGlobalApplicationCommandsAsync(Array.Empty());
-
- ///
- /// Registers a command class with permission and translation setup.
- ///
- /// The command class to register.
- /// The guild id to register it on.
- /// A callback to setup translations with.
- public void RegisterGuildCommands(ulong guildId, Action translationSetup = null) where T : ApplicationCommandsModule
- {
- if (this.Client.ShardId == 0)
- this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(typeof(T), translationSetup)));
- }
+ ///
+ /// Cleans the global application commands.
+ /// You normally don't need to execute it.
+ ///
+ public async Task CleanGlobalCommandsAsync()
+ => await this.Client.BulkOverwriteGlobalApplicationCommandsAsync(Array.Empty());
- ///
- /// Registers a command class with permission and translation setup.
- ///
- /// The of the command class to register.
- /// The guild id to register it on.
- /// A callback to setup translations with.
- public void RegisterGuildCommands(Type type, ulong guildId, Action translationSetup = null)
- {
- if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
- throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
- //If sharding, only register for shard 0
- if (this.Client.ShardId == 0)
- this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(type, translationSetup)));
- }
+ ///
+ /// Registers a command class with permission and translation setup.
+ ///
+ /// The command class to register.
+ /// The guild id to register it on.
+ /// A callback to setup translations with.
+ public void RegisterGuildCommands(ulong guildId, Action translationSetup = null) where T : ApplicationCommandsModule
+ {
+ if (this.Client.ShardId == 0)
+ this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(typeof(T), translationSetup)));
+ }
- ///
- /// Registers a command class with permission setup but without a guild id.
- ///
- /// The command class to register.
- /// A callback to setup translations with.
- public void RegisterGlobalCommands(Action translationSetup = null) where T : ApplicationCommandsModule
- {
- if (this.Client.ShardId == 0)
- this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(typeof(T), translationSetup)));
- }
+ ///
+ /// Registers a command class with permission and translation setup.
+ ///
+ /// The of the command class to register.
+ /// The guild id to register it on.
+ /// A callback to setup translations with.
+ public void RegisterGuildCommands(Type type, ulong guildId, Action translationSetup = null)
+ {
+ if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
+ throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
+ //If sharding, only register for shard 0
+ if (this.Client.ShardId == 0)
+ this._updateList.Add(new KeyValuePair(guildId, new ApplicationCommandsModuleConfiguration(type, translationSetup)));
+ }
- ///
- /// Registers a command class with permission setup but without a guild id.
- ///
- /// The of the command class to register.
- /// A callback to setup translations with.
- public void RegisterGlobalCommands(Type type, Action translationSetup = null)
- {
- if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
- throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
- //If sharding, only register for shard 0
- if (this.Client.ShardId == 0)
- this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(type, translationSetup)));
- }
+ ///
+ /// Registers a command class with permission setup but without a guild id.
+ ///
+ /// The command class to register.
+ /// A callback to setup translations with.
+ public void RegisterGlobalCommands(Action translationSetup = null) where T : ApplicationCommandsModule
+ {
+ if (this.Client.ShardId == 0)
+ this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(typeof(T), translationSetup)));
+ }
- ///
- /// Fired when the application commands module is ready.
- ///
- public event AsyncEventHandler ApplicationCommandsModuleReady
- {
- add => this._applicationCommandsModuleReady.Register(value);
- remove => this._applicationCommandsModuleReady.Unregister(value);
- }
- private AsyncEvent _applicationCommandsModuleReady;
+ ///
+ /// Registers a command class with permission setup but without a guild id.
+ ///
+ /// The of the command class to register.
+ /// A callback to setup translations with.
+ public void RegisterGlobalCommands(Type type, Action translationSetup = null)
+ {
+ if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
+ throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
+ //If sharding, only register for shard 0
+ if (this.Client.ShardId == 0)
+ this._updateList.Add(new KeyValuePair(null, new ApplicationCommandsModuleConfiguration(type, translationSetup)));
+ }
- ///
- /// Fired when the application commands modules startup is finished.
- ///
- public event AsyncEventHandler ApplicationCommandsModuleStartupFinished
- {
- add => this._applicationCommandsModuleStartupFinished.Register(value);
- remove => this._applicationCommandsModuleStartupFinished.Unregister(value);
- }
- private AsyncEvent _applicationCommandsModuleStartupFinished;
+ ///
+ /// Fired when the application commands module is ready.
+ ///
+ public event AsyncEventHandler ApplicationCommandsModuleReady
+ {
+ add => this._applicationCommandsModuleReady.Register(value);
+ remove => this._applicationCommandsModuleReady.Unregister(value);
+ }
+ private AsyncEvent _applicationCommandsModuleReady;
+ ///
+ /// Fired when the application commands modules startup is finished.
+ ///
+ public event AsyncEventHandler ApplicationCommandsModuleStartupFinished
+ {
+ add => this._applicationCommandsModuleStartupFinished.Register(value);
+ remove => this._applicationCommandsModuleStartupFinished.Unregister(value);
+ }
+ private AsyncEvent _applicationCommandsModuleStartupFinished;
- ///
- /// Fired when guild commands are registered on a guild.
- ///
- public event AsyncEventHandler GuildApplicationCommandsRegistered
- {
- add => this._guildApplicationCommandsRegistered.Register(value);
- remove => this._guildApplicationCommandsRegistered.Unregister(value);
- }
- private AsyncEvent _guildApplicationCommandsRegistered;
- ///
- /// Fired when the global commands are registered.
- ///
- public event AsyncEventHandler GlobalApplicationCommandsRegistered
- {
- add => this._globalApplicationCommandsRegistered.Register(value);
- remove => this._globalApplicationCommandsRegistered.Unregister(value);
- }
- private AsyncEvent _globalApplicationCommandsRegistered;
+ ///
+ /// Fired when guild commands are registered on a guild.
+ ///
+ public event AsyncEventHandler GuildApplicationCommandsRegistered
+ {
+ add => this._guildApplicationCommandsRegistered.Register(value);
+ remove => this._guildApplicationCommandsRegistered.Unregister(value);
+ }
+ private AsyncEvent _guildApplicationCommandsRegistered;
- ///
- /// Used for RegisterCommands and the event.
- ///
- internal async Task UpdateAsync()
+ ///
+ /// Fired when the global commands are registered.
+ ///
+ public event AsyncEventHandler GlobalApplicationCommandsRegistered
+ {
+ add => this._globalApplicationCommandsRegistered.Register(value);
+ remove => this._globalApplicationCommandsRegistered.Unregister(value);
+ }
+ private AsyncEvent _globalApplicationCommandsRegistered;
+
+ ///
+ /// Used for RegisterCommands and the event.
+ ///
+ internal async Task UpdateAsync()
+ {
+ //Only update for shard 0
+ if (this.Client.ShardId == 0)
{
- //Only update for shard 0
- if (this.Client.ShardId == 0)
- {
- GlobalDiscordCommands = new();
- GuildDiscordCommands = new();
+ GlobalDiscordCommands = new();
+ GuildDiscordCommands = new();
- var commandsPending = this._updateList.Select(x => x.Key).Distinct();
- s_expectedCount = commandsPending.Count();
+ var commandsPending = this._updateList.Select(x => x.Key).Distinct();
+ s_expectedCount = commandsPending.Count();
- this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Expected Count: {s_expectedCount}");
+ this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Expected Count: {s_expectedCount}");
- List failedGuilds = new();
- IEnumerable globalCommands = null;
- globalCommands = await this.Client.GetGlobalApplicationCommandsAsync(Configuration?.EnableLocalization ?? false) ?? null;
- var guilds = CheckAllGuilds ? this.Client.Guilds?.Keys : this._updateList.Select(x => x.Key)?.Distinct().Where(x => x != null)?.Select(x => x.Value);
+ List failedGuilds = new();
+ IEnumerable globalCommands = null;
+ globalCommands = await this.Client.GetGlobalApplicationCommandsAsync(Configuration?.EnableLocalization ?? false) ?? null;
+ var guilds = CheckAllGuilds ? this.Client.Guilds?.Keys : this._updateList.Select(x => x.Key)?.Distinct().Where(x => x != null)?.Select(x => x.Value);
- foreach (var guild in guilds)
+ foreach (var guild in guilds)
+ {
+ IEnumerable commands = null;
+ var unauthorized = false;
+ try
{
- IEnumerable commands = null;
- var unauthorized = false;
- try
- {
- commands = await this.Client.GetGuildApplicationCommandsAsync(guild, Configuration?.EnableLocalization ?? false) ?? null;
- }
- catch (UnauthorizedException)
- {
- unauthorized = true;
- }
- finally
- {
- if (!unauthorized && commands != null && commands.Any())
- GuildDiscordCommands.Add(guild, commands.ToList());
- else if (!unauthorized)
- GuildDiscordCommands.Add(guild, null);
- else
- failedGuilds.Add(guild);
- }
+ commands = await this.Client.GetGuildApplicationCommandsAsync(guild, Configuration?.EnableLocalization ?? false) ?? null;
}
-
- //Default should be to add the help and slash commands can be added without setting any configuration
- //so this should still add the default help
- if (Configuration is null || (Configuration is not null && Configuration.EnableDefaultHelp))
+ catch (UnauthorizedException)
{
- this._updateList.Add(new KeyValuePair
- (null, new ApplicationCommandsModuleConfiguration(typeof(DefaultHelpModule))));
- commandsPending = this._updateList.Select(x => x.Key).Distinct();
+ unauthorized = true;
}
-
- if (globalCommands != null && globalCommands.Any())
- GlobalDiscordCommands.AddRange(globalCommands);
-
- foreach (var key in commandsPending.ToList())
+ finally
{
- this.Client.Logger.LogInformation(key.HasValue ? $"Registering commands in guild {key.Value}" : "Registering global commands.");
- await this.RegisterCommands(this._updateList.Where(x => x.Key == key).Select(x => x.Value), key);
+ if (!unauthorized && commands != null && commands.Any())
+ GuildDiscordCommands.Add(guild, commands.ToList());
+ else if (!unauthorized)
+ GuildDiscordCommands.Add(guild, null);
+ else
+ failedGuilds.Add(guild);
}
+ }
+
+ //Default should be to add the help and slash commands can be added without setting any configuration
+ //so this should still add the default help
+ if (Configuration is null || (Configuration is not null && Configuration.EnableDefaultHelp))
+ {
+ this._updateList.Add(new KeyValuePair
+ (null, new ApplicationCommandsModuleConfiguration(typeof(DefaultHelpModule))));
+ commandsPending = this._updateList.Select(x => x.Key).Distinct();
+ }
- this._missingScopeGuildIds = failedGuilds;
+ if (globalCommands != null && globalCommands.Any())
+ GlobalDiscordCommands.AddRange(globalCommands);
- await this._applicationCommandsModuleReady.InvokeAsync(this, new ApplicationCommandsModuleReadyEventArgs(Configuration?.ServiceProvider)
- {
- Handled = true,
- GuildsWithoutScope = failedGuilds
- });
+ foreach (var key in commandsPending.ToList())
+ {
+ this.Client.Logger.LogInformation(key.HasValue ? $"Registering commands in guild {key.Value}" : "Registering global commands.");
+ await this.RegisterCommands(this._updateList.Where(x => x.Key == key).Select(x => x.Value), key);
}
+
+ this._missingScopeGuildIds = failedGuilds;
+
+ await this._applicationCommandsModuleReady.InvokeAsync(this, new ApplicationCommandsModuleReadyEventArgs(Configuration?.ServiceProvider)
+ {
+ Handled = true,
+ GuildsWithoutScope = failedGuilds
+ });
}
+ }
- ///
- /// Method for registering commands for a target from modules.
- ///
- /// The types.
- /// The optional guild id.
- private async Task RegisterCommands(IEnumerable types, ulong? guildId)
- {
- //Initialize empty lists to be added to the global ones at the end
- var commandMethods = new List();
- var groupCommands = new List();
- var subGroupCommands = new List();
- var contextMenuCommands = new List();
- var updateList = new List();
+ ///
+ /// Method for registering commands for a target from modules.
+ ///
+ /// The types.
+ /// The optional guild id.
+ private async Task RegisterCommands(IEnumerable types, ulong? guildId)
+ {
+ //Initialize empty lists to be added to the global ones at the end
+ var commandMethods = new List();
+ var groupCommands = new List();
+ var subGroupCommands = new List();
+ var contextMenuCommands = new List();
+ var updateList = new List();
- var commandTypeSources = new List>();
+ var commandTypeSources = new List>();
- //Iterates over all the modules
- foreach (var config in types)
+ //Iterates over all the modules
+ foreach (var config in types)
+ {
+ var type = config.Type;
+ try
{
- var type = config.Type;
- try
- {
- var module = type.GetTypeInfo();
- var classes = new List();
+ var module = type.GetTypeInfo();
+ var classes = new List();
- var ctx = new ApplicationCommandsTranslationContext(type, module.FullName);
- config.Translations?.Invoke(ctx);
+ var ctx = new ApplicationCommandsTranslationContext(type, module.FullName);
+ config.Translations?.Invoke(ctx);
- //Add module to classes list if it's a group
- if (module.GetCustomAttribute() != null)
- {
- classes.Add(module);
- }
- else
- {
- //Otherwise add the nested groups
- classes = module.DeclaredNestedTypes.Where(x => x.GetCustomAttribute() != null).ToList();
- }
+ //Add module to classes list if it's a group
+ if (module.GetCustomAttribute() != null)
+ {
+ classes.Add(module);
+ }
+ else
+ {
+ //Otherwise add the nested groups
+ classes = module.DeclaredNestedTypes.Where(x => x.GetCustomAttribute() != null).ToList();
+ }
- List groupTranslations = null;
+ List groupTranslations = null;
- if (!string.IsNullOrEmpty(ctx.Translations))
- {
- groupTranslations = JsonConvert.DeserializeObject>(ctx.Translations);
- }
+ if (!string.IsNullOrEmpty(ctx.Translations))
+ {
+ groupTranslations = JsonConvert.DeserializeObject>(ctx.Translations);
+ }
- var slashGroupsTuple = NestedCommandWorker.ParseSlashGroupsAsync(type, classes, guildId, groupTranslations).Result;
+ var slashGroupsTuple = NestedCommandWorker.ParseSlashGroupsAsync(type, classes, guildId, groupTranslations).Result;
- if (slashGroupsTuple.applicationCommands != null && slashGroupsTuple.applicationCommands.Any())
- updateList.AddRange(slashGroupsTuple.applicationCommands);
+ if (slashGroupsTuple.applicationCommands != null && slashGroupsTuple.applicationCommands.Any())
+ updateList.AddRange(slashGroupsTuple.applicationCommands);
- if (slashGroupsTuple.commandTypeSources != null && slashGroupsTuple.commandTypeSources.Any())
- commandTypeSources.AddRange(slashGroupsTuple.commandTypeSources);
+ if (slashGroupsTuple.commandTypeSources != null && slashGroupsTuple.commandTypeSources.Any())
+ commandTypeSources.AddRange(slashGroupsTuple.commandTypeSources);
- if (slashGroupsTuple.singletonModules != null && slashGroupsTuple.singletonModules.Any())
- s_singletonModules.AddRange(slashGroupsTuple.singletonModules);
+ if (slashGroupsTuple.singletonModules != null && slashGroupsTuple.singletonModules.Any())
+ s_singletonModules.AddRange(slashGroupsTuple.singletonModules);
- if (slashGroupsTuple.groupCommands != null && slashGroupsTuple.groupCommands.Any())
- groupCommands.AddRange(slashGroupsTuple.groupCommands);
+ if (slashGroupsTuple.groupCommands != null && slashGroupsTuple.groupCommands.Any())
+ groupCommands.AddRange(slashGroupsTuple.groupCommands);
- if (slashGroupsTuple.subGroupCommands != null && slashGroupsTuple.subGroupCommands.Any())
- subGroupCommands.AddRange(slashGroupsTuple.subGroupCommands);
+ if (slashGroupsTuple.subGroupCommands != null && slashGroupsTuple.subGroupCommands.Any())
+ subGroupCommands.AddRange(slashGroupsTuple.subGroupCommands);
- //Handles methods and context menus, only if the module isn't a group itself
- if (module.GetCustomAttribute() == null)
- {
- List commandTranslations = null;
+ //Handles methods and context menus, only if the module isn't a group itself
+ if (module.GetCustomAttribute() == null)
+ {
+ List commandTranslations = null;
- if (!string.IsNullOrEmpty(ctx.Translations))
- {
- commandTranslations = JsonConvert.DeserializeObject>(ctx.Translations);
- }
+ if (!string.IsNullOrEmpty(ctx.Translations))
+ {
+ commandTranslations = JsonConvert.DeserializeObject>(ctx.Translations);
+ }
- //Slash commands
- var methods = module.DeclaredMethods.Where(x => x.GetCustomAttribute() != null);
+ //Slash commands
+ var methods = module.DeclaredMethods.Where(x => x.GetCustomAttribute() != null);
- var slashCommands = CommandWorker.ParseBasicSlashCommandsAsync(type, methods, guildId, commandTranslations).Result;
+ var slashCommands = CommandWorker.ParseBasicSlashCommandsAsync(type, methods, guildId, commandTranslations).Result;
- if (slashCommands.applicationCommands != null && slashCommands.applicationCommands.Any())
- updateList.AddRange(slashCommands.applicationCommands);
+ if (slashCommands.applicationCommands != null && slashCommands.applicationCommands.Any())
+ updateList.AddRange(slashCommands.applicationCommands);
- if (slashCommands.commandTypeSources != null && slashCommands.commandTypeSources.Any())
- commandTypeSources.AddRange(slashCommands.commandTypeSources);
+ if (slashCommands.commandTypeSources != null && slashCommands.commandTypeSources.Any())
+ commandTypeSources.AddRange(slashCommands.commandTypeSources);
- if (slashCommands.commandMethods != null && slashCommands.commandMethods.Any())
- commandMethods.AddRange(slashCommands.commandMethods);
+ if (slashCommands.commandMethods != null && slashCommands.commandMethods.Any())
+ commandMethods.AddRange(slashCommands.commandMethods);
- //Context Menus
- var contextMethods = module.DeclaredMethods.Where(x => x.GetCustomAttribute() != null);
+ //Context Menus
+ var contextMethods = module.DeclaredMethods.Where(x => x.GetCustomAttribute() != null);
- var contextCommands = CommandWorker.ParseContextMenuCommands(type, contextMethods, commandTranslations).Result;
+ var contextCommands = CommandWorker.ParseContextMenuCommands(type, contextMethods, commandTranslations).Result;
- if (contextCommands.applicationCommands != null && contextCommands.applicationCommands.Any())
- updateList.AddRange(contextCommands.applicationCommands);
+ if (contextCommands.applicationCommands != null && contextCommands.applicationCommands.Any())
+ updateList.AddRange(contextCommands.applicationCommands);
- if (contextCommands.commandTypeSources != null && contextCommands.commandTypeSources.Any())
- commandTypeSources.AddRange(contextCommands.commandTypeSources);
+ if (contextCommands.commandTypeSources != null && contextCommands.commandTypeSources.Any())
+ commandTypeSources.AddRange(contextCommands.commandTypeSources);
- if (contextCommands.contextMenuCommands != null && contextCommands.contextMenuCommands.Any())
- contextMenuCommands.AddRange(contextCommands.contextMenuCommands);
+ if (contextCommands.contextMenuCommands != null && contextCommands.contextMenuCommands.Any())
+ contextMenuCommands.AddRange(contextCommands.contextMenuCommands);
- //Accounts for lifespans
- if (module.GetCustomAttribute() != null && module.GetCustomAttribute().Lifespan == ApplicationCommandModuleLifespan.Singleton)
- {
- s_singletonModules.Add(CreateInstance(module, Configuration?.ServiceProvider));
- }
+ //Accounts for lifespans
+ if (module.GetCustomAttribute() != null && module.GetCustomAttribute().Lifespan == ApplicationCommandModuleLifespan.Singleton)
+ {
+ s_singletonModules.Add(CreateInstance(module, Configuration?.ServiceProvider));
}
}
- catch (Exception ex)
- {
- if (ex is BadRequestException brex)
- this.Client.Logger.LogCritical(brex, $"There was an error registering application commands: {brex.JsonMessage}");
- else
- this.Client.Logger.LogCritical(ex, $"There was an error parsing the application commands");
- s_errored = true;
- }
}
- if (!s_errored)
+ catch (Exception ex)
{
+ if (ex is BadRequestException brex)
+ this.Client.Logger.LogCritical(brex, $"There was an error registering application commands: {brex.JsonMessage}");
+ else
+ this.Client.Logger.LogCritical(ex, $"There was an error parsing the application commands");
+ s_errored = true;
+ }
+ }
+ if (!s_errored)
+ {
+ try
+ {
+ List commands = new();
+
try
{
- List commands = new();
-
- try
+ if (guildId == null)
{
- if (guildId == null)
+ if (updateList != null && updateList.Any())
{
- if (updateList != null && updateList.Any())
- {
- var regCommands = RegistrationWorker.RegisterGlobalCommandsAsync(updateList).Result;
- var actualCommands = regCommands.Distinct().ToList();
- commands.AddRange(actualCommands);
- GlobalCommandsInternal.AddRange(actualCommands);
- }
- else
+ var regCommands = RegistrationWorker.RegisterGlobalCommandsAsync(updateList).Result;
+ var actualCommands = regCommands.Distinct().ToList();
+ commands.AddRange(actualCommands);
+ GlobalCommandsInternal.AddRange(actualCommands);
+ }
+ else
+ {
+ foreach (var cmd in GlobalDiscordCommands)
{
- foreach (var cmd in GlobalDiscordCommands)
+ try
{
- try
- {
- await this.Client.DeleteGlobalApplicationCommandAsync(cmd.Id);
- }
- catch (NotFoundException)
- {
- this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Could not delete global command {cmd.Id}. Please clean up manually");
- }
+ await this.Client.DeleteGlobalApplicationCommandAsync(cmd.Id);
}
+ catch (NotFoundException)
+ {
+ this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Could not delete global command {cmd.Id}. Please clean up manually");
+ }
+ }
+ }
+ }
+ else
+ {
+ if (updateList != null && updateList.Any())
+ {
+ var regCommands = RegistrationWorker.RegisterGuilldCommandsAsync(guildId.Value, updateList).Result;
+ var actualCommands = regCommands.Distinct().ToList();
+ commands.AddRange(actualCommands);
+ GuildCommandsInternal.Add(guildId.Value, actualCommands);
+ if (this.Client.Guilds.TryGetValue(guildId.Value, out var guild))
+ {
+ guild.InternalRegisteredApplicationCommands = new();
+ guild.InternalRegisteredApplicationCommands.AddRange(actualCommands);
}
+
}
else
{
- if (updateList != null && updateList.Any())
+ foreach (var cmd in GuildDiscordCommands[guildId.Value])
{
- var regCommands = RegistrationWorker.RegisterGuilldCommandsAsync(guildId.Value, updateList).Result;
- var actualCommands = regCommands.Distinct().ToList();
- commands.AddRange(actualCommands);
- GuildCommandsInternal.Add(guildId.Value, actualCommands);
- if (this.Client.Guilds.TryGetValue(guildId.Value, out var guild))
+ try
{
- guild.InternalRegisteredApplicationCommands = new();
- guild.InternalRegisteredApplicationCommands.AddRange(actualCommands);
+ await this.Client.DeleteGuildApplicationCommandAsync(guildId.Value, cmd.Id);
}
-
- }
- else
- {
- foreach (var cmd in GuildDiscordCommands[guildId.Value])
+ catch (NotFoundException)
{
- try
- {
- await this.Client.DeleteGuildApplicationCommandAsync(guildId.Value, cmd.Id);
- }
- catch (NotFoundException)
- {
- this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Could not delete guild command {cmd.Id} in guild {guildId.Value}. Please clean up manually");
- }
+ this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Could not delete guild command {cmd.Id} in guild {guildId.Value}. Please clean up manually");
}
}
}
}
- catch (UnauthorizedException ex)
- {
- this.Client.Logger.LogError($"Could not register application commands for guild {guildId}.\nError: {ex.JsonMessage}");
- return;
- }
- //Creates a guild command if a guild id is specified, otherwise global
- //Checks against the ids and adds them to the command method lists
- foreach (var command in commands)
- {
- if (commandMethods.GetFirstValueWhere(x => x.Name == command.Name, out var com))
- com.CommandId = command.Id;
- else if (groupCommands.GetFirstValueWhere(x => x.Name == command.Name, out var groupCom))
- groupCom.CommandId = command.Id;
- else if (subGroupCommands.GetFirstValueWhere(x => x.Name == command.Name, out var subCom))
- subCom.CommandId = command.Id;
- else if (contextMenuCommands.GetFirstValueWhere(x => x.Name == command.Name, out var cmCom))
- cmCom.CommandId = command.Id;
- }
+ }
+ catch (UnauthorizedException ex)
+ {
+ this.Client.Logger.LogError($"Could not register application commands for guild {guildId}.\nError: {ex.JsonMessage}");
+ return;
+ }
+ //Creates a guild command if a guild id is specified, otherwise global
+ //Checks against the ids and adds them to the command method lists
+ foreach (var command in commands)
+ {
+ if (commandMethods.GetFirstValueWhere(x => x.Name == command.Name, out var com))
+ com.CommandId = command.Id;
+ else if (groupCommands.GetFirstValueWhere(x => x.Name == command.Name, out var groupCom))
+ groupCom.CommandId = command.Id;
+ else if (subGroupCommands.GetFirstValueWhere(x => x.Name == command.Name, out var subCom))
+ subCom.CommandId = command.Id;
+ else if (contextMenuCommands.GetFirstValueWhere(x => x.Name == command.Name, out var cmCom))
+ cmCom.CommandId = command.Id;
+ }
- //Adds to the global lists finally
- s_commandMethods.AddRange(commandMethods);
- s_groupCommands.AddRange(groupCommands);
- s_subGroupCommands.AddRange(subGroupCommands);
- s_contextMenuCommands.AddRange(contextMenuCommands);
+ //Adds to the global lists finally
+ s_commandMethods.AddRange(commandMethods);
+ s_groupCommands.AddRange(groupCommands);
+ s_subGroupCommands.AddRange(subGroupCommands);
+ s_contextMenuCommands.AddRange(contextMenuCommands);
- s_registeredCommands.Add(new KeyValuePair>(guildId, commands.ToList()));
+ s_registeredCommands.Add(new KeyValuePair>(guildId, commands.ToList()));
- foreach (var command in commandMethods)
- {
- var app = types.First(t => t.Type == command.Method.DeclaringType);
- }
+ foreach (var command in commandMethods)
+ {
+ var app = types.First(t => t.Type == command.Method.DeclaringType);
+ }
- this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Expected Count: {s_expectedCount}\nCurrent Count: {s_registrationCount}");
+ this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Expected Count: {s_expectedCount}\nCurrent Count: {s_registrationCount}");
- if (guildId.HasValue)
- {
- await this._guildApplicationCommandsRegistered.InvokeAsync(this, new GuildApplicationCommandsRegisteredEventArgs(Configuration?.ServiceProvider)
- {
- Handled = true,
- GuildId = guildId.Value,
- RegisteredCommands = GuildCommandsInternal.Any(c => c.Key == guildId.Value) ? GuildCommandsInternal.FirstOrDefault(c => c.Key == guildId.Value).Value : null
- });
- }
- else
+ if (guildId.HasValue)
+ {
+ await this._guildApplicationCommandsRegistered.InvokeAsync(this, new GuildApplicationCommandsRegisteredEventArgs(Configuration?.ServiceProvider)
{
- await this._globalApplicationCommandsRegistered.InvokeAsync(this, new GlobalApplicationCommandsRegisteredEventArgs(Configuration?.ServiceProvider)
- {
- Handled = true,
- RegisteredCommands = GlobalCommandsInternal
- });
- }
-
- s_registrationCount++;
- this.CheckRegistrationStartup(ManOr);
+ Handled = true,
+ GuildId = guildId.Value,
+ RegisteredCommands = GuildCommandsInternal.Any(c => c.Key == guildId.Value) ? GuildCommandsInternal.FirstOrDefault(c => c.Key == guildId.Value).Value : null
+ });
}
- catch (Exception ex)
+ else
{
- if (ex is BadRequestException brex)
- this.Client.Logger.LogCritical(brex, $"There was an error registering application commands: {brex.JsonMessage}");
- else
- this.Client.Logger.LogCritical(ex, $"There was an general error registering application commands");
- s_errored = true;
+ await this._globalApplicationCommandsRegistered.InvokeAsync(this, new GlobalApplicationCommandsRegisteredEventArgs(Configuration?.ServiceProvider)
+ {
+ Handled = true,
+ RegisteredCommands = GlobalCommandsInternal
+ });
}
+
+ s_registrationCount++;
+ this.CheckRegistrationStartup(ManOr);
+ }
+ catch (Exception ex)
+ {
+ if (ex is BadRequestException brex)
+ this.Client.Logger.LogCritical(brex, $"There was an error registering application commands: {brex.JsonMessage}");
+ else
+ this.Client.Logger.LogCritical(ex, $"There was an general error registering application commands");
+ s_errored = true;
}
}
+ }
- private async void CheckRegistrationStartup(bool man = false)
- {
- this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Checking counts...\n\nExpected Count: {s_expectedCount}\nCurrent Count: {s_registrationCount}");
+ private async void CheckRegistrationStartup(bool man = false)
+ {
+ this.Client.Logger.Log(ApplicationCommandsLogLevel, $"Checking counts...\n\nExpected Count: {s_expectedCount}\nCurrent Count: {s_registrationCount}");
- if ((s_registrationCount == s_expectedCount) || man)
+ if ((s_registrationCount == s_expectedCount) || man)
+ {
+ await this._applicationCommandsModuleStartupFinished.InvokeAsync(this, new ApplicationCommandsModuleStartupFinishedEventArgs(Configuration?.ServiceProvider)
{
- await this._applicationCommandsModuleStartupFinished.InvokeAsync(this, new ApplicationCommandsModuleStartupFinishedEventArgs(Configuration?.ServiceProvider)
- {
- Handled = true,
- RegisteredGlobalCommands = GlobalCommandsInternal,
- RegisteredGuildCommands = GuildCommandsInternal,
- GuildsWithoutScope = this._missingScopeGuildIds
- });
+ Handled = true,
+ RegisteredGlobalCommands = GlobalCommandsInternal,
+ RegisteredGuildCommands = GuildCommandsInternal,
+ GuildsWithoutScope = this._missingScopeGuildIds
+ });
- this.FinishedRegistration();
- }
+ this.FinishedRegistration();
}
+ }
- ///
- /// Interaction handler.
- ///
- /// The client.
- /// The event args.
- private Task InteractionHandler(DiscordClient client, InteractionCreateEventArgs e)
+ ///
+ /// Interaction handler.
+ ///
+ /// The client.
+ /// The event args.
+ private Task InteractionHandler(DiscordClient client, InteractionCreateEventArgs e)
+ {
+ _ = Task.Run(async () =>
{
- _ = Task.Run(async () =>
+ if (e.Interaction.Type == InteractionType.ApplicationCommand)
{
- if (e.Interaction.Type == InteractionType.ApplicationCommand)
+ //Creates the context
+ var context = new InteractionContext
{
- //Creates the context
- var context = new InteractionContext
- {
- Interaction = e.Interaction,
- Channel = e.Interaction.Channel,
- Guild = e.Interaction.Guild,
- User = e.Interaction.User,
- Client = client,
- ApplicationCommandsExtension = this,
- CommandName = e.Interaction.Data.Name,
- InteractionId = e.Interaction.Id,
- Token = e.Interaction.Token,
- Services = Configuration?.ServiceProvider,
- ResolvedUserMentions = e.Interaction.Data.Resolved?.Users?.Values.ToList(),
- ResolvedRoleMentions = e.Interaction.Data.Resolved?.Roles?.Values.ToList(),
- ResolvedChannelMentions = e.Interaction.Data.Resolved?.Channels?.Values.ToList(),
- ResolvedAttachments = e.Interaction.Data.Resolved?.Attachments?.Values.ToList(),
- Type = ApplicationCommandType.ChatInput,
- Locale = e.Interaction.Locale,
- GuildLocale = e.Interaction.GuildLocale
- };
-
- try
- {
- if (s_errored)
- throw new InvalidOperationException("Slash commands failed to register properly on startup.");
-
- var methods = s_commandMethods.Where(x => x.CommandId == e.Interaction.Data.Id);
- var groups = s_groupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
- var subgroups = s_subGroupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
- if (!methods.Any() && !groups.Any() && !subgroups.Any())
- throw new InvalidOperationException("A slash command was executed, but no command was registered for it.");
-
- if (methods.Any())
- {
- var method = methods.First().Method;
-
- var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options);
-
- await this.RunCommandAsync(context, method, args);
- }
- else if (groups.Any())
- {
- var command = e.Interaction.Data.Options.First();
- var method = groups.First().Methods.First(x => x.Key == command.Name).Value;
-
- var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options.First().Options);
-
- await this.RunCommandAsync(context, method, args);
- }
- else if (subgroups.Any())
- {
- var command = e.Interaction.Data.Options.First();
- var group = subgroups.First().SubCommands.First(x => x.Name == command.Name);
-
- var method = group.Methods.First(x => x.Key == command.Options.First().Name).Value;
-
- var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options.First().Options.First().Options);
-
- await this.RunCommandAsync(context, method, args);
- }
+ Interaction = e.Interaction,
+ Channel = e.Interaction.Channel,
+ Guild = e.Interaction.Guild,
+ User = e.Interaction.User,
+ Client = client,
+ ApplicationCommandsExtension = this,
+ CommandName = e.Interaction.Data.Name,
+ InteractionId = e.Interaction.Id,
+ Token = e.Interaction.Token,
+ Services = Configuration?.ServiceProvider,
+ ResolvedUserMentions = e.Interaction.Data.Resolved?.Users?.Values.ToList(),
+ ResolvedRoleMentions = e.Interaction.Data.Resolved?.Roles?.Values.ToList(),
+ ResolvedChannelMentions = e.Interaction.Data.Resolved?.Channels?.Values.ToList(),
+ ResolvedAttachments = e.Interaction.Data.Resolved?.Attachments?.Values.ToList(),
+ Type = ApplicationCommandType.ChatInput,
+ Locale = e.Interaction.Locale,
+ GuildLocale = e.Interaction.GuildLocale
+ };
- await this._slashExecuted.InvokeAsync(this, new SlashCommandExecutedEventArgs(this.Client.ServiceProvider) { Context = context });
- }
- catch (Exception ex)
- {
- await this._slashError.InvokeAsync(this, new SlashCommandErrorEventArgs(this.Client.ServiceProvider) { Context = context, Exception = ex });
- }
- }
- else if (e.Interaction.Type == InteractionType.AutoComplete)
+ try
{
if (s_errored)
throw new InvalidOperationException("Slash commands failed to register properly on startup.");
var methods = s_commandMethods.Where(x => x.CommandId == e.Interaction.Data.Id);
var groups = s_groupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
var subgroups = s_subGroupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
if (!methods.Any() && !groups.Any() && !subgroups.Any())
- throw new InvalidOperationException("An autocomplete interaction was created, but no command was registered for it.");
+ throw new InvalidOperationException("A slash command was executed, but no command was registered for it.");
- try
+ if (methods.Any())
{
- if (methods.Any())
- {
- var focusedOption = e.Interaction.Data.Options.First(o => o.Focused);
- var method = methods.First().Method;
+ var method = methods.First().Method;
- var option = method.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name);
- var provider = option.GetCustomAttribute().ProviderType;
- var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
- var providerInstance = Activator.CreateInstance(provider);
+ var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options);
- var context = new AutocompleteContext
- {
- Interaction = e.Interaction,
- Client = this.Client,
- Services = Configuration?.ServiceProvider,
- ApplicationCommandsExtension = this,
- Guild = e.Interaction.Guild,
- Channel = e.Interaction.Channel,
- User = e.Interaction.User,
- Options = e.Interaction.Data.Options.ToList(),
- FocusedOption = focusedOption,
- Locale = e.Interaction.Locale,
- GuildLocale = e.Interaction.GuildLocale
- };
-
- var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context });
- await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices));
- }
- else if (groups.Any())
- {
- var command = e.Interaction.Data.Options.First();
- var group = groups.First().Methods.First(x => x.Key == command.Name).Value;
+ await this.RunCommandAsync(context, method, args);
+ }
+ else if (groups.Any())
+ {
+ var command = e.Interaction.Data.Options.First();
+ var method = groups.First().Methods.First(x => x.Key == command.Name).Value;
- var focusedOption = command.Options.First(o => o.Focused);
- var option = group.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name);
- var provider = option.GetCustomAttribute().ProviderType;
- var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
- var providerInstance = Activator.CreateInstance(provider);
+ var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options.First().Options);
- var context = new AutocompleteContext
- {
- Interaction = e.Interaction,
- Services = Configuration?.ServiceProvider,
- ApplicationCommandsExtension = this,
- Guild = e.Interaction.Guild,
- Channel = e.Interaction.Channel,
- User = e.Interaction.User,
- Options = command.Options.ToList(),
- FocusedOption = focusedOption,
- Locale = e.Interaction.Locale,
- GuildLocale = e.Interaction.GuildLocale
- };
-
- var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context });
- await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices));
- }
- else if (subgroups.Any())
- {
- var command = e.Interaction.Data.Options.First();
- var group = subgroups.First().SubCommands.First(x => x.Name == command.Name).Methods.First(x => x.Key == command.Options.First().Name).Value;
+ await this.RunCommandAsync(context, method, args);
+ }
+ else if (subgroups.Any())
+ {
+ var command = e.Interaction.Data.Options.First();
+ var group = subgroups.First().SubCommands.First(x => x.Name == command.Name);
- var focusedOption = command.Options.First().Options.First(o => o.Focused);
+ var method = group.Methods.First(x => x.Key == command.Options.First().Name).Value;
- var option = group.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name);
- var provider = option.GetCustomAttribute().ProviderType;
- var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
- var providerInstance = Activator.CreateInstance(provider);
+ var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options.First().Options.First().Options);
- var context = new AutocompleteContext
- {
- Interaction = e.Interaction,
- Services = Configuration?.ServiceProvider,
- ApplicationCommandsExtension = this,
- Guild = e.Interaction.Guild,
- Channel = e.Interaction.Channel,
- User = e.Interaction.User,
- Options = command.Options.First().Options.ToList(),
- FocusedOption = focusedOption,
- Locale = e.Interaction.Locale,
- GuildLocale = e.Interaction.GuildLocale
- };
-
- var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context });
- await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices));
- }
+ await this.RunCommandAsync(context, method, args);
}
- catch (Exception ex)
- {
- this.Client.Logger.LogError(ex, "Error in autocomplete interaction");
- }
- }
- });
- return Task.CompletedTask;
- }
- ///
- /// Context menu handler.
- ///
- /// The client.
- /// The event args.
- private Task ContextMenuHandler(DiscordClient client, ContextMenuInteractionCreateEventArgs e)
- {
- _ = Task.Run(async () =>
- {
- //Creates the context
- var context = new ContextMenuContext
+ await this._slashExecuted.InvokeAsync(this, new SlashCommandExecutedEventArgs(this.Client.ServiceProvider) { Context = context });
+ }
+ catch (Exception ex)
{
- Interaction = e.Interaction,
- Channel = e.Interaction.Channel,
- Client = client,
- Services = Configuration?.ServiceProvider,
- CommandName = e.Interaction.Data.Name,
- ApplicationCommandsExtension = this,
- Guild = e.Interaction.Guild,
- InteractionId = e.Interaction.Id,
- User = e.Interaction.User,
- Token = e.Interaction.Token,
- TargetUser = e.TargetUser,
- TargetMessage = e.TargetMessage,
- Type = e.Type,
- Locale = e.Interaction.Locale,
- GuildLocale = e.Interaction.GuildLocale
- };
+ await this._slashError.InvokeAsync(this, new SlashCommandErrorEventArgs(this.Client.ServiceProvider) { Context = context, Exception = ex });
+ }
+ }
+ else if (e.Interaction.Type == InteractionType.AutoComplete)
+ {
+ if (s_errored)
+ throw new InvalidOperationException("Slash commands failed to register properly on startup.");
+
+ var methods = s_commandMethods.Where(x => x.CommandId == e.Interaction.Data.Id);
+ var groups = s_groupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
+ var subgroups = s_subGroupCommands.Where(x => x.CommandId == e.Interaction.Data.Id);
+ if (!methods.Any() && !groups.Any() && !subgroups.Any())
+ throw new InvalidOperationException("An autocomplete interaction was created, but no command was registered for it.");
try
{
- if (s_errored)
- throw new InvalidOperationException("Context menus failed to register properly on startup.");
+ if (methods.Any())
+ {
+ var focusedOption = e.Interaction.Data.Options.First(o => o.Focused);
+ var method = methods.First().Method;
+
+ var option = method.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name);
+ var provider = option.GetCustomAttribute().ProviderType;
+ var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
+ var providerInstance = Activator.CreateInstance(provider);
+
+ var context = new AutocompleteContext
+ {
+ Interaction = e.Interaction,
+ Client = this.Client,
+ Services = Configuration?.ServiceProvider,
+ ApplicationCommandsExtension = this,
+ Guild = e.Interaction.Guild,
+ Channel = e.Interaction.Channel,
+ User = e.Interaction.User,
+ Options = e.Interaction.Data.Options.ToList(),
+ FocusedOption = focusedOption,
+ Locale = e.Interaction.Locale,
+ GuildLocale = e.Interaction.GuildLocale
+ };
+
+ var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context });
+ await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices));
+ }
+ else if (groups.Any())
+ {
+ var command = e.Interaction.Data.Options.First();
+ var group = groups.First().Methods.First(x => x.Key == command.Name).Value;
+
+ var focusedOption = command.Options.First(o => o.Focused);
+ var option = group.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name);
+ var provider = option.GetCustomAttribute().ProviderType;
+ var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
+ var providerInstance = Activator.CreateInstance(provider);
- //Gets the method for the command
- var method = s_contextMenuCommands.FirstOrDefault(x => x.CommandId == e.Interaction.Data.Id);
+ var context = new AutocompleteContext
+ {
+ Interaction = e.Interaction,
+ Services = Configuration?.ServiceProvider,
+ ApplicationCommandsExtension = this,
+ Guild = e.Interaction.Guild,
+ Channel = e.Interaction.Channel,
+ User = e.Interaction.User,
+ Options = command.Options.ToList(),
+ FocusedOption = focusedOption,
+ Locale = e.Interaction.Locale,
+ GuildLocale = e.Interaction.GuildLocale
+ };
+
+ var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context });
+ await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices));
+ }
+ else if (subgroups.Any())
+ {
+ var command = e.Interaction.Data.Options.First();
+ var group = subgroups.First().SubCommands.First(x => x.Name == command.Name).Methods.First(x => x.Key == command.Options.First().Name).Value;
- if (method == null)
- throw new InvalidOperationException("A context menu was executed, but no command was registered for it.");
+ var focusedOption = command.Options.First().Options.First(o => o.Focused);
- await this.RunCommandAsync(context, method.Method, new[] { context });
+ var option = group.GetParameters().Skip(1).First(p => p.GetCustomAttribute().Name == focusedOption.Name);
+ var provider = option.GetCustomAttribute().ProviderType;
+ var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
+ var providerInstance = Activator.CreateInstance(provider);
- await this._contextMenuExecuted.InvokeAsync(this, new ContextMenuExecutedEventArgs(this.Client.ServiceProvider) { Context = context });
+ var context = new AutocompleteContext
+ {
+ Interaction = e.Interaction,
+ Services = Configuration?.ServiceProvider,
+ ApplicationCommandsExtension = this,
+ Guild = e.Interaction.Guild,
+ Channel = e.Interaction.Channel,
+ User = e.Interaction.User,
+ Options = command.Options.First().Options.ToList(),
+ FocusedOption = focusedOption,
+ Locale = e.Interaction.Locale,
+ GuildLocale = e.Interaction.GuildLocale
+ };
+
+ var choices = await (Task>) providerMethod.Invoke(providerInstance, new[] { context });
+ await e.Interaction.CreateResponseAsync(InteractionResponseType.AutoCompleteResult, new DiscordInteractionResponseBuilder().AddAutoCompleteChoices(choices));
+ }
}
catch (Exception ex)
{
- await this._contextMenuErrored.InvokeAsync(this, new ContextMenuErrorEventArgs(this.Client.ServiceProvider) { Context = context, Exception = ex });
+ this.Client.Logger.LogError(ex, "Error in autocomplete interaction");
}
- });
-
- return Task.CompletedTask;
- }
+ }
+ });
+ return Task.CompletedTask;
+ }
- ///
- /// Runs a command.
- ///
- /// The base context.
- /// The method info.
- /// The arguments.
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "")]
- internal async Task RunCommandAsync(BaseContext context, MethodInfo method, IEnumerable args)
+ ///
+ /// Context menu handler.
+ ///
+ /// The client.
+ /// The event args.
+ private Task ContextMenuHandler(DiscordClient client, ContextMenuInteractionCreateEventArgs e)
+ {
+ _ = Task.Run(async () =>
{
- object classInstance;
-
- //Accounts for lifespans
- var moduleLifespan = (method.DeclaringType.GetCustomAttribute() != null ? method.DeclaringType.GetCustomAttribute()?.Lifespan : ApplicationCommandModuleLifespan.Transient) ?? ApplicationCommandModuleLifespan.Transient;
- switch (moduleLifespan)
+ //Creates the context
+ var context = new ContextMenuContext
{
- case ApplicationCommandModuleLifespan.Scoped:
- //Accounts for static methods and adds DI
- classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(Configuration?.ServiceProvider.CreateScope().ServiceProvider, method.DeclaringType) : CreateInstance(method.DeclaringType, Configuration?.ServiceProvider.CreateScope().ServiceProvider);
- break;
-
- case ApplicationCommandModuleLifespan.Transient:
- //Accounts for static methods and adds DI
- classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(Configuration?.ServiceProvider, method.DeclaringType) : CreateInstance(method.DeclaringType, Configuration?.ServiceProvider);
- break;
-
- //If singleton, gets it from the singleton list
- case ApplicationCommandModuleLifespan.Singleton:
- classInstance = s_singletonModules.First(x => ReferenceEquals(x.GetType(), method.DeclaringType));
- break;
-
- default:
- throw new Exception($"An unknown {nameof(ApplicationCommandModuleLifespanAttribute)} scope was specified on command {context.CommandName}");
- }
-
- ApplicationCommandsModule module = null;
- if (classInstance is ApplicationCommandsModule mod)
- module = mod;
-
- // Slash commands
- if (context is InteractionContext slashContext)
+ Interaction = e.Interaction,
+ Channel = e.Interaction.Channel,
+ Client = client,
+ Services = Configuration?.ServiceProvider,
+ CommandName = e.Interaction.Data.Name,
+ ApplicationCommandsExtension = this,
+ Guild = e.Interaction.Guild,
+ InteractionId = e.Interaction.Id,
+ User = e.Interaction.User,
+ Token = e.Interaction.Token,
+ TargetUser = e.TargetUser,
+ TargetMessage = e.TargetMessage,
+ Type = e.Type,
+ Locale = e.Interaction.Locale,
+ GuildLocale = e.Interaction.GuildLocale
+ };
+
+ try
{
- await this.RunPreexecutionChecksAsync(method, slashContext);
+ if (s_errored)
+ throw new InvalidOperationException("Context menus failed to register properly on startup.");
- var shouldExecute = await (module?.BeforeSlashExecutionAsync(slashContext) ?? Task.FromResult(true));
+ //Gets the method for the command
+ var method = s_contextMenuCommands.FirstOrDefault(x => x.CommandId == e.Interaction.Data.Id);
- if (shouldExecute)
- {
- if (AutoDeferEnabled)
- await context.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource);
- await (Task)method.Invoke(classInstance, args.ToArray());
+ if (method == null)
+ throw new InvalidOperationException("A context menu was executed, but no command was registered for it.");
- await (module?.AfterSlashExecutionAsync(slashContext) ?? Task.CompletedTask);
- }
+ await this.RunCommandAsync(context, method.Method, new[] { context });
+
+ await this._contextMenuExecuted.InvokeAsync(this, new ContextMenuExecutedEventArgs(this.Client.ServiceProvider) { Context = context });
}
- // Context menus
- if (context is ContextMenuContext contextMenuContext)
+ catch (Exception ex)
{
- await this.RunPreexecutionChecksAsync(method, contextMenuContext);
+ await this._contextMenuErrored.InvokeAsync(this, new ContextMenuErrorEventArgs(this.Client.ServiceProvider) { Context = context, Exception = ex });
+ }
+ });
- var shouldExecute = await (module?.BeforeContextMenuExecutionAsync(contextMenuContext) ?? Task.FromResult(true));
+ return Task.CompletedTask;
+ }
- if (shouldExecute)
- {
- if (AutoDeferEnabled)
- await context.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource);
- await (Task)method.Invoke(classInstance, args.ToArray());
+ ///
+ /// Runs a command.
+ ///
+ /// The base context.
+ /// The method info.
+ /// The arguments.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "")]
+ internal async Task RunCommandAsync(BaseContext context, MethodInfo method, IEnumerable args)
+ {
+ object classInstance;
- await (module?.AfterContextMenuExecutionAsync(contextMenuContext) ?? Task.CompletedTask);
- }
- }
+ //Accounts for lifespans
+ var moduleLifespan = (method.DeclaringType.GetCustomAttribute() != null ? method.DeclaringType.GetCustomAttribute()?.Lifespan : ApplicationCommandModuleLifespan.Transient) ?? ApplicationCommandModuleLifespan.Transient;
+ switch (moduleLifespan)
+ {
+ case ApplicationCommandModuleLifespan.Scoped:
+ //Accounts for static methods and adds DI
+ classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(Configuration?.ServiceProvider.CreateScope().ServiceProvider, method.DeclaringType) : CreateInstance(method.DeclaringType, Configuration?.ServiceProvider.CreateScope().ServiceProvider);
+ break;
+
+ case ApplicationCommandModuleLifespan.Transient:
+ //Accounts for static methods and adds DI
+ classInstance = method.IsStatic ? ActivatorUtilities.CreateInstance(Configuration?.ServiceProvider, method.DeclaringType) : CreateInstance(method.DeclaringType, Configuration?.ServiceProvider);
+ break;
+
+ //If singleton, gets it from the singleton list
+ case ApplicationCommandModuleLifespan.Singleton:
+ classInstance = s_singletonModules.First(x => ReferenceEquals(x.GetType(), method.DeclaringType));
+ break;
+
+ default:
+ throw new Exception($"An unknown {nameof(ApplicationCommandModuleLifespanAttribute)} scope was specified on command {context.CommandName}");
}
- ///
- /// Property injection
- ///
- /// The type.
- /// The services.
- internal static object CreateInstance(Type t, IServiceProvider services)
- {
- var ti = t.GetTypeInfo();
- var constructors = ti.DeclaredConstructors
- .Where(xci => xci.IsPublic)
- .ToArray();
+ ApplicationCommandsModule module = null;
+ if (classInstance is ApplicationCommandsModule mod)
+ module = mod;
- if (constructors.Length != 1)
- throw new ArgumentException("Specified type does not contain a public constructor or contains more than one public constructor.");
+ // Slash commands
+ if (context is InteractionContext slashContext)
+ {
+ await this.RunPreexecutionChecksAsync(method, slashContext);
- var constructor = constructors[0];
- var constructorArgs = constructor.GetParameters();
- var args = new object[constructorArgs.Length];
+ var shouldExecute = await (module?.BeforeSlashExecutionAsync(slashContext) ?? Task.FromResult(true));
- if (constructorArgs.Length != 0 && services == null)
- throw new InvalidOperationException("Dependency collection needs to be specified for parameterized constructors.");
+ if (shouldExecute)
+ {
+ if (AutoDeferEnabled)
+ await context.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource);
+ await (Task)method.Invoke(classInstance, args.ToArray());
- // inject via constructor
- if (constructorArgs.Length != 0)
- for (var i = 0; i < args.Length; i++)
- args[i] = services.GetRequiredService(constructorArgs[i].ParameterType);
+ await (module?.AfterSlashExecutionAsync(slashContext) ?? Task.CompletedTask);
+ }
+ }
+ // Context menus
+ if (context is ContextMenuContext contextMenuContext)
+ {
+ await this.RunPreexecutionChecksAsync(method, contextMenuContext);
- var moduleInstance = Activator.CreateInstance(t, args);
+ var shouldExecute = await (module?.BeforeContextMenuExecutionAsync(contextMenuContext) ?? Task.FromResult(true));
- // inject into properties
- var props = t.GetRuntimeProperties().Where(xp => xp.CanWrite && xp.SetMethod != null && !xp.SetMethod.IsStatic && xp.SetMethod.IsPublic);
- foreach (var prop in props)
+ if (shouldExecute)
{
- if (prop.GetCustomAttribute() != null)
- continue;
-
- var service = services.GetService(prop.PropertyType);
- if (service == null)
- continue;
+ if (AutoDeferEnabled)
+ await context.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource);
+ await (Task)method.Invoke(classInstance, args.ToArray());
- prop.SetValue(moduleInstance, service);
+ await (module?.AfterContextMenuExecutionAsync(contextMenuContext) ?? Task.CompletedTask);
}
+ }
+ }
- // inject into fields
- var fields = t.GetRuntimeFields().Where(xf => !xf.IsInitOnly && !xf.IsStatic && xf.IsPublic);
- foreach (var field in fields)
- {
- if (field.GetCustomAttribute() != null)
- continue;
+ ///
+ /// Property injection
+ ///
+ /// The type.
+ /// The services.
+ internal static object CreateInstance(Type t, IServiceProvider services)
+ {
+ var ti = t.GetTypeInfo();
+ var constructors = ti.DeclaredConstructors
+ .Where(xci => xci.IsPublic)
+ .ToArray();
- var service = services.GetService(field.FieldType);
- if (service == null)
- continue;
+ if (constructors.Length != 1)
+ throw new ArgumentException("Specified type does not contain a public constructor or contains more than one public constructor.");
- field.SetValue(moduleInstance, service);
- }
+ var constructor = constructors[0];
+ var constructorArgs = constructor.GetParameters();
+ var args = new object[constructorArgs.Length];
- return moduleInstance;
- }
+ if (constructorArgs.Length != 0 && services == null)
+ throw new InvalidOperationException("Dependency collection needs to be specified for parameterized constructors.");
+
+ // inject via constructor
+ if (constructorArgs.Length != 0)
+ for (var i = 0; i < args.Length; i++)
+ args[i] = services.GetRequiredService(constructorArgs[i].ParameterType);
+
+ var moduleInstance = Activator.CreateInstance(t, args);
- ///
- /// Resolves the slash command parameters.
- ///
- /// The event arguments.
- /// The interaction context.
- /// The method info.
- /// The options.
- private async Task> ResolveInteractionCommandParameters(InteractionCreateEventArgs e, InteractionContext context, MethodInfo method, IEnumerable options)
+ // inject into properties
+ var props = t.GetRuntimeProperties().Where(xp => xp.CanWrite && xp.SetMethod != null && !xp.SetMethod.IsStatic && xp.SetMethod.IsPublic);
+ foreach (var prop in props)
{
- var args = new List { context };
- var parameters = method.GetParameters().Skip(1);
+ if (prop.GetCustomAttribute() != null)
+ continue;
- foreach (var parameter in parameters)
- {
- //Accounts for optional arguments without values given
- if (parameter.IsOptional && (options == null || (!options?.Any(x => x.Name == parameter.GetCustomAttribute().Name.ToLower()) ?? true)))
- args.Add(parameter.DefaultValue);
- else
- {
- var option = options.Single(x => x.Name == parameter.GetCustomAttribute().Name.ToLower());
-
- if (parameter.ParameterType == typeof(string))
- args.Add(option.Value.ToString());
- else if (parameter.ParameterType.IsEnum)
- args.Add(Enum.Parse(parameter.ParameterType, (string)option.Value));
- else if (parameter.ParameterType == typeof(long) || parameter.ParameterType == typeof(long?))
- args.Add((long?)option.Value);
- else if (parameter.ParameterType == typeof(bool) || parameter.ParameterType == typeof(bool?))
- args.Add((bool?)option.Value);
- else if (parameter.ParameterType == typeof(double) || parameter.ParameterType == typeof(double?))
- args.Add((double?)option.Value);
- else if (parameter.ParameterType == typeof(int) || parameter.ParameterType == typeof(int?))
- args.Add((int?)option.Value);
- else if (parameter.ParameterType == typeof(DiscordAttachment))
- {
- //Checks through resolved
- if (e.Interaction.Data.Resolved.Attachments != null &&
- e.Interaction.Data.Resolved.Attachments.TryGetValue((ulong)option.Value, out var attachment))
- args.Add(attachment);
- else
- args.Add(new DiscordAttachment() { Id = (ulong)option.Value, Discord = this.Client.ApiClient.Discord });
- }
- else if (parameter.ParameterType == typeof(DiscordUser))
- {
- //Checks through resolved
- if (e.Interaction.Data.Resolved.Members != null &&
- e.Interaction.Data.Resolved.Members.TryGetValue((ulong)option.Value, out var member))
- args.Add(member);
- else if (e.Interaction.Data.Resolved.Users != null &&
- e.Interaction.Data.Resolved.Users.TryGetValue((ulong)option.Value, out var user))
- args.Add(user);
- else
- args.Add(await this.Client.GetUserAsync((ulong)option.Value));
- }
- else if (parameter.ParameterType == typeof(DiscordChannel))
- {
- //Checks through resolved
- if (e.Interaction.Data.Resolved.Channels != null &&
- e.Interaction.Data.Resolved.Channels.TryGetValue((ulong)option.Value, out var channel))
- args.Add(channel);
- else
- args.Add(e.Interaction.Guild.GetChannel((ulong)option.Value));
- }
- else if (parameter.ParameterType == typeof(DiscordRole))
- {
- //Checks through resolved
- if (e.Interaction.Data.Resolved.Roles != null &&
- e.Interaction.Data.Resolved.Roles.TryGetValue((ulong)option.Value, out var role))
- args.Add(role);
- else
- args.Add(e.Interaction.Guild.GetRole((ulong)option.Value));
- }
- else if (parameter.ParameterType == typeof(SnowflakeObject))
- {
- //Checks through resolved
- if (e.Interaction.Data.Resolved.Roles != null && e.Interaction.Data.Resolved.Roles.TryGetValue((ulong)option.Value, out var role))
- args.Add(role);
- else if (e.Interaction.Data.Resolved.Members != null && e.Interaction.Data.Resolved.Members.TryGetValue((ulong)option.Value, out var member))
- args.Add(member);
- else if (e.Interaction.Data.Resolved.Users != null && e.Interaction.Data.Resolved.Users.TryGetValue((ulong)option.Value, out var user))
- args.Add(user);
- else
- throw new ArgumentException("Error resolving mentionable option.");
- }
- else
- throw new ArgumentException($"Error resolving interaction.");
- }
- }
+ var service = services.GetService(prop.PropertyType);
+ if (service == null)
+ continue;
+
+ prop.SetValue(moduleInstance, service);
+ }
- return args;
+ // inject into fields
+ var fields = t.GetRuntimeFields().Where(xf => !xf.IsInitOnly && !xf.IsStatic && xf.IsPublic);
+ foreach (var field in fields)
+ {
+ if (field.GetCustomAttribute() != null)
+ continue;
+
+ var service = services.GetService(field.FieldType);
+ if (service == null)
+ continue;
+
+ field.SetValue(moduleInstance, service);
}
- ///
- /// Runs the pre-execution checks.
- ///
- /// The method info.
- /// The base context.
- private async Task RunPreexecutionChecksAsync(MethodInfo method, BaseContext context)
+ return moduleInstance;
+ }
+
+ ///
+ /// Resolves the slash command parameters.
+ ///
+ /// The event arguments.
+ /// The interaction context.
+ /// The method info.
+ /// The options.
+ private async Task> ResolveInteractionCommandParameters(InteractionCreateEventArgs e, InteractionContext context, MethodInfo method, IEnumerable options)
+ {
+ var args = new List { context };
+ var parameters = method.GetParameters().Skip(1);
+
+ foreach (var parameter in parameters)
{
- if (context is InteractionContext ctx)
+ //Accounts for optional arguments without values given
+ if (parameter.IsOptional && (options == null || (!options?.Any(x => x.Name == parameter.GetCustomAttribute().Name.ToLower()) ?? true)))
+ args.Add(parameter.DefaultValue);
+ else
{
- //Gets all attributes from parent classes as well and stuff
- var attributes = new List();
- attributes.AddRange(method.GetCustomAttributes(true));
- attributes.AddRange(method.DeclaringType.GetCustomAttributes());
- if (method.DeclaringType.DeclaringType != null)
+ var option = options.Single(x => x.Name == parameter.GetCustomAttribute().Name.ToLower());
+
+ if (parameter.ParameterType == typeof(string))
+ args.Add(option.Value.ToString());
+ else if (parameter.ParameterType.IsEnum)
+ args.Add(Enum.Parse(parameter.ParameterType, (string)option.Value));
+ else if (parameter.ParameterType == typeof(long) || parameter.ParameterType == typeof(long?))
+ args.Add((long?)option.Value);
+ else if (parameter.ParameterType == typeof(bool) || parameter.ParameterType == typeof(bool?))
+ args.Add((bool?)option.Value);
+ else if (parameter.ParameterType == typeof(double) || parameter.ParameterType == typeof(double?))
+ args.Add((double?)option.Value);
+ else if (parameter.ParameterType == typeof(int) || parameter.ParameterType == typeof(int?))
+ args.Add((int?)option.Value);
+ else if (parameter.ParameterType == typeof(DiscordAttachment))
{
- attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes());
- if (method.DeclaringType.DeclaringType.DeclaringType != null)
- {
- attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes());
- }
+ //Checks through resolved
+ if (e.Interaction.Data.Resolved.Attachments != null &&
+ e.Interaction.Data.Resolved.Attachments.TryGetValue((ulong)option.Value, out var attachment))
+ args.Add(attachment);
+ else
+ args.Add(new DiscordAttachment() { Id = (ulong)option.Value, Discord = this.Client.ApiClient.Discord });
}
-
- var dict = new Dictionary();
- foreach (var att in attributes)
+ else if (parameter.ParameterType == typeof(DiscordUser))
{
- //Runs the check and adds the result to a list
- var result = await att.ExecuteChecksAsync(ctx);
- dict.Add(att, result);
+ //Checks through resolved
+ if (e.Interaction.Data.Resolved.Members != null &&
+ e.Interaction.Data.Resolved.Members.TryGetValue((ulong)option.Value, out var member))
+ args.Add(member);
+ else if (e.Interaction.Data.Resolved.Users != null &&
+ e.Interaction.Data.Resolved.Users.TryGetValue((ulong)option.Value, out var user))
+ args.Add(user);
+ else
+ args.Add(await this.Client.GetUserAsync((ulong)option.Value));
}
-
- //Checks if any failed, and throws an exception
- if (dict.Any(x => x.Value == false))
- throw new SlashExecutionChecksFailedException { FailedChecks = dict.Where(x => x.Value == false).Select(x => x.Key).ToList() };
- }
- if (context is ContextMenuContext cMctx)
- {
- var attributes = new List();
- attributes.AddRange(method.GetCustomAttributes(true));
- attributes.AddRange(method.DeclaringType.GetCustomAttributes());
- if (method.DeclaringType.DeclaringType != null)
+ else if (parameter.ParameterType == typeof(DiscordChannel))
{
- attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes());
- if (method.DeclaringType.DeclaringType.DeclaringType != null)
- {
- attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes());
- }
+ //Checks through resolved
+ if (e.Interaction.Data.Resolved.Channels != null &&
+ e.Interaction.Data.Resolved.Channels.TryGetValue((ulong)option.Value, out var channel))
+ args.Add(channel);
+ else
+ args.Add(e.Interaction.Guild.GetChannel((ulong)option.Value));
}
-
- var dict = new Dictionary();
- foreach (var att in attributes)
+ else if (parameter.ParameterType == typeof(DiscordRole))
{
- //Runs the check and adds the result to a list
- var result = await att.ExecuteChecksAsync(cMctx);
- dict.Add(att, result);
+ //Checks through resolved
+ if (e.Interaction.Data.Resolved.Roles != null &&
+ e.Interaction.Data.Resolved.Roles.TryGetValue((ulong)option.Value, out var role))
+ args.Add(role);
+ else
+ args.Add(e.Interaction.Guild.GetRole((ulong)option.Value));
}
-
- //Checks if any failed, and throws an exception
- if (dict.Any(x => x.Value == false))
- throw new ContextMenuExecutionChecksFailedException { FailedChecks = dict.Where(x => x.Value == false).Select(x => x.Key).ToList() };
+ else if (parameter.ParameterType == typeof(SnowflakeObject))
+ {
+ //Checks through resolved
+ if (e.Interaction.Data.Resolved.Roles != null && e.Interaction.Data.Resolved.Roles.TryGetValue((ulong)option.Value, out var role))
+ args.Add(role);
+ else if (e.Interaction.Data.Resolved.Members != null && e.Interaction.Data.Resolved.Members.TryGetValue((ulong)option.Value, out var member))
+ args.Add(member);
+ else if (e.Interaction.Data.Resolved.Users != null && e.Interaction.Data.Resolved.Users.TryGetValue((ulong)option.Value, out var user))
+ args.Add(user);
+ else
+ throw new ArgumentException("Error resolving mentionable option.");
+ }
+ else
+ throw new ArgumentException($"Error resolving interaction.");
}
}
- ///
- /// Gets the choice attributes from choice provider.
- ///
- /// The custom attributes.
- /// The optional guild id
- private static async Task> GetChoiceAttributesFromProvider(IEnumerable customAttributes, ulong? guildId = null)
+ return args;
+ }
+
+ ///
+ /// Runs the pre-execution checks.
+ ///
+ /// The method info.
+ /// The base context.
+ private async Task RunPreexecutionChecksAsync(MethodInfo method, BaseContext context)
+ {
+ if (context is InteractionContext ctx)
{
- var choices = new List();
- foreach (var choiceProviderAttribute in customAttributes)
+ //Gets all attributes from parent classes as well and stuff
+ var attributes = new List();
+ attributes.AddRange(method.GetCustomAttributes(true));
+ attributes.AddRange(method.DeclaringType.GetCustomAttributes());
+ if (method.DeclaringType.DeclaringType != null)
{
- var method = choiceProviderAttribute.ProviderType.GetMethod(nameof(IChoiceProvider.Provider));
-
- if (method == null)
- throw new ArgumentException("ChoiceProviders must inherit from IChoiceProvider.");
- else
+ attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes());
+ if (method.DeclaringType.DeclaringType.DeclaringType != null)
{
- var instance = Activator.CreateInstance(choiceProviderAttribute.ProviderType);
-
- // Abstract class offers more properties that can be set
- if (choiceProviderAttribute.ProviderType.IsSubclassOf(typeof(ChoiceProvider)))
- {
- choiceProviderAttribute.ProviderType.GetProperty(nameof(ChoiceProvider.GuildId))
- ?.SetValue(instance, guildId);
-
- choiceProviderAttribute.ProviderType.GetProperty(nameof(ChoiceProvider.Services))
- ?.SetValue(instance, Configuration.ServiceProvider);
- }
-
- //Gets the choices from the method
- var result = await (Task>)method.Invoke(instance, null);
-
- if (result.Any())
- {
- choices.AddRange(result);
- }
+ attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes());
}
}
- return choices;
- }
-
- ///
- /// Gets the choice attributes from enum parameter.
- ///
- /// The enum parameter.
- private static List GetChoiceAttributesFromEnumParameter(Type enumParam)
- {
- var choices = new List();
- foreach (Enum enumValue in Enum.GetValues(enumParam))
+ var dict = new Dictionary();
+ foreach (var att in attributes)
{
- choices.Add(new DiscordApplicationCommandOptionChoice(enumValue.GetName(), enumValue.ToString()));
+ //Runs the check and adds the result to a list
+ var result = await att.ExecuteChecksAsync(ctx);
+ dict.Add(att, result);
}
- return choices;
- }
- ///
- /// Gets the parameter type.
- ///
- /// The type.
- private static ApplicationCommandOptionType GetParameterType(Type type)
- {
- var parameterType = type == typeof(string)
- ? ApplicationCommandOptionType.String
- : type == typeof(long) || type == typeof(long?) || type == typeof(int) || type == typeof(int?)
- ? ApplicationCommandOptionType.Integer
- : type == typeof(bool) || type == typeof(bool?)
- ? ApplicationCommandOptionType.Boolean
- : type == typeof(double) || type == typeof(double?)
- ? ApplicationCommandOptionType.Number
- : type == typeof(DiscordAttachment)
- ? ApplicationCommandOptionType.Attachment
- : type == typeof(DiscordChannel)
- ? ApplicationCommandOptionType.Channel
- : type == typeof(DiscordUser)
- ? ApplicationCommandOptionType.User
- : type == typeof(DiscordRole)
- ? ApplicationCommandOptionType.Role
- : type == typeof(SnowflakeObject)
- ? ApplicationCommandOptionType.Mentionable
- : type.IsEnum
- ? ApplicationCommandOptionType.String
- : throw new ArgumentException("Cannot convert type! Argument types must be string, int, long, bool, double, DiscordChannel, DiscordUser, DiscordRole, SnowflakeObject, DiscordAttachment or an Enum.");
- return parameterType;
+ //Checks if any failed, and throws an exception
+ if (dict.Any(x => x.Value == false))
+ throw new SlashExecutionChecksFailedException { FailedChecks = dict.Where(x => x.Value == false).Select(x => x.Key).ToList() };
}
-
- ///
- /// Gets the choice attributes from parameter.
- ///
- /// The choice attributes.
- private static List GetChoiceAttributesFromParameter(IEnumerable choiceAttributes) =>
- !choiceAttributes.Any()
- ? null
- : choiceAttributes.Select(att => new DiscordApplicationCommandOptionChoice(att.Name, att.Value)).ToList();
-
- ///
- /// Parses the parameters.
- ///
- /// The parameters.
- /// The optional guild id.
- internal static async Task> ParseParametersAsync(IEnumerable parameters, ulong? guildId)
+ if (context is ContextMenuContext cMctx)
{
- var options = new List();
- foreach (var parameter in parameters)
+ var attributes = new List();
+ attributes.AddRange(method.GetCustomAttributes(true));
+ attributes.AddRange(method.DeclaringType.GetCustomAttributes());
+ if (method.DeclaringType.DeclaringType != null)
{
- //Gets the attribute
- var optionAttribute = parameter.GetCustomAttribute();
- if (optionAttribute == null)
- throw new ArgumentException("Arguments must have the Option attribute!");
-
- var minimumValue = parameter.GetCustomAttribute()?.Value ?? null;
- var maximumValue = parameter.GetCustomAttribute()?.Value ?? null;
-
-
- var autocompleteAttribute = parameter.GetCustomAttribute();
- if (optionAttribute.Autocomplete && autocompleteAttribute == null)
- throw new ArgumentException("Autocomplete options must have the Autocomplete attribute!");
- if (!optionAttribute.Autocomplete && autocompleteAttribute != null)
- throw new ArgumentException("Setting an autocomplete provider requires the option to have autocomplete set to true!");
-
- //Sets the type
- var type = parameter.ParameterType;
- var parameterType = GetParameterType(type);
-
- //Handles choices
- //From attributes
- var choices = GetChoiceAttributesFromParameter(parameter.GetCustomAttributes());
- //From enums
- if (parameter.ParameterType.IsEnum)
- {
- choices = GetChoiceAttributesFromEnumParameter(parameter.ParameterType);
- }
- //From choice provider
- var choiceProviders = parameter.GetCustomAttributes();
- if (choiceProviders.Any())
+ attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes());
+ if (method.DeclaringType.DeclaringType.DeclaringType != null)
{
- choices = await GetChoiceAttributesFromProvider(choiceProviders, guildId);
+ attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes());
}
+ }
- var channelTypes = parameter.GetCustomAttribute()?.ChannelTypes ?? null;
-
- options.Add(new DiscordApplicationCommandOption(optionAttribute.Name, optionAttribute.Description, parameterType, !parameter.IsOptional, choices, null, channelTypes, optionAttribute.Autocomplete, minimumValue, maximumValue));
+ var dict = new Dictionary();
+ foreach (var att in attributes)
+ {
+ //Runs the check and adds the result to a list
+ var result = await att.ExecuteChecksAsync(cMctx);
+ dict.Add(att, result);
}
- return options;
+ //Checks if any failed, and throws an exception
+ if (dict.Any(x => x.Value == false))
+ throw new ContextMenuExecutionChecksFailedException { FailedChecks = dict.Where(x => x.Value == false).Select(x => x.Key).ToList() };
}
+ }
- ///
- /// Refreshes your commands, used for refreshing choice providers or applying commands registered after the ready event on the discord client.
- /// Should only be run on the slash command extension linked to shard 0 if sharding.
- /// Not recommended and should be avoided since it can make slash commands be unresponsive for a while.
- ///
- public async Task RefreshCommandsAsync()
+ ///
+ /// Gets the choice attributes from choice provider.
+ ///
+ /// The custom attributes.
+ /// The optional guild id
+ private static async Task> GetChoiceAttributesFromProvider(IEnumerable customAttributes, ulong? guildId = null)
+ {
+ var choices = new List();
+ foreach (var choiceProviderAttribute in customAttributes)
{
- s_commandMethods.Clear();
- s_groupCommands.Clear();
- s_subGroupCommands.Clear();
- s_registeredCommands.Clear();
- s_contextMenuCommands.Clear();
- GlobalDiscordCommands.Clear();
- GuildDiscordCommands.Clear();
- GuildCommandsInternal.Clear();
- GlobalCommandsInternal.Clear();
- GlobalDiscordCommands = null;
- GuildDiscordCommands = null;
- s_errored = false;
-
- if (Configuration != null && Configuration.EnableDefaultHelp)
+ var method = choiceProviderAttribute.ProviderType.GetMethod(nameof(IChoiceProvider.Provider));
+
+ if (method == null)
+ throw new ArgumentException("ChoiceProviders must inherit from IChoiceProvider.");
+ else
{
- this._updateList.RemoveAll(x => x.Value.Type == typeof(DefaultHelpModule));
- }
+ var instance = Activator.CreateInstance(choiceProviderAttribute.ProviderType);
- await this.UpdateAsync();
- }
+ // Abstract class offers more properties that can be set
+ if (choiceProviderAttribute.ProviderType.IsSubclassOf(typeof(ChoiceProvider)))
+ {
+ choiceProviderAttribute.ProviderType.GetProperty(nameof(ChoiceProvider.GuildId))
+ ?.SetValue(instance, guildId);
- ///
- /// Fires when the execution of a slash command fails.
- ///
- public event AsyncEventHandler SlashCommandErrored
- {
- add => this._slashError.Register(value);
- remove => this._slashError.Unregister(value);
- }
- private AsyncEvent _slashError;
+ choiceProviderAttribute.ProviderType.GetProperty(nameof(ChoiceProvider.Services))
+ ?.SetValue(instance, Configuration.ServiceProvider);
+ }
- ///
- /// Fires when the execution of a slash command is successful.
- ///
- public event AsyncEventHandler SlashCommandExecuted
- {
- add => this._slashExecuted.Register(value);
- remove => this._slashExecuted.Unregister(value);
+ //Gets the choices from the method
+ var result = await (Task>)method.Invoke(instance, null);
+
+ if (result.Any())
+ {
+ choices.AddRange(result);
+ }
+ }
}
- private AsyncEvent _slashExecuted;
- ///
- /// Fires when the execution of a context menu fails.
- ///
- public event AsyncEventHandler ContextMenuErrored
+ return choices;
+ }
+
+ ///
+ /// Gets the choice attributes from enum parameter.
+ ///
+ /// The enum parameter.
+ private static List GetChoiceAttributesFromEnumParameter(Type enumParam)
+ {
+ var choices = new List();
+ foreach (Enum enumValue in Enum.GetValues(enumParam))
{
- add => this._contextMenuErrored.Register(value);
- remove => this._contextMenuErrored.Unregister(value);
+ choices.Add(new DiscordApplicationCommandOptionChoice(enumValue.GetName(), enumValue.ToString()));
}
- private AsyncEvent _contextMenuErrored;
+ return choices;
+ }
+
+ ///
+ /// Gets the parameter type.
+ ///
+ /// The type.
+ private static ApplicationCommandOptionType GetParameterType(Type type)
+ {
+ var parameterType = type == typeof(string)
+ ? ApplicationCommandOptionType.String
+ : type == typeof(long) || type == typeof(long?) || type == typeof(int) || type == typeof(int?)
+ ? ApplicationCommandOptionType.Integer
+ : type == typeof(bool) || type == typeof(bool?)
+ ? ApplicationCommandOptionType.Boolean
+ : type == typeof(double) || type == typeof(double?)
+ ? ApplicationCommandOptionType.Number
+ : type == typeof(DiscordAttachment)
+ ? ApplicationCommandOptionType.Attachment
+ : type == typeof(DiscordChannel)
+ ? ApplicationCommandOptionType.Channel
+ : type == typeof(DiscordUser)
+ ? ApplicationCommandOptionType.User
+ : type == typeof(DiscordRole)
+ ? ApplicationCommandOptionType.Role
+ : type == typeof(SnowflakeObject)
+ ? ApplicationCommandOptionType.Mentionable
+ : type.IsEnum
+ ? ApplicationCommandOptionType.String
+ : throw new ArgumentException("Cannot convert type! Argument types must be string, int, long, bool, double, DiscordChannel, DiscordUser, DiscordRole, SnowflakeObject, DiscordAttachment or an Enum.");
+ return parameterType;
+ }
+
+ ///
+ /// Gets the choice attributes from parameter.
+ ///
+ /// The choice attributes.
+ private static List GetChoiceAttributesFromParameter(IEnumerable choiceAttributes) =>
+ !choiceAttributes.Any()
+ ? null
+ : choiceAttributes.Select(att => new DiscordApplicationCommandOptionChoice(att.Name, att.Value)).ToList();
- ///
- /// Fire when the execution of a context menu is successful.
- ///
- public event AsyncEventHandler ContextMenuExecuted
+ ///
+ /// Parses the parameters.
+ ///
+ /// The parameters.
+ /// The optional guild id.
+ internal static async Task> ParseParametersAsync(IEnumerable parameters, ulong? guildId)
+ {
+ var options = new List();
+ foreach (var parameter in parameters)
{
- add => this._contextMenuExecuted.Register(value);
- remove => this._contextMenuExecuted.Unregister(value);
+ //Gets the attribute
+ var optionAttribute = parameter.GetCustomAttribute();
+ if (optionAttribute == null)
+ throw new ArgumentException("Arguments must have the Option attribute!");
+
+ var minimumValue = parameter.GetCustomAttribute()?.Value ?? null;
+ var maximumValue = parameter.GetCustomAttribute()?.Value ?? null;
+
+
+ var autocompleteAttribute = parameter.GetCustomAttribute();
+ if (optionAttribute.Autocomplete && autocompleteAttribute == null)
+ throw new ArgumentException("Autocomplete options must have the Autocomplete attribute!");
+ if (!optionAttribute.Autocomplete && autocompleteAttribute != null)
+ throw new ArgumentException("Setting an autocomplete provider requires the option to have autocomplete set to true!");
+
+ //Sets the type
+ var type = parameter.ParameterType;
+ var parameterType = GetParameterType(type);
+
+ //Handles choices
+ //From attributes
+ var choices = GetChoiceAttributesFromParameter(parameter.GetCustomAttributes());
+ //From enums
+ if (parameter.ParameterType.IsEnum)
+ {
+ choices = GetChoiceAttributesFromEnumParameter(parameter.ParameterType);
+ }
+ //From choice provider
+ var choiceProviders = parameter.GetCustomAttributes();
+ if (choiceProviders.Any())
+ {
+ choices = await GetChoiceAttributesFromProvider(choiceProviders, guildId);
+ }
+
+ var channelTypes = parameter.GetCustomAttribute()?.ChannelTypes ?? null;
+
+ options.Add(new DiscordApplicationCommandOption(optionAttribute.Name, optionAttribute.Description, parameterType, !parameter.IsOptional, choices, null, channelTypes, optionAttribute.Autocomplete, minimumValue, maximumValue));
}
- private AsyncEvent _contextMenuExecuted;
+
+ return options;
}
///
- /// Holds configuration data for setting up an application command.
+ /// Refreshes your commands, used for refreshing choice providers or applying commands registered after the ready event on the discord client.
+ /// Should only be run on the slash command extension linked to shard 0 if sharding.
+ /// Not recommended and should be avoided since it can make slash commands be unresponsive for a while.
///
- internal class ApplicationCommandsModuleConfiguration
+ public async Task RefreshCommandsAsync()
{
- ///
- /// The type of the command module.
- ///
- public Type Type { get; }
-
- ///
- /// The translation setup.
- ///
- public Action Translations { get; }
-
- ///
- /// Creates a new command configuration.
- ///
- /// The type of the command module.
- /// The translation setup callback.
- public ApplicationCommandsModuleConfiguration(Type type, Action translations = null)
+ s_commandMethods.Clear();
+ s_groupCommands.Clear();
+ s_subGroupCommands.Clear();
+ s_registeredCommands.Clear();
+ s_contextMenuCommands.Clear();
+ GlobalDiscordCommands.Clear();
+ GuildDiscordCommands.Clear();
+ GuildCommandsInternal.Clear();
+ GlobalCommandsInternal.Clear();
+ GlobalDiscordCommands = null;
+ GuildDiscordCommands = null;
+ s_errored = false;
+
+ if (Configuration != null && Configuration.EnableDefaultHelp)
{
- this.Type = type;
- this.Translations = translations;
+ this._updateList.RemoveAll(x => x.Value.Type == typeof(DefaultHelpModule));
}
+
+ await this.UpdateAsync();
}
///
- /// Links a command to its original command module.
+ /// Fires when the execution of a slash command fails.
///
- internal class ApplicationCommandSourceLink
+ public event AsyncEventHandler SlashCommandErrored
{
- ///
- /// The command.
- ///
- public DiscordApplicationCommand ApplicationCommand { get; set; }
-
- ///
- /// The base/root module the command is contained in.
- ///
- public Type RootCommandContainerType { get; set; }
-
- ///
- /// The direct group the command is contained in.
- ///
- public Type CommandContainerType { get; set; }
+ add => this._slashError.Register(value);
+ remove => this._slashError.Unregister(value);
}
+ private AsyncEvent _slashError;
///