From 1468c0b435472cc42d8ffe22156e903736acb0b5 Mon Sep 17 00:00:00 2001 From: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> Date: Tue, 8 Feb 2022 22:18:41 +0300 Subject: [PATCH] add typereaders --- .../Builders/ModuleClassBuilder.cs | 9 ++-- .../ComponentCommandParameterBuilder.cs | 6 +-- .../CompTypeConverter.cs | 43 ---------------- .../Extensions/EnumExtensions.cs | 24 --------- .../Info/Commands/ComponentCommandInfo.cs | 41 ++++----------- .../ComponentCommandParameterInfo.cs | 7 +-- .../InteractionService.cs | 51 +++++++++++++++---- .../ComponentTypeConverter.cs | 18 +++++++ .../DefaultArrayComponentConverter.cs | 42 +++++++++++++++ .../DefaultValueComponentConverter.cs | 17 +++++++ .../DefaultEntityTypeConverter.cs | 0 .../DefaultValueConverter.cs | 0 .../{ => SlashCommands}/EnumConverter.cs | 0 .../{ => SlashCommands}/NullableConverter.cs | 0 .../{ => SlashCommands}/TimeSpanConverter.cs | 0 .../{ => SlashCommands}/TypeConverter.cs | 0 .../TypeReaders/DefaultSnowflakeReader.cs | 48 +++++++++++++++++ .../TypeReaders/DefaultValueReader.cs | 22 ++++++++ .../TypeReaders/EnumReader.cs | 27 ++++++++++ .../TypeReaders/EnumTypeReader.cs | 27 ++++++++++ .../TypeReaders/TypeReader.cs | 18 +++++++ 21 files changed, 282 insertions(+), 118 deletions(-) delete mode 100644 src/Discord.Net.Interactions/ComponentTypeConverters/CompTypeConverter.cs delete mode 100644 src/Discord.Net.Interactions/Extensions/EnumExtensions.cs create mode 100644 src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs create mode 100644 src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs create mode 100644 src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultValueComponentConverter.cs rename src/Discord.Net.Interactions/TypeConverters/{ => SlashCommands}/DefaultEntityTypeConverter.cs (100%) rename src/Discord.Net.Interactions/TypeConverters/{ => SlashCommands}/DefaultValueConverter.cs (100%) rename src/Discord.Net.Interactions/TypeConverters/{ => SlashCommands}/EnumConverter.cs (100%) rename src/Discord.Net.Interactions/TypeConverters/{ => SlashCommands}/NullableConverter.cs (100%) rename src/Discord.Net.Interactions/TypeConverters/{ => SlashCommands}/TimeSpanConverter.cs (100%) rename src/Discord.Net.Interactions/TypeConverters/{ => SlashCommands}/TypeConverter.cs (100%) create mode 100644 src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs create mode 100644 src/Discord.Net.Interactions/TypeReaders/DefaultValueReader.cs create mode 100644 src/Discord.Net.Interactions/TypeReaders/EnumReader.cs create mode 100644 src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs create mode 100644 src/Discord.Net.Interactions/TypeReaders/TypeReader.cs diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs index 6615f131c..94db3bb84 100644 --- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs @@ -231,9 +231,6 @@ namespace Discord.Interactions.Builders private static void BuildComponentCommand (ComponentCommandBuilder builder, Func createInstance, MethodInfo methodInfo, InteractionService commandService, IServiceProvider services) { - if (!methodInfo.GetParameters().All(x => x.ParameterType == typeof(string) || x.ParameterType == typeof(string[]))) - throw new InvalidOperationException($"Interaction method parameters all must be types of {typeof(string).Name} or {typeof(string[]).Name}"); - var attributes = methodInfo.GetCustomAttributes(); builder.MethodName = methodInfo.Name; @@ -445,6 +442,12 @@ namespace Discord.Interactions.Builders builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower(); } + private static void BuildComponentParameter(ComponentCommandParameterBuilder builder, ParameterInfo paramInfo) + { + BuildParameter(builder, paramInfo); + + } + private static void BuildParameter (ParameterBuilder builder, ParameterInfo paramInfo) where TInfo : class, IParameterInfo where TBuilder : ParameterBuilder diff --git a/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs b/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs index 051772b8b..4a8770c9a 100644 --- a/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs @@ -4,7 +4,7 @@ namespace Discord.Interactions.Builders { public class ComponentCommandParameterBuilder : ParameterBuilder { - public CompTypeConverter TypeReader { get; private set; } + public ComponentTypeConverter TypeConverter { get; private set; } protected override ComponentCommandParameterBuilder Instance => this; public ComponentCommandParameterBuilder(ICommandBuilder command) : base(command) { } @@ -18,14 +18,14 @@ namespace Discord.Interactions.Builders /// Sets . /// /// New value of the . - /// Service container to be used to resolve the dependencies of this parameters . + /// Service container to be used to resolve the dependencies of this parameters . /// /// The builder instance. /// public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) { base.SetParameterType(type); - TypeReader = Command.Module.InteractionService.GetTypeReader(ParameterType, services); + TypeConverter = Command.Module.InteractionService.GetComponentTypeConverter(ParameterType, services); return this; } diff --git a/src/Discord.Net.Interactions/ComponentTypeConverters/CompTypeConverter.cs b/src/Discord.Net.Interactions/ComponentTypeConverters/CompTypeConverter.cs deleted file mode 100644 index 741287dfe..000000000 --- a/src/Discord.Net.Interactions/ComponentTypeConverters/CompTypeConverter.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Discord.Interactions -{ - /// - /// Base class for creating s. uses s to parse string values into entities. - /// - /// - /// s are mainly used to parse message component values. For interfacing with Slash Command parameters use s instead. - /// - public abstract class CompTypeConverter : ITypeConverter - { - /// - /// Will be used to search for alternative TypeReaders whenever the Command Service encounters an unknown parameter type. - /// - /// - /// - public abstract bool CanConvertTo(Type type); - - /// - /// Will be used to read the incoming payload before executing the method body. - /// - /// Command exexution context. - /// Raw string input value. - /// Service provider that will be used to initialize the command module. - /// The result of the read process. - public abstract Task ReadAsync(IInteractionContext context, IComponentInteractionData data, IServiceProvider services); - - /// - /// Will be used to manipulate the outgoing command option, before the command gets registered to Discord. - /// - public virtual string Serialize(object value) => null; - } - - /// - public abstract class CompTypeConverter : CompTypeConverter - { - /// - public sealed override bool CanConvertTo(Type type) => - typeof(T).IsAssignableFrom(type); - } -} diff --git a/src/Discord.Net.Interactions/Extensions/EnumExtensions.cs b/src/Discord.Net.Interactions/Extensions/EnumExtensions.cs deleted file mode 100644 index 182e5b6a6..000000000 --- a/src/Discord.Net.Interactions/Extensions/EnumExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Discord.Interactions -{ - internal static class EnumExtensions - { - public static IEnumerable GetFlags(this T flags) where T : struct, Enum - { - if (!typeof(T).IsEnum || typeof(T).IsDefined(typeof(FlagsAttribute), false)) - throw new ArgumentException($"{typeof(T).FullName} isn't a flags enum.", nameof(T)); - - return Enum.GetValues().Where(x => flags.HasFlag(x)); - } - - public static TypeReaderTarget ToTypeReaderTarget(this ComponentType componentType) => - (componentType) switch - { - ComponentType.SelectMenu => TypeReaderTarget.SelectMenu, - _ => throw new InvalidOperationException($"{componentType} isn't supported by {nameof(CompTypeConverter)}s."); - }; - } -} diff --git a/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs index 1fa951b56..fa2836b22 100644 --- a/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/ComponentCommandInfo.cs @@ -42,19 +42,11 @@ 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(); - - if (additionalArgs is not null) - args.AddRange(additionalArgs); - - if (componentInteraction.Data?.Values is not null) - args.Add(componentInteraction.Data.Values); - - return await ExecuteAsync(context, Parameters, args, services); + return await ExecuteAsync(context, Parameters, additionalArgs, componentInteraction.Data, services); } /// - public async Task ExecuteAsync(IInteractionContext context, IEnumerable paramList, IEnumerable values, + public async Task ExecuteAsync(IInteractionContext context, IEnumerable paramList, IEnumerable wildcardCaptures, IComponentInteractionData data, IServiceProvider services) { if (context.Interaction is not IComponentInteraction messageComponent) @@ -62,33 +54,21 @@ namespace Discord.Interactions try { - var valueCount = values.Count(); - var args = new object[paramList.Count()]; + var paramCount = paramList.Count(); + var captureCount = wildcardCaptures?.Count() ?? 0; + var args = new object[paramCount]; - for(var i = 0; i < paramList.Count(); i++) + for(var i = 0; i < paramCount; 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; - } - - } + if (i <= captureCount) + args[i] = wildcardCaptures.ElementAt(i); else { - var value = values.ElementAt(i); - var typeReader = parameter.TypeReader; + var readResult = await parameter.TypeConverter.ReadAsync(context, data, services).ConfigureAwait(false); - var readResult = await typeReader.ReadAsync(context, value, services).ConfigureAwait(false); - - if (!readResult.IsSuccess) + if(!readResult.IsSuccess) { await InvokeModuleEvent(context, readResult).ConfigureAwait(false); return readResult; @@ -98,7 +78,6 @@ namespace Discord.Interactions } } - return await RunAsync(context, args, services).ConfigureAwait(false); } catch (Exception ex) diff --git a/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs b/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs index a8aa19c33..ec71617a3 100644 --- a/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs +++ b/src/Discord.Net.Interactions/Info/Parameters/ComponentCommandParameterInfo.cs @@ -1,4 +1,5 @@ using Discord.Interactions.Builders; +using System; namespace Discord.Interactions { @@ -8,14 +9,14 @@ namespace Discord.Interactions public class ComponentCommandParameterInfo : CommandParameterInfo { /// - /// Gets the that will be used to convert a message component value into + /// Gets the that will be used to convert a message component value into /// . /// - public CompTypeConverter TypeReader { get; } + public ComponentTypeConverter TypeConverter { get; } internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command) { - TypeReader = builder.TypeReader; + TypeConverter = builder.TypeConverter; } } } diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs index 8a8ce0716..f3feafa96 100644 --- a/src/Discord.Net.Interactions/InteractionService.cs +++ b/src/Discord.Net.Interactions/InteractionService.cs @@ -3,6 +3,7 @@ using Discord.Logging; using Discord.Rest; using Discord.WebSocket; using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -67,7 +68,8 @@ namespace Discord.Interactions private readonly CommandMap _modalCommandMap; private readonly HashSet _moduleDefs; private readonly TypeMap _typeConverterMap; - private readonly TypeMap _compTypeConverterMap; + private readonly TypeMap _compTypeConverterMap; + private readonly TypeMap _typeReaderMap; private readonly ConcurrentDictionary _autocompleteHandlers = new(); private readonly ConcurrentDictionary _modalInfos = new(); private readonly SemaphoreSlim _lock; @@ -194,11 +196,23 @@ namespace Discord.Interactions [typeof(Nullable<>)] = typeof(NullableConverter<>), }); - _compTypeConverterMap = new TypeMap(this, new Dictionary - { - }, new Dictionary - { - }); + _compTypeConverterMap = new TypeMap(this, new Dictionary(), + new Dictionary + { + [typeof(Array)] = typeof(DefaultArrayComponentConverter<>), + [typeof(string)] = typeof(DefaultValueConverter<>) + }); + + _typeReaderMap = new TypeMap(this, new Dictionary(), + new Dictionary + { + [typeof(IChannel)] = typeof(DefaultChannelReader<>), + [typeof(IRole)] = typeof(DefaultRoleReader<>), + [typeof(IUser)] = typeof(DefaultUserReader<>), + [typeof(IMessage)] = typeof(DefaultUserReader<>), + [typeof(IConvertible)] = typeof(DefaultValueReader<>), + [typeof(Enum)] = typeof(EnumReader<>) + }); } /// @@ -809,7 +823,7 @@ namespace Discord.Interactions public void AddGenericTypeConverter(Type targetType, Type converterType) => _typeConverterMap.AddGeneric(targetType, converterType); - internal CompTypeConverter GetComponentTypeConverter(Type type, IServiceProvider services = null) => + internal ComponentTypeConverter GetComponentTypeConverter(Type type, IServiceProvider services = null) => _compTypeConverterMap.Get(type, services); /// @@ -817,7 +831,7 @@ namespace Discord.Interactions /// /// Primary target of the . /// The instance. - public void AddComponentTypeConverter(CompTypeConverter converter) => + public void AddComponentTypeConverter(ComponentTypeConverter converter) => AddComponentTypeConverter(typeof(T), converter); /// @@ -825,7 +839,7 @@ namespace Discord.Interactions /// /// Primary target of the . /// The instance. - public void AddComponentTypeConverter(Type type, CompTypeConverter converter) => + public void AddComponentTypeConverter(Type type, ComponentTypeConverter converter) => _compTypeConverterMap.AddConcrete(type, converter); /// @@ -845,8 +859,23 @@ namespace Discord.Interactions public void AddGenericComponentTypeConverter(Type targetType, Type converterType) => _compTypeConverterMap.AddGeneric(targetType, converterType); - public string SerializeWithTypeReader(object obj, IServiceProvider services = null) => - _compTypeConverterMap.Get(typeof(T), services).Serialize(obj); + public Task SerializeValue(T obj, IServiceProvider services = null) => + _compTypeConverterMap.Get(typeof(T), services).SerializeAsync(obj); + + internal TypeReader GetTypeReader(Type type, IServiceProvider services = null) => + _typeReaderMap.Get(type, services); + + public void AddTypeReader(TypeReader reader) => + AddTypeReader(typeof(T), reader); + + public void AddTypeReader(Type type, TypeReader reader) => + _typeReaderMap.AddConcrete(type, reader); + + public void AddGenericTypeReader(Type readerType) => + AddGenericTypeReader(typeof(T), readerType); + + public void AddGenericTypeReader(Type targetType, Type readerType) => + _typeReaderMap.AddGeneric(targetType, readerType); internal IAutocompleteHandler GetAutocompleteHandler(Type autocompleteHandlerType, IServiceProvider services = null) { diff --git a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs new file mode 100644 index 000000000..283c94865 --- /dev/null +++ b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/ComponentTypeConverter.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + public abstract class ComponentTypeConverter : ITypeConverter + { + public abstract bool CanConvertTo(Type type); + public abstract Task ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services); + public virtual Task SerializeAsync(object obj) => Task.FromResult(obj.ToString()); + } + + public abstract class ComponentTypeConverter : ComponentTypeConverter + { + public sealed override bool CanConvertTo(Type type) => + typeof(T).IsAssignableFrom(type); + } +} diff --git a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs new file mode 100644 index 000000000..b17320bbe --- /dev/null +++ b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultArrayComponentConverter.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + internal sealed class DefaultArrayComponentConverter : ComponentTypeConverter + { + private readonly TypeReader _typeReader; + private readonly Type _underlyingType; + + public DefaultArrayComponentConverter(InteractionService interactionService) + { + var type = typeof(T); + + if (!type.IsArray) + throw new InvalidOperationException($"{nameof(DefaultArrayComponentConverter)} cannot be used to convert a non-array type."); + + _underlyingType = typeof(T).GetElementType(); + _typeReader = interactionService.GetTypeReader(_underlyingType); + } + + public override async Task ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services) + { + var results = new List(); + + foreach (var value in option.Values) + { + var result = await _typeReader.ReadAsync(context, value, services).ConfigureAwait(false); + + if (!result.IsSuccess) + return result; + + results.Add(result); + } + + return TypeConverterResult.FromSuccess(results.Select(x => x.Value).ToArray()); + } + } +} diff --git a/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultValueComponentConverter.cs b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultValueComponentConverter.cs new file mode 100644 index 000000000..7ee45b2f9 --- /dev/null +++ b/src/Discord.Net.Interactions/TypeConverters/ComponentInteractions/DefaultValueComponentConverter.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Interactions.TypeConverters +{ + internal sealed class DefaultValueComponentConverter : ComponentTypeConverter + where T : class, IConvertible + { + public override Task ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services) + { + return Task.FromResult(TypeConverterResult.FromSuccess(Convert.ChangeType(option.Values, TypeCode.Object))); + } + } +} diff --git a/src/Discord.Net.Interactions/TypeConverters/DefaultEntityTypeConverter.cs b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/DefaultEntityTypeConverter.cs similarity index 100% rename from src/Discord.Net.Interactions/TypeConverters/DefaultEntityTypeConverter.cs rename to src/Discord.Net.Interactions/TypeConverters/SlashCommands/DefaultEntityTypeConverter.cs diff --git a/src/Discord.Net.Interactions/TypeConverters/DefaultValueConverter.cs b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/DefaultValueConverter.cs similarity index 100% rename from src/Discord.Net.Interactions/TypeConverters/DefaultValueConverter.cs rename to src/Discord.Net.Interactions/TypeConverters/SlashCommands/DefaultValueConverter.cs diff --git a/src/Discord.Net.Interactions/TypeConverters/EnumConverter.cs b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/EnumConverter.cs similarity index 100% rename from src/Discord.Net.Interactions/TypeConverters/EnumConverter.cs rename to src/Discord.Net.Interactions/TypeConverters/SlashCommands/EnumConverter.cs diff --git a/src/Discord.Net.Interactions/TypeConverters/NullableConverter.cs b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs similarity index 100% rename from src/Discord.Net.Interactions/TypeConverters/NullableConverter.cs rename to src/Discord.Net.Interactions/TypeConverters/SlashCommands/NullableConverter.cs diff --git a/src/Discord.Net.Interactions/TypeConverters/TimeSpanConverter.cs b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/TimeSpanConverter.cs similarity index 100% rename from src/Discord.Net.Interactions/TypeConverters/TimeSpanConverter.cs rename to src/Discord.Net.Interactions/TypeConverters/SlashCommands/TimeSpanConverter.cs diff --git a/src/Discord.Net.Interactions/TypeConverters/TypeConverter.cs b/src/Discord.Net.Interactions/TypeConverters/SlashCommands/TypeConverter.cs similarity index 100% rename from src/Discord.Net.Interactions/TypeConverters/TypeConverter.cs rename to src/Discord.Net.Interactions/TypeConverters/SlashCommands/TypeConverter.cs diff --git a/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs b/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs new file mode 100644 index 000000000..db6c322e9 --- /dev/null +++ b/src/Discord.Net.Interactions/TypeReaders/DefaultSnowflakeReader.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + internal abstract class DefaultSnowflakeReader : TypeReader + where T : class, ISnowflakeEntity + { + protected abstract Task GetEntity(ulong id, IInteractionContext ctx); + + public override async Task ReadAsync(IInteractionContext context, string option, IServiceProvider services) + { + if (!ulong.TryParse(option, out var snowflake)) + return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option} isn't a valid snowflake thus cannot be converted into {typeof(T).Name}"); + + var result = GetEntity(snowflake, context); + + if (result is not null) + return TypeConverterResult.FromSuccess(result); + else + return TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"{option} must be a valid {typeof(T).Name} snowflake to be parsed."); + } + } + + internal sealed class DefaultUserReader : DefaultSnowflakeReader + where T : class, IUser + { + protected override async Task GetEntity(ulong id, IInteractionContext ctx) => await ctx.Client.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T; + } + + internal sealed class DefaultChannelReader : DefaultSnowflakeReader + where T : class, IChannel + { + protected override async Task GetEntity(ulong id, IInteractionContext ctx) => await ctx.Client.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T; + } + + internal sealed class DefaultRoleReader : DefaultSnowflakeReader + where T : class, IUser + { + protected override Task GetEntity(ulong id, IInteractionContext ctx) => Task.FromResult(ctx.Guild?.GetRole(id) as T); + } + + internal sealed class DefaultMessageReader : DefaultSnowflakeReader + where T : class, IMessage + { + protected override async Task GetEntity(ulong id, IInteractionContext ctx) => await ctx.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T; + } +} diff --git a/src/Discord.Net.Interactions/TypeReaders/DefaultValueReader.cs b/src/Discord.Net.Interactions/TypeReaders/DefaultValueReader.cs new file mode 100644 index 000000000..498b99e02 --- /dev/null +++ b/src/Discord.Net.Interactions/TypeReaders/DefaultValueReader.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + internal sealed class DefaultValueReader : TypeReader + where T : class, IConvertible + { + public override Task ReadAsync(IInteractionContext context, string option, IServiceProvider services) + { + try + { + var converted = Convert.ChangeType(option, typeof(T)); + return Task.FromResult(TypeConverterResult.FromSuccess(converted)); + } + catch (InvalidCastException castEx) + { + return Task.FromResult(TypeConverterResult.FromError(castEx)); + } + } + } +} diff --git a/src/Discord.Net.Interactions/TypeReaders/EnumReader.cs b/src/Discord.Net.Interactions/TypeReaders/EnumReader.cs new file mode 100644 index 000000000..8b2264c99 --- /dev/null +++ b/src/Discord.Net.Interactions/TypeReaders/EnumReader.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + internal sealed class EnumReader : TypeReader + where T : struct, Enum + { + public override Task ReadAsync(IInteractionContext context, string option, IServiceProvider services) + { + if (Enum.TryParse(option, out var result)) + return Task.FromResult(TypeConverterResult.FromSuccess(result)); + else + return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {option} cannot be converted to {nameof(T)}")); + } + + public override Task SerializeAsync(object obj) + { + var name = Enum.GetName(typeof(T), obj); + + if (name is null) + throw new ArgumentException($"Enum name cannot be parsed from {obj}"); + + return Task.FromResult(name); + } + } +} diff --git a/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs new file mode 100644 index 000000000..813959de3 --- /dev/null +++ b/src/Discord.Net.Interactions/TypeReaders/EnumTypeReader.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + internal sealed class EnumTypeReader : TypeReader + where T : struct, Enum + { + public override Task ReadAsync(IInteractionContext context, string option, IServiceProvider services) + { + if (Enum.TryParse(option, out var result)) + return Task.FromResult(TypeConverterResult.FromSuccess(result)); + else + return Task.FromResult(TypeConverterResult.FromError(InteractionCommandError.ConvertFailed, $"Value {option} cannot be converted to {nameof(T)}")); + } + + public override Task SerializeAsync(object obj) + { + var name = Enum.GetName(typeof(T), obj); + + if (name is null) + throw new ArgumentException($"Enum name cannot be parsed from {obj}"); + + return Task.FromResult(name); + } + } +} diff --git a/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs new file mode 100644 index 000000000..024047a55 --- /dev/null +++ b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Interactions +{ + public abstract class TypeReader : ITypeConverter + { + public abstract bool CanConvertTo(Type type); + public abstract Task ReadAsync(IInteractionContext context, string option, IServiceProvider services); + public virtual Task SerializeAsync(object obj) => Task.FromResult(obj.ToString()); + } + + public abstract class TypeReader : TypeReader + { + public sealed override bool CanConvertTo(Type type) => + typeof(T).IsAssignableFrom(type); + } +}