[Feature] RespondWithModal() which accepts an IModal instance as template (#2564)
* introduce overload for responding to an interaction with an instatiated IModal obj * add inline docs to ModalInfo.PropertyInfo * Apply suggestions from code review Co-authored-by: Casmir <68127614+csmir@users.noreply.github.com> --------- Co-authored-by: Casmir <68127614+csmir@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Discord.Interactions.Builders
|
namespace Discord.Interactions.Builders
|
||||||
{
|
{
|
||||||
@@ -38,6 +39,11 @@ namespace Discord.Interactions.Builders
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Type Type { get; }
|
Type Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the <see cref="PropertyInfo"/> of this component's property.
|
||||||
|
/// </summary>
|
||||||
|
PropertyInfo PropertyInfo { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the <see cref="ComponentTypeConverter"/> assigned to this input.
|
/// Get the <see cref="ComponentTypeConverter"/> assigned to this input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Discord.Interactions.Builders
|
namespace Discord.Interactions.Builders
|
||||||
{
|
{
|
||||||
@@ -33,6 +34,9 @@ namespace Discord.Interactions.Builders
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Type Type { get; private set; }
|
public Type Type { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public PropertyInfo PropertyInfo { get; internal set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ComponentTypeConverter TypeConverter { get; private set; }
|
public ComponentTypeConverter TypeConverter { get; private set; }
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ namespace Discord.Interactions.Builders
|
|||||||
if (!typeof(IModal).IsAssignableFrom(type))
|
if (!typeof(IModal).IsAssignableFrom(type))
|
||||||
throw new ArgumentException($"Must be an implementation of {nameof(IModal)}", nameof(type));
|
throw new ArgumentException($"Must be an implementation of {nameof(IModal)}", nameof(type));
|
||||||
|
|
||||||
|
Type = type;
|
||||||
|
|
||||||
_interactionService = interactionService;
|
_interactionService = interactionService;
|
||||||
_components = new();
|
_components = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -596,6 +596,7 @@ namespace Discord.Interactions.Builders
|
|||||||
builder.Label = propertyInfo.Name;
|
builder.Label = propertyInfo.Name;
|
||||||
builder.DefaultValue = defaultValue;
|
builder.DefaultValue = defaultValue;
|
||||||
builder.WithType(propertyInfo.PropertyType);
|
builder.WithType(propertyInfo.PropertyType);
|
||||||
|
builder.PropertyInfo = propertyInfo;
|
||||||
|
|
||||||
foreach(var attribute in attributes)
|
foreach(var attribute in attributes)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,6 +44,44 @@ namespace Discord.Interactions
|
|||||||
await SendModalResponseAsync(interaction, customId, modalInfo, options, modifyModal);
|
await SendModalResponseAsync(interaction, customId, modalInfo, options, modifyModal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Respond to an interaction with an <see cref="IModal"/> and fills the value fields of the modal using the property values of the provided
|
||||||
|
/// instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
|
||||||
|
/// <param name="interaction">The interaction to respond to.</param>
|
||||||
|
/// <param name="modal">The <see cref="IModal"/> instance to get field values from.</param>
|
||||||
|
/// <param name="options">The request options for this <see langword="async"/> request.</param>
|
||||||
|
/// <param name="modifyModal">Delegate that can be used to modify the modal.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task RespondWithModalAsync<T>(this IDiscordInteraction interaction, string customId, T modal, RequestOptions options = null,
|
||||||
|
Action<ModalBuilder> modifyModal = null)
|
||||||
|
where T : class, IModal
|
||||||
|
{
|
||||||
|
if (!ModalUtils.TryGet<T>(out var modalInfo))
|
||||||
|
throw new ArgumentException($"{typeof(T).FullName} isn't referenced by any registered Modal Interaction Command and doesn't have a cached {typeof(ModalInfo)}");
|
||||||
|
|
||||||
|
var builder = new ModalBuilder(modal.Title, customId);
|
||||||
|
|
||||||
|
foreach (var input in modalInfo.Components)
|
||||||
|
switch (input)
|
||||||
|
{
|
||||||
|
case TextInputComponentInfo textComponent:
|
||||||
|
{
|
||||||
|
builder.AddTextInput(textComponent.Label, textComponent.CustomId, textComponent.Style, textComponent.Placeholder, textComponent.IsRequired ? textComponent.MinLength : null,
|
||||||
|
textComponent.MaxLength, textComponent.IsRequired, textComponent.Getter(modal) as string);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"{input.GetType().FullName} isn't a valid component info class");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifyModal is not null)
|
||||||
|
modifyModal(builder);
|
||||||
|
|
||||||
|
await interaction.RespondWithModalAsync(builder.Build(), options).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task SendModalResponseAsync(IDiscordInteraction interaction, string customId, ModalInfo modalInfo, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
|
private static async Task SendModalResponseAsync(IDiscordInteraction interaction, string customId, ModalInfo modalInfo, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
|
||||||
{
|
{
|
||||||
var builder = new ModalBuilder(modalInfo.Title, customId);
|
var builder = new ModalBuilder(modalInfo.Title, customId);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Discord.Interactions
|
namespace Discord.Interactions
|
||||||
{
|
{
|
||||||
@@ -9,6 +10,10 @@ namespace Discord.Interactions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class InputComponentInfo
|
public abstract class InputComponentInfo
|
||||||
{
|
{
|
||||||
|
private Lazy<Func<object, object>> _getter;
|
||||||
|
internal Func<object, object> Getter => _getter.Value;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the parent modal of this component.
|
/// Gets the parent modal of this component.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -39,6 +44,11 @@ namespace Discord.Interactions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Type Type { get; }
|
public Type Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the property linked to this component.
|
||||||
|
/// </summary>
|
||||||
|
public PropertyInfo PropertyInfo { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="ComponentTypeConverter"/> assigned to this component.
|
/// Gets the <see cref="ComponentTypeConverter"/> assigned to this component.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -62,9 +72,12 @@ namespace Discord.Interactions
|
|||||||
IsRequired = builder.IsRequired;
|
IsRequired = builder.IsRequired;
|
||||||
ComponentType = builder.ComponentType;
|
ComponentType = builder.ComponentType;
|
||||||
Type = builder.Type;
|
Type = builder.Type;
|
||||||
|
PropertyInfo = builder.PropertyInfo;
|
||||||
TypeConverter = builder.TypeConverter;
|
TypeConverter = builder.TypeConverter;
|
||||||
DefaultValue = builder.DefaultValue;
|
DefaultValue = builder.DefaultValue;
|
||||||
Attributes = builder.Attributes.ToImmutableArray();
|
Attributes = builder.Attributes.ToImmutableArray();
|
||||||
|
|
||||||
|
_getter = new(() => ReflectionUtils<object>.CreateLambdaPropertyGetter(Modal.Type, PropertyInfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ namespace Discord.Interactions
|
|||||||
internal static class ReflectionUtils<T>
|
internal static class ReflectionUtils<T>
|
||||||
{
|
{
|
||||||
private static readonly TypeInfo ObjectTypeInfo = typeof(object).GetTypeInfo();
|
private static readonly TypeInfo ObjectTypeInfo = typeof(object).GetTypeInfo();
|
||||||
|
|
||||||
internal static T CreateObject (TypeInfo typeInfo, InteractionService commandService, IServiceProvider services = null) =>
|
internal static T CreateObject (TypeInfo typeInfo, InteractionService commandService, IServiceProvider services = null) =>
|
||||||
CreateBuilder(typeInfo, commandService)(services);
|
CreateBuilder(typeInfo, commandService)(services);
|
||||||
|
|
||||||
@@ -166,6 +165,21 @@ namespace Discord.Interactions
|
|||||||
return Expression.Lambda<Action<T, object>>(assign, instanceParam, valueParam).Compile();
|
return Expression.Lambda<Action<T, object>>(assign, instanceParam, valueParam).Compile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static Func<T, object> CreateLambdaPropertyGetter(PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
var instanceParam = Expression.Parameter(typeof(T), "instance");
|
||||||
|
var prop = Expression.Property(instanceParam, propertyInfo);
|
||||||
|
return Expression.Lambda<Func<T, object>>(prop, instanceParam).Compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Func<T, object> CreateLambdaPropertyGetter(Type type, PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
var instanceParam = Expression.Parameter(typeof(T), "instance");
|
||||||
|
var instanceAccess = Expression.Convert(instanceParam, type);
|
||||||
|
var prop = Expression.Property(instanceAccess, propertyInfo);
|
||||||
|
return Expression.Lambda<Func<T, object>>(prop, instanceParam).Compile();
|
||||||
|
}
|
||||||
|
|
||||||
internal static Func<object[], object[], T> CreateLambdaMemberInit(TypeInfo typeInfo, ConstructorInfo constructor, Predicate<PropertyInfo> propertySelect = null)
|
internal static Func<object[], object[], T> CreateLambdaMemberInit(TypeInfo typeInfo, ConstructorInfo constructor, Predicate<PropertyInfo> propertySelect = null)
|
||||||
{
|
{
|
||||||
propertySelect ??= x => true;
|
propertySelect ??= x => true;
|
||||||
|
|||||||
Reference in New Issue
Block a user