* 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.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) | ||||