diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs index ad2f07c73..68c26fd03 100644 --- a/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs @@ -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 /// Type Type { get; } + /// + /// Get the of this component's property. + /// + PropertyInfo PropertyInfo { get; } + /// /// Get the assigned to this input. /// diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs index 7d1d96712..af0ab3a70 100644 --- a/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs @@ -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 /// public Type Type { get; private set; } + /// + public PropertyInfo PropertyInfo { get; internal set; } + /// public ComponentTypeConverter TypeConverter { get; private set; } diff --git a/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs index c13ff40de..66aeadf75 100644 --- a/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs @@ -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(); } diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs index 3f0504e44..ea1875b60 100644 --- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs @@ -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) { diff --git a/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs b/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs index d970b9930..5f66df929 100644 --- a/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs +++ b/src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs @@ -44,6 +44,44 @@ namespace Discord.Interactions await SendModalResponseAsync(interaction, customId, modalInfo, options, modifyModal); } + /// + /// Respond to an interaction with an and fills the value fields of the modal using the property values of the provided + /// instance. + /// + /// Type of the implementation. + /// The interaction to respond to. + /// The instance to get field values from. + /// The request options for this request. + /// Delegate that can be used to modify the modal. + /// + public static async Task RespondWithModalAsync(this IDiscordInteraction interaction, string customId, T modal, RequestOptions options = null, + Action modifyModal = null) + where T : class, IModal + { + if (!ModalUtils.TryGet(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 modifyModal = null) { var builder = new ModalBuilder(modalInfo.Title, customId); diff --git a/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs b/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs index 05695f862..23a0db844 100644 --- a/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs +++ b/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs @@ -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 /// public abstract class InputComponentInfo { + private Lazy> _getter; + internal Func Getter => _getter.Value; + + /// /// Gets the parent modal of this component. /// @@ -39,6 +44,11 @@ namespace Discord.Interactions /// public Type Type { get; } + /// + /// Gets the property linked to this component. + /// + public PropertyInfo PropertyInfo { get; } + /// /// Gets the assigned to this component. /// @@ -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.CreateLambdaPropertyGetter(Modal.Type, PropertyInfo)); } } } diff --git a/src/Discord.Net.Interactions/Utilities/ReflectionUtils.cs b/src/Discord.Net.Interactions/Utilities/ReflectionUtils.cs index 5d3da4c5c..0988f1f0c 100644 --- a/src/Discord.Net.Interactions/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Interactions/Utilities/ReflectionUtils.cs @@ -10,7 +10,6 @@ namespace Discord.Interactions internal static class ReflectionUtils { 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>(assign, instanceParam, valueParam).Compile(); } + internal static Func CreateLambdaPropertyGetter(PropertyInfo propertyInfo) + { + var instanceParam = Expression.Parameter(typeof(T), "instance"); + var prop = Expression.Property(instanceParam, propertyInfo); + return Expression.Lambda>(prop, instanceParam).Compile(); + } + + internal static Func 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>(prop, instanceParam).Compile(); + } + internal static Func CreateLambdaMemberInit(TypeInfo typeInfo, ConstructorInfo constructor, Predicate propertySelect = null) { propertySelect ??= x => true;