From 83dfa0cea517ffdef8b138d72000fde2f035180e Mon Sep 17 00:00:00 2001
From: Misha133 <61027276+Misha-133@users.noreply.github.com>
Date: Fri, 31 Mar 2023 14:15:19 +0300
Subject: [PATCH] [Feature] Add `Message` property and `UpdateAsync()` to
`IModalInteraction` (#2645)
* initial commit
* better (?) comments
* whoops, accidentally commited this
---
.../Interactions/Modals/IModalInteraction.cs | 22 +++++
.../MessageComponents/RestMessageComponent.cs | 4 +-
.../Entities/Interactions/Modals/RestModal.cs | 99 +++++++++++++++++++
.../SocketMessageComponent.cs | 6 +-
.../Interaction/Modals/SocketModal.cs | 26 +++++
5 files changed, 150 insertions(+), 7 deletions(-)
diff --git a/src/Discord.Net.Core/Entities/Interactions/Modals/IModalInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/Modals/IModalInteraction.cs
index 5ce15384..0d967768 100644
--- a/src/Discord.Net.Core/Entities/Interactions/Modals/IModalInteraction.cs
+++ b/src/Discord.Net.Core/Entities/Interactions/Modals/IModalInteraction.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Threading.Tasks;
+
namespace Discord
{
///
@@ -9,5 +12,24 @@ namespace Discord
/// Gets the data received with this interaction; contains the clicked button.
///
new IModalInteractionData Data { get; }
+
+ ///
+ /// Gets the message the modal originates from.
+ ///
+ ///
+ /// This property is only populated if the modal was created from a message component.
+ ///
+ IUserMessage Message { get; }
+
+ ///
+ /// Updates the message which this modal originates from with the type
+ ///
+ /// A delegate containing the properties to modify the message with.
+ /// The options to be used when sending the request.
+ /// A task that represents the asynchronous operation of updating the message.
+ ///
+ /// This method can be used only if the modal was created from a message component.
+ ///
+ Task UpdateAsync(Action func, RequestOptions options = null);
}
}
diff --git a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
index f93e97ad..7280c761 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/MessageComponents/RestMessageComponent.cs
@@ -20,9 +20,7 @@ namespace Discord.Rest
///
public new RestMessageComponentData Data { get; }
- ///
- /// Gets the message that contained the trigger for this interaction.
- ///
+ ///
public RestUserMessage Message { get; private set; }
private object _lock = new object();
diff --git a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs
index 8016d5be..524b70ad 100644
--- a/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs
+++ b/src/Discord.Net.Rest/Entities/Interactions/Modals/RestModal.cs
@@ -1,11 +1,13 @@
using Discord.Net.Rest;
using Discord.Rest;
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
+
using DataModel = Discord.API.ModalInteractionData;
using ModelBase = Discord.API.Interaction;
@@ -23,6 +25,11 @@ namespace Discord.Rest
? (DataModel)model.Data.Value
: null;
+ if (model.Message.IsSpecified && model.ChannelId.IsSpecified)
+ {
+ Message = RestUserMessage.Create(Discord, Channel, User, model.Message.Value);
+ }
+
Data = new RestModalData(dataModel, client, Guild);
}
@@ -395,8 +402,100 @@ namespace Discord.Rest
public override string RespondWithModal(Modal modal, RequestOptions requestOptions = null)
=> throw new NotSupportedException("Modal interactions cannot have modal responces!");
+ ///
public new RestModalData Data { get; set; }
+ ///
+ public RestUserMessage Message { get; private set; }
+
+ IUserMessage IModalInteraction.Message => Message;
+
IModalInteractionData IModalInteraction.Data => Data;
+
+ ///
+ public async Task UpdateAsync(Action func, RequestOptions options = null)
+ {
+ var args = new MessageProperties();
+ func(args);
+
+ if (!IsValidToken)
+ throw new InvalidOperationException("Interaction token is no longer valid");
+
+ if (!InteractionHelper.CanSendResponse(this))
+ throw new TimeoutException($"Cannot respond to an interaction after {InteractionHelper.ResponseTimeLimit} seconds!");
+
+ if (args.AllowedMentions.IsSpecified)
+ {
+ var allowedMentions = args.AllowedMentions.Value;
+ Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 role Ids are allowed.");
+ Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions), "A max of 100 user Ids are allowed.");
+ }
+
+ var embed = args.Embed;
+ var embeds = args.Embeds;
+
+ bool hasText = args.Content.IsSpecified ? !string.IsNullOrEmpty(args.Content.Value) : false;
+ bool hasEmbeds = embed.IsSpecified && embed.Value != null || embeds.IsSpecified && embeds.Value?.Length > 0;
+
+ if (!hasText && !hasEmbeds)
+ Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content));
+
+ var apiEmbeds = embed.IsSpecified || embeds.IsSpecified ? new List() : null;
+
+ if (embed.IsSpecified && embed.Value != null)
+ {
+ apiEmbeds.Add(embed.Value.ToModel());
+ }
+
+ if (embeds.IsSpecified && embeds.Value != null)
+ {
+ apiEmbeds.AddRange(embeds.Value.Select(x => x.ToModel()));
+ }
+
+ Preconditions.AtMost(apiEmbeds?.Count ?? 0, 10, nameof(args.Embeds), "A max of 10 embeds are allowed.");
+
+ // check that user flag and user Id list are exclusive, same with role flag and role Id list
+ if (args.AllowedMentions.IsSpecified && args.AllowedMentions.Value != null && args.AllowedMentions.Value.AllowedTypes.HasValue)
+ {
+ var allowedMentions = args.AllowedMentions.Value;
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users)
+ && allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0)
+ {
+ throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(args.AllowedMentions));
+ }
+
+ if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles)
+ && allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0)
+ {
+ throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(args.AllowedMentions));
+ }
+ }
+
+ var response = new API.InteractionResponse
+ {
+ Type = InteractionResponseType.UpdateMessage,
+ Data = new API.InteractionCallbackData
+ {
+ Content = args.Content,
+ AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value?.ToModel() : Optional.Unspecified,
+ Embeds = apiEmbeds?.ToArray() ?? Optional.Unspecified,
+ Components = args.Components.IsSpecified
+ ? args.Components.Value?.Components.Select(x => new API.ActionRowComponent(x)).ToArray() ?? Array.Empty()
+ : Optional.Unspecified,
+ Flags = args.Flags.IsSpecified ? args.Flags.Value ?? Optional.Unspecified : Optional.Unspecified
+ }
+ };
+
+ lock (_lock)
+ {
+ if (HasResponded)
+ {
+ throw new InvalidOperationException("Cannot respond, update, or defer twice to the same interaction");
+ }
+ }
+
+ await InteractionHelper.SendInteractionResponseAsync(Discord, response, this, Channel, options).ConfigureAwait(false);
+ HasResponded = true;
+ }
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
index 830a2dc8..8758b987 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponent.cs
@@ -19,10 +19,8 @@ namespace Discord.WebSocket
/// Gets the data received with this interaction, contains the button that was clicked.
///
public new SocketMessageComponentData Data { get; }
-
- ///
- /// Gets the message that contained the trigger for this interaction.
- ///
+
+ ///
public SocketUserMessage Message { get; private set; }
private object _lock = new object();
diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
index 38532e51..c9fed6a4 100644
--- a/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
+++ b/src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModal.cs
@@ -1,11 +1,13 @@
using Discord.Net.Rest;
using Discord.Rest;
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
+
using DataModel = Discord.API.ModalInteractionData;
using ModelBase = Discord.API.Interaction;
@@ -21,6 +23,11 @@ namespace Discord.WebSocket
///
public new SocketModalData Data { get; set; }
+ ///
+ public SocketUserMessage Message { get; private set; }
+
+ IUserMessage IModalInteraction.Message => Message;
+
internal SocketModal(DiscordSocketClient client, ModelBase model, ISocketMessageChannel channel, SocketUser user)
: base(client, model.Id, channel, user)
{
@@ -28,6 +35,24 @@ namespace Discord.WebSocket
? (DataModel)model.Data.Value
: null;
+ if (model.Message.IsSpecified)
+ {
+ SocketUser author = null;
+ if (Channel is SocketGuildChannel ch)
+ {
+ if (model.Message.Value.WebhookId.IsSpecified)
+ author = SocketWebhookUser.Create(ch.Guild, Discord.State, model.Message.Value.Author.Value, model.Message.Value.WebhookId.Value);
+ else if (model.Message.Value.Author.IsSpecified)
+ author = ch.Guild.GetUser(model.Message.Value.Author.Value.Id);
+ }
+ else if (model.Message.Value.Author.IsSpecified)
+ author = (Channel as SocketChannel)?.GetUser(model.Message.Value.Author.Value.Id);
+
+ author ??= Discord.State.GetOrAddUser(model.Message.Value.Author.Value.Id, _ => SocketGlobalUser.Create(Discord, Discord.State, model.Message.Value.Author.Value));
+
+ Message = SocketUserMessage.Create(Discord, Discord.State, author, Channel, model.Message.Value);
+ }
+
Data = new SocketModalData(dataModel, client, client.State, client.State.GetGuild(model.GuildId.GetValueOrDefault()), model.User.GetValueOrDefault());
}
@@ -174,6 +199,7 @@ namespace Discord.WebSocket
HasResponded = true;
}
+ ///
public async Task UpdateAsync(Action func, RequestOptions options = null)
{
var args = new MessageProperties();