* add complex parameters * add complex parameters * fix build errors * add argument parsing * add nested complex parameter checks * add inline docs * add preferred constructor declaration * fix autocompletehandlers for complex parameters * make GetConstructor private * use flattened params in ToProps method * make DiscordType of SlashParameter nullable * add docs to Flattened parameters collection and move the GetComplexParameterCtor method * add inline docs to SlashCommandParameterBuilder.ComplexParameterFields * add check for validating required/optinal parameter order * implement change requests * return internal ParseResult as ExecuteResult Co-Authored-By: Cenk Ergen <57065323+Cenngo@users.noreply.github.com>pull/2155/head
| @@ -0,0 +1,30 @@ | |||||
| using System; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| /// <summary> | |||||
| /// Registers a parameter as a complex parameter. | |||||
| /// </summary> | |||||
| [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] | |||||
| public class ComplexParameterAttribute : Attribute | |||||
| { | |||||
| /// <summary> | |||||
| /// Gets the parameter array of the constructor method that should be prioritized. | |||||
| /// </summary> | |||||
| public Type[] PrioritizedCtorSignature { get; } | |||||
| /// <summary> | |||||
| /// Registers a slash command parameter as a complex parameter. | |||||
| /// </summary> | |||||
| public ComplexParameterAttribute() { } | |||||
| /// <summary> | |||||
| /// Registers a slash command parameter as a complex parameter with a specified constructor signature. | |||||
| /// </summary> | |||||
| /// <param name="types">Type array of the preferred constructor parameters.</param> | |||||
| public ComplexParameterAttribute(Type[] types) | |||||
| { | |||||
| PrioritizedCtorSignature = types; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| using System; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| /// <summary> | |||||
| /// Tag a type constructor as the preferred Complex command constructor. | |||||
| /// </summary> | |||||
| [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = true)] | |||||
| public class ComplexParameterCtorAttribute : Attribute { } | |||||
| } | |||||
| @@ -397,7 +397,6 @@ namespace Discord.Interactions.Builders | |||||
| builder.Description = paramInfo.Name; | builder.Description = paramInfo.Name; | ||||
| builder.IsRequired = !paramInfo.IsOptional; | builder.IsRequired = !paramInfo.IsOptional; | ||||
| builder.DefaultValue = paramInfo.DefaultValue; | builder.DefaultValue = paramInfo.DefaultValue; | ||||
| builder.SetParameterType(paramType, services); | |||||
| foreach (var attribute in attributes) | foreach (var attribute in attributes) | ||||
| { | { | ||||
| @@ -435,12 +434,32 @@ namespace Discord.Interactions.Builders | |||||
| case MinValueAttribute minValue: | case MinValueAttribute minValue: | ||||
| builder.MinValue = minValue.Value; | builder.MinValue = minValue.Value; | ||||
| break; | break; | ||||
| case ComplexParameterAttribute complexParameter: | |||||
| { | |||||
| builder.IsComplexParameter = true; | |||||
| ConstructorInfo ctor = GetComplexParameterConstructor(paramInfo.ParameterType.GetTypeInfo(), complexParameter); | |||||
| foreach (var parameter in ctor.GetParameters()) | |||||
| { | |||||
| if (parameter.IsDefined(typeof(ComplexParameterAttribute))) | |||||
| throw new InvalidOperationException("You cannot create nested complex parameters."); | |||||
| builder.AddComplexParameterField(fieldBuilder => BuildSlashParameter(fieldBuilder, parameter, services)); | |||||
| } | |||||
| var initializer = builder.Command.Module.InteractionService._useCompiledLambda ? | |||||
| ReflectionUtils<object>.CreateLambdaConstructorInvoker(paramInfo.ParameterType.GetTypeInfo()) : ctor.Invoke; | |||||
| builder.ComplexParameterInitializer = args => initializer(args); | |||||
| } | |||||
| break; | |||||
| default: | default: | ||||
| builder.AddAttributes(attribute); | builder.AddAttributes(attribute); | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| builder.SetParameterType(paramType, services); | |||||
| // Replace pascal casings with '-' | // Replace pascal casings with '-' | ||||
| builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower(); | builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower(); | ||||
| } | } | ||||
| @@ -608,5 +627,41 @@ namespace Discord.Interactions.Builders | |||||
| propertyInfo.SetMethod?.IsStatic == false && | propertyInfo.SetMethod?.IsStatic == false && | ||||
| propertyInfo.IsDefined(typeof(ModalInputAttribute)); | propertyInfo.IsDefined(typeof(ModalInputAttribute)); | ||||
| } | } | ||||
| private static ConstructorInfo GetComplexParameterConstructor(TypeInfo typeInfo, ComplexParameterAttribute complexParameter) | |||||
| { | |||||
| var ctors = typeInfo.GetConstructors(); | |||||
| if (ctors.Length == 0) | |||||
| throw new InvalidOperationException($"No constructor found for \"{typeInfo.FullName}\"."); | |||||
| if (complexParameter.PrioritizedCtorSignature is not null) | |||||
| { | |||||
| var ctor = typeInfo.GetConstructor(complexParameter.PrioritizedCtorSignature); | |||||
| if (ctor is null) | |||||
| throw new InvalidOperationException($"No constructor was found with the signature: {string.Join(",", complexParameter.PrioritizedCtorSignature.Select(x => x.Name))}"); | |||||
| return ctor; | |||||
| } | |||||
| var prioritizedCtors = ctors.Where(x => x.IsDefined(typeof(ComplexParameterCtorAttribute), true)); | |||||
| switch (prioritizedCtors.Count()) | |||||
| { | |||||
| case > 1: | |||||
| throw new InvalidOperationException($"{nameof(ComplexParameterCtorAttribute)} can only be used once in a type."); | |||||
| case 1: | |||||
| return prioritizedCtors.First(); | |||||
| } | |||||
| switch (ctors.Length) | |||||
| { | |||||
| case > 1: | |||||
| throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\"."); | |||||
| default: | |||||
| return ctors.First(); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | |||||
| namespace Discord.Interactions.Builders | namespace Discord.Interactions.Builders | ||||
| { | { | ||||
| @@ -10,6 +11,7 @@ namespace Discord.Interactions.Builders | |||||
| { | { | ||||
| private readonly List<ParameterChoice> _choices = new(); | private readonly List<ParameterChoice> _choices = new(); | ||||
| private readonly List<ChannelType> _channelTypes = new(); | private readonly List<ChannelType> _channelTypes = new(); | ||||
| private readonly List<SlashCommandParameterBuilder> _complexParameterFields = new(); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the description of this parameter. | /// Gets or sets the description of this parameter. | ||||
| @@ -36,6 +38,11 @@ namespace Discord.Interactions.Builders | |||||
| /// </summary> | /// </summary> | ||||
| public IReadOnlyCollection<ChannelType> ChannelTypes => _channelTypes; | public IReadOnlyCollection<ChannelType> ChannelTypes => _channelTypes; | ||||
| /// <summary> | |||||
| /// Gets the constructor parameters of this parameter, if <see cref="IsComplexParameter"/> is <see langword="true"/>. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SlashCommandParameterBuilder> ComplexParameterFields => _complexParameterFields; | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets whether this parameter should be configured for Autocomplete Interactions. | /// Gets or sets whether this parameter should be configured for Autocomplete Interactions. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -46,6 +53,16 @@ namespace Discord.Interactions.Builders | |||||
| /// </summary> | /// </summary> | ||||
| public TypeConverter TypeConverter { get; private set; } | public TypeConverter TypeConverter { get; private set; } | ||||
| /// <summary> | |||||
| /// Gets whether this type should be treated as a complex parameter. | |||||
| /// </summary> | |||||
| public bool IsComplexParameter { get; internal set; } | |||||
| /// <summary> | |||||
| /// Gets the initializer delegate for this parameter, if <see cref="IsComplexParameter"/> is <see langword="true"/>. | |||||
| /// </summary> | |||||
| public ComplexParameterInitializer ComplexParameterInitializer { get; internal set; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets or sets the <see cref="IAutocompleteHandler"/> of this parameter. | /// Gets or sets the <see cref="IAutocompleteHandler"/> of this parameter. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -60,7 +77,14 @@ namespace Discord.Interactions.Builders | |||||
| /// <param name="command">Parent command of this parameter.</param> | /// <param name="command">Parent command of this parameter.</param> | ||||
| /// <param name="name">Name of this command.</param> | /// <param name="name">Name of this command.</param> | ||||
| /// <param name="type">Type of this parameter.</param> | /// <param name="type">Type of this parameter.</param> | ||||
| public SlashCommandParameterBuilder(ICommandBuilder command, string name, Type type) : base(command, name, type) { } | |||||
| public SlashCommandParameterBuilder(ICommandBuilder command, string name, Type type, ComplexParameterInitializer complexParameterInitializer = null) | |||||
| : base(command, name, type) | |||||
| { | |||||
| ComplexParameterInitializer = complexParameterInitializer; | |||||
| if (complexParameterInitializer is not null) | |||||
| IsComplexParameter = true; | |||||
| } | |||||
| /// <summary> | /// <summary> | ||||
| /// Sets <see cref="Description"/>. | /// Sets <see cref="Description"/>. | ||||
| @@ -168,7 +192,47 @@ namespace Discord.Interactions.Builders | |||||
| public SlashCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) | public SlashCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) | ||||
| { | { | ||||
| base.SetParameterType(type); | base.SetParameterType(type); | ||||
| TypeConverter = Command.Module.InteractionService.GetTypeConverter(ParameterType, services); | |||||
| if(!IsComplexParameter) | |||||
| TypeConverter = Command.Module.InteractionService.GetTypeConverter(ParameterType, services); | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Adds a parameter builders to <see cref="ComplexParameterFields"/>. | |||||
| /// </summary> | |||||
| /// <param name="configure"><see cref="SlashCommandParameterBuilder"/> factory.</param> | |||||
| /// <returns> | |||||
| /// The builder instance. | |||||
| /// </returns> | |||||
| /// <exception cref="InvalidOperationException">Thrown if the added field has a <see cref="ComplexParameterAttribute"/>.</exception> | |||||
| public SlashCommandParameterBuilder AddComplexParameterField(Action<SlashCommandParameterBuilder> configure) | |||||
| { | |||||
| SlashCommandParameterBuilder builder = new(Command); | |||||
| configure(builder); | |||||
| if(builder.IsComplexParameter) | |||||
| throw new InvalidOperationException("You cannot create nested complex parameters."); | |||||
| _complexParameterFields.Add(builder); | |||||
| return this; | |||||
| } | |||||
| /// <summary> | |||||
| /// Adds parameter builders to <see cref="ComplexParameterFields"/>. | |||||
| /// </summary> | |||||
| /// <param name="fields">New parameter builders to be added to <see cref="ComplexParameterFields"/>.</param> | |||||
| /// <returns> | |||||
| /// The builder instance. | |||||
| /// </returns> | |||||
| /// <exception cref="InvalidOperationException">Thrown if the added field has a <see cref="ComplexParameterAttribute"/>.</exception> | |||||
| public SlashCommandParameterBuilder AddComplexParameterFields(params SlashCommandParameterBuilder[] fields) | |||||
| { | |||||
| if(fields.Any(x => x.IsComplexParameter)) | |||||
| throw new InvalidOperationException("You cannot create nested complex parameters."); | |||||
| _complexParameterFields.AddRange(fields); | |||||
| return this; | return this; | ||||
| } | } | ||||
| @@ -31,6 +31,8 @@ namespace Discord.Interactions | |||||
| private readonly ExecuteCallback _action; | private readonly ExecuteCallback _action; | ||||
| private readonly ILookup<string, PreconditionAttribute> _groupedPreconditions; | private readonly ILookup<string, PreconditionAttribute> _groupedPreconditions; | ||||
| internal IReadOnlyDictionary<string, TParameter> _parameterDictionary { get; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public ModuleInfo Module { get; } | public ModuleInfo Module { get; } | ||||
| @@ -79,6 +81,7 @@ namespace Discord.Interactions | |||||
| _action = builder.Callback; | _action = builder.Callback; | ||||
| _groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal); | _groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal); | ||||
| _parameterDictionary = Parameters?.ToDictionary(x => x.Name, x => x).ToImmutableDictionary(); | |||||
| } | } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| @@ -13,6 +13,8 @@ namespace Discord.Interactions | |||||
| /// </summary> | /// </summary> | ||||
| public class SlashCommandInfo : CommandInfo<SlashCommandParameterInfo>, IApplicationCommandInfo | public class SlashCommandInfo : CommandInfo<SlashCommandParameterInfo>, IApplicationCommandInfo | ||||
| { | { | ||||
| internal IReadOnlyDictionary<string, SlashCommandParameterInfo> _flattenedParameterDictionary { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the command description that will be displayed on Discord. | /// Gets the command description that will be displayed on Discord. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -30,11 +32,23 @@ namespace Discord.Interactions | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public override bool SupportsWildCards => false; | public override bool SupportsWildCards => false; | ||||
| /// <summary> | |||||
| /// Gets the flattened collection of command parameters and complex parameter fields. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SlashCommandParameterInfo> FlattenedParameters { get; } | |||||
| internal SlashCommandInfo (Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) | internal SlashCommandInfo (Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) | ||||
| { | { | ||||
| Description = builder.Description; | Description = builder.Description; | ||||
| DefaultPermission = builder.DefaultPermission; | DefaultPermission = builder.DefaultPermission; | ||||
| Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); | ||||
| FlattenedParameters = FlattenParameters(Parameters).ToImmutableArray(); | |||||
| for (var i = 0; i < FlattenedParameters.Count - 1; i++) | |||||
| if (!FlattenedParameters.ElementAt(i).IsRequired && FlattenedParameters.ElementAt(i + 1).IsRequired) | |||||
| throw new InvalidOperationException("Optional parameters must appear after all required parameters, ComplexParameters with optional parameters must be located at the end."); | |||||
| _flattenedParameterDictionary = FlattenedParameters?.ToDictionary(x => x.Name, x => x).ToImmutableDictionary(); | |||||
| } | } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| @@ -56,45 +70,81 @@ namespace Discord.Interactions | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| if (paramList?.Count() < argList?.Count()) | |||||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs ,"Command was invoked with too many parameters"); | |||||
| var args = new object[paramList.Count()]; | var args = new object[paramList.Count()]; | ||||
| for (var i = 0; i < paramList.Count(); i++) | for (var i = 0; i < paramList.Count(); i++) | ||||
| { | { | ||||
| var parameter = paramList.ElementAt(i); | var parameter = paramList.ElementAt(i); | ||||
| var arg = argList?.Find(x => string.Equals(x.Name, parameter.Name, StringComparison.OrdinalIgnoreCase)); | |||||
| var result = await ParseArgument(parameter, context, argList, services).ConfigureAwait(false); | |||||
| if (arg == default) | |||||
| if(!result.IsSuccess) | |||||
| { | { | ||||
| if (parameter.IsRequired) | |||||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters"); | |||||
| else | |||||
| args[i] = parameter.DefaultValue; | |||||
| var execResult = ExecuteResult.FromError(result); | |||||
| await InvokeModuleEvent(context, execResult).ConfigureAwait(false); | |||||
| return execResult; | |||||
| } | } | ||||
| if (result is ParseResult parseResult) | |||||
| args[i] = parseResult.Value; | |||||
| else | else | ||||
| { | |||||
| var typeConverter = parameter.TypeConverter; | |||||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason."); | |||||
| } | |||||
| var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false); | |||||
| return await RunAsync(context, args, services).ConfigureAwait(false); | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| var result = ExecuteResult.FromError(ex); | |||||
| await InvokeModuleEvent(context, result).ConfigureAwait(false); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| if (!readResult.IsSuccess) | |||||
| { | |||||
| await InvokeModuleEvent(context, readResult).ConfigureAwait(false); | |||||
| return readResult; | |||||
| } | |||||
| private async Task<IResult> ParseArgument(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List<IApplicationCommandInteractionDataOption> argList, | |||||
| IServiceProvider services) | |||||
| { | |||||
| if (parameterInfo.IsComplexParameter) | |||||
| { | |||||
| var ctorArgs = new object[parameterInfo.ComplexParameterFields.Count]; | |||||
| args[i] = readResult.Value; | |||||
| } | |||||
| 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 ParseResult parseResult) | |||||
| ctorArgs[i] = parseResult.Value; | |||||
| else | |||||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason."); | |||||
| } | } | ||||
| return await RunAsync(context, args, services).ConfigureAwait(false); | |||||
| return ParseResult.FromSuccess(parameterInfo._complexParameterInitializer(ctorArgs)); | |||||
| } | } | ||||
| catch (Exception ex) | |||||
| else | |||||
| { | { | ||||
| return ExecuteResult.FromError(ex); | |||||
| var arg = argList?.Find(x => string.Equals(x.Name, parameterInfo.Name, StringComparison.OrdinalIgnoreCase)); | |||||
| if (arg == default) | |||||
| { | |||||
| if (parameterInfo.IsRequired) | |||||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters"); | |||||
| else | |||||
| return ParseResult.FromSuccess(parameterInfo.DefaultValue); | |||||
| } | |||||
| else | |||||
| { | |||||
| var typeConverter = parameterInfo.TypeConverter; | |||||
| var readResult = await typeConverter.ReadAsync(context, arg, services).ConfigureAwait(false); | |||||
| if (!readResult.IsSuccess) | |||||
| return readResult; | |||||
| return ParseResult.FromSuccess(readResult.Value); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -108,5 +158,15 @@ namespace Discord.Interactions | |||||
| else | else | ||||
| return $"Slash Command: \"{base.ToString()}\" for {context.User} in {context.Channel}"; | return $"Slash Command: \"{base.ToString()}\" for {context.User} in {context.Channel}"; | ||||
| } | } | ||||
| private static IEnumerable<SlashCommandParameterInfo> FlattenParameters(IEnumerable<SlashCommandParameterInfo> parameters) | |||||
| { | |||||
| foreach (var parameter in parameters) | |||||
| if (!parameter.IsComplexParameter) | |||||
| yield return parameter; | |||||
| else | |||||
| foreach(var complexParameterField in parameter.ComplexParameterFields) | |||||
| yield return complexParameterField; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,13 +1,25 @@ | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Linq; | |||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| { | { | ||||
| /// <summary> | |||||
| /// Represents a cached argument constructor delegate. | |||||
| /// </summary> | |||||
| /// <param name="args">Method arguments array.</param> | |||||
| /// <returns> | |||||
| /// Returns the constructed object. | |||||
| /// </returns> | |||||
| public delegate object ComplexParameterInitializer(object[] args); | |||||
| /// <summary> | /// <summary> | ||||
| /// Represents the parameter info class for <see cref="SlashCommandInfo"/> commands. | /// Represents the parameter info class for <see cref="SlashCommandInfo"/> commands. | ||||
| /// </summary> | /// </summary> | ||||
| public class SlashCommandParameterInfo : CommandParameterInfo | public class SlashCommandParameterInfo : CommandParameterInfo | ||||
| { | { | ||||
| internal readonly ComplexParameterInitializer _complexParameterInitializer; | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public new SlashCommandInfo Command => base.Command as SlashCommandInfo; | public new SlashCommandInfo Command => base.Command as SlashCommandInfo; | ||||
| @@ -43,9 +55,14 @@ namespace Discord.Interactions | |||||
| public bool IsAutocomplete { get; } | public bool IsAutocomplete { get; } | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the Discord option type this parameter represents. | |||||
| /// Gets whether this type should be treated as a complex parameter. | |||||
| /// </summary> | /// </summary> | ||||
| public ApplicationCommandOptionType DiscordOptionType => TypeConverter.GetDiscordType(); | |||||
| public bool IsComplexParameter { get; } | |||||
| /// <summary> | |||||
| /// Gets the Discord option type this parameter represents. If the parameter is not a complex parameter. | |||||
| /// </summary> | |||||
| public ApplicationCommandOptionType? DiscordOptionType => TypeConverter?.GetDiscordType(); | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the parameter choices of this Slash Application Command parameter. | /// Gets the parameter choices of this Slash Application Command parameter. | ||||
| @@ -57,6 +74,11 @@ namespace Discord.Interactions | |||||
| /// </summary> | /// </summary> | ||||
| public IReadOnlyCollection<ChannelType> ChannelTypes { get; } | public IReadOnlyCollection<ChannelType> ChannelTypes { get; } | ||||
| /// <summary> | |||||
| /// Gets the constructor parameters of this parameter, if <see cref="IsComplexParameter"/> is <see langword="true"/>. | |||||
| /// </summary> | |||||
| public IReadOnlyCollection<SlashCommandParameterInfo> ComplexParameterFields { get; } | |||||
| internal SlashCommandParameterInfo(Builders.SlashCommandParameterBuilder builder, SlashCommandInfo command) : base(builder, command) | internal SlashCommandParameterInfo(Builders.SlashCommandParameterBuilder builder, SlashCommandInfo command) : base(builder, command) | ||||
| { | { | ||||
| TypeConverter = builder.TypeConverter; | TypeConverter = builder.TypeConverter; | ||||
| @@ -64,9 +86,13 @@ namespace Discord.Interactions | |||||
| Description = builder.Description; | Description = builder.Description; | ||||
| MaxValue = builder.MaxValue; | MaxValue = builder.MaxValue; | ||||
| MinValue = builder.MinValue; | MinValue = builder.MinValue; | ||||
| IsComplexParameter = builder.IsComplexParameter; | |||||
| IsAutocomplete = builder.Autocomplete; | IsAutocomplete = builder.Autocomplete; | ||||
| Choices = builder.Choices.ToImmutableArray(); | Choices = builder.Choices.ToImmutableArray(); | ||||
| ChannelTypes = builder.ChannelTypes.ToImmutableArray(); | ChannelTypes = builder.ChannelTypes.ToImmutableArray(); | ||||
| ComplexParameterFields = builder.ComplexParameterFields?.Select(x => x.Build(command)).ToImmutableArray(); | |||||
| _complexParameterInitializer = builder.ComplexParameterInitializer; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -747,9 +747,7 @@ namespace Discord.Interactions | |||||
| if(autocompleteHandlerResult.IsSuccess) | 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) | |||||
| if (autocompleteHandlerResult.Command._flattenedParameterDictionary.TryGetValue(interaction.Data.Current.Name, out var parameter) && parameter?.AutocompleteHandler is not null) | |||||
| return await parameter.AutocompleteHandler.ExecuteAsync(context, interaction, parameter, services).ConfigureAwait(false); | return await parameter.AutocompleteHandler.ExecuteAsync(context, interaction, parameter, services).ConfigureAwait(false); | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,36 @@ | |||||
| using System; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal struct ParseResult : IResult | |||||
| { | |||||
| public object Value { get; } | |||||
| public InteractionCommandError? Error { get; } | |||||
| public string ErrorReason { get; } | |||||
| public bool IsSuccess => !Error.HasValue; | |||||
| private ParseResult(object value, InteractionCommandError? error, string reason) | |||||
| { | |||||
| Value = value; | |||||
| Error = error; | |||||
| ErrorReason = reason; | |||||
| } | |||||
| public static ParseResult FromSuccess(object value) => | |||||
| new ParseResult(value, null, null); | |||||
| public static ParseResult FromError(Exception exception) => | |||||
| new ParseResult(null, InteractionCommandError.Exception, exception.Message); | |||||
| public static ParseResult FromError(InteractionCommandError error, string reason) => | |||||
| new ParseResult(null, error, reason); | |||||
| public static ParseResult FromError(IResult result) => | |||||
| new ParseResult(null, result.Error, result.ErrorReason); | |||||
| public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; | |||||
| } | |||||
| } | |||||
| @@ -13,7 +13,7 @@ namespace Discord.Interactions | |||||
| { | { | ||||
| Name = parameterInfo.Name, | Name = parameterInfo.Name, | ||||
| Description = parameterInfo.Description, | Description = parameterInfo.Description, | ||||
| Type = parameterInfo.DiscordOptionType, | |||||
| Type = parameterInfo.DiscordOptionType.Value, | |||||
| IsRequired = parameterInfo.IsRequired, | IsRequired = parameterInfo.IsRequired, | ||||
| Choices = parameterInfo.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties | Choices = parameterInfo.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties | ||||
| { | { | ||||
| @@ -46,7 +46,7 @@ namespace Discord.Interactions | |||||
| if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount) | if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount) | ||||
| throw new InvalidOperationException($"Slash Commands cannot have more than {SlashCommandBuilder.MaxOptionsCount} command parameters"); | throw new InvalidOperationException($"Slash Commands cannot have more than {SlashCommandBuilder.MaxOptionsCount} command parameters"); | ||||
| props.Options = commandInfo.Parameters.Select(x => x.ToApplicationCommandOptionProps())?.ToList() ?? Optional<List<ApplicationCommandOptionProperties>>.Unspecified; | |||||
| props.Options = commandInfo.FlattenedParameters.Select(x => x.ToApplicationCommandOptionProps())?.ToList() ?? Optional<List<ApplicationCommandOptionProperties>>.Unspecified; | |||||
| return props; | return props; | ||||
| } | } | ||||
| @@ -58,7 +58,7 @@ namespace Discord.Interactions | |||||
| Description = commandInfo.Description, | Description = commandInfo.Description, | ||||
| Type = ApplicationCommandOptionType.SubCommand, | Type = ApplicationCommandOptionType.SubCommand, | ||||
| IsRequired = false, | IsRequired = false, | ||||
| Options = commandInfo.Parameters?.Select(x => x.ToApplicationCommandOptionProps())?.ToList() | |||||
| Options = commandInfo.FlattenedParameters?.Select(x => x.ToApplicationCommandOptionProps())?.ToList() | |||||
| }; | }; | ||||
| public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo) | public static ApplicationCommandProperties ToApplicationCommandProps(this ContextCommandInfo commandInfo) | ||||
| @@ -205,5 +205,26 @@ namespace Discord.Interactions | |||||
| return instance; | return instance; | ||||
| }; | }; | ||||
| } | } | ||||
| internal static Func<object[], T> CreateLambdaConstructorInvoker(TypeInfo typeInfo) | |||||
| { | |||||
| var constructor = GetConstructor(typeInfo); | |||||
| var parameters = constructor.GetParameters(); | |||||
| var argsExp = Expression.Parameter(typeof(object[]), "args"); | |||||
| var parameterExps = new Expression[parameters.Length]; | |||||
| for (var i = 0; i < parameters.Length; i++) | |||||
| { | |||||
| var indexExp = Expression.Constant(i); | |||||
| var accessExp = Expression.ArrayIndex(argsExp, indexExp); | |||||
| parameterExps[i] = Expression.Convert(accessExp, parameters[i].ParameterType); | |||||
| } | |||||
| var newExp = Expression.New(constructor, parameterExps); | |||||
| return Expression.Lambda<Func<object[], T>>(newExp, argsExp).Compile(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||