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();