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:
Cenk Ergen
2022-03-09 23:10:00 +03:00
committed by GitHub
parent cc6918d157
commit fb4250b88c
34 changed files with 816 additions and 242 deletions

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
};

View File

@@ -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);
}
}

View File

@@ -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);
}