From a5afc406b965b39a9cc90ef9e0e7a4b460c4e04c Mon Sep 17 00:00:00 2001 From: Jaw0r3k Date: Wed, 28 Aug 2024 00:30:16 +0200 Subject: [PATCH] feat: super reactions (#9336) * feat: super reactions * docs: Touch-up * feat: count super reactions in events * feat: document me_burst property Co-authored-by: Danial Raza * feat: document type query for fetching reaction users * fix: cover case when burstColors can be undefined at init of a reaction * Update packages/discord.js/src/structures/MessageReaction.js Co-authored-by: Vlad Frangu * chore: futureproof so use an object --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Danial Raza Co-authored-by: Vlad Frangu Co-authored-by: Vlad Frangu --- .../src/client/actions/MessageReactionAdd.js | 12 +++- .../client/actions/MessageReactionRemove.js | 5 +- .../src/managers/ReactionUserManager.js | 7 ++- .../src/structures/MessageReaction.js | 61 ++++++++++++++++--- packages/discord.js/typings/index.d.ts | 26 +++++++- 5 files changed, 95 insertions(+), 16 deletions(-) diff --git a/packages/discord.js/src/client/actions/MessageReactionAdd.js b/packages/discord.js/src/client/actions/MessageReactionAdd.js index 6932358cf107..9c9ffbc874a0 100644 --- a/packages/discord.js/src/client/actions/MessageReactionAdd.js +++ b/packages/discord.js/src/client/actions/MessageReactionAdd.js @@ -9,6 +9,7 @@ const Partials = require('../../util/Partials'); message_id: 'id', emoji: { name: '�', id: null }, channel_id: 'id', + burst: boolean // If originating from a guild guild_id: 'id', member: { ..., user: { ... } } } @@ -36,17 +37,24 @@ class MessageReactionAdd extends Action { emoji: data.emoji, count: message.partial ? null : 0, me: user.id === this.client.user.id, + burst_colors: data.burst_colors, }); if (!reaction) return false; - reaction._add(user); + reaction._add(user, data.burst); if (fromStructure) return { message, reaction, user }; + /** + * Provides additional information about altered reaction + * @typedef {Object} MessageReactionEventDetails + * @property {boolean} burst Determines whether a super reaction was used + */ /** * Emitted whenever a reaction is added to a cached message. * @event Client#messageReactionAdd * @param {MessageReaction} messageReaction The reaction object * @param {User} user The user that applied the guild or reaction emoji + * @param {MessageReactionEventDetails} details Details of adding the reaction */ - this.client.emit(Events.MessageReactionAdd, reaction, user); + this.client.emit(Events.MessageReactionAdd, reaction, user, { burst: data.burst }); return { message, reaction, user }; } diff --git a/packages/discord.js/src/client/actions/MessageReactionRemove.js b/packages/discord.js/src/client/actions/MessageReactionRemove.js index 93874a777d26..5430a37c461e 100644 --- a/packages/discord.js/src/client/actions/MessageReactionRemove.js +++ b/packages/discord.js/src/client/actions/MessageReactionRemove.js @@ -29,14 +29,15 @@ class MessageReactionRemove extends Action { // Verify reaction const reaction = this.getReaction(data, message, user); if (!reaction) return false; - reaction._remove(user); + reaction._remove(user, data.burst); /** * Emitted whenever a reaction is removed from a cached message. * @event Client#messageReactionRemove * @param {MessageReaction} messageReaction The reaction object * @param {User} user The user whose emoji or reaction emoji was removed + * @param {MessageReactionEventDetails} details Details of removing the reaction */ - this.client.emit(Events.MessageReactionRemove, reaction, user); + this.client.emit(Events.MessageReactionRemove, reaction, user, { burst: data.burst }); return { message, reaction, user }; } diff --git a/packages/discord.js/src/managers/ReactionUserManager.js b/packages/discord.js/src/managers/ReactionUserManager.js index 014cea8a3eae..c9a88be617be 100644 --- a/packages/discord.js/src/managers/ReactionUserManager.js +++ b/packages/discord.js/src/managers/ReactionUserManager.js @@ -2,7 +2,7 @@ const { Collection } = require('@discordjs/collection'); const { makeURLSearchParams } = require('@discordjs/rest'); -const { Routes } = require('discord-api-types/v10'); +const { ReactionType, Routes } = require('discord-api-types/v10'); const CachedManager = require('./CachedManager'); const { DiscordjsError, ErrorCodes } = require('../errors'); const User = require('../structures/User'); @@ -31,6 +31,7 @@ class ReactionUserManager extends CachedManager { /** * Options used to fetch users who gave a reaction. * @typedef {Object} FetchReactionUsersOptions + * @property {ReactionType} [type=ReactionType.Normal] The reaction type to fetch * @property {number} [limit=100] The maximum amount of users to fetch, defaults to `100` * @property {Snowflake} [after] Limit fetching users to those with an id greater than the supplied id */ @@ -40,9 +41,9 @@ class ReactionUserManager extends CachedManager { * @param {FetchReactionUsersOptions} [options] Options for fetching the users * @returns {Promise>} */ - async fetch({ limit = 100, after } = {}) { + async fetch({ type = ReactionType.Normal, limit = 100, after } = {}) { const message = this.reaction.message; - const query = makeURLSearchParams({ limit, after }); + const query = makeURLSearchParams({ limit, after, type }); const data = await this.client.rest.get( Routes.channelMessageReaction(message.channelId, message.id, this.reaction.emoji.identifier), { query }, diff --git a/packages/discord.js/src/structures/MessageReaction.js b/packages/discord.js/src/structures/MessageReaction.js index 43f05e32b417..b6230bacb2ec 100644 --- a/packages/discord.js/src/structures/MessageReaction.js +++ b/packages/discord.js/src/structures/MessageReaction.js @@ -31,6 +31,12 @@ class MessageReaction { */ this.me = data.me; + /** + * Whether the client has super-reacted using this emoji + * @type {boolean} + */ + this.meBurst = data.me_burst; + /** * A manager of the users that have given this reaction * @type {ReactionUserManager} @@ -39,10 +45,20 @@ class MessageReaction { this._emoji = new ReactionEmoji(this, data.emoji); + this.burstColors = null; + this._patch(data); } _patch(data) { + if ('burst_colors' in data) { + /** + * Hexadecimal colors used for this super reaction + * @type {?string[]} + */ + this.burstColors = data.burst_colors; + } + if ('count' in data) { /** * The number of people that have given the same reaction @@ -50,6 +66,24 @@ class MessageReaction { */ this.count ??= data.count; } + + if ('count_details' in data) { + /** + * The reaction count details object contains information about super and normal reaction counts. + * @typedef {Object} ReactionCountDetailsData + * @property {number} burst Count of super reactions + * @property {number} normal Count of normal reactions + */ + + /** + * The reaction count details object contains information about super and normal reaction counts. + * @type {ReactionCountDetailsData} + */ + this.countDetails = { + burst: data.count_details.burst, + normal: data.count_details.normal, + }; + } } /** @@ -121,18 +155,31 @@ class MessageReaction { return this._emoji.id ?? this._emoji.name; } - _add(user) { + _add(user, burst) { if (this.partial) return; this.users.cache.set(user.id, user); - if (!this.me || user.id !== this.message.client.user.id || this.count === 0) this.count++; - this.me ||= user.id === this.message.client.user.id; + if (!this.me || user.id !== this.message.client.user.id || this.count === 0) { + this.count++; + if (burst) this.countDetails.burst++; + else this.countDetails.normal++; + } + if (user.id === this.message.client.user.id) { + if (burst) this.meBurst = true; + else this.me = true; + } } - - _remove(user) { + _remove(user, burst) { if (this.partial) return; this.users.cache.delete(user.id); - if (!this.me || user.id !== this.message.client.user.id) this.count--; - if (user.id === this.message.client.user.id) this.me = false; + if (!this.me || user.id !== this.message.client.user.id) { + this.count--; + if (burst) this.countDetails.burst--; + else this.countDetails.normal--; + } + if (user.id === this.message.client.user.id) { + if (burst) this.meBurst = false; + else this.me = false; + } if (this.count <= 0 && this.users.cache.size === 0) { this.message.reactions.cache.delete(this.emoji.id ?? this.emoji.name); } diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index a7b00f808e01..53429f66e45b 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -181,6 +181,7 @@ import { APISelectMenuDefaultValue, SelectMenuDefaultValueType, InviteType, + ReactionType, } from 'discord-api-types/v10'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -2414,10 +2415,13 @@ export class MessageReaction { private constructor(client: Client, data: RawMessageReactionData, message: Message); private _emoji: GuildEmoji | ReactionEmoji; + public burstColors: string[] | null; public readonly client: Client; public count: number; + public countDetails: ReactionCountDetailsData; public get emoji(): GuildEmoji | ReactionEmoji; public me: boolean; + public meBurst: boolean; public message: Message | PartialMessage; public get partial(): false; public users: ReactionUserManager; @@ -2428,6 +2432,10 @@ export class MessageReaction { public valueOf(): Snowflake | string; } +export interface MessageReactionEventDetails { + burst: boolean; +} + export interface ModalComponentData { customId: string; title: string; @@ -5294,8 +5302,16 @@ export interface ClientEvents { messages: ReadonlyCollection, channel: GuildTextBasedChannel, ]; - messageReactionAdd: [reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser]; - messageReactionRemove: [reaction: MessageReaction | PartialMessageReaction, user: User | PartialUser]; + messageReactionAdd: [ + reaction: MessageReaction | PartialMessageReaction, + user: User | PartialUser, + details: MessageReactionEventDetails, + ]; + messageReactionRemove: [ + reaction: MessageReaction | PartialMessageReaction, + user: User | PartialUser, + details: MessageReactionEventDetails, + ]; messageUpdate: [oldMessage: Message | PartialMessage, newMessage: Message | PartialMessage]; presenceUpdate: [oldPresence: Presence | null, newPresence: Presence]; ready: [client: Client]; @@ -5740,6 +5756,7 @@ export interface FetchMessagesOptions { } export interface FetchReactionUsersOptions { + type?: ReactionType; limit?: number; after?: Snowflake; } @@ -6475,6 +6492,11 @@ export interface MessageSelectOption { value: string; } +export interface ReactionCountDetailsData { + burst: number; + normal: number; +} + export interface SelectMenuComponentOptionData { default?: boolean; description?: string;