| @@ -231,9 +231,6 @@ namespace Discord.Interactions.Builders | |||||
| private static void BuildComponentCommand (ComponentCommandBuilder builder, Func<IServiceProvider, IInteractionModuleBase> createInstance, MethodInfo methodInfo, | private static void BuildComponentCommand (ComponentCommandBuilder builder, Func<IServiceProvider, IInteractionModuleBase> createInstance, MethodInfo methodInfo, | ||||
| InteractionService commandService, IServiceProvider services) | 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(); | var attributes = methodInfo.GetCustomAttributes(); | ||||
| builder.MethodName = methodInfo.Name; | builder.MethodName = methodInfo.Name; | ||||
| @@ -445,6 +442,12 @@ namespace Discord.Interactions.Builders | |||||
| builder.Name = Regex.Replace(builder.Name, "(?<=[a-z])(?=[A-Z])", "-").ToLower(); | 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<TInfo, TBuilder> (ParameterBuilder<TInfo, TBuilder> builder, ParameterInfo paramInfo) | private static void BuildParameter<TInfo, TBuilder> (ParameterBuilder<TInfo, TBuilder> builder, ParameterInfo paramInfo) | ||||
| where TInfo : class, IParameterInfo | where TInfo : class, IParameterInfo | ||||
| where TBuilder : ParameterBuilder<TInfo, TBuilder> | where TBuilder : ParameterBuilder<TInfo, TBuilder> | ||||
| @@ -4,7 +4,7 @@ namespace Discord.Interactions.Builders | |||||
| { | { | ||||
| public class ComponentCommandParameterBuilder : ParameterBuilder<ComponentCommandParameterInfo, ComponentCommandParameterBuilder> | public class ComponentCommandParameterBuilder : ParameterBuilder<ComponentCommandParameterInfo, ComponentCommandParameterBuilder> | ||||
| { | { | ||||
| public CompTypeConverter TypeReader { get; private set; } | |||||
| public ComponentTypeConverter TypeConverter { 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) { } | ||||
| @@ -18,14 +18,14 @@ namespace Discord.Interactions.Builders | |||||
| /// Sets <see cref="ParameterBuilder{TInfo, TBuilder}.ParameterType"/>. | /// Sets <see cref="ParameterBuilder{TInfo, TBuilder}.ParameterType"/>. | ||||
| /// </summary> | /// </summary> | ||||
| /// <param name="type">New value of the <see cref="ParameterBuilder{TInfo, TBuilder}.ParameterType"/>.</param> | /// <param name="type">New value of the <see cref="ParameterBuilder{TInfo, TBuilder}.ParameterType"/>.</param> | ||||
| /// <param name="services">Service container to be used to resolve the dependencies of this parameters <see cref="TypeConverter"/>.</param> | |||||
| /// <param name="services">Service container to be used to resolve the dependencies of this parameters <see cref="Interactions.TypeConverter"/>.</param> | |||||
| /// <returns> | /// <returns> | ||||
| /// The builder instance. | /// The builder instance. | ||||
| /// </returns> | /// </returns> | ||||
| public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) | public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) | ||||
| { | { | ||||
| base.SetParameterType(type); | base.SetParameterType(type); | ||||
| TypeReader = Command.Module.InteractionService.GetTypeReader(ParameterType, services); | |||||
| TypeConverter = Command.Module.InteractionService.GetComponentTypeConverter(ParameterType, services); | |||||
| return this; | return this; | ||||
| } | } | ||||
| @@ -1,43 +0,0 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| /// <summary> | |||||
| /// Base class for creating <see cref="CompTypeConverter"/>s. <see cref="InteractionService"/> uses <see cref="CompTypeConverter"/>s to parse string values into entities. | |||||
| /// </summary> | |||||
| /// <remarks> | |||||
| /// <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> | |||||
| public abstract class CompTypeConverter : ITypeConverter<IComponentInteractionData> | |||||
| { | |||||
| /// <summary> | |||||
| /// Will be used to search for alternative TypeReaders whenever the Command Service encounters an unknown parameter type. | |||||
| /// </summary> | |||||
| /// <param name="type"></param> | |||||
| /// <returns></returns> | |||||
| public abstract bool CanConvertTo(Type type); | |||||
| /// <summary> | |||||
| /// Will be used to read the incoming payload before executing the method body. | |||||
| /// </summary> | |||||
| /// <param name="context">Command exexution context.</param> | |||||
| /// <param name="input">Raw string input value.</param> | |||||
| /// <param name="services">Service provider that will be used to initialize the command module.</param> | |||||
| /// <returns>The result of the read process.</returns> | |||||
| public abstract Task<TypeConverterResult> ReadAsync(IInteractionContext context, IComponentInteractionData data, IServiceProvider services); | |||||
| /// <summary> | |||||
| /// Will be used to manipulate the outgoing command option, before the command gets registered to Discord. | |||||
| /// </summary> | |||||
| public virtual string Serialize(object value) => null; | |||||
| } | |||||
| /// <inheritdoc/> | |||||
| public abstract class CompTypeConverter<T> : CompTypeConverter | |||||
| { | |||||
| /// <inheritdoc/> | |||||
| public sealed override bool CanConvertTo(Type type) => | |||||
| typeof(T).IsAssignableFrom(type); | |||||
| } | |||||
| } | |||||
| @@ -1,24 +0,0 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal static class EnumExtensions | |||||
| { | |||||
| public static IEnumerable<T> GetFlags<T>(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<T>().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."); | |||||
| }; | |||||
| } | |||||
| } | |||||
| @@ -42,19 +42,11 @@ namespace Discord.Interactions | |||||
| if (context.Interaction is not IComponentInteraction componentInteraction) | if (context.Interaction is not IComponentInteraction componentInteraction) | ||||
| return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction"); | return ExecuteResult.FromError(InteractionCommandError.ParseFailed, $"Provided {nameof(IInteractionContext)} doesn't belong to a Message Component Interaction"); | ||||
| var args = new List<object>(); | |||||
| 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); | |||||
| } | } | ||||
| /// <inheritdoc/> | /// <inheritdoc/> | ||||
| public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<object> values, | |||||
| public async Task<IResult> ExecuteAsync(IInteractionContext context, IEnumerable<CommandParameterInfo> paramList, IEnumerable<string> wildcardCaptures, IComponentInteractionData data, | |||||
| IServiceProvider services) | IServiceProvider services) | ||||
| { | { | ||||
| if (context.Interaction is not IComponentInteraction messageComponent) | if (context.Interaction is not IComponentInteraction messageComponent) | ||||
| @@ -62,33 +54,21 @@ namespace Discord.Interactions | |||||
| try | 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); | 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 | 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); | await InvokeModuleEvent(context, readResult).ConfigureAwait(false); | ||||
| return readResult; | return readResult; | ||||
| @@ -98,7 +78,6 @@ namespace Discord.Interactions | |||||
| } | } | ||||
| } | } | ||||
| return await RunAsync(context, args, services).ConfigureAwait(false); | return await RunAsync(context, args, services).ConfigureAwait(false); | ||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| @@ -1,4 +1,5 @@ | |||||
| using Discord.Interactions.Builders; | using Discord.Interactions.Builders; | ||||
| using System; | |||||
| namespace Discord.Interactions | namespace Discord.Interactions | ||||
| { | { | ||||
| @@ -8,14 +9,14 @@ namespace Discord.Interactions | |||||
| public class ComponentCommandParameterInfo : CommandParameterInfo | public class ComponentCommandParameterInfo : CommandParameterInfo | ||||
| { | { | ||||
| /// <summary> | /// <summary> | ||||
| /// Gets the <see cref="TypeReader"/> that will be used to convert a message component value into | |||||
| /// Gets the <see cref="ComponentTypeConverter"/> that will be used to convert a message component value into | |||||
| /// <see cref="CommandParameterInfo.ParameterType"/>. | /// <see cref="CommandParameterInfo.ParameterType"/>. | ||||
| /// </summary> | /// </summary> | ||||
| public CompTypeConverter TypeReader { get; } | |||||
| public ComponentTypeConverter TypeConverter { get; } | |||||
| internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command) | internal ComponentCommandParameterInfo(ComponentCommandParameterBuilder builder, ICommandInfo command) : base(builder, command) | ||||
| { | { | ||||
| TypeReader = builder.TypeReader; | |||||
| TypeConverter = builder.TypeConverter; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -3,6 +3,7 @@ using Discord.Logging; | |||||
| using Discord.Rest; | using Discord.Rest; | ||||
| using Discord.WebSocket; | using Discord.WebSocket; | ||||
| using System; | using System; | ||||
| using System.Collections; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | using System.Linq; | ||||
| @@ -67,7 +68,8 @@ namespace Discord.Interactions | |||||
| private readonly CommandMap<ModalCommandInfo> _modalCommandMap; | private readonly CommandMap<ModalCommandInfo> _modalCommandMap; | ||||
| private readonly HashSet<ModuleInfo> _moduleDefs; | private readonly HashSet<ModuleInfo> _moduleDefs; | ||||
| private readonly TypeMap<TypeConverter, IApplicationCommandInteractionDataOption> _typeConverterMap; | private readonly TypeMap<TypeConverter, IApplicationCommandInteractionDataOption> _typeConverterMap; | ||||
| private readonly TypeMap<CompTypeConverter, IComponentInteractionData> _compTypeConverterMap; | |||||
| private readonly TypeMap<ComponentTypeConverter, IComponentInteractionData> _compTypeConverterMap; | |||||
| private readonly TypeMap<TypeReader, string> _typeReaderMap; | |||||
| 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; | ||||
| @@ -194,11 +196,23 @@ namespace Discord.Interactions | |||||
| [typeof(Nullable<>)] = typeof(NullableConverter<>), | [typeof(Nullable<>)] = typeof(NullableConverter<>), | ||||
| }); | }); | ||||
| _compTypeConverterMap = new TypeMap<CompTypeConverter, IComponentInteractionData>(this, new Dictionary<Type, CompTypeConverter> | |||||
| { | |||||
| }, new Dictionary<Type, Type> | |||||
| { | |||||
| }); | |||||
| _compTypeConverterMap = new TypeMap<ComponentTypeConverter, IComponentInteractionData>(this, new Dictionary<Type, ComponentTypeConverter>(), | |||||
| new Dictionary<Type, Type> | |||||
| { | |||||
| [typeof(Array)] = typeof(DefaultArrayComponentConverter<>), | |||||
| [typeof(string)] = typeof(DefaultValueConverter<>) | |||||
| }); | |||||
| _typeReaderMap = new TypeMap<TypeReader, string>(this, new Dictionary<Type, TypeReader>(), | |||||
| new Dictionary<Type, Type> | |||||
| { | |||||
| [typeof(IChannel)] = typeof(DefaultChannelReader<>), | |||||
| [typeof(IRole)] = typeof(DefaultRoleReader<>), | |||||
| [typeof(IUser)] = typeof(DefaultUserReader<>), | |||||
| [typeof(IMessage)] = typeof(DefaultUserReader<>), | |||||
| [typeof(IConvertible)] = typeof(DefaultValueReader<>), | |||||
| [typeof(Enum)] = typeof(EnumReader<>) | |||||
| }); | |||||
| } | } | ||||
| /// <summary> | /// <summary> | ||||
| @@ -809,7 +823,7 @@ 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 CompTypeConverter GetComponentTypeConverter(Type type, IServiceProvider services = null) => | |||||
| internal ComponentTypeConverter GetComponentTypeConverter(Type type, IServiceProvider services = null) => | |||||
| _compTypeConverterMap.Get(type, services); | _compTypeConverterMap.Get(type, services); | ||||
| /// <summary> | /// <summary> | ||||
| @@ -817,7 +831,7 @@ namespace Discord.Interactions | |||||
| /// </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="converter">The <see cref="TypeReader"/> instance.</param> | /// <param name="converter">The <see cref="TypeReader"/> instance.</param> | ||||
| public void AddComponentTypeConverter<T>(CompTypeConverter converter) => | |||||
| public void AddComponentTypeConverter<T>(ComponentTypeConverter converter) => | |||||
| AddComponentTypeConverter(typeof(T), converter); | AddComponentTypeConverter(typeof(T), converter); | ||||
| /// <summary> | /// <summary> | ||||
| @@ -825,7 +839,7 @@ namespace Discord.Interactions | |||||
| /// </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="converter">The <see cref="TypeReader"/> instance.</param> | /// <param name="converter">The <see cref="TypeReader"/> instance.</param> | ||||
| public void AddComponentTypeConverter(Type type, CompTypeConverter converter) => | |||||
| public void AddComponentTypeConverter(Type type, ComponentTypeConverter converter) => | |||||
| _compTypeConverterMap.AddConcrete(type, converter); | _compTypeConverterMap.AddConcrete(type, converter); | ||||
| /// <summary> | /// <summary> | ||||
| @@ -845,8 +859,23 @@ namespace Discord.Interactions | |||||
| public void AddGenericComponentTypeConverter(Type targetType, Type converterType) => | public void AddGenericComponentTypeConverter(Type targetType, Type converterType) => | ||||
| _compTypeConverterMap.AddGeneric(targetType, converterType); | _compTypeConverterMap.AddGeneric(targetType, converterType); | ||||
| public string SerializeWithTypeReader<T>(object obj, IServiceProvider services = null) => | |||||
| _compTypeConverterMap.Get(typeof(T), services).Serialize(obj); | |||||
| public Task<string> SerializeValue<T>(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<T>(TypeReader reader) => | |||||
| AddTypeReader(typeof(T), reader); | |||||
| public void AddTypeReader(Type type, TypeReader reader) => | |||||
| _typeReaderMap.AddConcrete(type, reader); | |||||
| public void AddGenericTypeReader<T>(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) | internal IAutocompleteHandler GetAutocompleteHandler(Type autocompleteHandlerType, IServiceProvider services = null) | ||||
| { | { | ||||
| @@ -0,0 +1,18 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| public abstract class ComponentTypeConverter : ITypeConverter<IComponentInteractionData> | |||||
| { | |||||
| public abstract bool CanConvertTo(Type type); | |||||
| public abstract Task<TypeConverterResult> ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services); | |||||
| public virtual Task<string> SerializeAsync(object obj) => Task.FromResult(obj.ToString()); | |||||
| } | |||||
| public abstract class ComponentTypeConverter<T> : ComponentTypeConverter | |||||
| { | |||||
| public sealed override bool CanConvertTo(Type type) => | |||||
| typeof(T).IsAssignableFrom(type); | |||||
| } | |||||
| } | |||||
| @@ -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<T> : ComponentTypeConverter<T> | |||||
| { | |||||
| private readonly TypeReader _typeReader; | |||||
| private readonly Type _underlyingType; | |||||
| public DefaultArrayComponentConverter(InteractionService interactionService) | |||||
| { | |||||
| var type = typeof(T); | |||||
| if (!type.IsArray) | |||||
| throw new InvalidOperationException($"{nameof(DefaultArrayComponentConverter<T>)} cannot be used to convert a non-array type."); | |||||
| _underlyingType = typeof(T).GetElementType(); | |||||
| _typeReader = interactionService.GetTypeReader(_underlyingType); | |||||
| } | |||||
| public override async Task<TypeConverterResult> ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services) | |||||
| { | |||||
| var results = new List<TypeConverterResult>(); | |||||
| 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()); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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<T> : ComponentTypeConverter<T> | |||||
| where T : class, IConvertible | |||||
| { | |||||
| public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, IComponentInteractionData option, IServiceProvider services) | |||||
| { | |||||
| return Task.FromResult(TypeConverterResult.FromSuccess(Convert.ChangeType(option.Values, TypeCode.Object))); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,48 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal abstract class DefaultSnowflakeReader<T> : TypeReader<T> | |||||
| where T : class, ISnowflakeEntity | |||||
| { | |||||
| protected abstract Task<T> GetEntity(ulong id, IInteractionContext ctx); | |||||
| public override async Task<TypeConverterResult> 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<T> : DefaultSnowflakeReader<T> | |||||
| where T : class, IUser | |||||
| { | |||||
| protected override async Task<T> GetEntity(ulong id, IInteractionContext ctx) => await ctx.Client.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T; | |||||
| } | |||||
| internal sealed class DefaultChannelReader<T> : DefaultSnowflakeReader<T> | |||||
| where T : class, IChannel | |||||
| { | |||||
| protected override async Task<T> GetEntity(ulong id, IInteractionContext ctx) => await ctx.Client.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T; | |||||
| } | |||||
| internal sealed class DefaultRoleReader<T> : DefaultSnowflakeReader<T> | |||||
| where T : class, IUser | |||||
| { | |||||
| protected override Task<T> GetEntity(ulong id, IInteractionContext ctx) => Task.FromResult(ctx.Guild?.GetRole(id) as T); | |||||
| } | |||||
| internal sealed class DefaultMessageReader<T> : DefaultSnowflakeReader<T> | |||||
| where T : class, IMessage | |||||
| { | |||||
| protected override async Task<T> GetEntity(ulong id, IInteractionContext ctx) => await ctx.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal sealed class DefaultValueReader<T> : TypeReader<T> | |||||
| where T : class, IConvertible | |||||
| { | |||||
| public override Task<TypeConverterResult> 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)); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,27 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal sealed class EnumReader<T> : TypeReader<T> | |||||
| where T : struct, Enum | |||||
| { | |||||
| public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services) | |||||
| { | |||||
| if (Enum.TryParse<T>(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<string> 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); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,27 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| internal sealed class EnumTypeReader<T> : TypeReader<T> | |||||
| where T : struct, Enum | |||||
| { | |||||
| public override Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services) | |||||
| { | |||||
| if (Enum.TryParse<T>(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<string> 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); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| using System; | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Interactions | |||||
| { | |||||
| public abstract class TypeReader : ITypeConverter<string> | |||||
| { | |||||
| public abstract bool CanConvertTo(Type type); | |||||
| public abstract Task<TypeConverterResult> ReadAsync(IInteractionContext context, string option, IServiceProvider services); | |||||
| public virtual Task<string> SerializeAsync(object obj) => Task.FromResult(obj.ToString()); | |||||
| } | |||||
| public abstract class TypeReader<T> : TypeReader | |||||
| { | |||||
| public sealed override bool CanConvertTo(Type type) => | |||||
| typeof(T).IsAssignableFrom(type); | |||||
| } | |||||
| } | |||||