* Interaction Service Complex Parameters * 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> * fix merge errors Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com>tags/3.4.0
| @@ -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.IsRequired = !paramInfo.IsOptional; | |||
| builder.DefaultValue = paramInfo.DefaultValue; | |||
| builder.SetParameterType(paramType, services); | |||
| foreach (var attribute in attributes) | |||
| { | |||
| @@ -435,12 +434,32 @@ namespace Discord.Interactions.Builders | |||
| case MinValueAttribute minValue: | |||
| builder.MinValue = minValue.Value; | |||
| 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: | |||
| builder.AddAttributes(attribute); | |||
| break; | |||
| } | |||
| } | |||
| builder.SetParameterType(paramType, services); | |||
| // Replace pascal casings with '-' | |||
| builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower(); | |||
| } | |||
| @@ -608,5 +627,41 @@ namespace Discord.Interactions.Builders | |||
| propertyInfo.SetMethod?.IsStatic == false && | |||
| 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.Collections.Generic; | |||
| using System.Linq; | |||
| namespace Discord.Interactions.Builders | |||
| { | |||
| @@ -10,6 +11,7 @@ namespace Discord.Interactions.Builders | |||
| { | |||
| private readonly List<ParameterChoice> _choices = new(); | |||
| private readonly List<ChannelType> _channelTypes = new(); | |||
| private readonly List<SlashCommandParameterBuilder> _complexParameterFields = new(); | |||
| /// <summary> | |||
| /// Gets or sets the description of this parameter. | |||
| @@ -36,6 +38,11 @@ namespace Discord.Interactions.Builders | |||
| /// </summary> | |||
| 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> | |||
| /// Gets or sets whether this parameter should be configured for Autocomplete Interactions. | |||
| /// </summary> | |||
| @@ -46,6 +53,16 @@ namespace Discord.Interactions.Builders | |||
| /// </summary> | |||
| 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> | |||
| /// Gets or sets the <see cref="IAutocompleteHandler"/> of this parameter. | |||
| /// </summary> | |||
| @@ -60,7 +77,14 @@ namespace Discord.Interactions.Builders | |||
| /// <param name="command">Parent command of this parameter.</param> | |||
| /// <param name="name">Name of this command.</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> | |||
| /// Sets <see cref="Description"/>. | |||
| @@ -168,7 +192,47 @@ namespace Discord.Interactions.Builders | |||
| public SlashCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) | |||
| { | |||
| 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; | |||
| } | |||
| @@ -31,6 +31,8 @@ namespace Discord.Interactions | |||
| private readonly ExecuteCallback _action; | |||
| private readonly ILookup<string, PreconditionAttribute> _groupedPreconditions; | |||
| internal IReadOnlyDictionary<string, TParameter> _parameterDictionary { get; } | |||
| /// <inheritdoc/> | |||
| public ModuleInfo Module { get; } | |||
| @@ -79,6 +81,7 @@ namespace Discord.Interactions | |||
| _action = builder.Callback; | |||
| _groupedPreconditions = builder.Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal); | |||
| _parameterDictionary = Parameters?.ToDictionary(x => x.Name, x => x).ToImmutableDictionary(); | |||
| } | |||
| /// <inheritdoc/> | |||
| @@ -13,6 +13,8 @@ namespace Discord.Interactions | |||
| /// </summary> | |||
| public class SlashCommandInfo : CommandInfo<SlashCommandParameterInfo>, IApplicationCommandInfo | |||
| { | |||
| internal IReadOnlyDictionary<string, SlashCommandParameterInfo> _flattenedParameterDictionary { get; } | |||
| /// <summary> | |||
| /// Gets the command description that will be displayed on Discord. | |||
| /// </summary> | |||
| @@ -30,11 +32,23 @@ namespace Discord.Interactions | |||
| /// <inheritdoc/> | |||
| 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) | |||
| { | |||
| Description = builder.Description; | |||
| DefaultPermission = builder.DefaultPermission; | |||
| 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/> | |||
| @@ -56,45 +70,81 @@ namespace Discord.Interactions | |||
| { | |||
| try | |||
| { | |||
| if (paramList?.Count() < argList?.Count()) | |||
| return ExecuteResult.FromError(InteractionCommandError.BadArgs ,"Command was invoked with too many parameters"); | |||
| var args = new object[paramList.Count()]; | |||
| for (var i = 0; i < paramList.Count(); 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 | |||
| { | |||
| 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 | |||
| 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.Immutable; | |||
| using System.Linq; | |||
| 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> | |||
| /// Represents the parameter info class for <see cref="SlashCommandInfo"/> commands. | |||
| /// </summary> | |||
| public class SlashCommandParameterInfo : CommandParameterInfo | |||
| { | |||
| internal readonly ComplexParameterInitializer _complexParameterInitializer; | |||
| /// <inheritdoc/> | |||
| public new SlashCommandInfo Command => base.Command as SlashCommandInfo; | |||
| @@ -43,9 +55,14 @@ namespace Discord.Interactions | |||
| public bool IsAutocomplete { get; } | |||
| /// <summary> | |||
| /// Gets the Discord option type this parameter represents. | |||
| /// Gets whether this type should be treated as a complex parameter. | |||
| /// </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> | |||
| /// Gets the parameter choices of this Slash Application Command parameter. | |||
| @@ -57,6 +74,11 @@ namespace Discord.Interactions | |||
| /// </summary> | |||
| 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) | |||
| { | |||
| TypeConverter = builder.TypeConverter; | |||
| @@ -64,9 +86,13 @@ namespace Discord.Interactions | |||
| Description = builder.Description; | |||
| MaxValue = builder.MaxValue; | |||
| MinValue = builder.MinValue; | |||
| IsComplexParameter = builder.IsComplexParameter; | |||
| IsAutocomplete = builder.Autocomplete; | |||
| Choices = builder.Choices.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) | |||
| { | |||
| 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); | |||
| } | |||
| } | |||
| @@ -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, | |||
| Description = parameterInfo.Description, | |||
| Type = parameterInfo.DiscordOptionType, | |||
| Type = parameterInfo.DiscordOptionType.Value, | |||
| IsRequired = parameterInfo.IsRequired, | |||
| Choices = parameterInfo.Choices?.Select(x => new ApplicationCommandOptionChoiceProperties | |||
| { | |||
| @@ -46,7 +46,7 @@ namespace Discord.Interactions | |||
| if (commandInfo.Parameters.Count > SlashCommandBuilder.MaxOptionsCount) | |||
| 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; | |||
| } | |||
| @@ -58,7 +58,7 @@ namespace Discord.Interactions | |||
| Description = commandInfo.Description, | |||
| Type = ApplicationCommandOptionType.SubCommand, | |||
| 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) | |||