Browse Source

Merge 5da33aa22f into 673b02dd36

pull/2241/merge
Cenk Ergen GitHub 2 years ago
parent
commit
4dfe154e26
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 164 additions and 9 deletions
  1. +73
    -7
      src/Discord.Net.Interactions/Utilities/InteractionUtility.cs
  2. +90
    -2
      src/Discord.Net.Interactions/Utilities/ModalUtils.cs
  3. +1
    -0
      src/Discord.Net.WebSocket/AssemblyInfo.cs

+ 73
- 7
src/Discord.Net.Interactions/Utilities/InteractionUtility.cs View File

@@ -65,13 +65,13 @@ namespace Discord.Interactions
/// A Task representing the asyncronous waiting operation with a <see cref="IDiscordInteraction"/> result, /// 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. /// the result is null if the process timed out before receiving a valid Interaction.
/// </returns> /// </returns>
public static Task<SocketInteraction> WaitForMessageComponentAsync(BaseSocketClient client, IUserMessage fromMessage, TimeSpan timeout,
public static async Task<SocketMessageComponent> WaitForMessageComponentAsync(BaseSocketClient client, IUserMessage fromMessage, TimeSpan timeout,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
bool Predicate(SocketInteraction interaction) => interaction is SocketMessageComponent component && bool Predicate(SocketInteraction interaction) => interaction is SocketMessageComponent component &&
component.Message.Id == fromMessage.Id; component.Message.Id == fromMessage.Id;


return WaitForInteractionAsync(client, timeout, Predicate, cancellationToken);
return await WaitForInteractionAsync(client, timeout, Predicate, cancellationToken) as SocketMessageComponent;
} }


/// <summary> /// <summary>
@@ -100,14 +100,80 @@ namespace Discord.Interactions


var prompt = await channel.SendMessageAsync(message, components: component).ConfigureAwait(false); 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); await prompt.DeleteAsync().ConfigureAwait(false);


if (response != null && response.Data.CustomId == confirmId)
return true;
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 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;
}

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

+ 90
- 2
src/Discord.Net.Interactions/Utilities/ModalUtils.cs View File

@@ -2,15 +2,31 @@ using Discord.Interactions.Builders;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;


namespace Discord.Interactions namespace Discord.Interactions
{ {
internal static class ModalUtils
/// <summary>
/// General utility class regarding <see cref="IModal"/> implementations.
/// </summary>
public static class ModalUtils
{ {
private static readonly ConcurrentDictionary<Type, ModalInfo> _modalInfos = new(); private static readonly ConcurrentDictionary<Type, ModalInfo> _modalInfos = new();


/// <summary>
/// Get a collection of built <see cref="ModalInfo"/> object of cached <see cref="IModal"/> implementatios.
/// </summary>
public static IReadOnlyCollection<ModalInfo> Modals => _modalInfos.Values.ToReadOnlyCollection(); public static IReadOnlyCollection<ModalInfo> Modals => _modalInfos.Values.ToReadOnlyCollection();


/// <summary>
/// Get or add a <see cref="ModalInfo"/> to the shared cache.
/// </summary>
/// <param name="type">Type of the <see cref="IModal"/> implementation.</param>
/// <param name="interactionService">Instance of <see cref="InteractionService"/> in use.</param>
/// <returns>
/// The built instance of <see cref="ModalInfo"/>.
/// </returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="type"/> isn't an implementation of <see cref="IModal"/>.</exception>
public static ModalInfo GetOrAdd(Type type, InteractionService interactionService) public static ModalInfo GetOrAdd(Type type, InteractionService interactionService)
{ {
if (!typeof(IModal).IsAssignableFrom(type)) if (!typeof(IModal).IsAssignableFrom(type))
@@ -19,9 +35,26 @@ namespace Discord.Interactions
return _modalInfos.GetOrAdd(type, ModuleClassBuilder.BuildModalInfo(type, interactionService)); return _modalInfos.GetOrAdd(type, ModuleClassBuilder.BuildModalInfo(type, interactionService));
} }


/// <summary>
/// Get or add a <see cref="ModalInfo"/> to the shared cache.
/// </summary>
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
/// <param name="interactionService">Instance of <see cref="InteractionService"/> in use.</param>
/// <returns>
/// The built instance of <see cref="ModalInfo"/>.
/// </returns>
public static ModalInfo GetOrAdd<T>(InteractionService interactionService) where T : class, IModal public static ModalInfo GetOrAdd<T>(InteractionService interactionService) where T : class, IModal
=> GetOrAdd(typeof(T), interactionService); => GetOrAdd(typeof(T), interactionService);


/// <summary>
/// Gets the <see cref="ModalInfo"/> associated with an <see cref="IModal"/> implementation.
/// </summary>
/// <param name="type">Type of the <see cref="IModal"/> implementation.</param>
/// <param name="modalInfo">The built instance of <see cref="ModalInfo"/>.</param>
/// <returns>
/// A bool representing whether the fetch operation was successful.
/// </returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="type"/> isn't an implementation of <see cref="IModal"/>.</exception>
public static bool TryGet(Type type, out ModalInfo modalInfo) public static bool TryGet(Type type, out ModalInfo modalInfo)
{ {
if (!typeof(IModal).IsAssignableFrom(type)) if (!typeof(IModal).IsAssignableFrom(type))
@@ -30,9 +63,26 @@ namespace Discord.Interactions
return _modalInfos.TryGetValue(type, out modalInfo); return _modalInfos.TryGetValue(type, out modalInfo);
} }


/// <summary>
/// Gets the <see cref="ModalInfo"/> associated with an <see cref="IModal"/> implementation.
/// </summary>
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
/// <param name="modalInfo">The built instance of <see cref="ModalInfo"/>.</param>
/// <returns>
/// A bool representing whether the fetch operation was successful.
/// </returns>
public static bool TryGet<T>(out ModalInfo modalInfo) where T : class, IModal public static bool TryGet<T>(out ModalInfo modalInfo) where T : class, IModal
=> TryGet(typeof(T), out modalInfo); => TryGet(typeof(T), out modalInfo);


/// <summary>
/// Remove the <see cref="ModalInfo"/> entry from the cache associated with an <see cref="IModal"/> implementation.
/// </summary>
/// <param name="type">Type of the <see cref="IModal"/> implementation.</param>
/// <param name="modalInfo">The instance of the removed <see cref="ModalInfo"/> entry.</param>
/// <returns>
/// A bool representing whether the removal operation was successful.
/// </returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="type"/> isn't an implementation of <see cref="IModal"/>.</exception>
public static bool TryRemove(Type type, out ModalInfo modalInfo) public static bool TryRemove(Type type, out ModalInfo modalInfo)
{ {
if (!typeof(IModal).IsAssignableFrom(type)) if (!typeof(IModal).IsAssignableFrom(type))
@@ -41,11 +91,49 @@ namespace Discord.Interactions
return _modalInfos.TryRemove(type, out modalInfo); return _modalInfos.TryRemove(type, out modalInfo);
} }


/// <summary>
/// Remove the <see cref="ModalInfo"/> entry from the cache associated with an <see cref="IModal"/> implementation.
/// </summary>
/// <typeparam name="T">Type of the <see cref="IModal"/> implementation.</typeparam>
/// <param name="modalInfo">The instance of the removed <see cref="ModalInfo"/> entry.</param>
/// <returns>
/// A bool representing whether the removal operation was successful.
/// </returns>
public static bool TryRemove<T>(out ModalInfo modalInfo) where T : class, IModal public static bool TryRemove<T>(out ModalInfo modalInfo) where T : class, IModal
=> TryRemove(typeof(T), out modalInfo); => TryRemove(typeof(T), out modalInfo);


/// <summary>
/// Initialize an <see cref="IModal"/> implementation from a <see cref="IModalInteraction"/> based <see cref="IInteractionContext"/>.
/// </summary>
/// <typeparam name="TModal">Type of the <see cref="IModal"/> implementation.</typeparam>
/// <param name="context">Context of the <see cref="IModalInteraction"/>.</param>
/// <param name="services">Service provider to be passed on to the <see cref="ComponentTypeConverter"/>s.</param>
/// <returns>
/// A Task representing the asyncronous <see cref="IModal"/> initialization operation with a <typeparamref name="TModal"/> result,
/// the result is <see langword="null"/> if the process was unsuccessful.
/// </returns>
public static async Task<TModal> CreateModalAsync<TModal>(IInteractionContext context, IServiceProvider services = null)
where TModal : class, IModal
{
if (!TryGet<TModal>(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;
}

/// <summary>
/// Clears the <see cref="ModalInfo"/> cache.
/// </summary>
public static void Clear() => _modalInfos.Clear(); public static void Clear() => _modalInfos.Clear();


public static int Count() => _modalInfos.Count;
/// <summary>
/// Gets the count <see cref="ModalInfo"/> entries in the cache.
/// </summary>
public static int Count => _modalInfos.Count;
} }
} }

+ 1
- 0
src/Discord.Net.WebSocket/AssemblyInfo.cs View File

@@ -3,3 +3,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Discord.Net.Relay")] [assembly: InternalsVisibleTo("Discord.Net.Relay")]
[assembly: InternalsVisibleTo("Discord.Net.Tests")] [assembly: InternalsVisibleTo("Discord.Net.Tests")]
[assembly: InternalsVisibleTo("Discord.Net.Tests.Unit")] [assembly: InternalsVisibleTo("Discord.Net.Tests.Unit")]
[assembly: InternalsVisibleTo("Discord.Net.Interactions")]

Loading…
Cancel
Save