| @@ -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) { } | ||||
| @@ -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) => | ||||
| @@ -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); | |||||
| } | |||||
| } | |||||
| @@ -1,9 +0,0 @@ | |||||
| using System; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal interface ITypeHandler | |||||
| { | |||||
| public bool CanConvertTo(Type type); | |||||
| } | |||||
| } | |||||
| @@ -1,11 +0,0 @@ | |||||
| using System; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| [Flags] | |||||
| public enum TypeReaderTarget | |||||
| { | |||||
| CustomId = 1, | |||||
| SelectMenu = 2 | |||||
| } | |||||
| } | |||||
| @@ -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."); | |||||
| }; | }; | ||||
| } | } | ||||
| } | } | ||||
| @@ -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) | ||||
| { | { | ||||
| @@ -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) | ||||
| { | { | ||||
| @@ -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)}"); | ||||
| @@ -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. | ||||
| @@ -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 )) | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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(); | |||||
| } | |||||
| } | |||||
| @@ -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(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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."); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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.")); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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")); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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."); | |||||
| } | |||||
| } | |||||
| } | |||||