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:
Quin Lynch
2022-02-09 00:17:56 -04:00
committed by GitHub
parent 33efd8981d
commit c8f175e11a
80 changed files with 3502 additions and 25 deletions

View 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;
}
}

View File

@@ -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;