diff --git a/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs index 87fc1664e..1fa951b56 100644 --- a/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs @@ -42,19 +42,19 @@ namespace Discord.Interactions if (context.Interaction is not IComponentInteraction componentInteraction) return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction"); - var args = new List(); + var args = new List(); if (additionalArgs is not null) args.AddRange(additionalArgs); if (componentInteraction.Data?.Values is not null) - args.AddRange(componentInteraction.Data.Values); + args.Add(componentInteraction.Data.Values); return await ExecuteAsync(context, Parameters, args, services); } /// - public async Task ExecuteAsync(IInteractionContext context, IEnumerable paramList, IEnumerable values, + public async Task ExecuteAsync(IInteractionContext context, IEnumerable paramList, IEnumerable values, IServiceProvider services) { if (context.Interaction is not IComponentInteraction messageComponent) @@ -62,27 +62,42 @@ namespace Discord.Interactions try { - var strCount = Parameters.Count(x => x.ParameterType == typeof(string)); + var valueCount = values.Count(); + var args = new object[paramList.Count()]; - if (strCount > values?.Count()) - return ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters"); + for(var i = 0; i < paramList.Count(); i++) + { + var parameter = Parameters.ElementAt(i); + + if(i > valueCount) + { + if (!parameter.IsRequired) + args[i] = parameter.DefaultValue; + else + { + var result = ExecuteResult.FromError(InteractionCommandError.BadArgs, "Command was invoked with too few parameters"); + await InvokeModuleEvent(context, result).ConfigureAwait(false); + return result; + } + + } + else + { + var value = values.ElementAt(i); + var typeReader = parameter.TypeReader; - var componentValues = messageComponent.Data?.Values; + var readResult = await typeReader.ReadAsync(context, value, services).ConfigureAwait(false); - var args = new object[Parameters.Count]; + if (!readResult.IsSuccess) + { + await InvokeModuleEvent(context, readResult).ConfigureAwait(false); + return readResult; + } - if (componentValues is not null) - { - var lastParam = Parameters.Last(); - - if (lastParam.ParameterType.IsArray) - args[args.Length - 1] = componentValues.Select(async x => await lastParam.TypeReader.ReadAsync(context, x, services).ConfigureAwait(false)).ToArray(); - else - return ExecuteResult.FromError(InteractionCommandError.BadArgs, $"Select Menu Interaction handlers must accept a {typeof(string[]).FullName} as its last parameter"); + args[i] = readResult.Value; + } } - for (var i = 0; i < strCount; i++) - args[i] = await Parameters.ElementAt(i).TypeReader.ReadAsync(context, values.ElementAt(i), services).ConfigureAwait(false); return await RunAsync(context, args, services).ConfigureAwait(false); } diff --git a/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs b/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs index dfcbf26bb..33d529411 100644 --- a/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs +++ b/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs @@ -1,16 +1,18 @@ using Discord.Interactions.Builders; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Discord.Interactions { + /// + /// Represents the parameter info class for commands. + /// public class ComponentCommandParameterInfo : CommandParameterInfo { + /// + /// Gets the that will be used to convert a message component value into + /// . + /// public TypeReader TypeReader { get; } - + internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command) { TypeReader = builder.TypeReader; diff --git a/src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs b/src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs new file mode 100644 index 000000000..4fb5218ff --- /dev/null +++ b/src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + internal class ArrayReader : TypeReader where T : IEnumerable + { + private readonly TypeReader _baseReader; + + public ArrayReader(InteractionService interactionService) + { + if() + + interactionService.GetTypeReader(typeof) + } + + public override Task ReadAsync(IInteractionContext context, object input, IServiceProvider services) + { + if(input is IEnumerable enumerable) + return Task.FromResult(TypeConverterResult.FromSuccess(new )) + } + } +} diff --git a/src/Discord.Net.Interactions/TypeReaders/ChannelTypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/ChannelTypeReader.cs index 8b9ef8d54..93e89587c 100644 --- a/src/Discord.Net.Interactions/TypeReaders/ChannelTypeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/ChannelTypeReader.cs @@ -14,27 +14,36 @@ namespace Discord.Interactions /// final output; otherwise, an erroneous is returned. /// /// The type to be checked; must implement . - public class ChannelTypeReader : TypeReader - where T : class, IChannel + internal class ChannelTypeReader : TypeReader where T : class, IChannel { /// public override async Task ReadAsync(IInteractionContext context, object input, IServiceProvider services) { - if (context.Guild is null) + if (context.Guild is not null) { var str = input as string; if (ulong.TryParse(str, out var channelId)) - return TypeConverterResult.FromSuccess(await context.Guild.GetChannelAsync(channelId).ConfigureAwait(false)); + { + var channel = await context.Guild.GetChannelAsync(channelId).ConfigureAwait(false); + + if(channel is not null) + return TypeConverterResult.FromSuccess(channel as T); + } if (MentionUtils.TryParseChannel(str, out channelId)) - return TypeConverterResult.FromSuccess(await context.Guild.GetChannelAsync(channelId).ConfigureAwait(false)); + { + var channel = await context.Guild.GetChannelAsync(channelId).ConfigureAwait(false); + + if(channel is not null) + return TypeConverterResult.FromSuccess(channel as T); + } var channels = await context.Guild.GetChannelsAsync().ConfigureAwait(false); var nameMatch = channels.FirstOrDefault(x => string.Equals(x.Name, str, StringComparison.OrdinalIgnoreCase)); if (nameMatch is not null) - return TypeConverterResult.FromSuccess(nameMatch); + return TypeConverterResult.FromSuccess(nameMatch as T); } return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, "Channel not found."); diff --git a/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs index 4c9773b5b..064b0f329 100644 --- a/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs @@ -6,14 +6,20 @@ namespace Discord.Interactions internal class EnumTypeReader : TypeReader where T : struct, Enum { /// - public override Task ReadAsync(IInteractionContext context, string input, IServiceProvider services) + public override Task ReadAsync(IInteractionContext context, object input, IServiceProvider services) { - if (Enum.TryParse(input, out var result)) + if (Enum.TryParse(input as string, out var result)) return Task.FromResult(TypeConverterResult.FromSuccess(result)); else return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {input} cannot be converted to {nameof(T)}")); } - public override string Serialize(object value) => value.ToString(); + public override string Serialize(object value) + { + if (value is not Enum) + throw new ArgumentException($"{value} isn't an {nameof(Enum)}.", nameof(value)); + + return value.ToString(); + } } } diff --git a/src/Discord.Net.Interactions/TypeReaders/MessageTypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/MessageTypeReader.cs index 67100d7d0..31db6e075 100644 --- a/src/Discord.Net.Interactions/TypeReaders/MessageTypeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/MessageTypeReader.cs @@ -8,20 +8,18 @@ namespace Discord.Interactions /// A for parsing objects implementing . /// /// The type to be checked; must implement . - public class MessageTypeReader : TypeReader - where T : class, IMessage + internal class MessageTypeReader : TypeReader where T : class, IMessage { /// - public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override async Task ReadAsync(IInteractionContext context, object input, IServiceProvider services) { - //By Id (1.0) - if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out ulong id)) + if (ulong.TryParse(input as string, NumberStyles.None, CultureInfo.InvariantCulture, out ulong id)) { if (await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) is T msg) - return TypeReaderResult.FromSuccess(msg); + return TypeConverterResult.FromSuccess(msg); } - return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Message not found."); + return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, "Message not found."); } } } diff --git a/src/Discord.Net.Interactions/TypeReaders/RoleTypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/RoleTypeReader.cs index 4c9aaf4d8..3e3d8c0c1 100644 --- a/src/Discord.Net.Interactions/TypeReaders/RoleTypeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/RoleTypeReader.cs @@ -1,48 +1,44 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading.Tasks; -namespace Discord.Commands +namespace Discord.Interactions { /// /// A for parsing objects implementing . /// /// The type to be checked; must implement . - public class RoleTypeReader : TypeReader - where T : class, IRole + internal class RoleTypeReader : TypeReader where T : class, IRole { /// - public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override Task ReadAsync(IInteractionContext context, object input, IServiceProvider services) { - if (context.Guild != null) + if (context.Guild is not null) { - var results = new Dictionary(); - var roles = context.Guild.Roles; + if (ulong.TryParse(input as string, out var id)) + { + var role = context.Guild.GetRole(id); - //By Mention (1.0) - if (MentionUtils.TryParseRole(input, out var id)) - AddResult(results, context.Guild.GetRole(id) as T, 1.00f); + if (role is not null) + return Task.FromResult(TypeConverterResult.FromSuccess(role as T)); + } - //By Id (0.9) - if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - AddResult(results, context.Guild.GetRole(id) as T, 0.90f); + if (MentionUtils.TryParseRole(input as string, out id)) + { + var role = context.Guild.GetRole(id); - //By Name (0.7-0.8) - foreach (var role in roles.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) - AddResult(results, role as T, role.Name == input ? 0.80f : 0.70f); + if (role is not null) + return Task.FromResult(TypeConverterResult.FromSuccess(role as T)); + } - if (results.Count > 0) - return Task.FromResult(TypeReaderResult.FromSuccess(results.Values.ToReadOnlyCollection())); + var channels = context.Guild.Roles; + var nameMatch = channels.First(x => string.Equals(x, input as string)); + + if (nameMatch is not null) + return Task.FromResult(TypeConverterResult.FromSuccess(nameMatch as T)); } - return Task.FromResult(TypeReaderResult.FromError(CommandError.ObjectNotFound, "Role not found.")); - } - private void AddResult(Dictionary results, T role, float score) - { - if (role != null && !results.ContainsKey(role.Id)) - results.Add(role.Id, new TypeReaderValue(role, score)); + return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, "Role not found.")); } } } diff --git a/src/Discord.Net.Interactions/TypeReaders/TimeSpanTypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/TimeSpanTypeReader.cs index 5448553b3..e93665e85 100644 --- a/src/Discord.Net.Interactions/TypeReaders/TimeSpanTypeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/TimeSpanTypeReader.cs @@ -2,9 +2,9 @@ using System; using System.Globalization; using System.Threading.Tasks; -namespace Discord.Commands +namespace Discord.Interactions { - internal class TimeSpanTypeReader : TypeReader + internal class TimeSpanTypeReader : TypeReader { /// /// TimeSpan try parse formats. @@ -29,26 +29,28 @@ namespace Discord.Commands }; /// - public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override Task ReadAsync(IInteractionContext context, object input, IServiceProvider services) { - if (string.IsNullOrEmpty(input)) - throw new ArgumentException(message: $"{nameof(input)} must not be null or empty.", paramName: nameof(input)); + var str = input as string; - var isNegative = input[0] == '-'; // Char for CultureInfo.InvariantCulture.NumberFormat.NegativeSign + if (string.IsNullOrEmpty(str)) + throw new ArgumentException($"{nameof(input)} must not be null or empty.", nameof(input)); + + var isNegative = str[0] == '-'; // Char for CultureInfo.InvariantCulture.NumberFormat.NegativeSign if (isNegative) { - input = input.Substring(1); + str = str.Substring(1); } - if (TimeSpan.TryParseExact(input.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan)) + if (TimeSpan.TryParseExact(str.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan)) { return isNegative - ? Task.FromResult(TypeReaderResult.FromSuccess(-timeSpan)) - : Task.FromResult(TypeReaderResult.FromSuccess(timeSpan)); + ? Task.FromResult(TypeConverterResult.FromSuccess(-timeSpan)) + : Task.FromResult(TypeConverterResult.FromSuccess(timeSpan)); } else { - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan")); + return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ParseFailed, "Failed to parse TimeSpan")); } } } diff --git a/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs index 62276c973..db349ce58 100644 --- a/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs @@ -9,7 +9,7 @@ namespace Discord.Interactions /// /// s are mainly used to parse message component values. For interfacing with Slash Command parameters use s instead. /// - public abstract class TypeReader : ITypeHandler + public abstract class TypeReader : ITypeHandler { /// /// Will be used to search for alternative TypeReaders whenever the Command Service encounters an unknown parameter type. @@ -25,19 +25,21 @@ namespace Discord.Interactions /// Raw string input value. /// Service provider that will be used to initialize the command module. /// The result of the read process. - public abstract Task ReadAsync(IInteractionContext context, object input, IServiceProvider services); + public abstract Task ReadAsync(IInteractionContext context, T input, IServiceProvider services); /// /// Will be used to manipulate the outgoing command option, before the command gets registered to Discord. /// - public virtual string Serialize(object value) => null; + public virtual T Serialize(object value) => default; } /// - public abstract class TypeReader : TypeReader + public abstract class TypeReader : TypeReader { /// public sealed override bool CanConvertTo(Type type) => typeof(T).IsAssignableFrom(type); } + + public abstract class TypeReader : TypeReader { } } diff --git a/src/Discord.Net.Interactions/TypeReaders/UserTypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/UserTypeReader.cs index c0104e341..236d50a3b 100644 --- a/src/Discord.Net.Interactions/TypeReaders/UserTypeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/UserTypeReader.cs @@ -5,17 +5,16 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; -namespace Discord.Commands +namespace Discord.Interactions { /// /// A for parsing objects implementing . /// /// The type to be checked; must implement . - public class UserTypeReader : TypeReader - where T : class, IUser + public class UserTypeReader : TypeReader where T : class, IUser { /// - public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override async Task ReadAsync(IInteractionContext context, string input, IServiceProvider services) { var results = new Dictionary(); IAsyncEnumerable channelUsers = context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten(); // it's better @@ -85,11 +84,5 @@ namespace Discord.Commands return TypeReaderResult.FromSuccess(results.Values.ToImmutableArray()); return TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found."); } - - private void AddResult(Dictionary results, T user, float score) - { - if (user != null && !results.ContainsKey(user.Id)) - results.Add(user.Id, new TypeReaderValue(user, score)); - } } }