From 793e858fbaaa96cbfcf50bfeb0ff62f57ca00d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Conor=E2=84=A2?= <35053522+iiFDCT@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:46:46 +0100 Subject: [PATCH] feat(Reactions): Support super reactions (#1470) Co-authored-by: bsian03 --- index.d.ts | 24 +++++++++++++++++++++--- lib/Client.js | 1 + lib/Constants.js | 5 +++++ lib/gateway/Shard.js | 26 ++++++++++++++++++++++---- lib/structures/DMChannel.js | 1 + lib/structures/GuildTextableChannel.js | 1 + lib/structures/Message.js | 8 ++++++-- 7 files changed, 57 insertions(+), 9 deletions(-) diff --git a/index.d.ts b/index.d.ts index 34fe82c1a..f5612eb7f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -160,6 +160,7 @@ declare namespace Eris { type MessageContent = string | AdvancedMessageContent; type MessageContentEdit = string | AdvancedMessageContentEdit; type PossiblyUncachedMessage = Message | { channel: TextableChannel | { id: string; guild?: Uncached }; guildID?: string; id: string }; + type ReactionTypes = Constants["ReactionTypes"][keyof Constants["ReactionTypes"]]; // Permission type PermissionType = Constants["PermissionOverwriteTypes"][keyof Constants["PermissionOverwriteTypes"]]; @@ -948,8 +949,8 @@ declare namespace Eris { messageCreate: [message: Message]; messageDelete: [message: PossiblyUncachedMessage]; messageDeleteBulk: [messages: PossiblyUncachedMessage[]]; - messageReactionAdd: [message: PossiblyUncachedMessage, emoji: PartialEmoji, reactor: Member | Uncached]; - messageReactionRemove: [message: PossiblyUncachedMessage, emoji: PartialEmoji, userID: string]; + messageReactionAdd: [message: PossiblyUncachedMessage, emoji: PartialEmoji, reactor: Member | Uncached, burst: boolean]; + messageReactionRemove: [message: PossiblyUncachedMessage, emoji: PartialEmoji, userID: string, burst: boolean]; messageReactionRemoveAll: [message: PossiblyUncachedMessage]; messageReactionRemoveEmoji: [message: PossiblyUncachedMessage, emoji: PartialEmoji]; messageUpdate: [message: Message, oldMessage: OldMessage | null]; @@ -1542,6 +1543,7 @@ declare namespace Eris { /** @deprecated */ before?: string; limit?: number; + type?: ReactionTypes; } interface InteractionButton extends ButtonBase { custom_id: string; @@ -1582,6 +1584,18 @@ declare namespace Eris { filename?: string; id: string | number; } + interface Reaction { + burst_colors: string[]; + count: number; + count_details: ReactionCountDetails; + me: boolean; + me_burst: boolean; + type: ReactionTypes; + } + interface ReactionCountDetails { + burst: number; + normal: number; + } interface SelectMenu { custom_id: string; disabled?: boolean; @@ -2456,6 +2470,10 @@ declare namespace Eris { RoleFlags: { IN_PROMPT: 1; }; + ReactionTypes: { + NORMAL: 0; + BURST: 1; + }; StageInstancePrivacyLevel: { PUBLIC: 1; GUILD_ONLY: 2; @@ -3816,7 +3834,7 @@ declare namespace Eris { messageReference: MessageReference | null; pinned: boolean; prefix?: string; - reactions: { [s: string]: { count: number; me: boolean } }; + reactions: { [s: string]: Reaction }; referencedMessage?: Message | null; roleMentions: string[]; roleSubscriptionData?: RoleSubscriptionData; diff --git a/lib/Client.js b/lib/Client.js index 2f1d1a1c2..f0f8d6d66 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -3091,6 +3091,7 @@ class Client extends EventEmitter { * @arg {Object} [options] Options for the request. If this is a number ([DEPRECATED] behavior), it is treated as `options.limit` * @arg {String} [options.after] Get users after this user ID * @arg {Number} [options.limit=100] The maximum number of users to get + * @arg {Number} [options.type=0] The type of reaction (`0` for normal, `1` for burst) * @arg {String} [before] [DEPRECATED] Get users before this user ID. Discord no longer supports this parameter * @arg {String} [after] [DEPRECATED] Get users after this user ID * @returns {Promise>} diff --git a/lib/Constants.js b/lib/Constants.js index 6d764fa93..12a733144 100644 --- a/lib/Constants.js +++ b/lib/Constants.js @@ -649,6 +649,11 @@ module.exports.PremiumTypes = { NITRO: 2 }; +module.exports.ReactionTypes = { + NORMAL: 0, + BURST: 1 +}; + module.exports.RoleConnectionMetadataTypes = { INTEGER_LESS_THAN_OR_EQUAL: 1, INTEGER_GREATER_THAN_OR_EQUAL: 2, diff --git a/lib/gateway/Shard.js b/lib/gateway/Shard.js index 53733ee9a..6a9458ae5 100644 --- a/lib/gateway/Shard.js +++ b/lib/gateway/Shard.js @@ -1117,13 +1117,25 @@ class Shard extends EventEmitter { const reaction = packet.d.emoji.id ? `${packet.d.emoji.name}:${packet.d.emoji.id}` : packet.d.emoji.name; if(message.reactions[reaction]) { ++message.reactions[reaction].count; + ++message.reactions[reaction].count_details[packet.d.burst ? "burst" : "normal"]; if(packet.d.user_id === this.client.user.id) { message.reactions[reaction].me = true; + if(packet.d.burst) { + message.reactions[reaction].me_burst = true; + } } + message.reactions[reaction].burst_colors = packet.d.burst_colors; } else { message.reactions[reaction] = { + burst_colors: packet.d.burst_colors, count: 1, - me: packet.d.user_id === this.client.user.id + count_details: { + burst: +packet.d.burst, + normal: +!packet.d.burst + }, + me: packet.d.user_id === this.client.user.id && !packet.d.burst, + me_burst: packet.d.user_id === this.client.user.id && packet.d.burst, + type: packet.d.type }; } } else { @@ -1148,8 +1160,9 @@ class Shard extends EventEmitter { * @prop {String?} emoji.id The emoji ID (null for non-custom emojis) * @prop {String} emoji.name The emoji name * @prop {Member | Object} reactor The member, if the reaction is in a guild. If the reaction is not in a guild, this will be an object with an `id` key. No other property is guaranteed + * @prop {Boolean} burst Whether the reaction is a super reaction */ - this.emit("messageReactionAdd", message, packet.d.emoji, member || {id: packet.d.user_id}); + this.emit("messageReactionAdd", message, packet.d.emoji, member || {id: packet.d.user_id}, packet.d.burst); break; } case "MESSAGE_REACTION_REMOVE": { @@ -1163,10 +1176,14 @@ class Shard extends EventEmitter { const reactionObj = message.reactions[reaction]; if(reactionObj) { --reactionObj.count; + --reactionObj.count_details[packet.d.burst ? "burst" : "normal"]; if(reactionObj.count === 0) { delete message.reactions[reaction]; } else if(packet.d.user_id === this.client.user.id) { - reactionObj.me = false; + message.reactions[reaction].me = false; + if(packet.d.burst) { + message.reactions[reaction].me_burst = false; + } } } } else { @@ -1191,8 +1208,9 @@ class Shard extends EventEmitter { * @prop {String?} emoji.id The ID of the emoji (null for non-custom emojis) * @prop {String} emoji.name The emoji name * @prop {String} userID The ID of the user that removed the reaction + * @prop {Boolean} burst Whether the reaction is a super reaction */ - this.emit("messageReactionRemove", message, packet.d.emoji, packet.d.user_id); + this.emit("messageReactionRemove", message, packet.d.emoji, packet.d.user_id, packet.d.burst); break; } case "MESSAGE_REACTION_REMOVE_ALL": { diff --git a/lib/structures/DMChannel.js b/lib/structures/DMChannel.js index 0b5fe42de..3e1168db3 100644 --- a/lib/structures/DMChannel.js +++ b/lib/structures/DMChannel.js @@ -168,6 +168,7 @@ class DMChannel extends Channel { * @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior) * @arg {String} [options.after] Get users after this user ID * @arg {Number} [options.limit=100] The maximum number of users to get + * @arg {Number} [options.type=0] The type of reaction (`0` for normal, `1` for burst) * @arg {String} [before] [DEPRECATED] Get users before this user ID. Discord no longer supports this parameter * @arg {String} [after] [DEPRECATED] Get users after this user ID * @returns {Promise>} diff --git a/lib/structures/GuildTextableChannel.js b/lib/structures/GuildTextableChannel.js index 072807598..6b819d288 100644 --- a/lib/structures/GuildTextableChannel.js +++ b/lib/structures/GuildTextableChannel.js @@ -133,6 +133,7 @@ class GuildTextableChannel extends GuildChannel { * @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior) * @arg {String} [options.after] Get users after this user ID * @arg {Number} [options.limit=100] The maximum number of users to get + * @arg {Number} [options.type=0] The type of reaction (`0` for normal, `1` for burst) * @arg {String} [before] [DEPRECATED] Get users before this user ID. Discord no longer supports this parameter * @arg {String} [after] [DEPRECATED] Get users after this user ID * @returns {Promise>} diff --git a/lib/structures/Message.js b/lib/structures/Message.js index 802e45a58..4263bfe11 100644 --- a/lib/structures/Message.js +++ b/lib/structures/Message.js @@ -41,7 +41,7 @@ const User = require("./User"); * @prop {String?} messageReference.messageID The ID of the original message this message was crossposted from * @prop {Boolean} pinned Whether the message is pinned or not * @prop {String?} prefix The prefix used in the Message, if any (CommandClient only) - * @prop {Object} reactions An object containing the reactions on the message. Each key is a reaction emoji and each value is an object with properties `me` (Boolean) and `count` (Number) for that specific reaction emoji. + * @prop {Object} reactions An object containing the reactions on the message. Each key is a reaction emoji and each value is an object with properties `burst_colors` (Array), `count` (Number), `count_details` (an object with `burst` and `normal` keys corresponding to the amount of reactions), `me` (Boolean) and `me_burst` for that specific reaction emoji. * @prop {Message?} referencedMessage The message that was replied to. If undefined, message data was not received. If null, the message was deleted. * @prop {Array} roleMentions Array of mentioned roles' ids * @prop {Object?} roleSubscriptionData An object containing the data of the role subscription purchase or renewal that prompted this `ROLE_SUBSCRIPTION_PURCHASE` message. See [the official Discord API documentation entry](https://discord.com/developers/docs/resources/channel#role-subscription-data-object) for object structure @@ -209,8 +209,11 @@ class Message extends Base { if(data.reactions) { data.reactions.forEach((reaction) => { this.reactions[reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name] = { + burst_colors: reaction.burst_colors, count: reaction.count, - me: reaction.me + count_details: reaction.count_details, + me: reaction.me, + me_burst: reaction.me_burst }; }); } @@ -447,6 +450,7 @@ class Message extends Base { * @arg {Object} [options] Options for the request. If this is a number, it is treated as `options.limit` ([DEPRECATED] behavior) * @arg {String} [options.after] Get users after this user ID * @arg {Number} [options.limit=100] The maximum number of users to get + * @arg {Number} [options.type=0] The type of reaction (`0` for normal, `1` for burst) * @arg {String} [before] [DEPRECATED] Get users before this user ID. Discord no longer supports this parameter * @arg {String} [after] [DEPRECATED] Get users after this user ID * @returns {Promise>}