diff --git a/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs b/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs index 56de1fc36..a6a100f46 100644 --- a/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Parameters/ComponentCommandParameterBuilder.cs @@ -25,7 +25,7 @@ namespace Discord.Interactions.Builders public ComponentCommandParameterBuilder SetParameterType(Type type, IServiceProvider services = null) { base.SetParameterType(type); - TypeReader = Command.Module.InteractionService.GetTypeReader(ParameterType, services); + TypeReader = Command.Module.InteractionService.GetTypeReader(ParameterType, services); return this; } diff --git a/src/Discord.Net.Interactions/Entities/TypeReaderTarget.cs b/src/Discord.Net.Interactions/Entities/TypeReaderTarget.cs new file mode 100644 index 000000000..5556b19c2 --- /dev/null +++ b/src/Discord.Net.Interactions/Entities/TypeReaderTarget.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord.Interactions +{ + [Flags] + public enum TypeReaderTarget + { + CustomId = 1, + SelectMenu = 2 + } +} diff --git a/src/Discord.Net.Interactions/Extensions/EnumExtensions.cs b/src/Discord.Net.Interactions/Extensions/EnumExtensions.cs new file mode 100644 index 000000000..af6bfc85f --- /dev/null +++ b/src/Discord.Net.Interactions/Extensions/EnumExtensions.cs @@ -0,0 +1,24 @@ +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(TypeReader)}s."); + }; + } +} diff --git a/src/Discord.Net.Interactions/InteractionService.cs b/src/Discord.Net.Interactions/InteractionService.cs index 38fbb7549..08db2e8f6 100644 --- a/src/Discord.Net.Interactions/InteractionService.cs +++ b/src/Discord.Net.Interactions/InteractionService.cs @@ -67,7 +67,7 @@ namespace Discord.Interactions private readonly CommandMap _modalCommandMap; private readonly HashSet _moduleDefs; private readonly TypeMap _typeConverterMap; - private readonly TypeMap _typeReaderMap; + private readonly ConcurrentDictionary> _typeReaderMaps; private readonly ConcurrentDictionary _autocompleteHandlers = new(); private readonly ConcurrentDictionary _modalInfos = new(); private readonly SemaphoreSlim _lock; @@ -194,7 +194,7 @@ namespace Discord.Interactions [typeof(Nullable<>)] = typeof(NullableConverter<>), }); - _typeReaderMap = new TypeMap(this); + _typeReaderMaps = new ConcurrentDictionary>(); } /// @@ -805,24 +805,39 @@ namespace Discord.Interactions public void AddGenericTypeConverter(Type targetType, Type converterType) => _typeConverterMap.AddGeneric(targetType, converterType); - internal TypeReader GetTypeReader(Type type, IServiceProvider services = null) - => _typeReaderMap.Get(type, services); + internal IEnumerable GetTypeReader(Type type, TypeReaderTarget targets, IServiceProvider services = null) + { + var flattenedTargets = targets.GetFlags(); + + foreach (var target in flattenedTargets) + yield return _typeReaderMaps[target].Get(type, services); + } /// /// Add a concrete type . /// /// Primary target of the . /// The instance. - public void AddTypeReader(TypeReader reader) => - _typeReaderMap.AddConcrete(reader); + public void AddTypeReader(TypeReader reader, TypeReaderTarget targets) + { + var flattenedTargets = targets.GetFlags(); + + foreach (var target in flattenedTargets) + _typeReaderMaps[target].AddConcrete(reader); + } /// /// Add a concrete type . /// /// Primary target of the . /// The instance. - public void AddTypeReader(Type type, TypeReader converter) => - _typeReaderMap.AddConcrete(type, converter); + public void AddTypeReader(Type type, TypeReader reader, TypeReaderTarget targets) + { + var flattenedTargets = targets.GetFlags(); + + foreach (var target in flattenedTargets) + _typeReaderMaps[target].AddConcrete(type, reader); + } /// /// Add a generic type . @@ -830,19 +845,36 @@ namespace Discord.Interactions /// Generic Type constraint of the of the . /// Type of the . - public void AddGenericTypeReader(Type readerType) => - _typeReaderMap.AddGeneric(readerType); + public void AddGenericTypeReader(Type readerType, TypeReaderTarget targets) + { + var flattenedTargets = targets.GetFlags(); + + foreach (var target in flattenedTargets) + _typeReaderMaps[target].AddGeneric(readerType); + } /// /// Add a generic type . /// /// Generic Type constraint of the of the . /// Type of the . - public void AddGenericTypeReader(Type targetType, Type readerType) => - _typeConverterMap.AddGeneric(targetType, readerType); + public void AddGenericTypeReader(Type targetType, Type readerType, TypeReaderTarget targets) + { + var flattenedTargets = targets.GetFlags(); + + foreach(var target in flattenedTargets) + _typeReaderMaps[target].AddGeneric(targetType, readerType); + } - public string SerializeWithTypeReader(object obj, IServiceProvider services = null) => - _typeReaderMap.Get(typeof(T), services)?.Serialize(obj); + public string SerializeWithTypeReader(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); + } internal IAutocompleteHandler GetAutocompleteHandler(Type autocompleteHandlerType, IServiceProvider services = null) { diff --git a/src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs b/src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs index 4fb5218ff..8cdb17417 100644 --- a/src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/ArrayReader.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace Discord.Interactions { - internal class ArrayReader : TypeReader where T : IEnumerable + internal class ArrayReader : TypeReader where T : IEnumerable { private readonly TypeReader _baseReader; @@ -18,6 +18,10 @@ namespace Discord.Interactions interactionService.GetTypeReader(typeof) } + public override TypeReaderTarget[] TypeReaderTargets { get; } + + public override bool CanConvertTo(Type type) => throw new NotImplementedException(); + public override Task ReadAsync(IInteractionContext context, object input, IServiceProvider services) { if(input is IEnumerable enumerable) diff --git a/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs index db349ce58..62276c973 100644 --- a/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs +++ b/src/Discord.Net.Interactions/TypeReaders/TypeReader.cs @@ -9,7 +9,7 @@ namespace Discord.Interactions /// /// s are mainly used to parse message component values. For interfacing with Slash Command parameters use s instead. /// - public abstract class TypeReader : ITypeHandler + public abstract class TypeReader : ITypeHandler { /// /// Will be used to search for alternative TypeReaders whenever the Command Service encounters an unknown parameter type. @@ -25,21 +25,19 @@ namespace Discord.Interactions /// 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, T input, IServiceProvider services); + public abstract Task ReadAsync(IInteractionContext context, object input, IServiceProvider services); /// /// Will be used to manipulate the outgoing command option, before the command gets registered to Discord. /// - public virtual T Serialize(object value) => default; + public virtual string Serialize(object value) => null; } /// - public abstract class TypeReader : TypeReader + public abstract class TypeReader : TypeReader { /// public sealed override bool CanConvertTo(Type type) => typeof(T).IsAssignableFrom(type); } - - public abstract class TypeReader : TypeReader { } }