diff --git a/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs b/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs index 9bdf8539e..b59151c92 100644 --- a/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs @@ -43,7 +43,7 @@ namespace Discord.Interactions.Builders /// /// The builder instance. /// - public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) + public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services) { base.SetParameterType(type); diff --git a/src/Discord.Net.Interactions/Info/Commands/AutocompleteCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/AutocompleteCommandInfo.cs index 712b058a3..9e30c55f4 100644 --- a/src/Discord.Net.Interactions/Info/Commands/AutocompleteCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/AutocompleteCommandInfo.cs @@ -41,14 +41,7 @@ namespace Discord.Interactions if (context.Interaction is not IAutocompleteInteraction) return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Autocomplete Interaction"); - try - { - return await RunAsync(context, Array.Empty(), services).ConfigureAwait(false); - } - catch (Exception ex) - { - return ExecuteResult.FromError(ex); - } + return await RunAsync(context, Array.Empty(), services).ConfigureAwait(false); } /// diff --git a/src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs index cf1a2dfa1..28a943c99 100644 --- a/src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/CommandInfo.cs @@ -31,6 +31,8 @@ namespace Discord.Interactions private readonly ExecuteCallback _action; private readonly ILookup _groupedPreconditions; + internal IReadOnlyDictionary _parameterDictionary; + /// public ModuleInfo Module { get; } @@ -120,10 +122,7 @@ namespace Discord.Interactions return moduleResult; var commandResult = await CheckGroups(_groupedPreconditions, "Command").ConfigureAwait(false); - if (!commandResult.IsSuccess) - return commandResult; - - return PreconditionResult.FromSuccess(); + return !commandResult.IsSuccess ? commandResult : PreconditionResult.FromSuccess(); } protected async Task RunAsync(IInteractionContext context, object[] args, IServiceProvider services) @@ -137,8 +136,8 @@ namespace Discord.Interactions using var scope = services?.CreateScope(); return await ExecuteInternalAsync(context, args, scope?.ServiceProvider ?? EmptyServiceProvider.Instance).ConfigureAwait(false); } - else - return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false); + + return await ExecuteInternalAsync(context, args, services).ConfigureAwait(false); } case RunMode.Async: _ = Task.Run(async () => @@ -167,20 +166,14 @@ namespace Discord.Interactions { var preconditionResult = await CheckPreconditionsAsync(context, services).ConfigureAwait(false); if (!preconditionResult.IsSuccess) - { - await InvokeModuleEvent(context, preconditionResult).ConfigureAwait(false); - return preconditionResult; - } + return await InvokeEventAndReturn(context, preconditionResult).ConfigureAwait(false); var index = 0; foreach (var parameter in Parameters) { var result = await parameter.CheckPreconditionsAsync(context, args[index++], services).ConfigureAwait(false); if (!result.IsSuccess) - { - await InvokeModuleEvent(context, result).ConfigureAwait(false); - return result; - } + return await InvokeEventAndReturn(context, result).ConfigureAwait(false); } var task = _action(context, args, services, this); @@ -189,20 +182,16 @@ namespace Discord.Interactions { var result = await resultTask.ConfigureAwait(false); await InvokeModuleEvent(context, result).ConfigureAwait(false); - if (result is RuntimeResult || result is ExecuteResult) + if (result is RuntimeResult or ExecuteResult) return result; } else { await task.ConfigureAwait(false); - var result = ExecuteResult.FromSuccess(); - await InvokeModuleEvent(context, result).ConfigureAwait(false); - return result; + return await InvokeEventAndReturn(context, ExecuteResult.FromSuccess()).ConfigureAwait(false); } - var failResult = ExecuteResult.FromError(InteractionCommandError.Unsuccessful, "Command execution failed for an unknown reason"); - await InvokeModuleEvent(context, failResult).ConfigureAwait(false); - return failResult; + return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.Unsuccessful, "Command execution failed for an unknown reason")).ConfigureAwait(false); } catch (Exception ex) { @@ -231,6 +220,12 @@ namespace Discord.Interactions } } + protected async ValueTask InvokeEventAndReturn(IInteractionContext context, IResult result) + { + await InvokeModuleEvent(context, result).ConfigureAwait(false); + return result; + } + private static bool CheckTopLevel(ModuleInfo parent) { var currentParent = parent; diff --git a/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs index 38d3890cc..186419cfb 100644 --- a/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs @@ -48,6 +48,11 @@ namespace Discord.Interactions public async Task ExecuteAsync(IInteractionContext context, IEnumerable paramList, IEnumerable wildcardCaptures, IComponentInteractionData data, IServiceProvider services) { + var paramCount = paramList.Count(); + var captureCount = wildcardCaptures?.Count() ?? 0; + + if (paramCount < captureCount + 1) + return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too many parameters")).ConfigureAwait(false); if (context.Interaction is not IComponentInteraction messageComponent) return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Component Command Interaction"); @@ -58,27 +63,16 @@ namespace Discord.Interactions for (var i = 0; i < paramCount; i++) { var parameter = Parameters.ElementAt(i); - bool isCapture = i < captureCount; + var isCapture = i < captureCount; if (isCapture ^ parameter.IsRouteSegmentParameter) - { - var result = ExecuteResult.FromError(InteractionCommandError.BadArgs, $"Argument type and parameter type didn't match (Wild Card capture/Component value)"); - await InvokeModuleEvent(context, result).ConfigureAwait(false); - return result; - } - - TypeConverterResult readResult; + return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Argument type and parameter type didn't match (Wild Card capture/Component value)")).ConfigureAwait(false); - if (isCapture) - readResult = await parameter.TypeReader.ReadAsync(context, wildcardCaptures.ElementAt(i), services).ConfigureAwait(false); - else - readResult = await parameter.TypeConverter.ReadAsync(context, data, services).ConfigureAwait(false); + var readResult = isCapture ? await parameter.TypeReader.ReadAsync(context, wildcardCaptures.ElementAt(i), services).ConfigureAwait(false) : + await parameter.TypeConverter.ReadAsync(context, data, services).ConfigureAwait(false); if (!readResult.IsSuccess) - { - await InvokeModuleEvent(context, readResult).ConfigureAwait(false); - return readResult; - } + return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false); args[i] = readResult.Value; } @@ -87,9 +81,7 @@ namespace Discord.Interactions } catch (Exception ex) { - var result = ExecuteResult.FromError(ex); - await InvokeModuleEvent(context, result).ConfigureAwait(false); - return result; + return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs index 1934b2293..a55a1307a 100644 --- a/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.Tracing; using System.Linq; using System.Threading.Tasks; namespace Discord.Interactions @@ -57,37 +58,28 @@ namespace Discord.Interactions if(i < captureCount) { var readResult = await parameter.TypeReader.ReadAsync(context, additionalArgs[i], services).ConfigureAwait(false); + if (!readResult.IsSuccess) + return await InvokeEventAndReturn(context, readResult).ConfigureAwait(false); - if(!readResult.IsSuccess) - { - await InvokeModuleEvent(context, readResult).ConfigureAwait(false); - return readResult; - } args[i] = readResult.Value; } else { var modalResult = await Modal.CreateModalAsync(context, services, Module.CommandService._exitOnMissingModalField).ConfigureAwait(false); - if (!modalResult.IsSuccess) - { - await InvokeModuleEvent(context, modalResult).ConfigureAwait(false); - return modalResult; - } + return await InvokeEventAndReturn(context, modalResult).ConfigureAwait(false); + + if (modalResult is not ParseResult parseResult) + return await InvokeEventAndReturn(context, ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.")); - if (modalResult is ParseResult parseResult) - args[i] = parseResult.Value; - else - return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason."); + args[i] = parseResult.Value; } } - return await RunAsync(context, args.ToArray(), services); + return await RunAsync(context, args, services); } catch (Exception ex) { - var result = ExecuteResult.FromError(ex); - await InvokeModuleEvent(context, result).ConfigureAwait(false); - return result; + return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs index 116a07ab4..d95695a54 100644 --- a/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs @@ -56,48 +56,67 @@ namespace Discord.Interactions { try { - if (paramList?.Count() < argList?.Count()) - return ExecuteResult.FromError(InteractionCommandError.BadArgs ,"Command was invoked with too many parameters"); + var slashCommandParameterInfos = paramList.ToList(); + var args = new object[slashCommandParameterInfos.Count]; - var args = new object[paramList.Count()]; - - for (var i = 0; i < paramList.Count(); i++) + for (var i = 0; i < slashCommandParameterInfos.Count; i++) { - var parameter = paramList.ElementAt(i); - - var arg = argList?.Find(x => string.Equals(x.Name, parameter.Name, StringComparison.OrdinalIgnoreCase)); - - if (arg == default) - { - if (parameter.IsRequired) - return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters"); - else - args[i] = parameter.DefaultValue; - } - else - { - var typeConverter = parameter.TypeConverter; - - var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false); - - if (!readResult.IsSuccess) - { - await InvokeModuleEvent(context, readResult).ConfigureAwait(false); - return readResult; - } - - args[i] = readResult.Value; - } - } + var parameter = slashCommandParameterInfos[i]; + var result = await ParseArgument(parameter, context, argList, services).ConfigureAwait(false); + + if (!result.IsSuccess) + return await InvokeEventAndReturn(context, result).ConfigureAwait(false); + if (result is not ParseResult parseResult) + return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason."); + + args[i] = parseResult.Value; + } return await RunAsync(context, args, services).ConfigureAwait(false); } catch (Exception ex) { - return ExecuteResult.FromError(ex); + return await InvokeEventAndReturn(context, ExecuteResult.FromError(ex)).ConfigureAwait(false); } } + private async Task ParseArgument(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List argList, + IServiceProvider services) + { + if (parameterInfo.IsComplexParameter) + { + var ctorArgs = new object[parameterInfo.ComplexParameterFields.Count]; + + for (var i = 0; i < ctorArgs.Length; i++) + { + var result = await ParseArgument(parameterInfo.ComplexParameterFields.ElementAt(i), context, argList, services).ConfigureAwait(false); + + if (!result.IsSuccess) + return result; + + if (result is not ParseResult parseResult) + return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason."); + + ctorArgs[i] = parseResult.Value; + } + + return ParseResult.FromSuccess(parameterInfo._complexParameterInitializer(ctorArgs)); + } + + var arg = argList?.Find(x => string.Equals(x.Name, parameterInfo.Name, StringComparison.OrdinalIgnoreCase)); + + if (arg == default) + return parameterInfo.IsRequired ? ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters") : + ParseResult.FromSuccess(parameterInfo.DefaultValue); + + var typeConverter = parameterInfo.TypeConverter; + var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false); + if (!readResult.IsSuccess) + return readResult; + + return ParseResult.FromSuccess(readResult.Value); + } + protected override Task InvokeModuleEvent (IInteractionContext context, IResult result) => CommandService._slashCommandExecutedEvent.InvokeAsync(this, context, result); diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs index 1b7521828..ff4372f31 100644 --- a/src/Discord.Net.Interactions/InteractionService.cs +++ b/src/Discord.Net.Interactions/InteractionService.cs @@ -193,7 +193,7 @@ namespace Discord.Interactions [typeof(IMentionable)] = typeof(DefaultMentionableConverter<>), [typeof(IConvertible)] = typeof(DefaultValueConverter<>), [typeof(Enum)] = typeof(EnumConverter<>), - [typeof(Nullable<>)] = typeof(NullableConverter<>), + [typeof(Nullable<>)] = typeof(NullableConverter<>) }); _compTypeConverterMap = new TypeMap(this, new ConcurrentDictionary(), @@ -209,7 +209,7 @@ namespace Discord.Interactions [typeof(IChannel)] = typeof(DefaultChannelReader<>), [typeof(IRole)] = typeof(DefaultRoleReader<>), [typeof(IUser)] = typeof(DefaultUserReader<>), - [typeof(IMessage)] = typeof(DefaultUserReader<>), + [typeof(IMessage)] = typeof(DefaultMessageReader<>), [typeof(IConvertible)] = typeof(DefaultValueReader<>), [typeof(Enum)] = typeof(EnumReader<>) }); @@ -311,7 +311,7 @@ namespace Discord.Interactions 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"); + throw new ArgumentException("Type parameter must be a type of Slash Module", nameof(type)); services ??= EmptyServiceProvider.Instance; @@ -344,7 +344,7 @@ namespace Discord.Interactions } /// - /// Register Application Commands from and to a guild. + /// 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 . @@ -440,7 +440,7 @@ namespace Discord.Interactions } /// - /// Register Application Commands from modules provided in to a guild. + /// Register Application Commands from modules provided in to a guild. /// /// The target guild. /// Modules to be registered to Discord. @@ -467,7 +467,7 @@ namespace Discord.Interactions } /// - /// Register Application Commands from modules provided in as global commands. + /// Register Application Commands from modules provided in as global commands. /// /// Modules to be registered to Discord. /// @@ -695,7 +695,7 @@ namespace Discord.Interactions public async Task ExecuteCommandAsync (IInteractionContext context, IServiceProvider services) { var interaction = context.Interaction; - + return interaction switch { ISlashCommandInteraction slashCommand => await ExecuteSlashCommandAsync(context, slashCommand, services).ConfigureAwait(false), @@ -865,7 +865,7 @@ namespace Discord.Interactions /// Add a concrete type . /// /// Primary target of the . - /// The instance. + /// The instance. public void AddTypeReader(TypeReader reader) => AddTypeReader(typeof(T), reader); @@ -873,7 +873,7 @@ namespace Discord.Interactions /// Add a concrete type . /// /// Primary target of the . - /// The instance. + /// The instance. public void AddTypeReader(Type type, TypeReader reader) => _typeReaderMap.AddConcrete(type, reader); @@ -1066,7 +1066,7 @@ namespace Discord.Interactions 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"); + throw new ArgumentException("Type parameter must be a type of Slash Module", nameof(TModule)); var module = _typedModuleDefs[typeof(TModule)]; diff --git a/src/Discord.Net.Interactions/Map/TypeMap.cs b/src/Discord.Net.Interactions/Map/TypeMap.cs index 9ffb1b49a..ef1ef4a53 100644 --- a/src/Discord.Net.Interactions/Map/TypeMap.cs +++ b/src/Discord.Net.Interactions/Map/TypeMap.cs @@ -25,8 +25,8 @@ namespace Discord.Interactions if (_concretes.TryGetValue(type, out var specific)) return specific; - else if (_generics.Any(x => x.Key.IsAssignableFrom(type) - || (x.Key.IsGenericTypeDefinition && type.IsGenericType && x.Key.GetGenericTypeDefinition() == type.GetGenericTypeDefinition()))) + if (_generics.Any(x => x.Key.IsAssignableFrom(type) + || x.Key.IsGenericTypeDefinition && type.IsGenericType && x.Key.GetGenericTypeDefinition() == type.GetGenericTypeDefinition())) { services ??= EmptyServiceProvider.Instance; @@ -36,10 +36,10 @@ namespace Discord.Interactions return converter; } - else if (_concretes.Any(x => x.Value.CanConvertTo(type))) + if (_concretes.Any(x => x.Value.CanConvertTo(type))) return _concretes.First(x => x.Value.CanConvertTo(type)).Value; - throw new ArgumentException($"No type {typeof(TConverter).Name} is defined for this {type.FullName}", "type"); + throw new ArgumentException($"No type {typeof(TConverter).Name} is defined for this {type.FullName}", nameof(type)); } public void AddConcrete(TConverter converter) => @@ -63,7 +63,7 @@ namespace Discord.Interactions var genericArguments = converterType.GetGenericArguments(); - if (genericArguments.Count() > 1) + if (genericArguments.Length > 1) throw new InvalidOperationException($"Valid generic {converterType.FullName}s cannot have more than 1 generic type parameter"); var constraints = genericArguments.SelectMany(x => x.GetGenericParameterConstraints()); diff --git a/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs b/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs index eebce8877..d1dcc1751 100644 --- a/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs @@ -15,10 +15,8 @@ namespace Discord.Interactions var result = await GetEntity(snowflake, context).ConfigureAwait(false); - if (result is not null) - return TypeConverterResult.FromSuccess(result); - else - return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option} must be a valid {typeof(T).Name} snowflake to be parsed."); + return result is not null ? + TypeConverterResult.FromSuccess(result) : TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option} must be a valid {typeof(T).Name} snowflake to be parsed."); } public override Task SerializeAsync(object obj) => Task.FromResult((obj as ISnowflakeEntity)?.Id.ToString()); diff --git a/src/Discord.Net.Interactions/TypeReaders/EnumReader.cs b/src/Discord.Net.Interactions/TypeReaders/EnumReader.cs index 8b2264c99..799138061 100644 --- a/src/Discord.Net.Interactions/TypeReaders/EnumReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/EnumReader.cs @@ -8,10 +8,8 @@ namespace Discord.Interactions { public override Task ReadAsync(IInteractionContext context, string option, IServiceProvider services) { - if (Enum.TryParse(option, out var result)) - return Task.FromResult(TypeConverterResult.FromSuccess(result)); - else - return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {option} cannot be converted to {nameof(T)}")); + return Task.FromResult(Enum.TryParse(option, out var result) ? + TypeConverterResult.FromSuccess(result) : TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {option} cannot be converted to {nameof(T)}")); } public override Task SerializeAsync(object obj) diff --git a/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs index 95dc3c8a5..c8420d556 100644 --- a/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs @@ -20,8 +20,8 @@ namespace Discord.Interactions /// /// Will be used to read the incoming payload before executing the method body. /// - /// Command exexution context. - /// Recieved option payload. + /// Command execution context. + /// Received option payload. /// Service provider that will be used to initialize the command module. /// The result of the read process. public abstract Task ReadAsync(IInteractionContext context, string option, IServiceProvider services); @@ -31,7 +31,7 @@ namespace Discord.Interactions /// /// Object to be serialized. /// - /// A task represting the conversion process. The result of the task contains the conversion result. + /// A task representing the conversion process. The result of the task contains the conversion result. /// public virtual Task SerializeAsync(object obj) => Task.FromResult(obj.ToString()); } diff --git a/src/Discord.Net.Interactions/Utilities/ModalUtils.cs b/src/Discord.Net.Interactions/Utilities/ModalUtils.cs index 9fdd195c2..e2d028e1f 100644 --- a/src/Discord.Net.Interactions/Utilities/ModalUtils.cs +++ b/src/Discord.Net.Interactions/Utilities/ModalUtils.cs @@ -7,7 +7,7 @@ namespace Discord.Interactions { internal static class ModalUtils { - private static ConcurrentDictionary _modalInfos = new(); + private static readonly ConcurrentDictionary _modalInfos = new(); public static IReadOnlyCollection Modals => _modalInfos.Values.ToReadOnlyCollection();