diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs
index 77569718a..ad2f07c73 100644
--- a/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/IInputComponentBuilder.cs
@@ -38,6 +38,9 @@ namespace Discord.Interactions.Builders
///
Type Type { get; }
+ ///
+ /// Get the assigned to this input.
+ ///
ComponentTypeConverter TypeConverter { get; }
///
diff --git a/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs b/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs
index 19674ac4d..7d1d96712 100644
--- a/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Modals/Inputs/InputComponentBuilder.cs
@@ -33,6 +33,7 @@ namespace Discord.Interactions.Builders
///
public Type Type { get; private set; }
+ ///
public ComponentTypeConverter TypeConverter { get; private set; }
///
diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
index ac727496c..07e62b1f1 100644
--- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs
@@ -446,8 +446,7 @@ namespace Discord.Interactions.Builders
private static void BuildComponentParameter(ComponentCommandParameterBuilder builder, ParameterInfo paramInfo, bool isComponentParam)
{
- builder.SetAsRouteSegment(!isComponentParam);
-
+ builder.SetIsRouteSegment(!isComponentParam);
BuildParameter(builder, paramInfo);
}
diff --git a/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs b/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs
index 81f4d05c8..9bdf8539e 100644
--- a/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs
+++ b/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs
@@ -4,13 +4,32 @@ namespace Discord.Interactions.Builders
{
public class ComponentCommandParameterBuilder : ParameterBuilder
{
+ ///
+ /// Get the assigned to this parameter, if is .
+ ///
public ComponentTypeConverter TypeConverter { get; private set; }
+
+ ///
+ /// Get the assigned to this parameter, if is .
+ ///
public TypeReader TypeReader { get; private set; }
+
+ ///
+ /// Gets whether this parameter is a CustomId segment or a Component value parameter.
+ ///
public bool IsRouteSegmentParameter { get; private set; }
+
+ ///
protected override ComponentCommandParameterBuilder Instance => this;
- public ComponentCommandParameterBuilder(ICommandBuilder command) : base(command) { }
+ internal ComponentCommandParameterBuilder(ICommandBuilder command) : base(command) { }
+ ///
+ /// Initializes a new .
+ ///
+ /// Parent command of this parameter.
+ /// Name of this command.
+ /// Type of this parameter.
public ComponentCommandParameterBuilder(ICommandBuilder command, string name, Type type) : base(command, name, type) { }
///
@@ -36,7 +55,14 @@ namespace Discord.Interactions.Builders
return this;
}
- public ComponentCommandParameterBuilder SetAsRouteSegment(bool isRouteSegment)
+ ///
+ /// Sets .
+ ///
+ /// New value of the .
+ ///
+ /// The builder instance.
+ ///
+ public ComponentCommandParameterBuilder SetIsRouteSegment(bool isRouteSegment)
{
IsRouteSegmentParameter = isRouteSegment;
return this;
diff --git a/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs
index 40f93ebfe..2b9306fda 100644
--- a/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs
+++ b/src/Discord.Net.Interactions/Info/Commands/ModalCommandInfo.cs
@@ -52,15 +52,18 @@ namespace Discord.Interactions
if (additionalArgs is not null)
args.AddRange(additionalArgs);
- var modalResult = await Modal.ParseModalAsync(context, services, Module.CommandService._exitOnMissingModalField).ConfigureAwait(false);
+ var modalResult = await Modal.CreateModalAsync(context, services, Module.CommandService._exitOnMissingModalField).ConfigureAwait(false);
- if(!modalResult.IsSuccess || modalResult is not ParseResult parseResult)
+ if(!modalResult.IsSuccess)
{
await InvokeModuleEvent(context, modalResult).ConfigureAwait(false);
return modalResult;
}
- args.Add(parseResult.Value);
+ if(modalResult is ParseResult parseResult)
+ args.Add(parseResult.Value);
+ else
+ return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command parameter parsing failed for an unknown reason.");
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 e457562a7..05695f862 100644
--- a/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs
+++ b/src/Discord.Net.Interactions/Info/InputComponents/InputComponentInfo.cs
@@ -39,6 +39,9 @@ namespace Discord.Interactions
///
public Type Type { get; }
+ ///
+ /// Gets the assigned to this component.
+ ///
public ComponentTypeConverter TypeConverter { get; }
///
diff --git a/src/Discord.Net.Interactions/Info/ModalInfo.cs b/src/Discord.Net.Interactions/Info/ModalInfo.cs
index 087551df9..5130c26a1 100644
--- a/src/Discord.Net.Interactions/Info/ModalInfo.cs
+++ b/src/Discord.Net.Interactions/Info/ModalInfo.cs
@@ -62,10 +62,11 @@ namespace Discord.Interactions
///
/// Creates an and fills it with provided message components.
///
- /// that will be injected into the modal.
+ /// that will be injected into the modal.
///
/// A filled with the provided components.
///
+ [Obsolete("This method is no longer supported with the introduction of Component TypeConverters, please use the CreateModalAsync method.")]
public IModal CreateModal(IModalInteraction modalInteraction, bool throwOnMissingField = false)
{
var args = new object[Components.Count];
@@ -90,10 +91,19 @@ namespace Discord.Interactions
return _initializer(args);
}
- internal async Task ParseModalAsync(IInteractionContext context, IServiceProvider services = null, bool throwOnMissingField = false)
+ ///
+ /// Creates an and fills it with provided message components.
+ ///
+ /// Context of the that will be injected into the modal.
+ /// Services to be passed onto the s of the modal fiels.
+ /// Wheter or not this method should exit on encountering a missing modal field.
+ ///
+ /// A if a type conversion has failed, else a .
+ ///
+ public async Task CreateModalAsync(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.");
+ return ParseResult.FromError(InteractionCommandError.Unsuccessful, "Provided context doesn't belong to a Modal Interaction.");
services ??= EmptyServiceProvider.Instance;
@@ -110,7 +120,7 @@ namespace Discord.Interactions
if (!throwOnMissingField)
args[i] = input.DefaultValue;
else
- throw new InvalidOperationException($"Modal interaction is missing the required field: {input.CustomId}");
+ return ParseResult.FromError(InteractionCommandError.BadArgs, $"Modal interaction is missing the required field: {input.CustomId}");
}
else
{
diff --git a/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs b/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs
index d281f681a..36b75ddb7 100644
--- a/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs
+++ b/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs
@@ -1,5 +1,4 @@
using Discord.Interactions.Builders;
-using System;
namespace Discord.Interactions
{
@@ -10,10 +9,19 @@ namespace Discord.Interactions
{
///
/// Gets the that will be used to convert a message component value into
- /// .
+ /// , if is false.
///
public ComponentTypeConverter TypeConverter { get; }
+
+ ///
+ /// Gets the that will be used to convert a CustomId segment value into
+ /// , if is .
+ ///
public TypeReader TypeReader { get; }
+
+ ///
+ /// Gets whether this parameter is a CustomId segment or a component value parameter.
+ ///
public bool IsRouteSegmentParameter { get; }
internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command)
diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs
index ec1c65318..1b7521828 100644
--- a/src/Discord.Net.Interactions/InteractionService.cs
+++ b/src/Discord.Net.Interactions/InteractionService.cs
@@ -181,30 +181,30 @@ namespace Discord.Interactions
_autoServiceScopes = config.AutoServiceScopes;
_restResponseCallback = config.RestResponseCallback;
- _typeConverterMap = new TypeMap(this, new Dictionary
- {
- [typeof(TimeSpan)] = new TimeSpanConverter()
- }, new Dictionary
- {
- [typeof(IChannel)] = typeof(DefaultChannelConverter<>),
- [typeof(IRole)] = typeof(DefaultRoleConverter<>),
- [typeof(IAttachment)] = typeof(DefaultAttachmentConverter<>),
- [typeof(IUser)] = typeof(DefaultUserConverter<>),
- [typeof(IMentionable)] = typeof(DefaultMentionableConverter<>),
- [typeof(IConvertible)] = typeof(DefaultValueConverter<>),
- [typeof(Enum)] = typeof(EnumConverter<>),
- [typeof(Nullable<>)] = typeof(NullableConverter<>),
- });
-
- _compTypeConverterMap = new TypeMap(this, new Dictionary(),
- new Dictionary
+ _typeConverterMap = new TypeMap(this, new ConcurrentDictionary
+ {
+ [typeof(TimeSpan)] = new TimeSpanConverter()
+ }, new ConcurrentDictionary
+ {
+ [typeof(IChannel)] = typeof(DefaultChannelConverter<>),
+ [typeof(IRole)] = typeof(DefaultRoleConverter<>),
+ [typeof(IAttachment)] = typeof(DefaultAttachmentConverter<>),
+ [typeof(IUser)] = typeof(DefaultUserConverter<>),
+ [typeof(IMentionable)] = typeof(DefaultMentionableConverter<>),
+ [typeof(IConvertible)] = typeof(DefaultValueConverter<>),
+ [typeof(Enum)] = typeof(EnumConverter<>),
+ [typeof(Nullable<>)] = typeof(NullableConverter<>),
+ });
+
+ _compTypeConverterMap = new TypeMap(this, new ConcurrentDictionary(),
+ new ConcurrentDictionary
{
[typeof(Array)] = typeof(DefaultArrayComponentConverter<>),
[typeof(IConvertible)] = typeof(DefaultValueComponentConverter<>)
});
- _typeReaderMap = new TypeMap(this, new Dictionary(),
- new Dictionary
+ _typeReaderMap = new TypeMap(this, new ConcurrentDictionary(),
+ new ConcurrentDictionary
{
[typeof(IChannel)] = typeof(DefaultChannelReader<>),
[typeof(IRole)] = typeof(DefaultRoleReader<>),
@@ -827,56 +827,84 @@ namespace Discord.Interactions
_compTypeConverterMap.Get(type, services);
///
- /// Add a concrete type .
+ /// Add a concrete type .
///
- /// Primary target of the .
- /// The instance.
+ /// Primary target of the .
+ /// The instance.
public void AddComponentTypeConverter(ComponentTypeConverter converter) =>
AddComponentTypeConverter(typeof(T), converter);
///
- /// Add a concrete type .
+ /// Add a concrete type .
///
- /// Primary target of the .
- /// The instance.
+ /// Primary target of the .
+ /// The instance.
public void AddComponentTypeConverter(Type type, ComponentTypeConverter converter) =>
_compTypeConverterMap.AddConcrete(type, converter);
///
- /// Add a generic type .
+ /// Add a generic type .
///
- /// Generic Type constraint of the of the .
- /// Type of the .
-
+ /// Generic Type constraint of the of the .
+ /// Type of the .
public void AddGenericComponentTypeConverter(Type converterType) =>
AddGenericComponentTypeConverter(typeof(T), converterType);
///
- /// Add a generic type .
+ /// Add a generic type .
///
- /// Generic Type constraint of the of the .
- /// Type of the .
+ /// Generic Type constraint of the of the .
+ /// Type of the .
public void AddGenericComponentTypeConverter(Type targetType, Type converterType) =>
_compTypeConverterMap.AddGeneric(targetType, converterType);
- public Task SerializeValue(T obj, IServiceProvider services = null) =>
- _compTypeConverterMap.Get(typeof(T), services).SerializeAsync(obj);
-
internal TypeReader GetTypeReader(Type type, IServiceProvider services = null) =>
_typeReaderMap.Get(type, services);
+ ///
+ /// Add a concrete type .
+ ///
+ /// Primary target of the .
+ /// The instance.
public void AddTypeReader(TypeReader reader) =>
AddTypeReader(typeof(T), reader);
+ ///
+ /// Add a concrete type .
+ ///
+ /// Primary target of the .
+ /// The instance.
public void AddTypeReader(Type type, TypeReader reader) =>
_typeReaderMap.AddConcrete(type, reader);
+ ///
+ /// Add a generic type .
+ ///
+ /// Generic Type constraint of the of the .
+ /// Type of the .
public void AddGenericTypeReader(Type readerType) =>
AddGenericTypeReader(typeof(T), readerType);
+ ///
+ /// Add a generic type .
+ ///
+ /// Generic Type constraint of the of the .
+ /// Type of the .
public void AddGenericTypeReader(Type targetType, Type readerType) =>
_typeReaderMap.AddGeneric(targetType, readerType);
+ ///
+ /// Serialize an object using a into a to be placed in a Component CustomId.
+ ///
+ /// Type of the object to be serialized.
+ /// Object to be serialized.
+ /// Services that will be passed on to the TypeReader.
+ ///
+ /// A task representing the conversion process. The task result contains the result of the conversion.
+ ///
+ public Task SerializeValue(T obj, IServiceProvider services = null) =>
+ _typeReaderMap.Get(typeof(T), services).SerializeAsync(obj);
+
///
/// Loads and caches an for the provided .
///
diff --git a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs
index 283c94865..e406d4a26 100644
--- a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs
+++ b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs
@@ -3,15 +3,36 @@ using System.Threading.Tasks;
namespace Discord.Interactions
{
+ ///
+ /// Base class for creating Component TypeConverters. uses TypeConverters to interface with Slash Command parameters.
+ ///
public abstract class ComponentTypeConverter : ITypeConverter
{
+ ///
+ /// Will be used to search for alternative TypeConverters whenever the Command Service encounters an unknown parameter type.
+ ///
+ /// An object type.
+ ///
+ /// The boolean result.
+ ///
public abstract bool CanConvertTo(Type type);
+
+ ///
+ /// Will be used to read the incoming payload before executing the method body.
+ ///
+ /// Command exexution context.
+ /// Recieved option payload.
+ /// Service provider that will be used to initialize the command module.
+ ///
+ /// The result of the read process.
+ ///
public abstract Task ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services);
- public virtual Task SerializeAsync(object obj) => Task.FromResult(obj.ToString());
}
+ ///
public abstract class ComponentTypeConverter : ComponentTypeConverter
{
+ ///
public sealed override bool CanConvertTo(Type type) =>
typeof(T).IsAssignableFrom(type);
}
diff --git a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs
index 043019013..87fc431c5 100644
--- a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs
+++ b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections;
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
namespace Discord.Interactions
diff --git a/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs b/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs
index db6c322e9..3b4e01165 100644
--- a/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs
+++ b/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs
@@ -20,6 +20,8 @@ namespace Discord.Interactions
else
return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option} must be a valid {typeof(T).Name} snowflake to be parsed.");
}
+
+ public override Task SerializeAsync(object obj) => Task.FromResult((obj as ISnowflakeEntity)?.Id.ToString());
}
internal sealed class DefaultUserReader : DefaultSnowflakeReader
diff --git a/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs
deleted file mode 100644
index 813959de3..000000000
--- a/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace Discord.Interactions
-{
- internal sealed class EnumTypeReader : TypeReader
- where T : struct, Enum
- {
- public override Task ReadAsync(IInteractionContext context, string option, IServiceProvider services)
- {
- if (Enum.TryParse(option, out var result))
- return Task.FromResult(TypeConverterResult.FromSuccess(result));
- else
- return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {option} cannot be converted to {nameof(T)}"));
- }
-
- public override Task SerializeAsync(object obj)
- {
- var name = Enum.GetName(typeof(T), obj);
-
- if (name is null)
- throw new ArgumentException($"Enum name cannot be parsed from {obj}");
-
- return Task.FromResult(name);
- }
- }
-}
diff --git a/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs
index 024047a55..95dc3c8a5 100644
--- a/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs
+++ b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs
@@ -3,15 +3,43 @@ using System.Threading.Tasks;
namespace Discord.Interactions
{
+ ///
+ /// Base class for creating TypeConverters. uses TypeConverters to interface with Slash Command parameters.
+ ///
public abstract class TypeReader : ITypeConverter
{
+ ///
+ /// Will be used to search for alternative TypeReaders whenever the Command Service encounters an unknown parameter type.
+ ///
+ /// An object type.
+ ///
+ /// The boolean result.
+ ///
public abstract bool CanConvertTo(Type type);
+
+ ///
+ /// Will be used to read the incoming payload before executing the method body.
+ ///
+ /// Command exexution context.
+ /// Recieved option payload.
+ /// Service provider that will be used to initialize the command module.
+ /// The result of the read process.
public abstract Task ReadAsync(IInteractionContext context, string option, IServiceProvider services);
+
+ ///
+ /// Will be used to serialize objects into strings.
+ ///
+ /// Object to be serialized.
+ ///
+ /// A task represting the conversion process. The result of the task contains the conversion result.
+ ///
public virtual Task SerializeAsync(object obj) => Task.FromResult(obj.ToString());
}
+ ///
public abstract class TypeReader : TypeReader
{
+ ///
public sealed override bool CanConvertTo(Type type) =>
typeof(T).IsAssignableFrom(type);
}