Skip to content

Commit 1e27c99

Browse files
authored
Modal Components v2 Single-select Typeconverters, Rest, and Patches (#3209)
* implement modal v2 components to rest interaction extensions * update rest extensions inline docs * rename respondwithmodal to respondwithmodalasync * add type converters for single user, role, channel, mentionable selects and file-uploads * update rest interaction module base with new async methods * code cleanup and bug fixes in default snowflake modal typeconverter * add default snowflake typeconverters to interaction service * fix nre in respondwithmodal extension method
1 parent ad8182f commit 1e27c99

File tree

6 files changed

+321
-53
lines changed

6 files changed

+321
-53
lines changed

src/Discord.Net.Interactions/Extensions/IDiscordInteractionExtensions.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,16 @@ public static Task RespondWithModalAsync<T>(this IDiscordInteraction interaction
6565
if (!ModalUtils.TryGet<T>(out var modalInfo))
6666
throw new ArgumentException($"{typeof(T).FullName} isn't referenced by any registered Modal Interaction Command and doesn't have a cached {typeof(ModalInfo)}");
6767

68-
return SendModalResponseAsync<T>(interaction, customId, modalInfo, modal, options, modifyModal);
68+
return SendModalResponseAsync(interaction, customId, modalInfo, modal, options, modifyModal);
6969
}
7070

71-
private static async Task SendModalResponseAsync<T>(IDiscordInteraction interaction, string customId, ModalInfo modalInfo, T modalInstance = null, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
71+
public static async Task<Modal> ToModalAsync<T>(this IDiscordInteraction interaction, string customId, ModalInfo modalInfo, T modalInstance = null, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
7272
where T : class, IModal
7373
{
7474
if (!modalInfo.Type.IsAssignableFrom(typeof(T)))
7575
throw new ArgumentException($"{modalInfo.Type.FullName} isn't assignable from {typeof(T).FullName}.");
7676

77-
var builder = new ModalBuilder(modalInstance.Title, customId);
77+
var builder = new ModalBuilder(modalInstance?.Title ?? modalInfo.Title, customId);
7878

7979
foreach (var input in modalInfo.Components)
8080
switch (input)
@@ -134,7 +134,7 @@ private static async Task SendModalResponseAsync<T>(IDiscordInteraction interact
134134
break;
135135
case TextDisplayComponentInfo textDisplayComponent:
136136
{
137-
var content = textDisplayComponent.Getter(modalInstance).ToString() ?? textDisplayComponent.Content;
137+
var content = modalInstance is not null ? textDisplayComponent.Getter(modalInstance).ToString() : (textDisplayComponent.DefaultValue as string) ?? textDisplayComponent.Content;
138138
var componentBuilder = new TextDisplayBuilder(content);
139139
builder.AddTextDisplay(componentBuilder);
140140
}
@@ -145,7 +145,15 @@ private static async Task SendModalResponseAsync<T>(IDiscordInteraction interact
145145

146146
modifyModal?.Invoke(builder);
147147

148-
await interaction.RespondWithModalAsync(builder.Build(), options);
148+
return builder.Build();
149+
}
150+
151+
private static async Task SendModalResponseAsync<T>(IDiscordInteraction interaction, string customId, ModalInfo modalInfo, T modalInstance = null, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
152+
where T : class, IModal
153+
{
154+
var modal = await interaction.ToModalAsync(customId, modalInfo, modalInstance, options, modifyModal);
155+
156+
await interaction.RespondWithModalAsync(modal, options);
149157
}
150158
}
151159
}
Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Discord.Interactions;
22
using System;
3+
using System.Threading.Tasks;
34

45
namespace Discord.Rest
56
{
@@ -11,14 +12,16 @@ public static class RestExtensions
1112
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
1213
/// <param name="interaction">The interaction to respond to.</param>
1314
/// <param name="options">The request options for this <see langword="async"/> request.</param>
14-
/// <returns>Serialized payload to be used to create a HTTP response.</returns>
15-
public static string RespondWithModal<T>(this RestInteraction interaction, string customId, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
15+
/// <returns>
16+
/// Task representing the asynchronous modal creation operation. The task result contains the serialized payload to be used to create a HTTP response.
17+
/// </returns>
18+
public static async Task<string> RespondWithModalAsync<T>(this RestInteraction interaction, string customId, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
1619
where T : class, IModal
1720
{
1821
if (!ModalUtils.TryGet<T>(out var modalInfo))
1922
throw new ArgumentException($"{typeof(T).FullName} isn't referenced by any registered Modal Interaction Command and doesn't have a cached {typeof(ModalInfo)}");
2023

21-
var modal = modalInfo.ToModal(customId, modifyModal);
24+
var modal = await interaction.ToModalAsync<T>(customId, modalInfo, null, options, modifyModal);
2225
return interaction.RespondWithModal(modal, options);
2326
}
2427

@@ -27,35 +30,20 @@ public static string RespondWithModal<T>(this RestInteraction interaction, strin
2730
/// </summary>
2831
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
2932
/// <param name="interaction">The interaction to respond to.</param>
30-
/// <param name="modal">The <see cref="IModal"/> instance to get field values from.</param>
33+
/// <param name="modalInstance">The <see cref="IModal"/> instance to get field values from.</param>
3134
/// <param name="options">The request options for this <see langword="async"/> request.</param>
3235
/// <param name="modifyModal">Delegate that can be used to modify the modal.</param>
33-
/// <returns>Serialized payload to be used to create a HTTP response.</returns>
34-
public static string RespondWithModal<T>(this RestInteraction interaction, string customId, T modal, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
36+
/// <returns>
37+
/// Task representing the asynchronous modal creation operation. The task result contains the serialized payload to be used to create a HTTP response.
38+
/// </returns>
39+
public static async Task<string> RespondWithModalAsync<T>(this RestInteraction interaction, string customId, T modalInstance, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
3540
where T : class, IModal
3641
{
3742
if (!ModalUtils.TryGet<T>(out var modalInfo))
3843
throw new ArgumentException($"{typeof(T).FullName} isn't referenced by any registered Modal Interaction Command and doesn't have a cached {typeof(ModalInfo)}");
3944

40-
var builder = new ModalBuilder(modal.Title, customId);
41-
42-
foreach (var input in modalInfo.Components)
43-
switch (input)
44-
{
45-
case TextInputComponentInfo textComponent:
46-
{
47-
builder.AddTextInput(textComponent.Label, textComponent.CustomId, textComponent.Style, textComponent.Placeholder, textComponent.IsRequired ? textComponent.MinLength : null,
48-
textComponent.MaxLength, textComponent.IsRequired, textComponent.Getter(modal) as string);
49-
}
50-
break;
51-
default:
52-
throw new InvalidOperationException($"{input.GetType().FullName} isn't a valid component info class");
53-
}
54-
55-
if (modifyModal is not null)
56-
modifyModal(builder);
57-
58-
return interaction.RespondWithModal(builder.Build(), options);
45+
var modal = await interaction.ToModalAsync<T>(customId, modalInfo, null, options, modifyModal);
46+
return interaction.RespondWithModal(modal, options);
5947
}
6048
}
6149
}

src/Discord.Net.Interactions/InteractionService.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,12 @@ private InteractionService(Func<DiscordRestClient> getRestClient, InteractionSer
236236
[typeof(IConvertible)] = typeof(DefaultValueModalComponentConverter<>),
237237
[typeof(Enum)] = typeof(EnumModalComponentConverter<>),
238238
[typeof(Nullable<>)] = typeof(NullableComponentConverter<>),
239-
[typeof(Array)] = typeof(DefaultArrayModalComponentConverter<>)
239+
[typeof(Array)] = typeof(DefaultArrayModalComponentConverter<>),
240+
[typeof(IChannel)] = typeof(DefaultChannelModalComponentConverter<>),
241+
[typeof(IUser)] = typeof(DefaultUserModalComponentConverter<>),
242+
[typeof(IRole)] = typeof(DefaultRoleModalComponentConverter<>),
243+
[typeof(IMentionable)] = typeof(DefaultMentionableModalComponentConverter<>),
244+
[typeof(IAttachment)] = typeof(DefaultAttachmentModalComponentConverter<>)
240245
});
241246
}
242247

src/Discord.Net.Interactions/RestInteractionModuleBase.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ protected override Task RespondWithModalAsync(Modal modal, RequestOptions option
7171
/// </returns>
7272
/// <exception cref="InvalidOperationException">Thrown if the interaction isn't a type of <see cref="RestInteraction"/>.</exception>
7373
protected override async Task RespondWithModalAsync<TModal>(string customId, TModal modal, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
74-
=> await HandleInteractionAsync(x => x.RespondWithModal(customId, modal, options, modifyModal));
74+
=> await HandleInteractionAsync(x => RestExtensions.RespondWithModalAsync(x, customId, modal, options, modifyModal));
7575

7676
/// <summary>
7777
/// Responds to the interaction with a modal.
@@ -85,7 +85,7 @@ protected override async Task RespondWithModalAsync<TModal>(string customId, TMo
8585
/// </returns>
8686
/// <exception cref="InvalidOperationException">Thrown if the interaction isn't a type of <see cref="RestInteraction"/>.</exception>
8787
protected override Task RespondWithModalAsync<TModal>(string customId, RequestOptions options = null, Action<ModalBuilder> modifyModal = null)
88-
=> HandleInteractionAsync(x => x.RespondWithModal<TModal>(customId, options, modifyModal));
88+
=> HandleInteractionAsync(x => RestExtensions.RespondWithModalAsync<TModal>(x, customId, options, modifyModal));
8989

9090
private Task HandleInteractionAsync(Func<RestInteraction, string> action)
9191
{
@@ -99,5 +99,18 @@ private Task HandleInteractionAsync(Func<RestInteraction, string> action)
9999
else
100100
return InteractionService._restResponseCallback(Context, payload);
101101
}
102+
103+
private async Task HandleInteractionAsync(Func<RestInteraction, Task<string>> action)
104+
{
105+
if (Context.Interaction is not RestInteraction restInteraction)
106+
throw new InvalidOperationException($"Interaction must be a type of {nameof(RestInteraction)} in order to execute this method.");
107+
108+
var payload = await action(restInteraction);
109+
110+
if (Context is IRestInteractionContext restContext && restContext.InteractionResponseCallback != null)
111+
await restContext.InteractionResponseCallback.Invoke(payload);
112+
else
113+
await InteractionService._restResponseCallback(Context, payload);
114+
}
102115
}
103116
}

0 commit comments

Comments
 (0)