From a89f0761f4674cd679b41e3844ed15c88ea01e58 Mon Sep 17 00:00:00 2001 From: Paulo Date: Mon, 15 Jun 2020 01:04:34 -0300 Subject: [PATCH] feature: Add MESSAGE_REACTION_REMOVE_EMOJI and RemoveAllReactionsForEmoteAsync (#1544) * Add event and method * Simplify convert to IEmote --- .../Entities/Messages/IMessage.cs | 9 ++++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 12 ++++++++ .../Entities/Messages/MessageHelper.cs | 5 ++++ .../Entities/Messages/RestMessage.cs | 3 ++ .../RemoveAllReactionsForEmoteEvent.cs | 16 +++++++++++ .../BaseSocketClient.Events.cs | 22 +++++++++++++++ .../DiscordShardedClient.cs | 1 + .../DiscordSocketClient.cs | 28 +++++++++++++++++++ .../Entities/Messages/SocketMessage.cs | 7 +++++ 9 files changed, 103 insertions(+) create mode 100644 src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsForEmoteEvent.cs diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index aac526831..530c1cd82 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -215,6 +215,15 @@ namespace Discord /// A task that represents the asynchronous removal operation. /// Task RemoveAllReactionsAsync(RequestOptions options = null); + /// + /// Removes all reactions with a specific emoji from this message. + /// + /// The emoji used to react to this message. + /// The options to be used when sending the request. + /// + /// A task that represents the asynchronous removal operation. + /// + Task RemoveAllReactionsForEmoteAsync(IEmote emote, RequestOptions options = null); /// /// Gets all users that reacted to a message with a given emote. diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 49a256378..3ee22446c 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -660,6 +660,18 @@ namespace Discord.API await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions", ids, options: options).ConfigureAwait(false); } + public async Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) + { + Preconditions.NotEqual(channelId, 0, nameof(channelId)); + Preconditions.NotEqual(messageId, 0, nameof(messageId)); + Preconditions.NotNullOrWhitespace(emoji, nameof(emoji)); + + options = RequestOptions.CreateOrClone(options); + + var ids = new BucketIds(channelId: channelId); + + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}", ids, options: options).ConfigureAwait(false); + } public async Task> GetReactionUsersAsync(ulong channelId, ulong messageId, string emoji, GetReactionUsersParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 57f8b2509..d6a718b3a 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -78,6 +78,11 @@ namespace Discord.Rest await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); } + public static async Task RemoveAllReactionsForEmoteAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) + { + await client.ApiClient.RemoveAllReactionsForEmoteAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false); + } + public static IAsyncEnumerable> GetReactionUsersAsync(IMessage msg, IEmote emote, int? limit, BaseDiscordClient client, RequestOptions options) { diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index b4a33c76c..809a55e9c 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -182,6 +182,9 @@ namespace Discord.Rest public Task RemoveAllReactionsAsync(RequestOptions options = null) => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); /// + public Task RemoveAllReactionsForEmoteAsync(IEmote emote, RequestOptions options = null) + => MessageHelper.RemoveAllReactionsForEmoteAsync(this, emote, Discord, options); + /// public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); } diff --git a/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsForEmoteEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsForEmoteEvent.cs new file mode 100644 index 000000000..7f804d3f5 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsForEmoteEvent.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway +{ + internal class RemoveAllReactionsForEmoteEvent + { + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } + [JsonProperty("message_id")] + public ulong MessageId { get; set; } + [JsonProperty("emoji")] + public Emoji Emoji { get; set; } + } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index 908314f6a..2cd62b3e8 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -234,6 +234,28 @@ namespace Discord.WebSocket remove { _reactionsClearedEvent.Remove(value); } } internal readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + /// + /// Fired when all reactions to a message with a specific emote are removed. + /// + /// + /// + /// This event is fired when all reactions to a message with a specific emote are removed. + /// The event handler must return a and accept a and + /// a as its parameters. + /// + /// + /// The channel where this message was sent will be passed into the parameter. + /// + /// + /// The emoji that all reactions had and were removed will be passed into the parameter. + /// + /// + public event Func, ISocketMessageChannel, IEmote, Task> ReactionsRemovedForEmote + { + add { _reactionsRemovedForEmoteEvent.Add(value); } + remove { _reactionsRemovedForEmoteEvent.Remove(value); } + } + internal readonly AsyncEvent, ISocketMessageChannel, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent, ISocketMessageChannel, IEmote, Task>>(); //Roles /// Fired when a role is created. diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 8359ca048..930ea1585 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -313,6 +313,7 @@ namespace Discord.WebSocket client.ReactionAdded += (cache, channel, reaction) => _reactionAddedEvent.InvokeAsync(cache, channel, reaction); client.ReactionRemoved += (cache, channel, reaction) => _reactionRemovedEvent.InvokeAsync(cache, channel, reaction); client.ReactionsCleared += (cache, channel) => _reactionsClearedEvent.InvokeAsync(cache, channel); + client.ReactionsRemovedForEmote += (cache, channel, emote) => _reactionsRemovedForEmoteEvent.InvokeAsync(cache, channel, emote); client.RoleCreated += (role) => _roleCreatedEvent.InvokeAsync(role); client.RoleDeleted += (role) => _roleDeletedEvent.InvokeAsync(role); diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index b56498061..10470365f 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1391,6 +1391,34 @@ namespace Discord.WebSocket } } break; + case "MESSAGE_REACTION_REMOVE_EMOJI": + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + { + var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + bool isCached = cachedMsg != null; + + var optionalMsg = !isCached + ? Optional.Create() + : Optional.Create(cachedMsg); + + var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); + var emote = data.Emoji.ToIEmote(); + + cachedMsg?.RemoveAllReactionsForEmoteAsync(emote); + + await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false); + } + else + { + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + return; + } + } + break; case "MESSAGE_DELETE_BULK": { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 55902035c..f392614ad 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -200,6 +200,10 @@ namespace Discord.WebSocket { _reactions.Clear(); } + internal void RemoveReactionsForEmote(IEmote emote) + { + _reactions.RemoveAll(x => x.Emote.Equals(emote)); + } /// public Task AddReactionAsync(IEmote emote, RequestOptions options = null) @@ -214,6 +218,9 @@ namespace Discord.WebSocket public Task RemoveAllReactionsAsync(RequestOptions options = null) => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); /// + public Task RemoveAllReactionsForEmoteAsync(IEmote emote, RequestOptions options = null) + => MessageHelper.RemoveAllReactionsForEmoteAsync(this, emote, Discord, options); + /// public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); }