-
-
Notifications
You must be signed in to change notification settings - Fork 741
/
Copy pathInteractionUtility.cs
179 lines (156 loc) · 9.61 KB
/
InteractionUtility.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
using Discord.WebSocket;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Discord.Interactions
{
/// <summary>
/// Utility class containing helper methods for interacting with Discord Interactions.
/// </summary>
public static class InteractionUtility
{
/// <summary>
/// Wait for an Interaction event for a given amount of time as an asynchronous opration.
/// </summary>
/// <param name="client">Client that should be listened to for the <see cref="BaseSocketClient.InteractionCreated"/> event.</param>
/// <param name="timeout">Timeout duration for this operation.</param>
/// <param name="predicate">Delegate for cheking whether an Interaction meets the requirements.</param>
/// <param name="cancellationToken">Token for canceling the wait operation.</param>
/// <returns>
/// A Task representing the asyncronous waiting operation. If the user responded in the given amount of time, Task result contains the user response,
/// otherwise the Task result is <see langword="null"/>.
/// </returns>
public static async Task<SocketInteraction> WaitForInteractionAsync (BaseSocketClient client, TimeSpan timeout,
Predicate<SocketInteraction> predicate, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<SocketInteraction>();
var waitCancelSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
Task wait = Task.Delay(timeout, waitCancelSource.Token)
.ContinueWith((t) =>
{
if (!t.IsCanceled)
tcs.SetResult(null);
});
cancellationToken.Register(( ) => tcs.SetCanceled());
client.InteractionCreated += HandleInteraction;
var result = await tcs.Task.ConfigureAwait(false);
client.InteractionCreated -= HandleInteraction;
return result;
Task HandleInteraction (SocketInteraction interaction)
{
if (predicate(interaction))
{
waitCancelSource.Cancel();
tcs.SetResult(interaction);
}
return Task.CompletedTask;
}
}
/// <summary>
/// Wait for an Message Component Interaction event for a given amount of time as an asynchronous opration .
/// </summary>
/// <param name="client">Client that should be listened to for the <see cref="BaseSocketClient.InteractionCreated"/> event.</param>
/// <param name="fromMessage">The message that <see cref="BaseSocketClient.ButtonExecuted"/> or <see cref="BaseSocketClient.SelectMenuExecuted"/> should originate from.</param>
/// <param name="timeout">Timeout duration for this operation.</param>
/// <param name="cancellationToken">Token for canceling the wait operation.</param>
/// <returns>
/// A Task representing the asyncronous waiting operation with a <see cref="IDiscordInteraction"/> result,
/// the result is null if the process timed out before receiving a valid Interaction.
/// </returns>
public static async Task<SocketMessageComponent> WaitForMessageComponentAsync(BaseSocketClient client, IUserMessage fromMessage, TimeSpan timeout,
CancellationToken cancellationToken = default)
{
bool Predicate (SocketInteraction interaction) => interaction is SocketMessageComponent component &&
component.Message.Id == fromMessage.Id;
return await WaitForInteractionAsync(client, timeout, Predicate, cancellationToken) as SocketMessageComponent;
}
/// <summary>
/// Create a confirmation dialog and wait for user input asynchronously.
/// </summary>
/// <param name="client">Client that should be listened to for the <see cref="BaseSocketClient.InteractionCreated"/> event.</param>
/// <param name="channel">Send the confirmation prompt to this channel.</param>
/// <param name="timeout">Timeout duration of this operation.</param>
/// <param name="message">Optional custom prompt message.</param>
/// <param name="cancellationToken">Token for canceling the wait operation.</param>
/// <returns>
/// A Task representing the asyncronous waiting operation with a <see cref="bool"/> result,
/// the result is <see langword="false"/> if the user declined the prompt or didnt answer in time, <see langword="true"/> if the user confirmed the prompt.
/// </returns>
public static async Task<bool> ConfirmAsync (BaseSocketClient client, IMessageChannel channel, TimeSpan timeout, string message = null,
CancellationToken cancellationToken = default)
{
message ??= "Would you like to continue?";
var confirmId = $"confirm";
var declineId = $"decline";
var component = new ComponentBuilder()
.WithButton("Confirm", confirmId, ButtonStyle.Success)
.WithButton("Cancel", declineId, ButtonStyle.Danger)
.Build();
var prompt = await channel.SendMessageAsync(message, components: component).ConfigureAwait(false);
var response = await WaitForMessageComponentAsync(client, prompt, timeout, cancellationToken).ConfigureAwait(false);
await prompt.DeleteAsync().ConfigureAwait(false);
return response is not null && response.Data.CustomId == confirmId;
}
/// <summary>
/// Create a confirmation dialog and wait for user input asynchronously.
/// </summary>
/// <param name="interaction">Interaction to send the response/followup message to.</param>
/// <param name="timeout">Timeout duration of this operation.</param>
/// <param name="message">Optional custom prompt message.</param>
/// <param name="cancellationToken">Token for canceling the wait operation.</param>
/// <returns>
/// A Task representing the asyncronous waiting operation with a <see cref="bool"/> result,
/// the result is <see langword="false"/> if the user declined the prompt or didnt answer in time, <see langword="true"/> if the user confirmed the prompt.
/// </returns>
public static async Task<bool> ConfirmAsync(SocketInteraction interaction, TimeSpan timeout, string message = null, Action<MessageProperties> updateMessage = null,
CancellationToken cancellationToken = default)
{
message ??= "Would you like to continue?";
var confirmId = $"confirm";
var declineId = $"decline";
var component = new ComponentBuilder()
.WithButton("Confirm", confirmId, ButtonStyle.Success)
.WithButton("Cancel", declineId, ButtonStyle.Danger)
.Build();
IUserMessage prompt;
if (!interaction.HasResponded)
{
await interaction.RespondAsync(message, components: component, ephemeral: true);
prompt = await interaction.GetOriginalResponseAsync();
}
else
prompt = await interaction.FollowupAsync(message, components: component, ephemeral: true);
var response = await WaitForMessageComponentAsync(interaction.Discord, prompt, timeout, cancellationToken).ConfigureAwait(false);
if(updateMessage is not null)
await response.UpdateAsync(updateMessage);
return response is not null && response.Data.CustomId == confirmId;
}
/// <summary>
/// Responds to an interaction with a modal and asyncronously wait for the users response.
/// </summary>
/// <typeparam name="TModal">The type of <see cref="IModal"/> to respond with.</typeparam>
/// <param name="interaction">The interaction to respond to.</param>
/// <param name="timeout">Timeout duration of this operation.</param>
/// <param name="contextFactory">Delegate for creating <see cref="IInteractionContext"/>s to be passed on to the <see cref="ComponentTypeConverter"/>s.</param>
/// <param name="services">Service collection to be passed on to the <see cref="ComponentTypeConverter"/>s.</param>
/// <param name="cancellationToken">Token for canceling the wait operation.</param>
/// <returns>
/// A Task representing the asyncronous waiting operation with a <typeparamref name="TModal"/> result,
/// the result is <see langword="null"/>q if the process timed out before receiving a valid Interaction.
/// </returns>
public static async Task<TModal> SendModalAsync<TModal>(this SocketInteraction interaction, TimeSpan timeout,
Func<SocketModal, DiscordSocketClient, IInteractionContext> contextFactory, IServiceProvider services = null, CancellationToken cancellationToken = default)
where TModal : class, IModal
{
var customId = Guid.NewGuid().ToString();
await interaction.RespondWithModalAsync<TModal>(customId);
var response = await WaitForInteractionAsync(interaction.Discord, timeout, interaction =>
{
return interaction is SocketModal socketModal &&
socketModal.Data.CustomId == customId;
}, cancellationToken) as SocketModal;
var modal = await ModalUtils.CreateModalAsync<TModal>(contextFactory(response, response.Discord), services).ConfigureAwait(false);
return modal;
}
}
}