From c4d90cd6f1dd2b59304aaf93bce2dc53ac625bb4 Mon Sep 17 00:00:00 2001 From: Mihail Gribkov <61027276+Misha-133@users.noreply.github.com> Date: Sat, 31 Aug 2024 14:29:20 +0300 Subject: [PATCH] [Feature] Message Forwards (#2918) * code * no guild * iTS ALIVE (THANKS aDVAITH) --- .../Entities/Messages/IUserMessage.cs | 5 +++++ .../Entities/Messages/MessageReference.cs | 9 ++++++++- .../Entities/Messages/MessageReferenceType.cs | 17 +++++++++++++++++ .../Entities/Messages/MessageSnapshot.cs | 17 +++++++++++++++++ src/Discord.Net.Core/Utils/Preconditions.cs | 5 ++++- src/Discord.Net.Rest/API/Common/Message.cs | 3 +++ .../API/Common/MessageReference.cs | 3 +++ .../API/Common/MessageSnapshot.cs | 9 +++++++++ .../Entities/Channels/ChannelHelper.cs | 4 ++-- .../Entities/Messages/RestMessage.cs | 3 ++- .../Entities/Messages/RestUserMessage.cs | 11 +++++++++++ .../Extensions/EntityExtensions.cs | 3 ++- .../Entities/Messages/SocketMessage.cs | 3 ++- .../Entities/Messages/SocketUserMessage.cs | 11 +++++++++++ 14 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/MessageReferenceType.cs create mode 100644 src/Discord.Net.Core/Entities/Messages/MessageSnapshot.cs create mode 100644 src/Discord.Net.Rest/API/Common/MessageSnapshot.cs diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index 6bd764f9..e8bcc6ba 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -30,6 +30,11 @@ namespace Discord /// IMessageInteractionMetadata InteractionMetadata { get; } + /// + /// Gets a collection of partial messages that were forwarded with this message. + /// + IReadOnlyCollection ForwardedMessages { get; } + /// /// Gets the poll sent with this message. /// diff --git a/src/Discord.Net.Core/Entities/Messages/MessageReference.cs b/src/Discord.Net.Core/Entities/Messages/MessageReference.cs index 7fdc448a..032879a0 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageReference.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageReference.cs @@ -33,6 +33,11 @@ namespace Discord /// public Optional FailIfNotExists { get; internal set; } + /// + /// Gets the type of message reference. + /// + public Optional ReferenceType { get; internal set; } + /// /// Initializes a new instance of the class. /// @@ -48,12 +53,14 @@ namespace Discord /// /// Whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message. Defaults to true. /// - public MessageReference(ulong? messageId = null, ulong? channelId = null, ulong? guildId = null, bool? failIfNotExists = null) + public MessageReference(ulong? messageId = null, ulong? channelId = null, ulong? guildId = null, bool? failIfNotExists = null, + MessageReferenceType referenceType = MessageReferenceType.Default) { MessageId = messageId ?? Optional.Create(); InternalChannelId = channelId ?? Optional.Create(); GuildId = guildId ?? Optional.Create(); FailIfNotExists = failIfNotExists ?? Optional.Create(); + ReferenceType = referenceType; } private string DebuggerDisplay diff --git a/src/Discord.Net.Core/Entities/Messages/MessageReferenceType.cs b/src/Discord.Net.Core/Entities/Messages/MessageReferenceType.cs new file mode 100644 index 00000000..4fed6f41 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/MessageReferenceType.cs @@ -0,0 +1,17 @@ +namespace Discord; + +/// +/// Determines how associated data is populated. +/// +public enum MessageReferenceType +{ + /// + /// A standard reference used by replies. + /// + Default = 0, + + /// + /// Reference used to point to a message at a point in time. + /// + Forward = 1, +} diff --git a/src/Discord.Net.Core/Entities/Messages/MessageSnapshot.cs b/src/Discord.Net.Core/Entities/Messages/MessageSnapshot.cs new file mode 100644 index 00000000..918b2299 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/MessageSnapshot.cs @@ -0,0 +1,17 @@ +namespace Discord; + +/// +/// Represents a snapshot of a message. +/// +public readonly struct MessageSnapshot +{ + /// + /// Gets the partial message that was forwarded. + /// + public readonly IMessage Message; + + internal MessageSnapshot(IMessage message) + { + Message = message; + } +} diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index aa61495b..83da3a66 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -98,7 +98,7 @@ namespace Discord } public static void MessageAtLeastOneOf(string text = null, MessageComponent components = null, ICollection embeds = null, - ICollection stickers = null, IEnumerable attachments = null, PollProperties poll = null) + ICollection stickers = null, IEnumerable attachments = null, PollProperties poll = null, MessageReference messageReference = null) { if (!string.IsNullOrEmpty(text)) return; @@ -118,6 +118,9 @@ namespace Discord if (poll is not null) return; + if (messageReference?.ReferenceType.GetValueOrDefault(MessageReferenceType.Default) is MessageReferenceType.Forward) + return; + throw new ArgumentException($"At least one of 'Content', 'Embeds', 'Components', 'Stickers', 'Attachments' or 'Poll' must be specified."); } diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index 3f579cdd..48d9d9a3 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -102,6 +102,9 @@ internal class Message [JsonProperty("interaction_metadata")] public Optional InteractionMetadata { get; set; } + [JsonProperty("message_snapshots")] + public Optional MessageSnapshots { get; set; } + [JsonProperty("poll")] public Optional Poll { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/MessageReference.cs b/src/Discord.Net.Rest/API/Common/MessageReference.cs index 70ef4e67..3e582b2f 100644 --- a/src/Discord.Net.Rest/API/Common/MessageReference.cs +++ b/src/Discord.Net.Rest/API/Common/MessageReference.cs @@ -4,6 +4,9 @@ namespace Discord.API { internal class MessageReference { + [JsonProperty("type")] + public Optional Type { get; set; } + [JsonProperty("message_id")] public Optional MessageId { get; set; } diff --git a/src/Discord.Net.Rest/API/Common/MessageSnapshot.cs b/src/Discord.Net.Rest/API/Common/MessageSnapshot.cs new file mode 100644 index 00000000..8ec3173a --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/MessageSnapshot.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class MessageSnapshot +{ + [JsonProperty("message")] + public Message Message { get; set; } +} diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 6b1fa561..3fd65d02 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -284,7 +284,7 @@ namespace Discord.Rest Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); Preconditions.AtMost(embeds.Length, DiscordConfig.MaxEmbedsPerMessage, nameof(embeds), $"A max of {DiscordConfig.MaxEmbedsPerMessage} Embeds are allowed."); - Preconditions.MessageAtLeastOneOf(text, components, embeds, stickers, poll: poll); + Preconditions.MessageAtLeastOneOf(text, components, embeds, stickers, poll: poll, messageReference: messageReference); Preconditions.ValidatePoll(poll); // check that user flag and user Id list are exclusive, same with role flag and role Id list @@ -400,7 +400,7 @@ namespace Discord.Rest Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); Preconditions.AtMost(embeds.Length, DiscordConfig.MaxEmbedsPerMessage, nameof(embeds), $"A max of {DiscordConfig.MaxEmbedsPerMessage} Embeds are allowed."); - Preconditions.MessageAtLeastOneOf(text, components, embeds, stickers, attachments, poll); + Preconditions.MessageAtLeastOneOf(text, components, embeds, stickers, attachments, poll, messageReference); Preconditions.ValidatePoll(poll); foreach (var attachment in attachments) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 51ea852a..132f1c97 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -165,7 +165,8 @@ namespace Discord.Rest GuildId = model.Reference.Value.GuildId, InternalChannelId = model.Reference.Value.ChannelId, MessageId = model.Reference.Value.MessageId, - FailIfNotExists = model.Reference.Value.FailIfNotExists + FailIfNotExists = model.Reference.Value.FailIfNotExists, + ReferenceType = model.Reference.Value.Type }; } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 985798a5..4f80fa43 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -57,6 +57,9 @@ namespace Discord.Rest /// public MessageResolvedData ResolvedData { get; internal set; } + /// + public IReadOnlyCollection ForwardedMessages { get; internal set; } + internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) : base(discord, id, channel, author, source) { @@ -171,6 +174,14 @@ namespace Discord.Rest if (model.InteractionMetadata.IsSpecified) InteractionMetadata = model.InteractionMetadata.Value.ToInteractionMetadata(Discord); + if (model.MessageSnapshots.IsSpecified) + { + ForwardedMessages = model.MessageSnapshots.Value.Select(x => + new MessageSnapshot(RestMessage.Create(Discord, null, null, x.Message))).ToImmutableArray(); + } + else + ForwardedMessages = ImmutableArray.Empty; + if (model.Poll.IsSpecified) Poll = model.Poll.Value.ToEntity(); } diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index b6b27ee6..240cf71b 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -102,7 +102,8 @@ namespace Discord.Rest ChannelId = entity.InternalChannelId, GuildId = entity.GuildId, MessageId = entity.MessageId, - FailIfNotExists = entity.FailIfNotExists + FailIfNotExists = entity.FailIfNotExists, + Type = entity.ReferenceType, }; } public static IEnumerable EnumerateMentionTypes(this AllowedMentionTypes mentionTypes) diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 4e86208e..c778137e 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -198,7 +198,8 @@ namespace Discord.WebSocket GuildId = model.Reference.Value.GuildId, InternalChannelId = model.Reference.Value.ChannelId, MessageId = model.Reference.Value.MessageId, - FailIfNotExists = model.Reference.Value.FailIfNotExists + FailIfNotExists = model.Reference.Value.FailIfNotExists, + ReferenceType = model.Reference.Value.Type }; } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 34af97d8..dc86503c 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -58,6 +58,9 @@ namespace Discord.WebSocket /// public MessageResolvedData ResolvedData { get; internal set; } + /// + public IReadOnlyCollection ForwardedMessages { get; internal set; } + internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) : base(discord, id, channel, author, source) { @@ -213,6 +216,14 @@ namespace Discord.WebSocket if (model.InteractionMetadata.IsSpecified) InteractionMetadata = model.InteractionMetadata.Value.ToInteractionMetadata(Discord); + if (model.MessageSnapshots.IsSpecified) + { + ForwardedMessages = model.MessageSnapshots.Value.Select(x => + new MessageSnapshot(RestMessage.Create(Discord, null, null, x.Message))).ToImmutableArray(); + } + else + ForwardedMessages = ImmutableArray.Empty; + if (model.Poll.IsSpecified) Poll = model.Poll.Value.ToEntity(); }