Feature: Implement modals (#2087)
* Implement Modals (#428) * Socket Modal Support * fix shareded client support * Properly use `HasResponded` instead of `_hasResponded` * `ModalBuilder` and `TextInputBuilder` validation. * make orginisation more consistant. * Rest Modals. * Docs + add missing methods * fix message signatures and missing abstract members * modal changes * um????? * update modal docs * update docs - again for some reason * cleanup * fix message signatures * add modal commands support to interaction service * Fix _hasResponded * update to new unsupported standard. * Sending modals with Interaction service. * fix spelling in ComponentBuilder * sending IModals when responding to interactions * interaction service modals * fix rest modals * spelling and minor improvements. * improve interaction service modal proformance * use precompiled lambda for interaction service modals * respect user compiled lambda choice * changes to modals in the interaction service (more) * support compiled lambdas in modal properties. * modal interactions tweaks * fix inline doc * more modal docs * configure responce to faild modal component * init * solve runtime errors * solve build errors * add default value parsing * make modal info caching static * make ModalUtils static * add inline docs * fix build errors * code cleanup * Introduce Required and Label properties as seperate attributes. * replace internal dictionary of ModalInfo with a list * change input building logic of modals * update RespondWithModalAsync method * add initial value parameter back to ModalTextInput and fix optional modal field * add missing inline docs * dispose the reference modal instance after building * code cleanup on modalcommandbuilder * Update docs/guides/int_basics/message-components/text-input.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/message-components/text-input.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_framework/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_framework/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_framework/samples/intro/modal.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/IModalInteraction.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Interactions/Attributes/Commands/ModalInteractionAttribute.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Interactions/Attributes/Modals/RequiredInputAttribute.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Interactions/InteractionServiceConfig.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * update interaction service modal docs * implements ExitOnMissingmModalField config option and adds Type field to modal info * Add WithValue to text input builders * Fix rare NRE on component enumeration * Fix RequestOptions being required in some methods * Use 'OfType' instead of 'Where' * Remove android unsported warning * Change publicity of properties in IInputComponeontBuilder.cs Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Remove complex parameter ref Co-authored-by: CottageDwellingCat <80918250+CottageDwellingCat@users.noreply.github.com> Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
This commit is contained in:
51
src/Discord.Net.Interactions/Utilities/ModalUtils.cs
Normal file
51
src/Discord.Net.Interactions/Utilities/ModalUtils.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Discord.Interactions.Builders;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.Interactions
|
||||
{
|
||||
internal static class ModalUtils
|
||||
{
|
||||
private static ConcurrentDictionary<Type, ModalInfo> _modalInfos = new();
|
||||
|
||||
public static IReadOnlyCollection<ModalInfo> Modals => _modalInfos.Values.ToReadOnlyCollection();
|
||||
|
||||
public static ModalInfo GetOrAdd(Type type)
|
||||
{
|
||||
if (!typeof(IModal).IsAssignableFrom(type))
|
||||
throw new ArgumentException($"Must be an implementation of {nameof(IModal)}", nameof(type));
|
||||
|
||||
return _modalInfos.GetOrAdd(type, ModuleClassBuilder.BuildModalInfo(type));
|
||||
}
|
||||
|
||||
public static ModalInfo GetOrAdd<T>() where T : class, IModal
|
||||
=> GetOrAdd(typeof(T));
|
||||
|
||||
public static bool TryGet(Type type, out ModalInfo modalInfo)
|
||||
{
|
||||
if (!typeof(IModal).IsAssignableFrom(type))
|
||||
throw new ArgumentException($"Must be an implementation of {nameof(IModal)}", nameof(type));
|
||||
|
||||
return _modalInfos.TryGetValue(type, out modalInfo);
|
||||
}
|
||||
|
||||
public static bool TryGet<T>(out ModalInfo modalInfo) where T : class, IModal
|
||||
=> TryGet(typeof(T), out modalInfo);
|
||||
|
||||
public static bool TryRemove(Type type, out ModalInfo modalInfo)
|
||||
{
|
||||
if (!typeof(IModal).IsAssignableFrom(type))
|
||||
throw new ArgumentException($"Must be an implementation of {nameof(IModal)}", nameof(type));
|
||||
|
||||
return _modalInfos.TryRemove(type, out modalInfo);
|
||||
}
|
||||
|
||||
public static bool TryRemove<T>(out ModalInfo modalInfo) where T : class, IModal
|
||||
=> TryRemove(typeof(T), out modalInfo);
|
||||
|
||||
public static void Clear() => _modalInfos.Clear();
|
||||
|
||||
public static int Count() => _modalInfos.Count;
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,67 @@ namespace Discord.Interactions
|
||||
var parameters = constructor.GetParameters();
|
||||
var properties = GetProperties(typeInfo);
|
||||
|
||||
var lambda = CreateLambdaMemberInit(typeInfo, constructor);
|
||||
|
||||
return (services) =>
|
||||
{
|
||||
var args = new object[parameters.Length];
|
||||
var props = new object[properties.Length];
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
args[i] = GetMember(commandService, services, parameters[i].ParameterType, typeInfo);
|
||||
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
props[i] = GetMember(commandService, services, properties[i].PropertyType, typeInfo);
|
||||
|
||||
var instance = lambda(args, props);
|
||||
|
||||
return instance;
|
||||
};
|
||||
}
|
||||
|
||||
internal static Func<object[], T> CreateLambdaConstructorInvoker(TypeInfo typeInfo)
|
||||
{
|
||||
var constructor = GetConstructor(typeInfo);
|
||||
var parameters = constructor.GetParameters();
|
||||
|
||||
var argsExp = Expression.Parameter(typeof(object[]), "args");
|
||||
|
||||
var parameterExps = new Expression[parameters.Length];
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var indexExp = Expression.Constant(i);
|
||||
var accessExp = Expression.ArrayIndex(argsExp, indexExp);
|
||||
parameterExps[i] = Expression.Convert(accessExp, parameters[i].ParameterType);
|
||||
}
|
||||
|
||||
var newExp = Expression.New(constructor, parameterExps);
|
||||
|
||||
return Expression.Lambda<Func<object[], T>>(newExp, argsExp).Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a compiled lambda property setter.
|
||||
/// </summary>
|
||||
internal static Action<T, object> CreateLambdaPropertySetter(PropertyInfo propertyInfo)
|
||||
{
|
||||
var instanceParam = Expression.Parameter(typeof(T), "instance");
|
||||
var valueParam = Expression.Parameter(typeof(object), "value");
|
||||
|
||||
var prop = Expression.Property(instanceParam, propertyInfo);
|
||||
var assign = Expression.Assign(prop, Expression.Convert(valueParam, propertyInfo.PropertyType));
|
||||
|
||||
return Expression.Lambda<Action<T, object>>(assign, instanceParam, valueParam).Compile();
|
||||
}
|
||||
|
||||
internal static Func<object[], object[], T> CreateLambdaMemberInit(TypeInfo typeInfo, ConstructorInfo constructor, Predicate<PropertyInfo> propertySelect = null)
|
||||
{
|
||||
propertySelect ??= x => true;
|
||||
|
||||
var parameters = constructor.GetParameters();
|
||||
var properties = GetProperties(typeInfo).Where(x => propertySelect(x)).ToArray();
|
||||
|
||||
var argsExp = Expression.Parameter(typeof(object[]), "args");
|
||||
var propsExp = Expression.Parameter(typeof(object[]), "props");
|
||||
|
||||
@@ -137,17 +198,8 @@ namespace Discord.Interactions
|
||||
var memberInit = Expression.MemberInit(newExp, memberExps);
|
||||
var lambda = Expression.Lambda<Func<object[], object[], T>>(memberInit, argsExp, propsExp).Compile();
|
||||
|
||||
return (services) =>
|
||||
return (args, props) =>
|
||||
{
|
||||
var args = new object[parameters.Length];
|
||||
var props = new object[properties.Length];
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
args[i] = GetMember(commandService, services, parameters[i].ParameterType, typeInfo);
|
||||
|
||||
for (int i = 0; i < properties.Length; i++)
|
||||
props[i] = GetMember(commandService, services, properties[i].PropertyType, typeInfo);
|
||||
|
||||
var instance = lambda(args, props);
|
||||
|
||||
return instance;
|
||||
|
||||
Reference in New Issue
Block a user