using Discord.Interactions.Builders; using Discord.Logging; using Discord.Rest; using Discord.WebSocket; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; namespace Discord.Interactions { /// /// Provides the framework for building and registering Discord Application Commands. /// public class InteractionService : IDisposable { /// /// Occurs when a Slash Command related information is recieved. /// public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } internal readonly AsyncEvent> _logEvent = new (); /// /// Occurs when a Slash Command is executed. /// public event Func SlashCommandExecuted { add { _slashCommandExecutedEvent.Add(value); } remove { _slashCommandExecutedEvent.Remove(value); } } internal readonly AsyncEvent> _slashCommandExecutedEvent = new (); /// /// Occurs when a Context Command is executed. /// public event Func ContextCommandExecuted { add { _contextCommandExecutedEvent.Add(value); } remove { _contextCommandExecutedEvent.Remove(value); } } internal readonly AsyncEvent> _contextCommandExecutedEvent = new (); /// /// Occurs when a Message Component command is executed. /// public event Func ComponentCommandExecuted { add { _componentCommandExecutedEvent.Add(value); } remove { _componentCommandExecutedEvent.Remove(value); } } internal readonly AsyncEvent> _componentCommandExecutedEvent = new (); /// /// Occurs when a Autocomplete command is executed. /// public event Func AutocompleteCommandExecuted { add { _autocompleteCommandExecutedEvent.Add(value); } remove { _autocompleteCommandExecutedEvent.Remove(value); } } internal readonly AsyncEvent> _autocompleteCommandExecutedEvent = new(); /// /// Occurs when a AutocompleteHandler is executed. /// public event Func AutocompleteHandlerExecuted { add { _autocompleteHandlerExecutedEvent.Add(value); } remove { _autocompleteHandlerExecutedEvent.Remove(value); } } internal readonly AsyncEvent> _autocompleteHandlerExecutedEvent = new(); /// /// Occurs when a Modal command is executed. /// public event Func ModalCommandExecuted { add { _modalCommandExecutedEvent.Add(value); } remove { _modalCommandExecutedEvent.Remove(value); } } internal readonly AsyncEvent> _modalCommandExecutedEvent = new(); private readonly ConcurrentDictionary _typedModuleDefs; private readonly CommandMap _slashCommandMap; private readonly ConcurrentDictionary> _contextCommandMaps; private readonly CommandMap _componentCommandMap; private readonly CommandMap _autocompleteCommandMap; private readonly CommandMap _modalCommandMap; private readonly HashSet _moduleDefs; private readonly TypeMap _typeConverterMap; private readonly TypeMap _compTypeConverterMap; private readonly ConcurrentDictionary _autocompleteHandlers = new(); private readonly ConcurrentDictionary _modalInfos = new(); private readonly SemaphoreSlim _lock; internal readonly Logger _cmdLogger; internal readonly LogManager _logManager; internal readonly Func _getRestClient; internal readonly bool _throwOnError, _useCompiledLambda, _enableAutocompleteHandlers, _autoServiceScopes, _exitOnMissingModalField; internal readonly string _wildCardExp; internal readonly RunMode _runMode; internal readonly RestResponseCallback _restResponseCallback; /// /// Rest client to be used to register application commands. /// public DiscordRestClient RestClient { get => _getRestClient(); } /// /// Represents all modules loaded within . /// public IReadOnlyList Modules => _moduleDefs.ToList(); /// /// Represents all Slash Commands loaded within . /// public IReadOnlyList SlashCommands => _moduleDefs.SelectMany(x => x.SlashCommands).ToList(); /// /// Represents all Context Commands loaded within . /// public IReadOnlyList ContextCommands => _moduleDefs.SelectMany(x => x.ContextCommands).ToList(); /// /// Represents all Component Commands loaded within . /// public IReadOnlyCollection ComponentCommands => _moduleDefs.SelectMany(x => x.ComponentCommands).ToList(); /// /// Represents all Modal Commands loaded within . /// public IReadOnlyCollection ModalCommands => _moduleDefs.SelectMany(x => x.ModalCommands).ToList(); /// /// Gets a collection of the cached classes that are referenced in registered s. /// public IReadOnlyCollection Modals => ModalUtils.Modals; /// /// Initialize a with provided configurations. /// /// The discord client. /// The configuration class. public InteractionService (DiscordSocketClient discord, InteractionServiceConfig config = null) : this(() => discord.Rest, config ?? new InteractionServiceConfig()) { } /// /// Initialize a with provided configurations. /// /// The discord client. /// The configuration class. public InteractionService (DiscordShardedClient discord, InteractionServiceConfig config = null) : this(() => discord.Rest, config ?? new InteractionServiceConfig()) { } /// /// Initialize a with provided configurations. /// /// The discord client. /// The configuration class. public InteractionService (BaseSocketClient discord, InteractionServiceConfig config = null) :this(() => discord.Rest, config ?? new InteractionServiceConfig()) { } /// /// Initialize a with provided configurations. /// /// The discord client. /// The configuration class. public InteractionService (DiscordRestClient discord, InteractionServiceConfig config = null) :this(() => discord, config ?? new InteractionServiceConfig()) { } private InteractionService (Func getRestClient, InteractionServiceConfig config = null) { config ??= new InteractionServiceConfig(); _lock = new SemaphoreSlim(1, 1); _typedModuleDefs = new ConcurrentDictionary(); _moduleDefs = new HashSet(); _logManager = new LogManager(config.LogLevel); _logManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); _cmdLogger = _logManager.CreateLogger("App Commands"); _slashCommandMap = new CommandMap(this); _contextCommandMaps = new ConcurrentDictionary>(); _componentCommandMap = new CommandMap(this, config.InteractionCustomIdDelimiters); _autocompleteCommandMap = new CommandMap(this); _modalCommandMap = new CommandMap(this, config.InteractionCustomIdDelimiters); _getRestClient = getRestClient; _runMode = config.DefaultRunMode; if (_runMode == RunMode.Default) throw new InvalidOperationException($"RunMode cannot be set to {RunMode.Default}"); _throwOnError = config.ThrowOnError; _wildCardExp = config.WildCardExpression; _useCompiledLambda = config.UseCompiledLambda; _exitOnMissingModalField = config.ExitOnMissingModalField; _enableAutocompleteHandlers = config.EnableAutocompleteHandlers; _autoServiceScopes = config.AutoServiceScopes; _restResponseCallback = config.RestResponseCallback; _typeConverterMap = new TypeMap(this, new Dictionary { [typeof(TimeSpan)] = new TimeSpanConverter() }, new Dictionary { [typeof(IChannel)] = typeof(DefaultChannelConverter<>), [typeof(IRole)] = typeof(DefaultRoleConverter<>), [typeof(IAttachment)] = typeof(DefaultAttachmentConverter<>), [typeof(IUser)] = typeof(DefaultUserConverter<>), [typeof(IMentionable)] = typeof(DefaultMentionableConverter<>), [typeof(IConvertible)] = typeof(DefaultValueConverter<>), [typeof(Enum)] = typeof(EnumConverter<>), [typeof(Nullable<>)] = typeof(NullableConverter<>), }); _compTypeConverterMap = new TypeMap(this, new Dictionary { }, new Dictionary { }); } /// /// Create and loads a using a builder factory. /// /// Name of the module. /// The for your dependency injection solution if using one; otherwise, pass null. /// Module builder factory. /// /// A task representing the operation for adding modules. The task result contains the built module instance. /// public async Task CreateModuleAsync(string name, IServiceProvider services, Action buildFunc) { services ??= EmptyServiceProvider.Instance; await _lock.WaitAsync().ConfigureAwait(false); try { var builder = new ModuleBuilder(this, name); buildFunc(builder); var moduleInfo = builder.Build(this, services); LoadModuleInternal(moduleInfo); return moduleInfo; } finally { _lock.Release(); } } /// /// Discover and load command modules from an . /// /// the command modules are defined in. /// The for your dependency injection solution if using one; otherwise, pass null. /// /// A task representing the operation for adding modules. The task result contains a collection of the modules added. /// public async Task> AddModulesAsync (Assembly assembly, IServiceProvider services) { services ??= EmptyServiceProvider.Instance; await _lock.WaitAsync().ConfigureAwait(false); try { var types = await ModuleClassBuilder.SearchAsync(assembly, this); var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this, services); foreach (var info in moduleDefs) { _typedModuleDefs[info.Key] = info.Value; LoadModuleInternal(info.Value); } return moduleDefs.Values; } finally { _lock.Release(); } } /// /// Add a command module from a . /// /// Type of the module. /// The for your dependency injection solution if using one; otherwise, pass null . /// /// A task representing the operation for adding the module. The task result contains the built module. /// /// /// Thrown if this module has already been added. /// /// /// Thrown when the is not a valid module definition. /// public Task AddModuleAsync (IServiceProvider services) where T : class => AddModuleAsync(typeof(T), services); /// /// Add a command module from a . /// /// Type of the module. /// The for your dependency injection solution if using one; otherwise, pass null . /// /// A task representing the operation for adding the module. The task result contains the built module. /// /// /// Thrown if this module has already been added. /// /// /// Thrown when the is not a valid module definition. /// public async Task AddModuleAsync (Type type, IServiceProvider services) { if (!typeof(IInteractionModuleBase).IsAssignableFrom(type)) throw new ArgumentException("Type parameter must be a type of Slash Module", "T"); services ??= EmptyServiceProvider.Instance; await _lock.WaitAsync().ConfigureAwait(false); try { var typeInfo = type.GetTypeInfo(); if (_typedModuleDefs.ContainsKey(typeInfo)) throw new ArgumentException("Module definition for this type already exists."); var moduleDef = ( await ModuleClassBuilder.BuildAsync(new List { typeInfo }, this, services).ConfigureAwait(false) ).FirstOrDefault(); if (moduleDef.Value == default) throw new InvalidOperationException($"Could not build the module {typeInfo.FullName}, did you pass an invalid type?"); if (!_typedModuleDefs.TryAdd(type, moduleDef.Value)) throw new ArgumentException("Module definition for this type already exists."); _typedModuleDefs[moduleDef.Key] = moduleDef.Value; LoadModuleInternal(moduleDef.Value); return moduleDef.Value; } finally { _lock.Release(); } } /// /// Register Application Commands from and to a guild. /// /// Id of the target guild. /// If , this operation will not delete the commands that are missing from . /// /// A task representing the command registration process. The task result contains the active application commands of the target guild. /// public async Task> RegisterCommandsToGuildAsync (ulong guildId, bool deleteMissing = true) { EnsureClientReady(); var topLevelModules = _moduleDefs.Where(x => !x.IsSubModule); var props = topLevelModules.SelectMany(x => x.ToApplicationCommandProps()).ToList(); if (!deleteMissing) { var existing = await RestClient.GetGuildApplicationCommands(guildId).ConfigureAwait(false); var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); } return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guildId).ConfigureAwait(false); } /// /// Register Application Commands from and to Discord on in global scope. /// /// If , this operation will not delete the commands that are missing from . /// /// A task representing the command registration process. The task result contains the active global application commands of bot. /// public async Task> RegisterCommandsGloballyAsync (bool deleteMissing = true) { EnsureClientReady(); var topLevelModules = _moduleDefs.Where(x => !x.IsSubModule); var props = topLevelModules.SelectMany(x => x.ToApplicationCommandProps()).ToList(); if (!deleteMissing) { var existing = await RestClient.GetGlobalApplicationCommands().ConfigureAwait(false); var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); } return await RestClient.BulkOverwriteGlobalCommands(props.ToArray()).ConfigureAwait(false); } /// /// Register Application Commands from to a guild. /// /// /// Commands will be registered as standalone commands, if you want the to take effect, /// use . Registering a commands without group names might cause the command traversal to fail. /// /// The target guild. /// Commands to be registered to Discord. /// /// A task representing the command registration process. The task result contains the active application commands of the target guild. /// public async Task> AddCommandsToGuildAsync(IGuild guild, bool deleteMissing = false, params ICommandInfo[] commands) { EnsureClientReady(); if (guild is null) throw new ArgumentNullException(nameof(guild)); var props = new List(); foreach (var command in commands) { switch (command) { case SlashCommandInfo slashCommand: props.Add(slashCommand.ToApplicationCommandProps()); break; case ContextCommandInfo contextCommand: props.Add(contextCommand.ToApplicationCommandProps()); break; default: throw new InvalidOperationException($"Command type {command.GetType().FullName} isn't supported yet"); } } if (!deleteMissing) { var existing = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false); var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); } return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guild.Id).ConfigureAwait(false); } /// /// Register Application Commands from modules provided in to a guild. /// /// The target guild. /// Modules to be registered to Discord. /// /// A task representing the command registration process. The task result contains the active application commands of the target guild. /// public async Task> AddModulesToGuildAsync(IGuild guild, bool deleteMissing = false, params ModuleInfo[] modules) { EnsureClientReady(); if (guild is null) throw new ArgumentNullException(nameof(guild)); var props = modules.SelectMany(x => x.ToApplicationCommandProps(true)).ToList(); if (!deleteMissing) { var existing = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false); var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); } return await RestClient.BulkOverwriteGuildCommands(props.ToArray(), guild.Id).ConfigureAwait(false); } /// /// Register Application Commands from modules provided in as global commands. /// /// Modules to be registered to Discord. /// /// A task representing the command registration process. The task result contains the active application commands of the target guild. /// public async Task> AddModulesGloballyAsync(bool deleteMissing = false, params ModuleInfo[] modules) { EnsureClientReady(); var props = modules.SelectMany(x => x.ToApplicationCommandProps(true)).ToList(); if (!deleteMissing) { var existing = await RestClient.GetGlobalApplicationCommands().ConfigureAwait(false); var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); } return await RestClient.BulkOverwriteGlobalCommands(props.ToArray()).ConfigureAwait(false); } /// /// Register Application Commands from as global commands. /// /// /// Commands will be registered as standalone commands, if you want the to take effect, /// use . Registering a commands without group names might cause the command traversal to fail. /// /// Commands to be registered to Discord. /// /// A task representing the command registration process. The task result contains the active application commands of the target guild. /// public async Task> AddCommandsGloballyAsync(bool deleteMissing = false, params IApplicationCommandInfo[] commands) { EnsureClientReady(); var props = new List(); foreach (var command in commands) { switch (command) { case SlashCommandInfo slashCommand: props.Add(slashCommand.ToApplicationCommandProps()); break; case ContextCommandInfo contextCommand: props.Add(contextCommand.ToApplicationCommandProps()); break; default: throw new InvalidOperationException($"Command type {command.GetType().FullName} isn't supported yet"); } } if (!deleteMissing) { var existing = await RestClient.GetGlobalApplicationCommands().ConfigureAwait(false); var missing = existing.Where(x => !props.Any(y => y.Name.IsSpecified && y.Name.Value == x.Name)); props.AddRange(missing.Select(x => x.ToApplicationCommandProps())); } return await RestClient.BulkOverwriteGlobalCommands(props.ToArray()).ConfigureAwait(false); } private void LoadModuleInternal (ModuleInfo module) { _moduleDefs.Add(module); foreach (var command in module.SlashCommands) _slashCommandMap.AddCommand(command, command.IgnoreGroupNames); foreach (var command in module.ContextCommands) _contextCommandMaps.GetOrAdd(command.CommandType, new CommandMap(this)).AddCommand(command, command.IgnoreGroupNames); foreach (var interaction in module.ComponentCommands) _componentCommandMap.AddCommand(interaction, interaction.IgnoreGroupNames); foreach (var command in module.AutocompleteCommands) _autocompleteCommandMap.AddCommand(command.GetCommandKeywords(), command); foreach (var command in module.ModalCommands) _modalCommandMap.AddCommand(command, command.IgnoreGroupNames); foreach (var subModule in module.SubModules) LoadModuleInternal(subModule); } /// /// Remove a command module. /// /// The of the module. /// /// A task that represents the asynchronous removal operation. The task result contains a value that /// indicates whether the module is successfully removed. /// public Task RemoveModuleAsync ( ) => RemoveModuleAsync(typeof(T)); /// /// Remove a command module. /// /// The of the module. /// /// A task that represents the asynchronous removal operation. The task result contains a value that /// indicates whether the module is successfully removed. /// public async Task RemoveModuleAsync (Type type) { await _lock.WaitAsync().ConfigureAwait(false); try { if (!_typedModuleDefs.TryRemove(type, out var module)) return false; return RemoveModuleInternal(module); } finally { _lock.Release(); } } /// /// Remove a command module. /// /// The to be removed from the service. /// /// A task that represents the asynchronous removal operation. The task result contains a value that /// indicates whether the is successfully removed. /// public async Task RemoveModuleAsync(ModuleInfo module) { await _lock.WaitAsync().ConfigureAwait(false); try { var typeModulePair = _typedModuleDefs.FirstOrDefault(x => x.Value.Equals(module)); if (!typeModulePair.Equals(default(KeyValuePair))) _typedModuleDefs.TryRemove(typeModulePair.Key, out var _); return RemoveModuleInternal(module); } finally { _lock.Release(); } } private bool RemoveModuleInternal (ModuleInfo moduleInfo) { if (!_moduleDefs.Remove(moduleInfo)) return false; foreach (var command in moduleInfo.SlashCommands) { _slashCommandMap.RemoveCommand(command); } return true; } /// /// Search the registered slash commands using a . /// /// Interaction entity to perform the search with. /// /// The search result. When successful, result contains the found . /// public SearchResult SearchSlashCommand(ISlashCommandInteraction slashCommandInteraction) => _slashCommandMap.GetCommand(slashCommandInteraction.Data.GetCommandKeywords()); /// /// Search the registered slash commands using a . /// /// Interaction entity to perform the search with. /// /// The search result. When successful, result contains the found . /// public SearchResult SearchComponentCommand(IComponentInteraction componentInteraction) => _componentCommandMap.GetCommand(componentInteraction.Data.CustomId); /// /// Search the registered slash commands using a . /// /// Interaction entity to perform the search with. /// /// The search result. When successful, result contains the found . /// public SearchResult SearchUserCommand(IUserCommandInteraction userCommandInteraction) => _contextCommandMaps[ApplicationCommandType.User].GetCommand(userCommandInteraction.Data.Name); /// /// Search the registered slash commands using a . /// /// Interaction entity to perform the search with. /// /// The search result. When successful, result contains the found . /// public SearchResult SearchMessageCommand(IMessageCommandInteraction messageCommandInteraction) => _contextCommandMaps[ApplicationCommandType.Message].GetCommand(messageCommandInteraction.Data.Name); /// /// Search the registered slash commands using a . /// /// Interaction entity to perform the search with. /// /// The search result. When successful, result contains the found . /// public SearchResult SearchAutocompleteCommand(IAutocompleteInteraction autocompleteInteraction) { var keywords = autocompleteInteraction.Data.GetCommandKeywords(); keywords.Add(autocompleteInteraction.Data.Current.Name); return _autocompleteCommandMap.GetCommand(keywords); } /// /// Execute a Command from a given . /// /// Name context of the command. /// The service to be used in the command's dependency injection. /// /// A task representing the command execution process. The task result contains the result of the execution. /// public async Task ExecuteCommandAsync (IInteractionContext context, IServiceProvider services) { var interaction = context.Interaction; return interaction switch { ISlashCommandInteraction slashCommand => await ExecuteSlashCommandAsync(context, slashCommand, services).ConfigureAwait(false), IComponentInteraction messageComponent => await ExecuteComponentCommandAsync(context, messageComponent.Data.CustomId, services).ConfigureAwait(false), IUserCommandInteraction userCommand => await ExecuteContextCommandAsync(context, userCommand.Data.Name, ApplicationCommandType.User, services).ConfigureAwait(false), IMessageCommandInteraction messageCommand => await ExecuteContextCommandAsync(context, messageCommand.Data.Name, ApplicationCommandType.Message, services).ConfigureAwait(false), IAutocompleteInteraction autocomplete => await ExecuteAutocompleteAsync(context, autocomplete, services).ConfigureAwait(false), IModalInteraction modalCommand => await ExecuteModalCommandAsync(context, modalCommand.Data.CustomId, services).ConfigureAwait(false), _ => throw new InvalidOperationException($"{interaction.Type} interaction type cannot be executed by the Interaction service"), }; } private async Task ExecuteSlashCommandAsync (IInteractionContext context, ISlashCommandInteraction interaction, IServiceProvider services) { var keywords = interaction.Data.GetCommandKeywords(); var result = _slashCommandMap.GetCommand(keywords); if (!result.IsSuccess) { await _cmdLogger.DebugAsync($"Unknown slash command, skipping execution ({string.Join(" ", keywords).ToUpper()})"); await _slashCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false); return result; } return await result.Command.ExecuteAsync(context, services).ConfigureAwait(false); } private async Task ExecuteContextCommandAsync (IInteractionContext context, string input, ApplicationCommandType commandType, IServiceProvider services) { if (!_contextCommandMaps.TryGetValue(commandType, out var map)) return SearchResult.FromError(input, InteractionCommandError.UnknownCommand, $"No {commandType} command found."); var result = map.GetCommand(input); if (!result.IsSuccess) { await _cmdLogger.DebugAsync($"Unknown context command, skipping execution ({result.Text.ToUpper()})"); await _contextCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false); return result; } return await result.Command.ExecuteAsync(context, services).ConfigureAwait(false); } private async Task ExecuteComponentCommandAsync (IInteractionContext context, string input, IServiceProvider services) { var result = _componentCommandMap.GetCommand(input); if (!result.IsSuccess) { await _cmdLogger.DebugAsync($"Unknown custom interaction id, skipping execution ({input.ToUpper()})"); await _componentCommandExecutedEvent.InvokeAsync(null, context, result).ConfigureAwait(false); return result; } return await result.Command.ExecuteAsync(context, services, result.RegexCaptureGroups).ConfigureAwait(false); } private async Task ExecuteAutocompleteAsync (IInteractionContext context, IAutocompleteInteraction interaction, IServiceProvider services ) { var keywords = interaction.Data.GetCommandKeywords(); if(_enableAutocompleteHandlers) { var autocompleteHandlerResult = _slashCommandMap.GetCommand(keywords); if(autocompleteHandlerResult.IsSuccess) { var parameter = autocompleteHandlerResult.Command.Parameters.FirstOrDefault(x => string.Equals(x.Name, interaction.Data.Current.Name, StringComparison.Ordinal)); if(parameter?.AutocompleteHandler is not null) return await parameter.AutocompleteHandler.ExecuteAsync(context, interaction, parameter, services).ConfigureAwait(false); } } keywords.Add(interaction.Data.Current.Name); var commandResult = _autocompleteCommandMap.GetCommand(keywords); if(!commandResult.IsSuccess) { await _cmdLogger.DebugAsync($"Unknown command name, skipping autocomplete process ({interaction.Data.CommandName.ToUpper()})"); await _autocompleteCommandExecutedEvent.InvokeAsync(null, context, commandResult).ConfigureAwait(false); return commandResult; } return await commandResult.Command.ExecuteAsync(context, services).ConfigureAwait(false); } internal TypeConverter GetTypeConverter(Type type, IServiceProvider services = null) => _typeConverterMap.Get(type, services); /// /// Add a concrete type . /// /// Primary target of the . /// The instance. public void AddTypeConverter(TypeConverter converter) => _typeConverterMap.AddConcrete(converter); /// /// Add a concrete type . /// /// Primary target of the . /// The instance. public void AddTypeConverter(Type type, TypeConverter converter) => _typeConverterMap.AddConcrete(type, converter); /// /// Add a generic type . /// /// Generic Type constraint of the of the . /// Type of the . public void AddGenericTypeConverter(Type converterType) => _typeConverterMap.AddGeneric(converterType); /// /// Add a generic type . /// /// Generic Type constraint of the of the . /// Type of the . public void AddGenericTypeConverter(Type targetType, Type converterType) => _typeConverterMap.AddGeneric(targetType, converterType); internal CompTypeConverter GetComponentTypeConverter(Type type, IServiceProvider services = null) => _compTypeConverterMap.Get(type, services); /// /// Add a concrete type . /// /// Primary target of the . /// The instance. public void AddComponentTypeConverter(CompTypeConverter converter) => AddComponentTypeConverter(typeof(T), converter); /// /// Add a concrete type . /// /// Primary target of the . /// The instance. public void AddComponentTypeConverter(Type type, CompTypeConverter converter) => _compTypeConverterMap.AddConcrete(type, converter); /// /// Add a generic type . /// /// Generic Type constraint of the of the . /// Type of the . public void AddGenericComponentTypeConverter(Type converterType) => AddGenericComponentTypeConverter(typeof(T), converterType); /// /// Add a generic type . /// /// Generic Type constraint of the of the . /// Type of the . public void AddGenericComponentTypeConverter(Type targetType, Type converterType) => _compTypeConverterMap.AddGeneric(targetType, converterType); public string SerializeWithTypeReader(object obj, IServiceProvider services = null) => _compTypeConverterMap.Get(typeof(T), services).Serialize(obj); internal IAutocompleteHandler GetAutocompleteHandler(Type autocompleteHandlerType, IServiceProvider services = null) { services ??= EmptyServiceProvider.Instance; if (!_enableAutocompleteHandlers) throw new InvalidOperationException($"{nameof(IAutocompleteHandler)}s are not enabled. To use this feature set {nameof(InteractionServiceConfig.EnableAutocompleteHandlers)} to TRUE"); if (_autocompleteHandlers.TryGetValue(autocompleteHandlerType, out var autocompleteHandler)) return autocompleteHandler; else { autocompleteHandler = ReflectionUtils.CreateObject(autocompleteHandlerType.GetTypeInfo(), this, services); _autocompleteHandlers[autocompleteHandlerType] = autocompleteHandler; return autocompleteHandler; } } /// /// Modify the command permissions of the matching Discord Slash Command. /// /// Module representing the top level Slash Command. /// Target guild. /// New permission values. /// /// The active command permissions after the modification. /// public async Task ModifySlashCommandPermissionsAsync (ModuleInfo module, IGuild guild, params ApplicationCommandPermission[] permissions) { if (!module.IsSlashGroup) throw new InvalidOperationException($"This module does not have a {nameof(GroupAttribute)} and does not represent an Application Command"); if (!module.IsTopLevelGroup) throw new InvalidOperationException("This module is not a top level application command. You cannot change its permissions"); if (guild is null) throw new ArgumentNullException("guild"); var commands = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false); var appCommand = commands.First(x => x.Name == module.SlashGroupName); return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false); } /// /// Modify the command permissions of the matching Discord Slash Command. /// /// The Slash Command. /// Target guild. /// New permission values. /// /// The active command permissions after the modification. /// public async Task ModifySlashCommandPermissionsAsync (SlashCommandInfo command, IGuild guild, params ApplicationCommandPermission[] permissions) => await ModifyApplicationCommandPermissionsAsync(command, guild, permissions).ConfigureAwait(false); /// /// Modify the command permissions of the matching Discord Slash Command. /// /// The Context Command. /// Target guild. /// New permission values. /// /// The active command permissions after the modification. /// public async Task ModifyContextCommandPermissionsAsync (ContextCommandInfo command, IGuild guild, params ApplicationCommandPermission[] permissions) => await ModifyApplicationCommandPermissionsAsync(command, guild, permissions).ConfigureAwait(false); private async Task ModifyApplicationCommandPermissionsAsync (T command, IGuild guild, params ApplicationCommandPermission[] permissions) where T : class, IApplicationCommandInfo, ICommandInfo { if (!command.IsTopLevelCommand) throw new InvalidOperationException("This command is not a top level application command. You cannot change its permissions"); if (guild is null) throw new ArgumentNullException("guild"); var commands = await RestClient.GetGuildApplicationCommands(guild.Id).ConfigureAwait(false); var appCommand = commands.First(x => x.Name == ( command as IApplicationCommandInfo ).Name); return await appCommand.ModifyCommandPermissions(permissions).ConfigureAwait(false); } /// /// Gets a . /// /// Declaring module type of this command, must be a type of . /// Method name of the handler, use of is recommended. /// /// instance for this command. /// /// Module or Slash Command couldn't be found. public SlashCommandInfo GetSlashCommandInfo (string methodName) where TModule : class { var module = GetModuleInfo(); return module.SlashCommands.First(x => x.MethodName == methodName); } /// /// Gets a . /// /// Declaring module type of this command, must be a type of . /// Method name of the handler, use of is recommended. /// /// instance for this command. /// /// Module or Context Command couldn't be found. public ContextCommandInfo GetContextCommandInfo (string methodName) where TModule : class { var module = GetModuleInfo(); return module.ContextCommands.First(x => x.MethodName == methodName); } /// /// Gets a . /// /// Declaring module type of this command, must be a type of . /// Method name of the handler, use of is recommended. /// /// instance for this command. /// /// Module or Component Command couldn't be found. public ComponentCommandInfo GetComponentCommandInfo (string methodName) where TModule : class { var module = GetModuleInfo(); return module.ComponentCommands.First(x => x.MethodName == methodName); } /// /// Gets a built . /// /// Type of the module, must be a type of . /// /// instance for this module. /// public ModuleInfo GetModuleInfo ( ) where TModule : class { if (!typeof(IInteractionModuleBase).IsAssignableFrom(typeof(TModule))) throw new ArgumentException("Type parameter must be a type of Slash Module", "TModule"); var module = _typedModuleDefs[typeof(TModule)]; if (module is null) throw new InvalidOperationException($"{typeof(TModule).FullName} is not loaded to the Slash Command Service"); return module; } /// public void Dispose ( ) { _lock.Dispose(); } private void EnsureClientReady() { if (RestClient?.CurrentUser is null || RestClient?.CurrentUser?.Id == 0) throw new InvalidOperationException($"Provided client is not ready to execute this operation, invoke this operation after a `Client Ready` event"); } } }