Feature: Component TypeConverters and CustomID TypeReaders (#2169)
* fix sharded client current user * add custom setter to group property of module builder * rename serilazation method * init * create typemap and default typereaders * add default readers * create typereader targetting flags * seperate custom id readers with component typeconverters * add typereaders * add customid readers * clean up component info argument parsing * remove obsolete method * add component typeconverters to modals * fix build errors * add inline docs * bug fixes * code cleanup and refactorings * fix build errors * add GenerateCustomIdString method to interaction service * add GenerateCustomIdString method to interaction service * add inline docs to componentparameterbuilder * add inline docs to GenerateCustomIdStringAsync method
This commit is contained in:
@@ -5,7 +5,7 @@ namespace Discord.Interactions.Builders
|
||||
/// <summary>
|
||||
/// Represents a builder for creating <see cref="ComponentCommandInfo"/>.
|
||||
/// </summary>
|
||||
public sealed class ComponentCommandBuilder : CommandBuilder<ComponentCommandInfo, ComponentCommandBuilder, CommandParameterBuilder>
|
||||
public sealed class ComponentCommandBuilder : CommandBuilder<ComponentCommandInfo, ComponentCommandBuilder, ComponentCommandParameterBuilder>
|
||||
{
|
||||
protected override ComponentCommandBuilder Instance => this;
|
||||
|
||||
@@ -26,9 +26,9 @@ namespace Discord.Interactions.Builders
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public override ComponentCommandBuilder AddParameter (Action<CommandParameterBuilder> configure)
|
||||
public override ComponentCommandBuilder AddParameter (Action<ComponentCommandParameterBuilder> configure)
|
||||
{
|
||||
var parameter = new CommandParameterBuilder(this);
|
||||
var parameter = new ComponentCommandParameterBuilder(this);
|
||||
configure(parameter);
|
||||
AddParameters(parameter);
|
||||
return this;
|
||||
|
||||
@@ -38,6 +38,11 @@ namespace Discord.Interactions.Builders
|
||||
/// </summary>
|
||||
Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ComponentTypeConverter"/> assigned to this input.
|
||||
/// </summary>
|
||||
ComponentTypeConverter TypeConverter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default value of this input component.
|
||||
/// </summary>
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace Discord.Interactions.Builders
|
||||
/// <inheritdoc/>
|
||||
public Type Type { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ComponentTypeConverter TypeConverter { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object DefaultValue { get; set; }
|
||||
|
||||
@@ -111,6 +114,7 @@ namespace Discord.Interactions.Builders
|
||||
public TBuilder WithType(Type type)
|
||||
{
|
||||
Type = type;
|
||||
TypeConverter = Modal._interactionService.GetComponentTypeConverter(type);
|
||||
return Instance;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Discord.Interactions.Builders
|
||||
/// </summary>
|
||||
public class ModalBuilder
|
||||
{
|
||||
internal readonly InteractionService _interactionService;
|
||||
internal readonly List<IInputComponentBuilder> _components;
|
||||
|
||||
/// <summary>
|
||||
@@ -31,11 +32,12 @@ namespace Discord.Interactions.Builders
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<IInputComponentBuilder> Components => _components;
|
||||
|
||||
internal ModalBuilder(Type type)
|
||||
internal ModalBuilder(Type type, InteractionService interactionService)
|
||||
{
|
||||
if (!typeof(IModal).IsAssignableFrom(type))
|
||||
throw new ArgumentException($"Must be an implementation of {nameof(IModal)}", nameof(type));
|
||||
|
||||
_interactionService = interactionService;
|
||||
_components = new();
|
||||
}
|
||||
|
||||
@@ -43,7 +45,7 @@ namespace Discord.Interactions.Builders
|
||||
/// Initializes a new <see cref="ModalBuilder"/>
|
||||
/// </summary>
|
||||
/// <param name="modalInitializer">The initialization delegate for this modal.</param>
|
||||
public ModalBuilder(Type type, ModalInitializer modalInitializer) : this(type)
|
||||
public ModalBuilder(Type type, ModalInitializer modalInitializer, InteractionService interactionService) : this(type, interactionService)
|
||||
{
|
||||
ModalInitializer = modalInitializer;
|
||||
}
|
||||
|
||||
@@ -231,9 +231,6 @@ namespace Discord.Interactions.Builders
|
||||
private static void BuildComponentCommand (ComponentCommandBuilder builder, Func<IServiceProvider, IInteractionModuleBase> createInstance, MethodInfo methodInfo,
|
||||
InteractionService commandService, IServiceProvider services)
|
||||
{
|
||||
if (!methodInfo.GetParameters().All(x => x.ParameterType == typeof(string) || x.ParameterType == typeof(string[])))
|
||||
throw new InvalidOperationException($"Interaction method parameters all must be types of {typeof(string).Name} or {typeof(string[]).Name}");
|
||||
|
||||
var attributes = methodInfo.GetCustomAttributes();
|
||||
|
||||
builder.MethodName = methodInfo.Name;
|
||||
@@ -260,8 +257,10 @@ namespace Discord.Interactions.Builders
|
||||
|
||||
var parameters = methodInfo.GetParameters();
|
||||
|
||||
var wildCardCount = Regex.Matches(Regex.Escape(builder.Name), Regex.Escape(commandService._wildCardExp)).Count;
|
||||
|
||||
foreach (var parameter in parameters)
|
||||
builder.AddParameter(x => BuildParameter(x, parameter));
|
||||
builder.AddParameter(x => BuildComponentParameter(x, parameter, parameter.Position >= wildCardCount));
|
||||
|
||||
builder.Callback = CreateCallback(createInstance, methodInfo, commandService);
|
||||
}
|
||||
@@ -310,8 +309,8 @@ namespace Discord.Interactions.Builders
|
||||
if (parameters.Count(x => typeof(IModal).IsAssignableFrom(x.ParameterType)) > 1)
|
||||
throw new InvalidOperationException($"A modal command can only have one {nameof(IModal)} parameter.");
|
||||
|
||||
if (!parameters.All(x => x.ParameterType == typeof(string) || typeof(IModal).IsAssignableFrom(x.ParameterType)))
|
||||
throw new InvalidOperationException($"All parameters of a modal command must be either a string or an implementation of {nameof(IModal)}");
|
||||
if (!typeof(IModal).IsAssignableFrom(parameters.Last().ParameterType))
|
||||
throw new InvalidOperationException($"Last parameter of a modal command must be an implementation of {nameof(IModal)}");
|
||||
|
||||
var attributes = methodInfo.GetCustomAttributes();
|
||||
|
||||
@@ -464,6 +463,12 @@ namespace Discord.Interactions.Builders
|
||||
builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower();
|
||||
}
|
||||
|
||||
private static void BuildComponentParameter(ComponentCommandParameterBuilder builder, ParameterInfo paramInfo, bool isComponentParam)
|
||||
{
|
||||
builder.SetIsRouteSegment(!isComponentParam);
|
||||
BuildParameter(builder, paramInfo);
|
||||
}
|
||||
|
||||
private static void BuildParameter<TInfo, TBuilder> (ParameterBuilder<TInfo, TBuilder> builder, ParameterInfo paramInfo)
|
||||
where TInfo : class, IParameterInfo
|
||||
where TBuilder : ParameterBuilder<TInfo, TBuilder>
|
||||
@@ -495,7 +500,7 @@ namespace Discord.Interactions.Builders
|
||||
#endregion
|
||||
|
||||
#region Modals
|
||||
public static ModalInfo BuildModalInfo(Type modalType)
|
||||
public static ModalInfo BuildModalInfo(Type modalType, InteractionService interactionService)
|
||||
{
|
||||
if (!typeof(IModal).IsAssignableFrom(modalType))
|
||||
throw new InvalidOperationException($"{modalType.FullName} isn't an implementation of {typeof(IModal).FullName}");
|
||||
@@ -504,7 +509,7 @@ namespace Discord.Interactions.Builders
|
||||
|
||||
try
|
||||
{
|
||||
var builder = new ModalBuilder(modalType)
|
||||
var builder = new ModalBuilder(modalType, interactionService)
|
||||
{
|
||||
Title = instance.Title
|
||||
};
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
|
||||
namespace Discord.Interactions.Builders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a builder for creating <see cref="ComponentCommandParameterInfo"/>.
|
||||
/// </summary>
|
||||
public class ComponentCommandParameterBuilder : ParameterBuilder<ComponentCommandParameterInfo, ComponentCommandParameterBuilder>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="ComponentTypeConverter"/> assigned to this parameter, if <see cref="IsRouteSegmentParameter"/> is <see langword="false"/>.
|
||||
/// </summary>
|
||||
public ComponentTypeConverter TypeConverter { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Discord.Interactions.TypeReader"/> assigned to this parameter, if <see cref="IsRouteSegmentParameter"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public TypeReader TypeReader { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this parameter is a CustomId segment or a Component value parameter.
|
||||
/// </summary>
|
||||
public bool IsRouteSegmentParameter { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override ComponentCommandParameterBuilder Instance => this;
|
||||
|
||||
internal ComponentCommandParameterBuilder(ICommandBuilder command) : base(command) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="ComponentCommandParameterBuilder"/>.
|
||||
/// </summary>
|
||||
/// <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 ComponentCommandParameterBuilder(ICommandBuilder command, string name, Type type) : base(command, name, type) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ComponentCommandParameterBuilder SetParameterType(Type type) => SetParameterType(type, null);
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="ParameterBuilder{TInfo, TBuilder}.ParameterType"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">New value of the <see cref="ParameterBuilder{TInfo, TBuilder}.ParameterType"/>.</param>
|
||||
/// <param name="services">Service container to be used to resolve the dependencies of this parameters <see cref="Interactions.TypeConverter"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services)
|
||||
{
|
||||
base.SetParameterType(type);
|
||||
|
||||
if (IsRouteSegmentParameter)
|
||||
TypeReader = Command.Module.InteractionService.GetTypeReader(type);
|
||||
else
|
||||
TypeConverter = Command.Module.InteractionService.GetComponentTypeConverter(ParameterType, services);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="IsRouteSegmentParameter"/>.
|
||||
/// </summary>
|
||||
/// <param name="isRouteSegment">New value of the <see cref="IsRouteSegmentParameter"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public ComponentCommandParameterBuilder SetIsRouteSegment(bool isRouteSegment)
|
||||
{
|
||||
IsRouteSegmentParameter = isRouteSegment;
|
||||
return this;
|
||||
}
|
||||
|
||||
internal override ComponentCommandParameterInfo Build(ICommandInfo command)
|
||||
=> new(this, command);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,11 @@ namespace Discord.Interactions.Builders
|
||||
/// </summary>
|
||||
public bool IsModalParameter => Modal is not null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="TypeReader"/> assigned to this parameter, if <see cref="IsModalParameter"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public TypeReader TypeReader { get; private set; }
|
||||
|
||||
internal ModalCommandParameterBuilder(ICommandBuilder command) : base(command) { }
|
||||
|
||||
/// <summary>
|
||||
@@ -34,7 +39,9 @@ namespace Discord.Interactions.Builders
|
||||
public override ModalCommandParameterBuilder SetParameterType(Type type)
|
||||
{
|
||||
if (typeof(IModal).IsAssignableFrom(type))
|
||||
Modal = ModalUtils.GetOrAdd(type);
|
||||
Modal = ModalUtils.GetOrAdd(type, Command.Module.InteractionService);
|
||||
else
|
||||
TypeReader = Command.Module.InteractionService.GetTypeReader(type);
|
||||
|
||||
return base.SetParameterType(type);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user