[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.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Discord.Interactions.Builders
|
||||
{
|
||||
@@ -38,6 +39,11 @@ namespace Discord.Interactions.Builders
|
||||
/// </summary>
|
||||
Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="PropertyInfo"/> of this component's property.
|
||||
/// </summary>
|
||||
PropertyInfo PropertyInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ComponentTypeConverter"/> assigned to this input.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Discord.Interactions.Builders
|
||||
{
|
||||
@@ -33,6 +34,9 @@ namespace Discord.Interactions.Builders
|
||||
/// <inheritdoc/>
|
||||
public Type Type { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PropertyInfo PropertyInfo { get; internal set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ComponentTypeConverter TypeConverter { get; private set; }
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace Discord.Interactions.Builders
|
||||
if (!typeof(IModal).IsAssignableFrom(type))
|
||||
throw new ArgumentException($"Must be an implementation of {nameof(IModal)}", nameof(type));
|
||||
|
||||
Type = type;
|
||||
|
||||
_interactionService = interactionService;
|
||||
_components = new();
|
||||
}
|
||||
|
||||
@@ -596,6 +596,7 @@ namespace Discord.Interactions.Builders
|
||||
builder.Label = propertyInfo.Name;
|
||||
builder.DefaultValue = defaultValue;
|
||||
builder.WithType(propertyInfo.PropertyType);
|
||||
builder.PropertyInfo = propertyInfo;
|
||||
|
||||
foreach(var attribute in attributes)
|
||||
{
|
||||
|
||||
@@ -44,6 +44,44 @@ namespace Discord.Interactions
|
||||
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)
|
||||
{
|
||||
var builder = new ModalBuilder(modalInfo.Title, customId);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Discord.Interactions
|
||||
{
|
||||
@@ -9,6 +10,10 @@ namespace Discord.Interactions
|
||||
/// </summary>
|
||||
public abstract class InputComponentInfo
|
||||
{
|
||||
private Lazy<Func<object, object>> _getter;
|
||||
internal Func<object, object> Getter => _getter.Value;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent modal of this component.
|
||||
/// </summary>
|
||||
@@ -39,6 +44,11 @@ namespace Discord.Interactions
|
||||
/// </summary>
|
||||
public Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property linked to this component.
|
||||
/// </summary>
|
||||
public PropertyInfo PropertyInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ComponentTypeConverter"/> assigned to this component.
|
||||
/// </summary>
|
||||
@@ -62,9 +72,12 @@ namespace Discord.Interactions
|
||||
IsRequired = builder.IsRequired;
|
||||
ComponentType = builder.ComponentType;
|
||||
Type = builder.Type;
|
||||
PropertyInfo = builder.PropertyInfo;
|
||||
TypeConverter = builder.TypeConverter;
|
||||
DefaultValue = builder.DefaultValue;
|
||||
Attributes = builder.Attributes.ToImmutableArray();
|
||||
|
||||
_getter = new(() => ReflectionUtils<object>.CreateLambdaPropertyGetter(Modal.Type, PropertyInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace Discord.Interactions
|
||||
internal static class ReflectionUtils<T>
|
||||
{
|
||||
private static readonly TypeInfo ObjectTypeInfo = typeof(object).GetTypeInfo();
|
||||
|
||||
internal static T CreateObject (TypeInfo typeInfo, InteractionService commandService, IServiceProvider services = null) =>
|
||||
CreateBuilder(typeInfo, commandService)(services);
|
||||
|
||||
@@ -166,6 +165,21 @@ namespace Discord.Interactions
|
||||
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)
|
||||
{
|
||||
propertySelect ??= x => true;
|
||||
|
||||
Reference in New Issue
Block a user