diff --git a/src/Discord.Net.Interactions/Utilities/InteractionUtility.cs b/src/Discord.Net.Interactions/Utilities/InteractionUtility.cs index 9751b613d..4c1a14268 100644 --- a/src/Discord.Net.Interactions/Utilities/InteractionUtility.cs +++ b/src/Discord.Net.Interactions/Utilities/InteractionUtility.cs @@ -65,13 +65,13 @@ Task HandleInteraction (SocketInteraction interaction) /// A Task representing the asyncronous waiting operation with a result, /// the result is null if the process timed out before receiving a valid Interaction. /// - public static Task WaitForMessageComponentAsync(BaseSocketClient client, IUserMessage fromMessage, TimeSpan timeout, + public static async Task WaitForMessageComponentAsync(BaseSocketClient client, IUserMessage fromMessage, TimeSpan timeout, CancellationToken cancellationToken = default) { bool Predicate (SocketInteraction interaction) => interaction is SocketMessageComponent component && component.Message.Id == fromMessage.Id; - return WaitForInteractionAsync(client, timeout, Predicate, cancellationToken); + return await WaitForInteractionAsync(client, timeout, Predicate, cancellationToken) as SocketMessageComponent; } /// @@ -100,14 +100,80 @@ public static async Task ConfirmAsync (BaseSocketClient client, IMessageCh var prompt = await channel.SendMessageAsync(message, components: component).ConfigureAwait(false); - var response = await WaitForMessageComponentAsync(client, prompt, timeout, cancellationToken).ConfigureAwait(false) as SocketMessageComponent; - + var response = await WaitForMessageComponentAsync(client, prompt, timeout, cancellationToken).ConfigureAwait(false); await prompt.DeleteAsync().ConfigureAwait(false); - if (response != null && response.Data.CustomId == confirmId) - return true; + return response is not null && response.Data.CustomId == confirmId; + } + + /// + /// Create a confirmation dialog and wait for user input asynchronously. + /// + /// Interaction to send the response/followup message to. + /// Timeout duration of this operation. + /// Optional custom prompt message. + /// Token for canceling the wait operation. + /// + /// A Task representing the asyncronous waiting operation with a result, + /// the result is if the user declined the prompt or didnt answer in time, if the user confirmed the prompt. + /// + public static async Task ConfirmAsync(SocketInteraction interaction, TimeSpan timeout, string message = null, Action 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 - return false; + 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; + } + + /// + /// Responds to an interaction with a modal and asyncronously wait for the users response. + /// + /// The type of to respond with. + /// The interaction to respond to. + /// Timeout duration of this operation. + /// Delegate for creating s to be passed on to the s. + /// Service collection to be passed on to the s. + /// Token for canceling the wait operation. + /// + /// A Task representing the asyncronous waiting operation with a result, + /// the result is q if the process timed out before receiving a valid Interaction. + /// + public static async Task SendModalAsync(this SocketInteraction interaction, TimeSpan timeout, + Func contextFactory, IServiceProvider services = null, CancellationToken cancellationToken = default) + where TModal : class, IModal + { + var customId = Guid.NewGuid().ToString(); + await interaction.RespondWithModalAsync(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(contextFactory(response, response.Discord), services).ConfigureAwait(false); + return modal; } } } diff --git a/src/Discord.Net.Interactions/Utilities/ModalUtils.cs b/src/Discord.Net.Interactions/Utilities/ModalUtils.cs index e2d028e1f..10bbc0df0 100644 --- a/src/Discord.Net.Interactions/Utilities/ModalUtils.cs +++ b/src/Discord.Net.Interactions/Utilities/ModalUtils.cs @@ -2,15 +2,31 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Threading.Tasks; namespace Discord.Interactions { - internal static class ModalUtils + /// + /// General utility class regarding implementations. + /// + public static class ModalUtils { private static readonly ConcurrentDictionary _modalInfos = new(); + /// + /// Get a collection of built object of cached implementatios. + /// public static IReadOnlyCollection Modals => _modalInfos.Values.ToReadOnlyCollection(); + /// + /// Get or add a to the shared cache. + /// + /// Type of the implementation. + /// Instance of in use. + /// + /// The built instance of . + /// + /// Thrown when isn't an implementation of . public static ModalInfo GetOrAdd(Type type, InteractionService interactionService) { if (!typeof(IModal).IsAssignableFrom(type)) @@ -19,9 +35,26 @@ public static ModalInfo GetOrAdd(Type type, InteractionService interactionServic return _modalInfos.GetOrAdd(type, ModuleClassBuilder.BuildModalInfo(type, interactionService)); } + /// + /// Get or add a to the shared cache. + /// + /// Type of the implementation. + /// Instance of in use. + /// + /// The built instance of . + /// public static ModalInfo GetOrAdd(InteractionService interactionService) where T : class, IModal => GetOrAdd(typeof(T), interactionService); + /// + /// Gets the associated with an implementation. + /// + /// Type of the implementation. + /// The built instance of . + /// + /// A bool representing whether the fetch operation was successful. + /// + /// Thrown when isn't an implementation of . public static bool TryGet(Type type, out ModalInfo modalInfo) { if (!typeof(IModal).IsAssignableFrom(type)) @@ -30,9 +63,26 @@ public static bool TryGet(Type type, out ModalInfo modalInfo) return _modalInfos.TryGetValue(type, out modalInfo); } + /// + /// Gets the associated with an implementation. + /// + /// Type of the implementation. + /// The built instance of . + /// + /// A bool representing whether the fetch operation was successful. + /// public static bool TryGet(out ModalInfo modalInfo) where T : class, IModal => TryGet(typeof(T), out modalInfo); + /// + /// Remove the entry from the cache associated with an implementation. + /// + /// Type of the implementation. + /// The instance of the removed entry. + /// + /// A bool representing whether the removal operation was successful. + /// + /// Thrown when isn't an implementation of . public static bool TryRemove(Type type, out ModalInfo modalInfo) { if (!typeof(IModal).IsAssignableFrom(type)) @@ -41,11 +91,49 @@ public static bool TryRemove(Type type, out ModalInfo modalInfo) return _modalInfos.TryRemove(type, out modalInfo); } + /// + /// Remove the entry from the cache associated with an implementation. + /// + /// Type of the implementation. + /// The instance of the removed entry. + /// + /// A bool representing whether the removal operation was successful. + /// public static bool TryRemove(out ModalInfo modalInfo) where T : class, IModal => TryRemove(typeof(T), out modalInfo); + /// + /// Initialize an implementation from a based . + /// + /// Type of the implementation. + /// Context of the . + /// Service provider to be passed on to the s. + /// + /// A Task representing the asyncronous initialization operation with a result, + /// the result is if the process was unsuccessful. + /// + public static async Task CreateModalAsync(IInteractionContext context, IServiceProvider services = null) + where TModal : class, IModal + { + if (!TryGet(out var modalInfo)) + return null; + + var result = await modalInfo.CreateModalAsync(context, services, true).ConfigureAwait(false); + + if (!result.IsSuccess || result is not ParseResult parseResult) + return null; + + return parseResult.Value as TModal; + } + + /// + /// Clears the cache. + /// public static void Clear() => _modalInfos.Clear(); - public static int Count() => _modalInfos.Count; + /// + /// Gets the count entries in the cache. + /// + public static int Count => _modalInfos.Count; } } diff --git a/src/Discord.Net.WebSocket/AssemblyInfo.cs b/src/Discord.Net.WebSocket/AssemblyInfo.cs index 442ec7dd8..2f895af38 100644 --- a/src/Discord.Net.WebSocket/AssemblyInfo.cs +++ b/src/Discord.Net.WebSocket/AssemblyInfo.cs @@ -3,3 +3,4 @@ [assembly: InternalsVisibleTo("Discord.Net.Relay")] [assembly: InternalsVisibleTo("Discord.Net.Tests")] [assembly: InternalsVisibleTo("Discord.Net.Tests.Unit")] +[assembly: InternalsVisibleTo("Discord.Net.Interactions")]