Browse Source

add default readers

pull/2169/head
Cenk Ergen 3 years ago
parent
commit
df26b67650
10 changed files with 141 additions and 91 deletions
  1. +33
    -18
      src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs
  2. +8
    -6
      src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs
  3. +27
    -0
      src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs
  4. +15
    -6
      src/Discord.Net.Interactions/TypeReaders/ChannelTypeReader.cs
  5. +9
    -3
      src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs
  6. +5
    -7
      src/Discord.Net.Interactions/TypeReaders/MessageTypeReader.cs
  7. +22
    -26
      src/Discord.Net.Interactions/TypeReaders/RoleTypeReader.cs
  8. +13
    -11
      src/Discord.Net.Interactions/TypeReaders/TimeSpanTypeReader.cs
  9. +6
    -4
      src/Discord.Net.Interactions/TypeReaders/TypeReader.cs
  10. +3
    -10
      src/Discord.Net.Interactions/TypeReaders/UserTypeReader.cs

+ 33
- 18
src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs View File

@@ -42,19 +42,19 @@ namespace Discord.Interactions
if (context.Interaction is not IComponentInteraction componentInteraction) if (context.Interaction is not IComponentInteraction componentInteraction)
return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction"); return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction");


var args = new List<string>();
var args = new List<object>();


if (additionalArgs is not null) if (additionalArgs is not null)
args.AddRange(additionalArgs); args.AddRange(additionalArgs);


if (componentInteraction.Data?.Values is not null) if (componentInteraction.Data?.Values is not null)
args.AddRange(componentInteraction.Data.Values);
args.Add(componentInteraction.Data.Values);


return await ExecuteAsync(context, Parameters, args, services); return await ExecuteAsync(context, Parameters, args, services);
} }


/// <inheritdoc/> /// <inheritdoc/>
public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<string> values,
public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<object> values,
IServiceProvider services) IServiceProvider services)
{ {
if (context.Interaction is not IComponentInteraction messageComponent) if (context.Interaction is not IComponentInteraction messageComponent)
@@ -62,27 +62,42 @@ namespace Discord.Interactions


try 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); return await RunAsync(context, args, services).ConfigureAwait(false);
} }


+ 8
- 6
src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs View File

@@ -1,16 +1,18 @@
using Discord.Interactions.Builders; using Discord.Interactions.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace Discord.Interactions namespace Discord.Interactions
{ {
/// <summary>
/// Represents the parameter info class for <see cref="ComponentCommandInfo"/> commands.
/// </summary>
public class ComponentCommandParameterInfo : CommandParameterInfo public class ComponentCommandParameterInfo : CommandParameterInfo
{ {
/// <summary>
/// Gets the <see cref="TypeReader"/> that will be used to convert a message component value into
/// <see cref="CommandParameterInfo.ParameterType"/>.
/// </summary>
public TypeReader TypeReader { get; } public TypeReader TypeReader { get; }
internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command) internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command)
{ {
TypeReader = builder.TypeReader; TypeReader = builder.TypeReader;


+ 27
- 0
src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs View File

@@ -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<T> : TypeReader<T> where T : IEnumerable
{
private readonly TypeReader _baseReader;

public ArrayReader(InteractionService interactionService)
{
if()

interactionService.GetTypeReader(typeof)
}

public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services)
{
if(input is IEnumerable enumerable)
return Task.FromResult(TypeConverterResult.FromSuccess(new ))
}
}
}

+ 15
- 6
src/Discord.Net.Interactions/TypeReaders/ChannelTypeReader.cs View File

@@ -14,27 +14,36 @@ namespace Discord.Interactions
/// final output; otherwise, an erroneous <see cref="TypeReaderResult"/> is returned. /// final output; otherwise, an erroneous <see cref="TypeReaderResult"/> is returned.
/// </remarks> /// </remarks>
/// <typeparam name="T">The type to be checked; must implement <see cref="IChannel"/>.</typeparam> /// <typeparam name="T">The type to be checked; must implement <see cref="IChannel"/>.</typeparam>
public class ChannelTypeReader<T> : TypeReader<T>
where T : class, IChannel
internal class ChannelTypeReader<T> : TypeReader<T> where T : class, IChannel
{ {
/// <inheritdoc /> /// <inheritdoc />
public override async Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services) public override async Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services)
{ {
if (context.Guild is null)
if (context.Guild is not null)
{ {
var str = input as string; var str = input as string;


if (ulong.TryParse(str, out var channelId)) 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)) 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 channels = await context.Guild.GetChannelsAsync().ConfigureAwait(false);
var nameMatch = channels.FirstOrDefault(x => string.Equals(x.Name, str, StringComparison.OrdinalIgnoreCase)); var nameMatch = channels.FirstOrDefault(x => string.Equals(x.Name, str, StringComparison.OrdinalIgnoreCase));


if (nameMatch is not null) if (nameMatch is not null)
return TypeConverterResult.FromSuccess(nameMatch);
return TypeConverterResult.FromSuccess(nameMatch as T);
} }


return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, "Channel not found."); return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, "Channel not found.");


+ 9
- 3
src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs View File

@@ -6,14 +6,20 @@ namespace Discord.Interactions
internal class EnumTypeReader<T> : TypeReader<T> where T : struct, Enum internal class EnumTypeReader<T> : TypeReader<T> where T : struct, Enum
{ {
/// <inheritdoc /> /// <inheritdoc />
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, string input, IServiceProvider services)
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services)
{ {
if (Enum.TryParse<T>(input, out var result))
if (Enum.TryParse<T>(input as string, out var result))
return Task.FromResult(TypeConverterResult.FromSuccess(result)); return Task.FromResult(TypeConverterResult.FromSuccess(result));
else else
return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {input} cannot be converted to {nameof(T)}")); 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();
}
} }
} }

+ 5
- 7
src/Discord.Net.Interactions/TypeReaders/MessageTypeReader.cs View File

@@ -8,20 +8,18 @@ namespace Discord.Interactions
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IMessage"/>. /// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IMessage"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type to be checked; must implement <see cref="IMessage"/>.</typeparam> /// <typeparam name="T">The type to be checked; must implement <see cref="IMessage"/>.</typeparam>
public class MessageTypeReader<T> : TypeReader
where T : class, IMessage
internal class MessageTypeReader<T> : TypeReader<T> where T : class, IMessage
{ {
/// <inheritdoc /> /// <inheritdoc />
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
public override async Task<TypeConverterResult> 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) 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.");
} }
} }
} }

+ 22
- 26
src/Discord.Net.Interactions/TypeReaders/RoleTypeReader.cs View File

@@ -1,48 +1,44 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Commands
namespace Discord.Interactions
{ {
/// <summary> /// <summary>
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IRole"/>. /// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IRole"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type to be checked; must implement <see cref="IRole"/>.</typeparam> /// <typeparam name="T">The type to be checked; must implement <see cref="IRole"/>.</typeparam>
public class RoleTypeReader<T> : TypeReader
where T : class, IRole
internal class RoleTypeReader<T> : TypeReader<T> where T : class, IRole
{ {
/// <inheritdoc /> /// <inheritdoc />
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services)
{ {
if (context.Guild != null)
if (context.Guild is not null)
{ {
var results = new Dictionary<ulong, TypeReaderValue>();
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<ulong, TypeReaderValue> 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."));
} }
} }
} }

+ 13
- 11
src/Discord.Net.Interactions/TypeReaders/TimeSpanTypeReader.cs View File

@@ -2,9 +2,9 @@ using System;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Commands
namespace Discord.Interactions
{ {
internal class TimeSpanTypeReader : TypeReader
internal class TimeSpanTypeReader : TypeReader<TimeSpan>
{ {
/// <summary> /// <summary>
/// TimeSpan try parse formats. /// TimeSpan try parse formats.
@@ -29,26 +29,28 @@ namespace Discord.Commands
}; };


/// <inheritdoc /> /// <inheritdoc />
public override Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
public override Task<TypeConverterResult> 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) 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 return isNegative
? Task.FromResult(TypeReaderResult.FromSuccess(-timeSpan))
: Task.FromResult(TypeReaderResult.FromSuccess(timeSpan));
? Task.FromResult(TypeConverterResult.FromSuccess(-timeSpan))
: Task.FromResult(TypeConverterResult.FromSuccess(timeSpan));
} }
else else
{ {
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan"));
return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ParseFailed, "Failed to parse TimeSpan"));
} }
} }
} }


+ 6
- 4
src/Discord.Net.Interactions/TypeReaders/TypeReader.cs View File

@@ -9,7 +9,7 @@ namespace Discord.Interactions
/// <remarks> /// <remarks>
/// <see cref="TypeReader"/>s are mainly used to parse message component values. For interfacing with Slash Command parameters use <see cref="TypeConverter"/>s instead. /// <see cref="TypeReader"/>s are mainly used to parse message component values. For interfacing with Slash Command parameters use <see cref="TypeConverter"/>s instead.
/// </remarks> /// </remarks>
public abstract class TypeReader : ITypeHandler
public abstract class TypeReader<T> : ITypeHandler
{ {
/// <summary> /// <summary>
/// Will be used to search for alternative TypeReaders whenever the Command Service encounters an unknown parameter type. /// Will be used to search for alternative TypeReaders whenever the Command Service encounters an unknown parameter type.
@@ -25,19 +25,21 @@ namespace Discord.Interactions
/// <param name="input">Raw string input value.</param> /// <param name="input">Raw string input value.</param>
/// <param name="services">Service provider that will be used to initialize the command module.</param> /// <param name="services">Service provider that will be used to initialize the command module.</param>
/// <returns>The result of the read process.</returns> /// <returns>The result of the read process.</returns>
public abstract Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services);
public abstract Task<TypeConverterResult> ReadAsync(IInteractionContext context, T input, IServiceProvider services);


/// <summary> /// <summary>
/// Will be used to manipulate the outgoing command option, before the command gets registered to Discord. /// Will be used to manipulate the outgoing command option, before the command gets registered to Discord.
/// </summary> /// </summary>
public virtual string Serialize(object value) => null;
public virtual T Serialize(object value) => default;
} }


/// <inheritdoc/> /// <inheritdoc/>
public abstract class TypeReader<T> : TypeReader
public abstract class TypeReader<TGeneric, T> : TypeReader<T>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public sealed override bool CanConvertTo(Type type) => public sealed override bool CanConvertTo(Type type) =>
typeof(T).IsAssignableFrom(type); typeof(T).IsAssignableFrom(type);
} }

public abstract class TypeReader : TypeReader<object> { }
} }

+ 3
- 10
src/Discord.Net.Interactions/TypeReaders/UserTypeReader.cs View File

@@ -5,17 +5,16 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;


namespace Discord.Commands
namespace Discord.Interactions
{ {
/// <summary> /// <summary>
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IUser"/>. /// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IUser"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type to be checked; must implement <see cref="IUser"/>.</typeparam> /// <typeparam name="T">The type to be checked; must implement <see cref="IUser"/>.</typeparam>
public class UserTypeReader<T> : TypeReader
where T : class, IUser
public class UserTypeReader<T> : TypeReader<T> where T : class, IUser
{ {
/// <inheritdoc /> /// <inheritdoc />
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
public override async Task<TypeConverterResult> ReadAsync(IInteractionContext context, string input, IServiceProvider services)
{ {
var results = new Dictionary<ulong, TypeReaderValue>(); var results = new Dictionary<ulong, TypeReaderValue>();
IAsyncEnumerable<IUser> channelUsers = context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten(); // it's better IAsyncEnumerable<IUser> 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.FromSuccess(results.Values.ToImmutableArray());
return TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found."); return TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found.");
} }

private void AddResult(Dictionary<ulong, TypeReaderValue> results, T user, float score)
{
if (user != null && !results.ContainsKey(user.Id))
results.Add(user.Id, new TypeReaderValue(user, score));
}
} }
} }

Loading…
Cancel
Save