diff --git a/src/client/actions/Action.js b/src/client/actions/Action.js index bc9ed267ade9..ce4fd6db2221 100644 --- a/src/client/actions/Action.js +++ b/src/client/actions/Action.js @@ -54,7 +54,7 @@ class GenericAction { return this.getPayload({ emoji: data.emoji, count: message.partial ? null : 0, - me: user.id === this.client.user.id, + me: user ? user.id === this.client.user.id : false, }, message.reactions, id, PartialTypes.REACTION); } diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js index 268e4371bb7a..0c1d734b3b25 100644 --- a/src/client/actions/ActionsManager.js +++ b/src/client/actions/ActionsManager.js @@ -11,6 +11,7 @@ class ActionsManager { this.register(require('./MessageReactionAdd')); this.register(require('./MessageReactionRemove')); this.register(require('./MessageReactionRemoveAll')); + this.register(require('./MessageReactionRemoveEmoji')); this.register(require('./ChannelCreate')); this.register(require('./ChannelDelete')); this.register(require('./ChannelUpdate')); diff --git a/src/client/actions/MessageReactionRemoveEmoji.js b/src/client/actions/MessageReactionRemoveEmoji.js new file mode 100644 index 000000000000..ab0eaa7708ea --- /dev/null +++ b/src/client/actions/MessageReactionRemoveEmoji.js @@ -0,0 +1,28 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class MessageReactionRemoveEmoji extends Action { + handle(data) { + const channel = this.getChannel(data); + if (!channel || channel.type === 'voice') return false; + + const message = this.getMessage(data, channel); + if (!message) return false; + + const reaction = this.getReaction(data, message); + if (!reaction) return false; + if (!message.partial) message.reactions.delete(reaction.emoji.id || reaction.emoji.name); + + /** + * Emitted when a bot removes an emoji reaction from a cached message. + * @event Client#messageReactionRemoveEmoji + * @param {MessageReaction} reaction The reaction that was removed + */ + this.client.emit(Events.MESSAGE_REACTION_REMOVE_EMOJI, reaction); + return { reaction }; + } +} + +module.exports = MessageReactionRemoveEmoji; diff --git a/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js b/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js new file mode 100644 index 000000000000..579444c97da5 --- /dev/null +++ b/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageReactionRemoveEmoji.handle(packet.d); +}; diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index f32f0843cd45..5cb9210fb77e 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -15,6 +15,13 @@ class MessageReaction { * @param {Message} message The message the reaction refers to */ constructor(client, data, message) { + /** + * The client that instantiated this message reaction + * @name MessageReaction#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); /** * The message that this reaction refers to * @type {Message} @@ -47,6 +54,16 @@ class MessageReaction { if (this.count == undefined) this.count = data.count; } + /** + * Removes all users from this reaction. + * @returns {Promise} + */ + async remove() { + await this.client.api.channels(this.message.channel.id).messages(this.message.id).reactions(this._emoji.identifier) + .delete(); + return this; + } + /** * The emoji of this reaction, either an GuildEmoji object for known custom emojis, or a ReactionEmoji * object which has fewer properties. Whatever the prototype of the emoji, it will still have diff --git a/src/util/Constants.js b/src/util/Constants.js index a2e86d4ae629..e89e52e6c859 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -260,6 +260,7 @@ exports.Events = { MESSAGE_REACTION_ADD: 'messageReactionAdd', MESSAGE_REACTION_REMOVE: 'messageReactionRemove', MESSAGE_REACTION_REMOVE_ALL: 'messageReactionRemoveAll', + MESSAGE_REACTION_REMOVE_EMOJI: 'messageReactionRemoveEmoji', USER_UPDATE: 'userUpdate', PRESENCE_UPDATE: 'presenceUpdate', VOICE_SERVER_UPDATE: 'voiceServerUpdate', @@ -375,6 +376,7 @@ exports.WSEvents = keyMirror([ 'MESSAGE_REACTION_ADD', 'MESSAGE_REACTION_REMOVE', 'MESSAGE_REACTION_REMOVE_ALL', + 'MESSAGE_REACTION_REMOVE_EMOJI', 'USER_UPDATE', 'PRESENCE_UPDATE', 'TYPING_START', diff --git a/typings/index.d.ts b/typings/index.d.ts index 6d719a347bc1..26d644f6e9db 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -190,6 +190,7 @@ declare module 'discord.js' { public on(event: 'guildUpdate', listener: (oldGuild: Guild, newGuild: Guild) => void): this; public on(event: 'guildIntegrationsUpdate', listener: (guild: Guild) => void): this; public on(event: 'message' | 'messageDelete' | 'messageReactionRemoveAll', listener: (message: Message | PartialMessage) => void): this; + public on(event: 'messageReactionRemoveEmoji', listener: (reaction: MessageReaction) => void): this; public on(event: 'messageDeleteBulk', listener: (messages: Collection) => void): this; public on(event: 'messageReactionAdd' | 'messageReactionRemove', listener: (messageReaction: MessageReaction, user: User | PartialUser) => void): this; public on(event: 'messageUpdate', listener: (oldMessage: Message | PartialMessage, newMessage: Message | PartialMessage) => void): this; @@ -1111,6 +1112,7 @@ declare module 'discord.js' { public message: Message; public readonly partial: boolean; public users: ReactionUserStore; + public remove(): Promise; public fetch(): Promise; public toJSON(): object; }