diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs index 37cd861c4..77569718a 100644 --- a/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs @@ -38,6 +38,8 @@ namespace Discord.Interactions.Builders /// Type Type { get; } + ComponentTypeConverter TypeConverter { get; } + /// /// Gets the default value of this input component. /// diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs index c2b9b0645..19674ac4d 100644 --- a/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs @@ -33,6 +33,8 @@ namespace Discord.Interactions.Builders /// public Type Type { get; private set; } + public ComponentTypeConverter TypeConverter { get; private set; } + /// public object DefaultValue { get; set; } @@ -111,6 +113,7 @@ namespace Discord.Interactions.Builders public TBuilder WithType(Type type) { Type = type; + TypeConverter = Modal._interactionService.GetComponentTypeConverter(type); return Instance; } diff --git a/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs index e120e78be..fc1dbdc0e 100644 --- a/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs @@ -9,6 +9,7 @@ namespace Discord.Interactions.Builders /// public class ModalBuilder { + internal readonly InteractionService _interactionService; internal readonly List _components; /// @@ -31,11 +32,12 @@ namespace Discord.Interactions.Builders /// public IReadOnlyCollection 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 /// /// The initialization delegate for this modal. - public ModalBuilder(Type type, ModalInitializer modalInitializer) : this(type) + public ModalBuilder(Type type, ModalInitializer modalInitializer, InteractionService interactionService) : this(type, interactionService) { ModalInitializer = modalInitializer; } diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs index c163ec83d..ac727496c 100644 --- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs @@ -482,7 +482,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}"); @@ -491,7 +491,7 @@ namespace Discord.Interactions.Builders try { - var builder = new ModalBuilder(modalType) + var builder = new ModalBuilder(modalType, interactionService) { Title = instance.Title }; diff --git a/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs index a750603fc..40f93ebfe 100644 --- a/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs @@ -52,8 +52,15 @@ namespace Discord.Interactions if (additionalArgs is not null) args.AddRange(additionalArgs); - var modal = Modal.CreateModal(modalInteraction, Module.CommandService._exitOnMissingModalField); - args.Add(modal); + var modalResult = await Modal.ParseModalAsync(context, services, Module.CommandService._exitOnMissingModalField).ConfigureAwait(false); + + if(!modalResult.IsSuccess || modalResult is not ParseResult parseResult) + { + await InvokeModuleEvent(context, modalResult).ConfigureAwait(false); + return modalResult; + } + + args.Add(parseResult.Value); return await RunAsync(context, args.ToArray(), services); } diff --git a/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs b/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs index 790838ad9..e457562a7 100644 --- a/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs +++ b/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs @@ -39,6 +39,8 @@ namespace Discord.Interactions /// public Type Type { get; } + public ComponentTypeConverter TypeConverter { get; } + /// /// Gets the default value of this component. /// @@ -57,6 +59,7 @@ namespace Discord.Interactions IsRequired = builder.IsRequired; ComponentType = builder.ComponentType; Type = builder.Type; + TypeConverter = builder.TypeConverter; DefaultValue = builder.DefaultValue; Attributes = builder.Attributes.ToImmutableArray(); } diff --git a/src/Discord.Net.Interactions/Info/ModalInfo.cs b/src/Discord.Net.Interactions/Info/ModalInfo.cs index edc31373e..1b1b198ac 100644 --- a/src/Discord.Net.Interactions/Info/ModalInfo.cs +++ b/src/Discord.Net.Interactions/Info/ModalInfo.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading.Tasks; namespace Discord.Interactions { @@ -86,5 +87,41 @@ namespace Discord.Interactions return _initializer(args); } + + internal async Task ParseModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false) + { + if (context.Interaction is not IModalInteraction modalInteraction) + throw new InvalidOperationException("Provided context doesn't belong to a Modal Interaction."); + + services ??= EmptyServiceProvider.Instance; + + var args = new object[Components.Count]; + var components = modalInteraction.Data.Components.ToList(); + + for (var i = 0; i < Components.Count; i++) + { + var input = Components.ElementAt(i); + var component = components.Find(x => x.CustomId == input.CustomId); + + if (component is null) + { + if (!throwOnMissingField) + args[i] = input.DefaultValue; + else + throw new InvalidOperationException($"Modal interaction is missing the required field: {input.CustomId}"); + } + else + { + var readResult = await input.TypeConverter.ReadAsync(context, component, services).ConfigureAwait(false); + + if (!readResult.IsSuccess) + return readResult; + + args[i] = readResult.Value; + } + } + + return ParseResult.FromSuccess(_initializer(args)); + } } } diff --git a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultValueComponentConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultValueComponentConverter.cs new file mode 100644 index 000000000..ed2ee951b --- /dev/null +++ b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultValueComponentConverter.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + internal sealed class DefaultValueComponentConverter : ComponentTypeConverter + where T : IConvertible + { + public override Task ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services) + { + try + { + return option.Type switch + { + ComponentType.SelectMenu => Task.FromResult(TypeConverterResult.FromSuccess(Convert.ChangeType(string.Join(',', option.Values), typeof(T)))), + ComponentType.TextInput => Task.FromResult(TypeConverterResult.FromSuccess(Convert.ChangeType(option.Value, typeof(T)))), + _ => Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option.Type} doesn't have a convertible value.")) + }; + } + catch (InvalidCastException castEx) + { + return Task.FromResult(TypeConverterResult.FromError(castEx)); + } + } + } +} diff --git a/src/Discord.Net.Interactions/Utilities/ModalUtils.cs b/src/Discord.Net.Interactions/Utilities/ModalUtils.cs index d42cc2fe9..9fdd195c2 100644 --- a/src/Discord.Net.Interactions/Utilities/ModalUtils.cs +++ b/src/Discord.Net.Interactions/Utilities/ModalUtils.cs @@ -11,16 +11,16 @@ namespace Discord.Interactions public static IReadOnlyCollection Modals => _modalInfos.Values.ToReadOnlyCollection(); - public static ModalInfo GetOrAdd(Type type) + public static ModalInfo GetOrAdd(Type type, InteractionService interactionService) { if (!typeof(IModal).IsAssignableFrom(type)) throw new ArgumentException($"Must be an implementation of {nameof(IModal)}", nameof(type)); - return _modalInfos.GetOrAdd(type, ModuleClassBuilder.BuildModalInfo(type)); + return _modalInfos.GetOrAdd(type, ModuleClassBuilder.BuildModalInfo(type, interactionService)); } - public static ModalInfo GetOrAdd() where T : class, IModal - => GetOrAdd(typeof(T)); + public static ModalInfo GetOrAdd(InteractionService interactionService) where T : class, IModal + => GetOrAdd(typeof(T), interactionService); public static bool TryGet(Type type, out ModalInfo modalInfo) {