From 9fd5c6c27e6770f4892132bc1dabd26bb961b855 Mon Sep 17 00:00:00 2001 From: Mihail Gribkov <61027276+Misha-133@users.noreply.github.com> Date: Sat, 18 Nov 2023 23:57:11 +0300 Subject: [PATCH] [Feature] Super reactions support (#2707) * super reactions * add type to `GetReactionUsers` methods * add `MeBurst` --- src/Discord.Net.Core/DiscordErrorCode.cs | 1 + .../Entities/Messages/IMessage.cs | 4 +- .../Entities/Messages/IReaction.cs | 27 +- .../Entities/Messages/ReactionMetadata.cs | 54 ++-- .../Entities/Messages/ReactionType.cs | 14 ++ src/Discord.Net.Rest/API/Common/Reaction.cs | 30 ++- .../API/Common/ReactionCountDetails.cs | 12 + src/Discord.Net.Rest/DiscordRestApiClient.cs | 6 +- .../Entities/Messages/MessageHelper.cs | 4 +- .../Entities/Messages/RestMessage.cs | 13 +- .../Entities/Messages/RestReaction.cs | 37 ++- .../Net/Converters/ColorConverter.cs | 27 ++ .../Net/Converters/DiscordContractResolver.cs | 2 + .../API/Gateway/Reaction.cs | 45 ++-- .../Entities/Messages/SocketMessage.cs | 4 +- .../Entities/Messages/SocketReaction.cs | 236 ++++++++++-------- 16 files changed, 347 insertions(+), 169 deletions(-) create mode 100644 src/Discord.Net.Core/Entities/Messages/ReactionType.cs create mode 100644 src/Discord.Net.Rest/API/Common/ReactionCountDetails.cs create mode 100644 src/Discord.Net.Rest/Net/Converters/ColorConverter.cs diff --git a/src/Discord.Net.Core/DiscordErrorCode.cs b/src/Discord.Net.Core/DiscordErrorCode.cs index 5fc71f4b..37e469f5 100644 --- a/src/Discord.Net.Core/DiscordErrorCode.cs +++ b/src/Discord.Net.Core/DiscordErrorCode.cs @@ -202,6 +202,7 @@ namespace Discord #region Reactions (90XXX) ReactionBlocked = 90001, + CannotUseBurstReaction = 90002, #endregion #region API Status (130XXX) diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 75cb98b5..034c0e27 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -323,9 +323,11 @@ namespace Discord /// The emoji that represents the reaction that you wish to get. /// The number of users to request. /// The options to be used when sending the request. + /// The type of the reaction you wish to get users for. /// /// Paged collection of users. /// - IAsyncEnumerable> GetReactionUsersAsync(IEmote emoji, int limit, RequestOptions options = null); + IAsyncEnumerable> GetReactionUsersAsync(IEmote emoji, int limit, RequestOptions options = null, + ReactionType type = ReactionType.Normal); } } diff --git a/src/Discord.Net.Core/Entities/Messages/IReaction.cs b/src/Discord.Net.Core/Entities/Messages/IReaction.cs index b7d7128c..de591648 100644 --- a/src/Discord.Net.Core/Entities/Messages/IReaction.cs +++ b/src/Discord.Net.Core/Entities/Messages/IReaction.cs @@ -1,13 +1,22 @@ -namespace Discord +using System.Collections.Generic; + +namespace Discord; + +/// +/// Represents a generic reaction object. +/// +public interface IReaction { /// - /// Represents a generic reaction object. + /// The used in the reaction. /// - public interface IReaction - { - /// - /// The used in the reaction. - /// - IEmote Emote { get; } - } + IEmote Emote { get; } + + /// + /// Gets colors used for the super reaction. + /// + /// + /// The collection will be empty if the reaction is a normal reaction. + /// + public IReadOnlyCollection BurstColors { get; } } diff --git a/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs b/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs index f6a11634..1fcea9fd 100644 --- a/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs +++ b/src/Discord.Net.Core/Entities/Messages/ReactionMetadata.cs @@ -1,24 +1,40 @@ -namespace Discord +using System.Collections.Generic; + +namespace Discord; + +/// +/// A metadata containing reaction information. +/// +public struct ReactionMetadata { /// - /// A metadata containing reaction information. + /// Gets the number of reactions. /// - public struct ReactionMetadata - { - /// - /// Gets the number of reactions. - /// - /// - /// An representing the number of this reactions that has been added to this message. - /// - public int ReactionCount { get; internal set; } + /// + /// An representing the number of this reactions that has been added to this message. + /// + public int ReactionCount { get; internal set; } + + /// + /// Gets a value that indicates whether the current user has reacted to this. + /// + /// + /// if the user has reacted to the message; otherwise . + /// + public bool IsMe { get; internal set; } - /// - /// Gets a value that indicates whether the current user has reacted to this. - /// - /// - /// if the user has reacted to the message; otherwise . - /// - public bool IsMe { get; internal set; } - } + /// + /// Gets the number of burst reactions added to this message. + /// + public int BurstCount { get; internal set; } + + /// + /// Gets the number of normal reactions added to this message. + /// + public int NormalCount { get; internal set; } + + /// + /// Gets colors used for super reaction. + /// + public IReadOnlyCollection BurstColors { get; internal set; } } diff --git a/src/Discord.Net.Core/Entities/Messages/ReactionType.cs b/src/Discord.Net.Core/Entities/Messages/ReactionType.cs new file mode 100644 index 00000000..d69e15c3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/ReactionType.cs @@ -0,0 +1,14 @@ +namespace Discord; + +public enum ReactionType +{ + /// + /// The reaction is a normal reaction. + /// + Normal = 0, + + /// + /// The reaction is a super reaction. + /// + Burst = 1, +} diff --git a/src/Discord.Net.Rest/API/Common/Reaction.cs b/src/Discord.Net.Rest/API/Common/Reaction.cs index ffe7a478..b85d3b02 100644 --- a/src/Discord.Net.Rest/API/Common/Reaction.cs +++ b/src/Discord.Net.Rest/API/Common/Reaction.cs @@ -1,14 +1,24 @@ using Newtonsoft.Json; -namespace Discord.API +namespace Discord.API; + +internal class Reaction { - internal class Reaction - { - [JsonProperty("count")] - public int Count { get; set; } - [JsonProperty("me")] - public bool Me { get; set; } - [JsonProperty("emoji")] - public Emoji Emoji { get; set; } - } + [JsonProperty("count")] + public int Count { get; set; } + + [JsonProperty("me")] + public bool Me { get; set; } + + [JsonProperty("me_burst")] + public bool MeBurst { get; set; } + + [JsonProperty("emoji")] + public Emoji Emoji { get; set; } + + [JsonProperty("count_details")] + public ReactionCountDetails CountDetails { get; set; } + + [JsonProperty("burst_colors")] + public Color[] Colors { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/ReactionCountDetails.cs b/src/Discord.Net.Rest/API/Common/ReactionCountDetails.cs new file mode 100644 index 00000000..b8ab0487 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/ReactionCountDetails.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class ReactionCountDetails +{ + [JsonProperty("normal")] + public int NormalCount { get; set;} + + [JsonProperty("burst")] + public int BurstCount { get; set;} +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index e4818fd3..d07d63c1 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1143,7 +1143,8 @@ namespace Discord.API 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) + + public async Task> GetReactionUsersAsync(ulong channelId, ulong messageId, string emoji, GetReactionUsersParams args, ReactionType reactionType, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); @@ -1158,9 +1159,10 @@ namespace Discord.API ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); var ids = new BucketIds(channelId: channelId); - Expression> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}?limit={limit}&after={afterUserId}"; + Expression> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}?limit={limit}&after={afterUserId}&type={(int)reactionType}"; return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } + public async Task AckMessageAsync(ulong channelId, ulong messageId, 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 a0aab863..8e7f9377 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -164,7 +164,7 @@ namespace Discord.Rest } public static IAsyncEnumerable> GetReactionUsersAsync(IMessage msg, IEmote emote, - int? limit, BaseDiscordClient client, RequestOptions options) + int? limit, BaseDiscordClient client, ReactionType reactionType, RequestOptions options) { Preconditions.NotNull(emote, nameof(emote)); var emoji = (emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name)); @@ -181,7 +181,7 @@ namespace Discord.Rest if (info.Position != null) args.AfterUserId = info.Position.Value; - var models = await client.ApiClient.GetReactionUsersAsync(msg.Channel.Id, msg.Id, emoji, args, options).ConfigureAwait(false); + var models = await client.ApiClient.GetReactionUsersAsync(msg.Channel.Id, msg.Id, emoji, args, reactionType, options).ConfigureAwait(false); return models.Select(x => RestUser.Create(client, x)).ToImmutableArray(); }, nextPage: (info, lastPage) => diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 0f4dd291..9dcbb496 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -316,7 +316,14 @@ namespace Discord.Rest #endregion /// - public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); + public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata + { + ReactionCount = x.Count, + IsMe = x.Me, + BurstColors = x.BurstColors, + BurstCount = x.BurstCount, + NormalCount = x.NormalCount, + }); /// public Task AddReactionAsync(IEmote emote, RequestOptions options = null) @@ -334,7 +341,7 @@ namespace Discord.Rest 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); + public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null, ReactionType type = ReactionType.Normal) + => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, type, options); } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs index c38efe32..2be12f8e 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Model = Discord.API.Reaction; namespace Discord.Rest @@ -9,20 +10,44 @@ namespace Discord.Rest { /// public IEmote Emote { get; } + /// /// Gets the number of reactions added. /// public int Count { get; } + /// - /// Gets whether the reactions is added by the user. + /// Gets whether the reaction is added by the user. /// public bool Me { get; } - internal RestReaction(IEmote emote, int count, bool me) + /// + /// Gets whether the super-reaction is added by the user. + /// + public bool MeBurst { get; } + + /// + /// Gets the number of burst reactions added. + /// + public int BurstCount { get; } + + /// + /// Gets the number of normal reactions added. + /// + public int NormalCount { get; } + + /// + public IReadOnlyCollection BurstColors { get; } + + internal RestReaction(IEmote emote, int count, bool me, int burst, int normal, IReadOnlyCollection colors, bool meBurst) { Emote = emote; Count = count; Me = me; + BurstCount = burst; + NormalCount = normal; + BurstColors = colors; + MeBurst = meBurst; } internal static RestReaction Create(Model model) { @@ -31,7 +56,13 @@ namespace Discord.Rest emote = new Emote(model.Emoji.Id.Value, model.Emoji.Name, model.Emoji.Animated.GetValueOrDefault()); else emote = new Emoji(model.Emoji.Name); - return new RestReaction(emote, model.Count, model.Me); + return new RestReaction(emote, + model.Count, + model.Me, + model.CountDetails.BurstCount, + model.CountDetails.NormalCount, + model.Colors.ToReadOnlyCollection(), + model.MeBurst); } } } diff --git a/src/Discord.Net.Rest/Net/Converters/ColorConverter.cs b/src/Discord.Net.Rest/Net/Converters/ColorConverter.cs new file mode 100644 index 00000000..4c0ab529 --- /dev/null +++ b/src/Discord.Net.Rest/Net/Converters/ColorConverter.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +using System.Globalization; +using System; + +namespace Discord.Net.Converters; + +internal class ColorConverter : JsonConverter +{ + public static readonly ColorConverter Instance = new (); + + public override bool CanConvert(Type objectType) => true; + public override bool CanRead => true; + public override bool CanWrite => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.Value is null) + return null; + return new Color(uint.Parse(reader.Value.ToString()!.TrimStart('#'), NumberStyles.HexNumber)); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue($"#{(uint)value:X}"); + } +} diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs index 62c0e580..8de2c026 100644 --- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs @@ -93,6 +93,8 @@ namespace Discord.Net.Converters return DiscordErrorConverter.Instance; if (type == typeof(GuildFeatures)) return GuildFeaturesConverter.Instance; + if(type == typeof(Color)) + return ColorConverter.Instance; //Entities var typeInfo = type.GetTypeInfo(); diff --git a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs index 0d17cbff..051da453 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs @@ -1,20 +1,33 @@ using Newtonsoft.Json; -namespace Discord.API.Gateway +namespace Discord.API.Gateway; + +internal class Reaction { - internal class Reaction - { - [JsonProperty("user_id")] - public ulong UserId { get; set; } - [JsonProperty("message_id")] - public ulong MessageId { get; set; } - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } - [JsonProperty("guild_id")] - public Optional GuildId { get; set; } - [JsonProperty("emoji")] - public Emoji Emoji { get; set; } - [JsonProperty("member")] - public Optional Member { get; set; } - } + [JsonProperty("user_id")] + public ulong UserId { get; set; } + + [JsonProperty("message_id")] + public ulong MessageId { get; set; } + + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } + + [JsonProperty("emoji")] + public Emoji Emoji { get; set; } + + [JsonProperty("member")] + public Optional Member { get; set; } + + [JsonProperty("burst")] + public bool IsBurst { get; set; } + + [JsonProperty("burst_colors")] + public Optional BurstColors { get; set; } + + [JsonProperty("type")] + public ReactionType Type { get; set; } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 24c9dce2..8d964726 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -374,8 +374,8 @@ namespace Discord.WebSocket 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); + public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null, ReactionType type = ReactionType.Normal) + => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, type, options); #endregion } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs index fe24057d..113093cc 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs @@ -1,113 +1,145 @@ +using System; +using System.Collections.Generic; using Model = Discord.API.Gateway.Reaction; -namespace Discord.WebSocket +namespace Discord.WebSocket; + +/// +/// Represents a WebSocket-based reaction object. +/// +public class SocketReaction : IReaction { /// - /// Represents a WebSocket-based reaction object. + /// Gets the ID of the user who added the reaction. /// - public class SocketReaction : IReaction + /// + /// This property retrieves the snowflake identifier of the user responsible for this reaction. This + /// property will always contain the user identifier in event that + /// cannot be retrieved. + /// + /// + /// A user snowflake identifier associated with the user. + /// + public ulong UserId { get; } + + /// + /// Gets the user who added the reaction if possible. + /// + /// + /// + /// This property attempts to retrieve a WebSocket-cached user that is responsible for this reaction from + /// the client. In other words, when the user is not in the WebSocket cache, this property may not + /// contain a value, leaving the only identifiable information to be + /// . + /// + /// + /// If you wish to obtain an identifiable user object, consider utilizing + /// which will attempt to retrieve the user from REST. + /// + /// + /// + /// A user object where possible; a value is not always returned. + /// + /// + public Optional User { get; } + + /// + /// Gets the ID of the message that has been reacted to. + /// + /// + /// A message snowflake identifier associated with the message. + /// + public ulong MessageId { get; } + + /// + /// Gets the message that has been reacted to if possible. + /// + /// + /// A WebSocket-based message where possible; a value is not always returned. + /// + /// + public Optional Message { get; } + + /// + /// Gets the channel where the reaction takes place in. + /// + /// + /// A WebSocket-based message channel. + /// + public ISocketMessageChannel Channel { get; } + + /// + public IEmote Emote { get; } + + /// + /// Gets whether the reaction is a super reaction. + /// + public bool IsBurst { get; } + + /// + public IReadOnlyCollection BurstColors { get; } + + /// + /// Gets the type of the reaction. + /// + public ReactionType ReactionType { get; } + + internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional message, ulong userId, Optional user, + IEmote emoji, bool isBurst, IReadOnlyCollection colors, ReactionType reactionType) { - /// - /// Gets the ID of the user who added the reaction. - /// - /// - /// This property retrieves the snowflake identifier of the user responsible for this reaction. This - /// property will always contain the user identifier in event that - /// cannot be retrieved. - /// - /// - /// A user snowflake identifier associated with the user. - /// - public ulong UserId { get; } - /// - /// Gets the user who added the reaction if possible. - /// - /// - /// - /// This property attempts to retrieve a WebSocket-cached user that is responsible for this reaction from - /// the client. In other words, when the user is not in the WebSocket cache, this property may not - /// contain a value, leaving the only identifiable information to be - /// . - /// - /// - /// If you wish to obtain an identifiable user object, consider utilizing - /// which will attempt to retrieve the user from REST. - /// - /// - /// - /// A user object where possible; a value is not always returned. - /// - /// - public Optional User { get; } - /// - /// Gets the ID of the message that has been reacted to. - /// - /// - /// A message snowflake identifier associated with the message. - /// - public ulong MessageId { get; } - /// - /// Gets the message that has been reacted to if possible. - /// - /// - /// A WebSocket-based message where possible; a value is not always returned. - /// - /// - public Optional Message { get; } - /// - /// Gets the channel where the reaction takes place in. - /// - /// - /// A WebSocket-based message channel. - /// - public ISocketMessageChannel Channel { get; } - /// - public IEmote Emote { get; } + Channel = channel; + MessageId = messageId; + Message = message; + UserId = userId; + User = user; + Emote = emoji; + IsBurst = isBurst; + BurstColors = colors; + ReactionType = reactionType; + } - internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional message, ulong userId, Optional user, IEmote emoji) + internal static SocketReaction Create(Model model, ISocketMessageChannel channel, Optional message, Optional user) + { + IEmote emote; + if (model.Emoji.Id.HasValue) + emote = new Emote(model.Emoji.Id.Value, model.Emoji.Name, model.Emoji.Animated.GetValueOrDefault()); + else + emote = new Emoji(model.Emoji.Name); + return new SocketReaction(channel, + model.MessageId, + message, + model.UserId, + user, + emote, + model.IsBurst, + model.BurstColors.GetValueOrDefault(Array.Empty()).ToReadOnlyCollection(), + model.Type); + } + + /// + public override bool Equals(object other) + { + if (other == null) + return false; + if (other == this) + return true; + + var otherReaction = other as SocketReaction; + if (otherReaction == null) + return false; + + return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote); + } + + /// + public override int GetHashCode() + { + unchecked { - Channel = channel; - MessageId = messageId; - Message = message; - UserId = userId; - User = user; - Emote = emoji; - } - internal static SocketReaction Create(Model model, ISocketMessageChannel channel, Optional message, Optional user) - { - IEmote emote; - if (model.Emoji.Id.HasValue) - emote = new Emote(model.Emoji.Id.Value, model.Emoji.Name, model.Emoji.Animated.GetValueOrDefault()); - else - emote = new Emoji(model.Emoji.Name); - return new SocketReaction(channel, model.MessageId, message, model.UserId, user, emote); - } - - /// - public override bool Equals(object other) - { - if (other == null) - return false; - if (other == this) - return true; - - var otherReaction = other as SocketReaction; - if (otherReaction == null) - return false; - - return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote); - } - - /// - public override int GetHashCode() - { - unchecked - { - var hashCode = UserId.GetHashCode(); - hashCode = (hashCode * 397) ^ MessageId.GetHashCode(); - hashCode = (hashCode * 397) ^ Emote.GetHashCode(); - return hashCode; - } + var hashCode = UserId.GetHashCode(); + hashCode = (hashCode * 397) ^ MessageId.GetHashCode(); + hashCode = (hashCode * 397) ^ Emote.GetHashCode(); + return hashCode; } } }