| @@ -38,6 +38,8 @@ namespace Discord.Interactions.Builders | |||||
| /// </summary> | /// </summary> | ||||
| Type Type { get; } | Type Type { get; } | ||||
| ComponentTypeConverter TypeConverter { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the default value of this input component. | /// Gets the default value of this input component. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -33,6 +33,8 @@ namespace Discord.Interactions.Builders | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public Type Type { get; private set; } | public Type Type { get; private set; } | ||||
| public ComponentTypeConverter TypeConverter { get; private set; } | |||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public object DefaultValue { get; set; } | public object DefaultValue { get; set; } | ||||
| @@ -111,6 +113,7 @@ namespace Discord.Interactions.Builders | |||||
| public TBuilder WithType(Type type) | public TBuilder WithType(Type type) | ||||
| { | { | ||||
| Type = type; | Type = type; | ||||
| TypeConverter = Modal._interactionService.GetComponentTypeConverter(type); | |||||
| return Instance; | return Instance; | ||||
| } | } | ||||
| @@ -9,6 +9,7 @@ namespace Discord.Interactions.Builders | |||||
| /// </summary> | /// </summary> | ||||
| public class ModalBuilder | public class ModalBuilder | ||||
| { | { | ||||
| internal readonly InteractionService _interactionService; | |||||
| internal readonly List<IInputComponentBuilder> _components; | internal readonly List<IInputComponentBuilder> _components; | ||||
| /// <summary> | /// <summary> | ||||
| @@ -31,11 +32,12 @@ namespace Discord.Interactions.Builders | |||||
| /// </summary> | /// </summary> | ||||
| public IReadOnlyCollection<IInputComponentBuilder> Components => _components; | public IReadOnlyCollection<IInputComponentBuilder> Components => _components; | ||||
| internal ModalBuilder(Type type) | |||||
| internal ModalBuilder(Type type, InteractionService interactionService) | |||||
| { | { | ||||
| 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)); | ||||
| _interactionService = interactionService; | |||||
| _components = new(); | _components = new(); | ||||
| } | } | ||||
| @@ -43,7 +45,7 @@ namespace Discord.Interactions.Builders | |||||
| /// Initializes a new <see cref="ModalBuilder"/> | /// Initializes a new <see cref="ModalBuilder"/> | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="modalInitializer">The initialization delegate for this modal.</param> | /// <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; | ModalInitializer = modalInitializer; | ||||
| } | } | ||||
| @@ -482,7 +482,7 @@ namespace Discord.Interactions.Builders | |||||
| #endregion | #endregion | ||||
| #region Modals | #region Modals | ||||
| public static ModalInfo BuildModalInfo(Type modalType) | |||||
| public static ModalInfo BuildModalInfo(Type modalType, InteractionService interactionService) | |||||
| { | { | ||||
| if (!typeof(IModal).IsAssignableFrom(modalType)) | if (!typeof(IModal).IsAssignableFrom(modalType)) | ||||
| throw new InvalidOperationException($"{modalType.FullName} isn't an implementation of {typeof(IModal).FullName}"); | throw new InvalidOperationException($"{modalType.FullName} isn't an implementation of {typeof(IModal).FullName}"); | ||||
| @@ -491,7 +491,7 @@ namespace Discord.Interactions.Builders | |||||
| try | try | ||||
| { | { | ||||
| var builder = new ModalBuilder(modalType) | |||||
| var builder = new ModalBuilder(modalType, interactionService) | |||||
| { | { | ||||
| Title = instance.Title | Title = instance.Title | ||||
| }; | }; | ||||
| @@ -52,8 +52,15 @@ namespace Discord.Interactions | |||||
| if (additionalArgs is not null) | if (additionalArgs is not null) | ||||
| args.AddRange(additionalArgs); | 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); | return await RunAsync(context, args.ToArray(), services); | ||||
| } | } | ||||
| @@ -39,6 +39,8 @@ namespace Discord.Interactions | |||||
| /// </summary> | /// </summary> | ||||
| public Type Type { get; } | public Type Type { get; } | ||||
| public ComponentTypeConverter TypeConverter { get; } | |||||
| /// <summary> | /// <summary> | ||||
| /// Gets the default value of this component. | /// Gets the default value of this component. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -57,6 +59,7 @@ namespace Discord.Interactions | |||||
| IsRequired = builder.IsRequired; | IsRequired = builder.IsRequired; | ||||
| ComponentType = builder.ComponentType; | ComponentType = builder.ComponentType; | ||||
| Type = builder.Type; | Type = builder.Type; | ||||
| TypeConverter = builder.TypeConverter; | |||||
| DefaultValue = builder.DefaultValue; | DefaultValue = builder.DefaultValue; | ||||
| Attributes = builder.Attributes.ToImmutableArray(); | Attributes = builder.Attributes.ToImmutableArray(); | ||||
| } | } | ||||
| @@ -2,6 +2,7 @@ using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| { | { | ||||
| @@ -86,5 +87,41 @@ namespace Discord.Interactions | |||||
| return _initializer(args); | return _initializer(args); | ||||
| } | } | ||||
| internal async Task<IResult> 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)); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,26 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal sealed class DefaultValueComponentConverter<T> : ComponentTypeConverter<T> | |||||
| where T : IConvertible | |||||
| { | |||||
| public override Task<TypeConverterResult> 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)); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -11,16 +11,16 @@ namespace Discord.Interactions | |||||
| public static IReadOnlyCollection<ModalInfo> Modals => _modalInfos.Values.ToReadOnlyCollection(); | public static IReadOnlyCollection<ModalInfo> Modals => _modalInfos.Values.ToReadOnlyCollection(); | ||||
| public static ModalInfo GetOrAdd(Type type) | |||||
| public static ModalInfo GetOrAdd(Type type, InteractionService interactionService) | |||||
| { | { | ||||
| 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)); | ||||
| return _modalInfos.GetOrAdd(type, ModuleClassBuilder.BuildModalInfo(type)); | |||||
| return _modalInfos.GetOrAdd(type, ModuleClassBuilder.BuildModalInfo(type, interactionService)); | |||||
| } | } | ||||
| public static ModalInfo GetOrAdd<T>() where T : class, IModal | |||||
| => GetOrAdd(typeof(T)); | |||||
| public static ModalInfo GetOrAdd<T>(InteractionService interactionService) where T : class, IModal | |||||
| => GetOrAdd(typeof(T), interactionService); | |||||
| public static bool TryGet(Type type, out ModalInfo modalInfo) | public static bool TryGet(Type type, out ModalInfo modalInfo) | ||||
| { | { | ||||