diff --git a/src/Discord.Net.Core/Entities/Messages/Emoji.cs b/src/Discord.Net.Core/Entities/Messages/Emoji.cs index 612e99f29..fc71e944b 100644 --- a/src/Discord.Net.Core/Entities/Messages/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Messages/Emoji.cs @@ -19,6 +19,11 @@ namespace Discord Name = name; } + internal static Emoji FromApi(API.Emoji emoji) + { + return new Emoji(emoji.Id, emoji.Name); + } + public static Emoji Parse(string text) { Emoji result; diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 13f3b662a..6bb44368b 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -31,8 +31,6 @@ namespace Discord IReadOnlyCollection Attachments { get; } /// Returns all embeds included in this message. IReadOnlyCollection Embeds { get; } - /// Returns all reactions included in this message. - IReadOnlyCollection Reactions { get; } /// Returns all tags included in this message's content. IReadOnlyCollection Tags { get; } /// Returns the ids of channels mentioned in this message. diff --git a/src/Discord.Net.Core/Entities/Messages/IReaction.cs b/src/Discord.Net.Core/Entities/Messages/IReaction.cs index 5d7eae44e..66832760b 100644 --- a/src/Discord.Net.Core/Entities/Messages/IReaction.cs +++ b/src/Discord.Net.Core/Entities/Messages/IReaction.cs @@ -7,6 +7,6 @@ namespace Discord { public interface IReaction { - API.Emoji Emoji { get; } + Emoji Emoji { get; } } } diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index 3a7b5821a..5b6ab2773 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -14,6 +14,9 @@ namespace Discord /// Removes this message from its channel's pinned messages. Task UnpinAsync(RequestOptions options = null); + /// Returns all reactions included in this message. + IReadOnlyDictionary Reactions { get; } + /// Adds a reaction to this message. Task AddReactionAsync(Emoji emoji, RequestOptions options = null); /// Adds a reaction to this message. diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 41cff4380..ea064dd81 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -27,7 +27,6 @@ namespace Discord.Rest public virtual IReadOnlyCollection MentionedRoleIds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); - public virtual IReadOnlyCollection Reactions => ImmutableArray.Create(); public virtual ulong? WebhookId => null; public bool IsWebhook => WebhookId != null; @@ -71,6 +70,5 @@ namespace Discord.Rest IReadOnlyCollection IMessage.Attachments => Attachments; IReadOnlyCollection IMessage.Embeds => Embeds; IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); - IReadOnlyCollection IMessage.Reactions => Reactions; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs index 2fb534ce4..7512dd4d8 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs @@ -10,17 +10,13 @@ namespace Discord { internal RestReaction(Model model) { - _emoji = model.Emoji; - _count = model.Count; - + Emoji = Emoji.FromApi(model.Emoji); + Count = model.Count; + Me = model.Me; } - internal readonly API.Emoji _emoji; - internal readonly int _count; - internal readonly bool _me; - - public API.Emoji Emoji => _emoji; - public int Count => _count; - public bool Me => _me; + public Emoji Emoji { get; private set; } + public int Count { get; private set; } + public bool Me { get; private set; } } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 2625718c7..1af844501 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -30,7 +30,7 @@ namespace Discord.Rest public override IReadOnlyCollection MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); public override IReadOnlyCollection Tags => _tags; - public override IReadOnlyCollection Reactions => _reactions; + public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emoji, x => x.Count); internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, RestUser author, IGuild guild) : base(discord, id, channel, author, guild) diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs index 35e4ef52e..d0464487b 100644 --- a/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcMessage.cs @@ -28,7 +28,6 @@ namespace Discord.Rpc public virtual IReadOnlyCollection MentionedRoleIds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedUserIds => ImmutableArray.Create(); public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); - public virtual IReadOnlyCollection Reactions => ImmutableArray.Create(); public virtual ulong? WebhookId => null; public bool IsWebhook => WebhookId != null; diff --git a/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs index 01cff3611..832e54d81 100644 --- a/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs +++ b/src/Discord.Net.Rpc/Entities/Messages/RpcUserMessage.cs @@ -30,6 +30,7 @@ namespace Discord.Rpc public override IReadOnlyCollection MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); public override IReadOnlyCollection MentionedUserIds => MessageHelper.FilterTagsByKey(TagType.UserMention, _tags); public override IReadOnlyCollection Tags => _tags; + public IReadOnlyDictionary Reactions => ImmutableDictionary.Create(); internal RpcUserMessage(DiscordRpcClient discord, ulong id, IMessageChannel channel, RpcUser author) : base(discord, id, channel, author) diff --git a/src/Discord.Net.WebSocket/API/Gateway/GatewayReaction.cs b/src/Discord.Net.WebSocket/API/Gateway/GatewayReaction.cs index 096159478..2b9d6becc 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GatewayReaction.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GatewayReaction.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json; namespace Discord.API.Gateway { - public class GatewayReaction : Reaction + public class GatewayReaction { [JsonProperty("user_id")] public ulong UserId { get; set; } @@ -15,6 +15,6 @@ namespace Discord.API.Gateway [JsonProperty("channel_id")] public ulong ChannelId { get; set; } [JsonProperty("emoji")] - public Discord.API.Emoji Emoji { get; set; } + public Emoji Emoji { get; set; } } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs index 529caaa87..4454e619f 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs @@ -71,6 +71,18 @@ namespace Discord.WebSocket remove { _messageUpdatedEvent.Remove(value); } } private readonly AsyncEvent, SocketMessage, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, Task>>(); + public event Func, SocketReaction, Task> ReactionAdded + { + add { _reactionAddedEvent.Add(value); } + remove { _reactionAddedEvent.Remove(value); } + } + private readonly AsyncEvent, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, SocketReaction, Task>>(); + public event Func, SocketReaction, Task> ReactionRemoved + { + add { _reactionRemovedEvent.Add(value); } + remove { _reactionRemovedEvent.Remove(value); } + } + private readonly AsyncEvent, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, SocketReaction, Task>>(); //Roles public event Func RoleCreated diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 3eb4158d1..2a70824d4 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1304,6 +1304,54 @@ namespace Discord.WebSocket } } break; + case "MESSAGE_REACTION_ADD": + { + await _gatewayLogger.DebugAsync("Received Disbatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; + if (channel != null) + { + SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + SocketReaction reaction = new SocketReaction(data); + if (cachedMsg != null) + { + cachedMsg.AddReaction(reaction); + await _reactionAddedEvent.InvokeAsync(data.MessageId, cachedMsg, reaction).ConfigureAwait(false); + } + await _reactionAddedEvent.InvokeAsync(data.MessageId, Optional.Create(), reaction).ConfigureAwait(false); + } + else + { + await _gatewayLogger.WarningAsync("MESSAGE_REACTION_ADD referenced an unknown channel.").ConfigureAwait(false); + return; + } + break; + } + case "MESSAGE_REACTION_REMOVE": + { + await _gatewayLogger.DebugAsync("Received Disbatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var channel = State.GetChannel(data.ChannelId) as ISocketMessageChannel; + if (channel != null) + { + SocketUserMessage cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + SocketReaction reaction = new SocketReaction(data); + if (cachedMsg != null) + { + cachedMsg.RemoveReaction(reaction); + await _reactionRemovedEvent.InvokeAsync(data.MessageId, cachedMsg, reaction).ConfigureAwait(false); + } + await _reactionRemovedEvent.InvokeAsync(data.MessageId, Optional.Create(), reaction).ConfigureAwait(false); + } + else + { + await _gatewayLogger.WarningAsync("MESSAGE_REACTION_REMOVE referenced an unknown channel.").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 3f7512295..0b09d2d22 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -27,7 +27,6 @@ namespace Discord.WebSocket public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); - public virtual IReadOnlyCollection Reactions => ImmutableArray.Create(); public virtual ulong? WebhookId => null; public bool IsWebhook => WebhookId != null; diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs index b96513cc1..0ac8c4342 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs @@ -13,12 +13,12 @@ namespace Discord.WebSocket UserId = model.UserId; MessageId = model.MessageId; ChannelId = model.ChannelId; - Emoji = model.Emoji; + Emoji = Emoji.FromApi(model.Emoji); } - public ulong UserId { get; internal set; } - public ulong MessageId { get; internal set; } - public ulong ChannelId { get; internal set; } - public API.Emoji Emoji { get; internal set; } + public ulong UserId { get; private set; } + public ulong MessageId { get; private set; } + public ulong ChannelId { get; private set; } + public Emoji Emoji { get; private set; } } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 5ccf1825f..957fa64b3 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Threading.Tasks; +using System.Linq; using Discord.API.Gateway; using Model = Discord.API.Message; @@ -19,7 +20,7 @@ namespace Discord.WebSocket private ImmutableArray _attachments; private ImmutableArray _embeds; private ImmutableArray _tags; - private ImmutableArray _reactions; + private List _reactions = new List(); public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; @@ -31,7 +32,7 @@ namespace Discord.WebSocket public override IReadOnlyCollection MentionedChannels => MessageHelper.FilterTagsByValue(TagType.ChannelMention, _tags); public override IReadOnlyCollection MentionedRoles => MessageHelper.FilterTagsByValue(TagType.RoleMention, _tags); public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); - public override IReadOnlyCollection Reactions => _reactions; + public IReadOnlyDictionary Reactions => _reactions.GroupBy(r => r.Emoji).ToDictionary(x => x.Key, x => x.Count()); internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) : base(discord, id, channel, author) @@ -104,20 +105,6 @@ namespace Discord.WebSocket } } - if (model.Reactions.IsSpecified) - { - var value = model.Reactions.Value; - if (value.Length > 0) - { - var reactions = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) - reactions.Add(new SocketReaction(value[i] as GatewayReaction)); - _reactions = reactions.ToImmutable(); - } - else - _reactions = ImmutableArray.Create(); - } - if (model.Content.IsSpecified) { var text = model.Content.Value; @@ -126,6 +113,15 @@ namespace Discord.WebSocket model.Content = text; } } + internal void AddReaction(SocketReaction reaction) + { + _reactions.Add(reaction); + } + internal void RemoveReaction(SocketReaction reaction) + { + if (_reactions.Contains(reaction)) + _reactions.Remove(reaction); + } public Task ModifyAsync(Action func, RequestOptions options = null) => MessageHelper.ModifyAsync(this, Discord, func, options);