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)
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)
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);
}

/// <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)
{
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);
}


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

@@ -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
{
/// <summary>
/// Represents the parameter info class for <see cref="ComponentCommandInfo"/> commands.
/// </summary>
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; }
internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command)
{
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.
/// </remarks>
/// <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 />
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;

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.");


+ 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
{
/// <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));
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();
}
}
}

+ 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"/>.
/// </summary>
/// <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 />
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)
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.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;

namespace Discord.Commands
namespace Discord.Interactions
{
/// <summary>
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IRole"/>.
/// </summary>
/// <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 />
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.Threading.Tasks;

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

/// <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)
{
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"));
}
}
}


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

@@ -9,7 +9,7 @@ namespace Discord.Interactions
/// <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.
/// </remarks>
public abstract class TypeReader : ITypeHandler
public abstract class TypeReader<T> : ITypeHandler
{
/// <summary>
/// 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="services">Service provider that will be used to initialize the command module.</param>
/// <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>
/// Will be used to manipulate the outgoing command option, before the command gets registered to Discord.
/// </summary>
public virtual string Serialize(object value) => null;
public virtual T Serialize(object value) => default;
}

/// <inheritdoc/>
public abstract class TypeReader<T> : TypeReader
public abstract class TypeReader<TGeneric, T> : TypeReader<T>
{
/// <inheritdoc/>
public sealed override bool CanConvertTo(Type 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.Threading.Tasks;

namespace Discord.Commands
namespace Discord.Interactions
{
/// <summary>
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IUser"/>.
/// </summary>
/// <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 />
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>();
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.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