Browse Source

seperate custom id readers with component typeconverters

pull/2169/head
Cenk Ergen 3 years ago
parent
commit
fcb8927fc1
18 changed files with 58 additions and 452 deletions
  1. +1
    -1
      src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs
  2. +5
    -5
      src/Discord.Net.Interactions/ComponentTypeConverters/CompTypeConverter.cs
  3. +12
    -0
      src/Discord.Net.Interactions/Entities/ITypeConverter.cs
  4. +0
    -9
      src/Discord.Net.Interactions/Entities/ITypeHandler.cs
  5. +0
    -11
      src/Discord.Net.Interactions/Entities/TypeReaderTarget.cs
  6. +1
    -1
      src/Discord.Net.Interactions/Extensions/EnumExtensions.cs
  7. +1
    -1
      src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs
  8. +28
    -56
      src/Discord.Net.Interactions/InteractionService.cs
  9. +9
    -8
      src/Discord.Net.Interactions/Map/TypeMap.cs
  10. +1
    -1
      src/Discord.Net.Interactions/TypeConverters/TypeConverter.cs
  11. +0
    -31
      src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs
  12. +0
    -54
      src/Discord.Net.Interactions/TypeReaders/ChannelTypeReader.cs
  13. +0
    -25
      src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs
  14. +0
    -25
      src/Discord.Net.Interactions/TypeReaders/MessageTypeReader.cs
  15. +0
    -35
      src/Discord.Net.Interactions/TypeReaders/NullableTypeReader.cs
  16. +0
    -44
      src/Discord.Net.Interactions/TypeReaders/RoleTypeReader.cs
  17. +0
    -57
      src/Discord.Net.Interactions/TypeReaders/TimeSpanTypeReader.cs
  18. +0
    -88
      src/Discord.Net.Interactions/TypeReaders/UserTypeReader.cs

+ 1
- 1
src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs View File

@@ -4,7 +4,7 @@ namespace Discord.Interactions.Builders
{ {
public class ComponentCommandParameterBuilder : ParameterBuilder<ComponentCommandParameterInfo, ComponentCommandParameterBuilder> public class ComponentCommandParameterBuilder : ParameterBuilder<ComponentCommandParameterInfo, ComponentCommandParameterBuilder>
{ {
public TypeReader TypeReader { get; private set; }
public CompTypeConverter TypeReader { get; private set; }
protected override ComponentCommandParameterBuilder Instance => this; protected override ComponentCommandParameterBuilder Instance => this;


public ComponentCommandParameterBuilder(ICommandBuilder command) : base(command) { } public ComponentCommandParameterBuilder(ICommandBuilder command) : base(command) { }


src/Discord.Net.Interactions/TypeReaders/TypeReader.cs → src/Discord.Net.Interactions/ComponentTypeConverters/CompTypeConverter.cs View File

@@ -4,12 +4,12 @@ using System.Threading.Tasks;
namespace Discord.Interactions namespace Discord.Interactions
{ {
/// <summary> /// <summary>
/// Base class for creating <see cref="TypeReader"/>s. <see cref="InteractionService"/> uses <see cref="TypeReader"/>s to parse string values into entities.
/// Base class for creating <see cref="CompTypeConverter"/>s. <see cref="InteractionService"/> uses <see cref="CompTypeConverter"/>s to parse string values into entities.
/// </summary> /// </summary>
/// <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="CompTypeConverter"/>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 CompTypeConverter : ITypeConverter<IComponentInteractionData>
{ {
/// <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,7 +25,7 @@ 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, IComponentInteractionData data, 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.
@@ -34,7 +34,7 @@ namespace Discord.Interactions
} }


/// <inheritdoc/> /// <inheritdoc/>
public abstract class TypeReader<T> : TypeReader
public abstract class CompTypeConverter<T> : CompTypeConverter
{ {
/// <inheritdoc/> /// <inheritdoc/>
public sealed override bool CanConvertTo(Type type) => public sealed override bool CanConvertTo(Type type) =>

+ 12
- 0
src/Discord.Net.Interactions/Entities/ITypeConverter.cs View File

@@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;

namespace Discord.Interactions
{
internal interface ITypeConverter<T>
{
public bool CanConvertTo(Type type);

public Task<TypeConverterResult> ReadAsync(IInteractionContext context, T option, IServiceProvider services);
}
}

+ 0
- 9
src/Discord.Net.Interactions/Entities/ITypeHandler.cs View File

@@ -1,9 +0,0 @@
using System;

namespace Discord.Interactions
{
internal interface ITypeHandler
{
public bool CanConvertTo(Type type);
}
}

+ 0
- 11
src/Discord.Net.Interactions/Entities/TypeReaderTarget.cs View File

@@ -1,11 +0,0 @@
using System;

namespace Discord.Interactions
{
[Flags]
public enum TypeReaderTarget
{
CustomId = 1,
SelectMenu = 2
}
}

+ 1
- 1
src/Discord.Net.Interactions/Extensions/EnumExtensions.cs View File

@@ -18,7 +18,7 @@ namespace Discord.Interactions
(componentType) switch (componentType) switch
{ {
ComponentType.SelectMenu => TypeReaderTarget.SelectMenu, ComponentType.SelectMenu => TypeReaderTarget.SelectMenu,
_ => throw new InvalidOperationException($"{componentType} isn't supported by {nameof(TypeReader)}s.");
_ => throw new InvalidOperationException($"{componentType} isn't supported by {nameof(CompTypeConverter)}s.");
}; };
} }
} }

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

@@ -11,7 +11,7 @@ namespace Discord.Interactions
/// Gets the <see cref="TypeReader"/> that will be used to convert a message component value into /// Gets the <see cref="TypeReader"/> that will be used to convert a message component value into
/// <see cref="CommandParameterInfo.ParameterType"/>. /// <see cref="CommandParameterInfo.ParameterType"/>.
/// </summary> /// </summary>
public TypeReader TypeReader { get; }
public CompTypeConverter TypeReader { get; }


internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command) internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command)
{ {


+ 28
- 56
src/Discord.Net.Interactions/InteractionService.cs View File

@@ -66,8 +66,8 @@ namespace Discord.Interactions
private readonly CommandMap<AutocompleteCommandInfo> _autocompleteCommandMap; private readonly CommandMap<AutocompleteCommandInfo> _autocompleteCommandMap;
private readonly CommandMap<ModalCommandInfo> _modalCommandMap; private readonly CommandMap<ModalCommandInfo> _modalCommandMap;
private readonly HashSet<ModuleInfo> _moduleDefs; private readonly HashSet<ModuleInfo> _moduleDefs;
private readonly TypeMap<TypeConverter> _typeConverterMap;
private readonly ConcurrentDictionary<TypeReaderTarget, TypeMap<TypeReader>> _typeReaderMaps;
private readonly TypeMap<TypeConverter, IApplicationCommandInteractionDataOption> _typeConverterMap;
private readonly TypeMap<CompTypeConverter, IComponentInteractionData> _compTypeConverterMap;
private readonly ConcurrentDictionary<Type, IAutocompleteHandler> _autocompleteHandlers = new(); private readonly ConcurrentDictionary<Type, IAutocompleteHandler> _autocompleteHandlers = new();
private readonly ConcurrentDictionary<Type, ModalInfo> _modalInfos = new(); private readonly ConcurrentDictionary<Type, ModalInfo> _modalInfos = new();
private readonly SemaphoreSlim _lock; private readonly SemaphoreSlim _lock;
@@ -179,7 +179,7 @@ namespace Discord.Interactions
_autoServiceScopes = config.AutoServiceScopes; _autoServiceScopes = config.AutoServiceScopes;
_restResponseCallback = config.RestResponseCallback; _restResponseCallback = config.RestResponseCallback;


_typeConverterMap = new TypeMap<TypeConverter>(this, new Dictionary<Type, TypeConverter>
_typeConverterMap = new TypeMap<TypeConverter, IApplicationCommandInteractionDataOption>(this, new Dictionary<Type, TypeConverter>
{ {
[typeof(TimeSpan)] = new TimeSpanConverter() [typeof(TimeSpan)] = new TimeSpanConverter()
}, new Dictionary<Type, Type> }, new Dictionary<Type, Type>
@@ -194,7 +194,11 @@ namespace Discord.Interactions
[typeof(Nullable<>)] = typeof(NullableConverter<>), [typeof(Nullable<>)] = typeof(NullableConverter<>),
}); });


_typeReaderMaps = new ConcurrentDictionary<TypeReaderTarget, TypeMap<TypeReader>>();
_compTypeConverterMap = new TypeMap<CompTypeConverter, IComponentInteractionData>(this, new Dictionary<Type, CompTypeConverter>
{
}, new Dictionary<Type, Type>
{
});
} }


/// <summary> /// <summary>
@@ -805,76 +809,44 @@ namespace Discord.Interactions
public void AddGenericTypeConverter(Type targetType, Type converterType) => public void AddGenericTypeConverter(Type targetType, Type converterType) =>
_typeConverterMap.AddGeneric(targetType, converterType); _typeConverterMap.AddGeneric(targetType, converterType);


internal IEnumerable<TypeReader> GetTypeReader(Type type, TypeReaderTarget targets, IServiceProvider services = null)
{
var flattenedTargets = targets.GetFlags();

foreach (var target in flattenedTargets)
yield return _typeReaderMaps[target].Get(type, services);
}
internal CompTypeConverter GetComponentTypeConverter(Type type, IServiceProvider services = null) =>
_compTypeConverterMap.Get(type, services);


/// <summary> /// <summary>
/// Add a concrete type <see cref="TypeReader"/>. /// Add a concrete type <see cref="TypeReader"/>.
/// </summary> /// </summary>
/// <typeparam name="T">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</typeparam> /// <typeparam name="T">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</typeparam>
/// <param name="reader">The <see cref="TypeReader"/> instance.</param>
public void AddTypeReader<T>(TypeReader reader, TypeReaderTarget targets)
{
var flattenedTargets = targets.GetFlags();

foreach (var target in flattenedTargets)
_typeReaderMaps[target].AddConcrete<T>(reader);
}
/// <param name="converter">The <see cref="TypeReader"/> instance.</param>
public void AddComponentTypeConverter<T>(CompTypeConverter converter) =>
AddComponentTypeConverter(typeof(T), converter);


/// <summary> /// <summary>
/// Add a concrete type <see cref="TypeReader"/>. /// Add a concrete type <see cref="TypeReader"/>.
/// </summary> /// </summary>
/// <param name="type">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</param> /// <param name="type">Primary target <see cref="Type"/> of the <see cref="TypeReader"/>.</param>
/// <param name="reader">The <see cref="TypeReader"/> instance.</param>
public void AddTypeReader(Type type, TypeReader reader, TypeReaderTarget targets)
{
var flattenedTargets = targets.GetFlags();

foreach (var target in flattenedTargets)
_typeReaderMaps[target].AddConcrete(type, reader);
}
/// <param name="converter">The <see cref="TypeReader"/> instance.</param>
public void AddComponentTypeConverter(Type type, CompTypeConverter converter) =>
_compTypeConverterMap.AddConcrete(type, converter);


/// <summary> /// <summary>
/// Add a generic type <see cref="TypeReader{T}"/>.
/// Add a generic type <see cref="CompTypeConverter{T}"/>.
/// </summary> /// </summary>
/// <typeparam name="T">Generic Type constraint of the <see cref="Type"/> of the <see cref="TypeReader{T}"/>.</typeparam>
/// <param name="readerType">Type of the <see cref="TypeReader{T}"/>.</param>

public void AddGenericTypeReader<T>(Type readerType, TypeReaderTarget targets)
{
var flattenedTargets = targets.GetFlags();
/// <typeparam name="T">Generic Type constraint of the <see cref="Type"/> of the <see cref="CompTypeConverter{T}"/>.</typeparam>
/// <param name="converterType">Type of the <see cref="CompTypeConverter{T}"/>.</param>


foreach (var target in flattenedTargets)
_typeReaderMaps[target].AddGeneric<T>(readerType);
}
public void AddGenericComponentTypeConverter<T>(Type converterType) =>
AddGenericComponentTypeConverter(typeof(T), converterType);


/// <summary> /// <summary>
/// Add a generic type <see cref="TypeReader{T}"/>.
/// Add a generic type <see cref="CompTypeConverter{T}"/>.
/// </summary> /// </summary>
/// <param name="targetType">Generic Type constraint of the <see cref="Type"/> of the <see cref="TypeReader{T}"/>.</param>
/// <param name="readerType">Type of the <see cref="TypeReader{T}"/>.</param>
public void AddGenericTypeReader(Type targetType, Type readerType, TypeReaderTarget targets)
{
var flattenedTargets = targets.GetFlags();
/// <param name="targetType">Generic Type constraint of the <see cref="Type"/> of the <see cref="CompTypeConverter{T}"/>.</param>
/// <param name="converterType">Type of the <see cref="CompTypeConverter{T}"/>.</param>
public void AddGenericComponentTypeConverter(Type targetType, Type converterType) =>
_compTypeConverterMap.AddGeneric(targetType, converterType);


foreach(var target in flattenedTargets)
_typeReaderMaps[target].AddGeneric(targetType, readerType);
}

public string SerializeWithTypeReader<T>(object obj, TypeReaderTarget targets, IServiceProvider services = null)
{
var flattenedTargets = targets.GetFlags();

if (flattenedTargets.Count() != 1)
throw new ArgumentException("Cannot serialize object for multiple targets.", nameof(targets));

return _typeReaderMaps[flattenedTargets.First()].Get(typeof(T), services)?.Serialize(obj);
}
public string SerializeWithTypeReader<T>(object obj, IServiceProvider services = null) =>
_compTypeConverterMap.Get(typeof(T), services).Serialize(obj);


internal IAutocompleteHandler GetAutocompleteHandler(Type autocompleteHandlerType, IServiceProvider services = null) internal IAutocompleteHandler GetAutocompleteHandler(Type autocompleteHandlerType, IServiceProvider services = null)
{ {


+ 9
- 8
src/Discord.Net.Interactions/Map/TypeMap.cs View File

@@ -6,20 +6,21 @@ using System.Reflection;


namespace Discord.Interactions namespace Discord.Interactions
{ {
internal class TypeMap<T> where T : class, ITypeHandler
internal class TypeMap<TConverter, TData>
where TConverter : class, ITypeConverter<TData>
{ {
private readonly ConcurrentDictionary<Type, T> _concretes;
private readonly ConcurrentDictionary<Type, TConverter> _concretes;
private readonly ConcurrentDictionary<Type, Type> _generics; private readonly ConcurrentDictionary<Type, Type> _generics;
private readonly InteractionService _interactionService; private readonly InteractionService _interactionService;


public TypeMap(InteractionService interactionService, IDictionary<Type, T> concretes = null, IDictionary<Type, Type> generics = null)
public TypeMap(InteractionService interactionService, IDictionary<Type, TConverter> concretes = null, IDictionary<Type, Type> generics = null)
{ {
_interactionService = interactionService; _interactionService = interactionService;
_concretes = concretes is not null ? new(concretes) : new(); _concretes = concretes is not null ? new(concretes) : new();
_generics = generics is not null ? new(generics) : new(); _generics = generics is not null ? new(generics) : new();
} }


internal T Get(Type type, IServiceProvider services = null)
internal TConverter Get(Type type, IServiceProvider services = null)
{ {
if (_concretes.TryGetValue(type, out var specific)) if (_concretes.TryGetValue(type, out var specific))
return specific; return specific;
@@ -30,7 +31,7 @@ namespace Discord.Interactions
services ??= EmptyServiceProvider.Instance; services ??= EmptyServiceProvider.Instance;


var converterType = GetMostSpecific(type); var converterType = GetMostSpecific(type);
var converter = ReflectionUtils<T>.CreateObject(converterType.MakeGenericType(type).GetTypeInfo(), _interactionService, services);
var converter = ReflectionUtils<TConverter>.CreateObject(converterType.MakeGenericType(type).GetTypeInfo(), _interactionService, services);
_concretes[type] = converter; _concretes[type] = converter;
return converter; return converter;
} }
@@ -38,13 +39,13 @@ namespace Discord.Interactions
else if (_concretes.Any(x => x.Value.CanConvertTo(type))) else if (_concretes.Any(x => x.Value.CanConvertTo(type)))
return _concretes.First(x => x.Value.CanConvertTo(type)).Value; return _concretes.First(x => x.Value.CanConvertTo(type)).Value;


throw new ArgumentException($"No type {nameof(T)} is defined for this {type.FullName}", "type");
throw new ArgumentException($"No type {nameof(TConverter)} is defined for this {type.FullName}", "type");
} }


public void AddConcrete<TTarget>(T converter) =>
public void AddConcrete<TTarget>(TConverter converter) =>
AddConcrete(typeof(TTarget), converter); AddConcrete(typeof(TTarget), converter);


public void AddConcrete(Type type, T converter)
public void AddConcrete(Type type, TConverter converter)
{ {
if (!converter.CanConvertTo(type)) if (!converter.CanConvertTo(type))
throw new ArgumentException($"This {converter.GetType().FullName} cannot read {type.FullName} and cannot be registered as its {nameof(TypeConverter)}"); throw new ArgumentException($"This {converter.GetType().FullName} cannot read {type.FullName} and cannot be registered as its {nameof(TypeConverter)}");


+ 1
- 1
src/Discord.Net.Interactions/TypeConverters/TypeConverter.cs View File

@@ -6,7 +6,7 @@ namespace Discord.Interactions
/// <summary> /// <summary>
/// Base class for creating TypeConverters. <see cref="InteractionService"/> uses TypeConverters to interface with Slash Command parameters. /// Base class for creating TypeConverters. <see cref="InteractionService"/> uses TypeConverters to interface with Slash Command parameters.
/// </summary> /// </summary>
public abstract class TypeConverter : ITypeHandler
public abstract class TypeConverter : ITypeConverter<IApplicationCommandInteractionDataOption>
{ {
/// <summary> /// <summary>
/// Will be used to search for alternative TypeConverters whenever the Command Service encounters an unknown parameter type. /// Will be used to search for alternative TypeConverters whenever the Command Service encounters an unknown parameter type.


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

@@ -1,31 +0,0 @@
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 where T : IEnumerable
{
private readonly TypeReader _baseReader;

public ArrayReader(InteractionService interactionService)
{
if()

interactionService.GetTypeReader(typeof)
}

public override TypeReaderTarget[] TypeReaderTargets { get; }

public override bool CanConvertTo(Type type) => throw new NotImplementedException();

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

+ 0
- 54
src/Discord.Net.Interactions/TypeReaders/ChannelTypeReader.cs View File

@@ -1,54 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Discord.Interactions
{
/// <summary>
/// A <see cref="TypeReader"/> for parsing objects implementing <see cref="IChannel"/>.
/// </summary>
/// <remarks>
/// This <see cref="TypeReader"/> is shipped with Discord.Net and is used by default to parse any
/// <see cref="IChannel"/> implemented object within a command. The TypeReader will attempt to first parse the
/// input by mention, then the snowflake identifier, then by name; the highest candidate will be chosen as the
/// 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>
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 not null)
{
var str = input as string;

if (ulong.TryParse(str, out var channelId))
{
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))
{
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 as T);
}

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

public override string Serialize(object value) => (value as IChannel)?.Id.ToString();
}
}

+ 0
- 25
src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs View File

@@ -1,25 +0,0 @@
using System;
using System.Threading.Tasks;

namespace Discord.Interactions
{
internal class EnumTypeReader<T> : TypeReader<T> where T : struct, Enum
{
/// <inheritdoc />
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services)
{
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)
{
if (value is not Enum)
throw new ArgumentException($"{value} isn't an {nameof(Enum)}.", nameof(value));

return value.ToString();
}
}
}

+ 0
- 25
src/Discord.Net.Interactions/TypeReaders/MessageTypeReader.cs View File

@@ -1,25 +0,0 @@
using System;
using System.Globalization;
using System.Threading.Tasks;

namespace Discord.Interactions
{
/// <summary>
/// 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>
internal class MessageTypeReader<T> : TypeReader<T> where T : class, IMessage
{
/// <inheritdoc />
public override async Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services)
{
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 TypeConverterResult.FromSuccess(msg);
}

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

+ 0
- 35
src/Discord.Net.Interactions/TypeReaders/NullableTypeReader.cs View File

@@ -1,35 +0,0 @@
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Discord.Commands
{
internal static class NullableTypeReader
{
public static TypeReader Create(Type type, TypeReader reader)
{
var constructor = typeof(NullableTypeReader<>).MakeGenericType(type).GetTypeInfo().DeclaredConstructors.First();
return (TypeReader)constructor.Invoke(new object[] { reader });
}
}

internal class NullableTypeReader<T> : TypeReader
where T : struct
{
private readonly TypeReader _baseTypeReader;

public NullableTypeReader(TypeReader baseTypeReader)
{
_baseTypeReader = baseTypeReader;
}

/// <inheritdoc />
public override async Task<TypeReaderResult> ReadAsync(ICommandContext context, string input, IServiceProvider services)
{
if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase))
return TypeReaderResult.FromSuccess(new T?());
return await _baseTypeReader.ReadAsync(context, input, services).ConfigureAwait(false);
}
}
}

+ 0
- 44
src/Discord.Net.Interactions/TypeReaders/RoleTypeReader.cs View File

@@ -1,44 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;

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>
internal class RoleTypeReader<T> : TypeReader<T> where T : class, IRole
{
/// <inheritdoc />
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services)
{
if (context.Guild is not null)
{
if (ulong.TryParse(input as string, out var id))
{
var role = context.Guild.GetRole(id);

if (role is not null)
return Task.FromResult(TypeConverterResult.FromSuccess(role as T));
}

if (MentionUtils.TryParseRole(input as string, out id))
{
var role = context.Guild.GetRole(id);

if (role is not null)
return Task.FromResult(TypeConverterResult.FromSuccess(role as T));
}

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(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, "Role not found."));
}
}
}

+ 0
- 57
src/Discord.Net.Interactions/TypeReaders/TimeSpanTypeReader.cs View File

@@ -1,57 +0,0 @@
using System;
using System.Globalization;
using System.Threading.Tasks;

namespace Discord.Interactions
{
internal class TimeSpanTypeReader : TypeReader<TimeSpan>
{
/// <summary>
/// TimeSpan try parse formats.
/// </summary>
private static readonly string[] Formats =
{
"%d'd'%h'h'%m'm'%s's'", // 4d3h2m1s
"%d'd'%h'h'%m'm'", // 4d3h2m
"%d'd'%h'h'%s's'", // 4d3h 1s
"%d'd'%h'h'", // 4d3h
"%d'd'%m'm'%s's'", // 4d 2m1s
"%d'd'%m'm'", // 4d 2m
"%d'd'%s's'", // 4d 1s
"%d'd'", // 4d
"%h'h'%m'm'%s's'", // 3h2m1s
"%h'h'%m'm'", // 3h2m
"%h'h'%s's'", // 3h 1s
"%h'h'", // 3h
"%m'm'%s's'", // 2m1s
"%m'm'", // 2m
"%s's'", // 1s
};

/// <inheritdoc />
public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, object input, IServiceProvider services)
{
var str = input as string;

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)
{
str = str.Substring(1);
}

if (TimeSpan.TryParseExact(str.ToLowerInvariant(), Formats, CultureInfo.InvariantCulture, out var timeSpan))
{
return isNegative
? Task.FromResult(TypeConverterResult.FromSuccess(-timeSpan))
: Task.FromResult(TypeConverterResult.FromSuccess(timeSpan));
}
else
{
return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ParseFailed, "Failed to parse TimeSpan"));
}
}
}
}

+ 0
- 88
src/Discord.Net.Interactions/TypeReaders/UserTypeReader.cs View File

@@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;

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<T> where T : class, IUser
{
/// <inheritdoc />
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
IReadOnlyCollection<IGuildUser> guildUsers = ImmutableArray.Create<IGuildUser>();

if (context.Guild != null)
guildUsers = await context.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false);

//By Mention (1.0)
if (MentionUtils.TryParseUser(input, out var id))
{
if (context.Guild != null)
AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f);
else
AddResult(results, await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f);
}

//By Id (0.9)
if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id))
{
if (context.Guild != null)
AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f);
else
AddResult(results, await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f);
}

//By Username + Discriminator (0.7-0.85)
int index = input.LastIndexOf('#');
if (index >= 0)
{
string username = input.Substring(0, index);
if (ushort.TryParse(input.Substring(index + 1), out ushort discriminator))
{
var channelUser = await channelUsers.FirstOrDefaultAsync(x => x.DiscriminatorValue == discriminator &&
string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false);
AddResult(results, channelUser as T, channelUser?.Username == username ? 0.85f : 0.75f);

var guildUser = guildUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator &&
string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase));
AddResult(results, guildUser as T, guildUser?.Username == username ? 0.80f : 0.70f);
}
}

//By Username (0.5-0.6)
{
await channelUsers
.Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase))
.ForEachAsync(channelUser => AddResult(results, channelUser as T, channelUser.Username == input ? 0.65f : 0.55f))
.ConfigureAwait(false);
foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase)))
AddResult(results, guildUser as T, guildUser.Username == input ? 0.60f : 0.50f);
}

//By Nickname (0.5-0.6)
{
await channelUsers
.Where(x => string.Equals(input, (x as IGuildUser)?.Nickname, StringComparison.OrdinalIgnoreCase))
.ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f))
.ConfigureAwait(false);

foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase)))
AddResult(results, guildUser as T, guildUser.Nickname == input ? 0.60f : 0.50f);
}

if (results.Count > 0)
return TypeReaderResult.FromSuccess(results.Values.ToImmutableArray());
return TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found.");
}
}
}

Loading…
Cancel
Save