@@ -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) | ||||
{ | { | ||||