Interaction Service Complex Parameters (#2155)
* 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>
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
|
if(!IsComplexParameter)
|
||||||
TypeConverter = Command.Module.InteractionService.GetTypeConverter(ParameterType, services);
|
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)
|
var execResult = ExecuteResult.FromError(result);
|
||||||
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters");
|
await InvokeModuleEvent(context, execResult).ConfigureAwait(false);
|
||||||
|
return execResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result is ParseResult parseResult)
|
||||||
|
args[i] = parseResult.Value;
|
||||||
else
|
else
|
||||||
args[i] = parameter.DefaultValue;
|
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.");
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await RunAsync(context, args, services).ConfigureAwait(false);
|
return await RunAsync(context, args, services).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return ExecuteResult.FromError(ex);
|
var result = ExecuteResult.FromError(ex);
|
||||||
|
await InvokeModuleEvent(context, result).ConfigureAwait(false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IResult> ParseArgument(SlashCommandParameterInfo parameterInfo, IInteractionContext context, List<IApplicationCommandInteractionDataOption> 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 ParseResult parseResult)
|
||||||
|
ctorArgs[i] = parseResult.Value;
|
||||||
|
else
|
||||||
|
return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Complex command parsing failed for an unknown reason.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult.FromSuccess(parameterInfo._complexParameterInitializer(ctorArgs));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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 (autocompleteHandlerResult.Command._flattenedParameterDictionary.TryGetValue(interaction.Data.Current.Name, out var parameter) && parameter?.AutocompleteHandler is not null)
|
||||||
|
|
||||||
if(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/Discord.Net.Interactions/Results/ParseResult.cs
Normal file
36
src/Discord.Net.Interactions/Results/ParseResult.cs
Normal file
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user