diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 11f7ef23e..dfdc7ae2e 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -8,6 +8,8 @@ const Message = require(`../structures/Message`); const Guild = require(`../structures/Guild`); const Channel = require(`../structures/Channel`); const GuildMember = require(`../structures/GuildMember`); +const Emoji = require(`../structures/Emoji`); +const ReactionEmoji = require(`../structures/ReactionEmoji`); /** * The DataResolver identifies different objects and tries to resolve a specific piece of information from them, e.g. @@ -264,6 +266,27 @@ class ClientDataResolver { return Promise.reject(new TypeError('The resource must be a string or Buffer.')); } + + /** + * Data that can be resolved to give an emoji identifier. This can be: + * * A string + * * An Emoji + * * A ReactionEmoji + * @typedef {string|Emoji|ReactionEmoji} EmojiIdentifierResolvable + */ + + /** + * Resolves an EmojiResolvable to an emoji identifier + * @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve + * @returns {string} + */ + resolveEmojiIdentifier(emoji) { + if (emoji instanceof Emoji || emoji instanceof ReactionEmoji) return emoji.identifier; + if (typeof emoji === 'string') { + if (!emoji.includes('%')) return encodeURIComponent(emoji); + } + return null; + } } module.exports = ClientDataResolver; diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js index e4545fd73..1986f5a77 100644 --- a/src/client/actions/ActionsManager.js +++ b/src/client/actions/ActionsManager.js @@ -8,6 +8,7 @@ class ActionsManager { this.register('MessageUpdate'); this.register('MessageReactionAdd'); this.register('MessageReactionRemove'); + this.register('MessageReactionRemoveAll'); this.register('ChannelCreate'); this.register('ChannelDelete'); this.register('ChannelUpdate'); diff --git a/src/client/actions/MessageReactionRemoveAll.js b/src/client/actions/MessageReactionRemoveAll.js new file mode 100644 index 000000000..54f38a6c4 --- /dev/null +++ b/src/client/actions/MessageReactionRemoveAll.js @@ -0,0 +1,21 @@ +const Action = require('./Action'); +const Constants = require('../../util/Constants'); + +class MessageReactionRemoveAll extends Action { + handle(data) { + const channel = this.client.channels.get(data.channel_id); + if (!channel || channel.type === 'voice') return false; + + const message = channel.messages.get(data.message_id); + if (!message) return false; + + message._clearReactions(); + this.client.emit(Constants.Events.MESSAGE_REACTION_REMOVE_ALL, message); + + return { + message, + }; + } +} + +module.exports = MessageReactionRemoveAll; diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index b9898016d..cadaee76a 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -582,35 +582,40 @@ class RESTMethods { ); } - addMessageReaction(channelID, messageID, emoji) { - return this.rest.makeRequest('put', Constants.Endpoints.selfMessageReaction(channelID, messageID, emoji), true) + addMessageReaction(message, emoji) { + return this.rest.makeRequest('put', Constants.Endpoints.selfMessageReaction(message.channel.id, message.id, emoji), true) .then(() => this.rest.client.actions.MessageReactionAdd.handle({ user_id: this.rest.client.user.id, - message_id: messageID, + message_id: message.id, emoji: parseEmoji(emoji), - channel_id: channelID, + channel_id: message.channel.id, }).reaction ); } - removeMessageReaction(channelID, messageID, emoji, userID) { - let endpoint = Constants.Endpoints.selfMessageReaction(channelID, messageID, emoji); - if (userID !== this.rest.client.user.id) { - endpoint = Constants.Endpoints.userMessageReaction(channelID, messageID, emoji, null, userID); + removeMessageReaction(message, emoji, user) { + let endpoint = Constants.Endpoints.selfMessageReaction(message.channel.id, message.id, emoji); + if (user.id !== this.rest.client.user.id) { + endpoint = Constants.Endpoints.userMessageReaction(message.channel.id, message.id, emoji, null, user.id); } return this.rest.makeRequest('delete', endpoint, true).then(() => this.rest.client.actions.MessageReactionRemove.handle({ - user_id: userID, - message_id: messageID, + user_id: user.id, + message_id: message.id, emoji: parseEmoji(emoji), - channel_id: channelID, + channel_id: message.channel.id, }).reaction ); } - getMessageReactionUsers(channelID, messageID, emoji, limit = 100) { - return this.rest.makeRequest('get', Constants.Endpoints.messageReaction(channelID, messageID, emoji, limit), true); + removeMessageReactions(message) { + this.rest.makeRequest('delete', Constants.Endpoints.messageReactions(message.channel.id, message.id), true) + .then(() => message); + } + + getMessageReactionUsers(message, emoji, limit = 100) { + return this.rest.makeRequest('get', Constants.Endpoints.messageReaction(message.channel.id, message.id, emoji, limit), true); } getMyApplication() { diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index ecff0eec4..e2bdcf3f8 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -47,6 +47,7 @@ class WebSocketPacketManager { this.register(Constants.WSEvents.RELATIONSHIP_REMOVE, 'RelationshipRemove'); this.register(Constants.WSEvents.MESSAGE_REACTION_ADD, 'MessageReactionAdd'); this.register(Constants.WSEvents.MESSAGE_REACTION_REMOVE, 'MessageReactionRemove'); + this.register(Constants.WSEvents.MESSAGE_REACTION_REMOVE_ALL, 'MessageReactionRemoveAll'); } get client() { diff --git a/src/client/websocket/packets/handlers/MessageReactionRemoveAll.js b/src/client/websocket/packets/handlers/MessageReactionRemoveAll.js new file mode 100644 index 000000000..303da9ca0 --- /dev/null +++ b/src/client/websocket/packets/handlers/MessageReactionRemoveAll.js @@ -0,0 +1,11 @@ +const AbstractHandler = require('./AbstractHandler'); + +class MessageReactionRemoveAll extends AbstractHandler { + handle(packet) { + const client = this.packetManager.client; + const data = packet.d; + client.actions.MessageReactionRemoveAll.handle(data); + } +} + +module.exports = MessageReactionRemoveAll; diff --git a/src/structures/Message.js b/src/structures/Message.js index 8831d3f4d..434ca6c54 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -26,7 +26,7 @@ class Message { if (data) this.setup(data); } - setup(data) { + setup(data) { // eslint-disable-line complexity /** * The ID of the message (unique in the channel it was sent) * @type {string} @@ -389,20 +389,23 @@ class Message { } /** - * Adds a reaction to the message - * @param {Emoji|ReactionEmoji|string} emoji Emoji to react with + * Add a reaction to the message + * @param {string|Emoji|ReactionEmoji} emoji Emoji to react with * @returns {Promise} */ react(emoji) { - if (emoji.identifier) { - emoji = emoji.identifier; - } else if (typeof emoji === 'string') { - if (!emoji.includes('%')) emoji = encodeURIComponent(emoji); - } else { - return Promise.reject('The emoji must be a string or an Emoji/ReactionEmoji.'); - } + emoji = this.client.resolver.resolveEmojiIdentifier(emoji); + if (!emoji) throw new TypeError('Emoji must be a string or Emoji/ReactionEmoji'); - return this.client.rest.methods.addMessageReaction(this.channel.id, this.id, emoji); + return this.client.rest.methods.addMessageReaction(this, emoji); + } + + /** + * Remove all reactions from a message + * @returns {Promise} + */ + clearReactions() { + return this.client.rest.methods.removeMessageReactions(this); } /** @@ -523,6 +526,10 @@ class Message { } return null; } + + _clearReactions() { + this.reactions.clear(); + } } module.exports = Message; diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index 5f6a215a0..263f2b6b7 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -64,7 +64,7 @@ class MessageReaction { user = this.message.client.resolver.resolveUserID(user); if (!user) return Promise.reject('Couldn\'t resolve the user ID to remove from the reaction.'); return message.client.rest.methods.removeMessageReaction( - message.channel.id, message.id, this.emoji.identifier, user + message, this.emoji.identifier, user ); } @@ -77,7 +77,7 @@ class MessageReaction { fetchUsers(limit = 100) { const message = this.message; return message.client.rest.methods.getMessageReactionUsers( - message.channel.id, message.id, this.emoji.identifier, limit + message, this.emoji.identifier, limit ).then(users => { this.users = new Collection(); for (const rawUser of users) { diff --git a/src/util/Constants.js b/src/util/Constants.js index 167cd13f5..7efe40690 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -207,6 +207,7 @@ exports.Events = { MESSAGE_BULK_DELETE: 'messageDeleteBulk', MESSAGE_REACTION_ADD: 'messageReactionAdd', MESSAGE_REACTION_REMOVE: 'messageReactionRemove', + MESSAGE_REACTION_REMOVE_ALL: 'messageReactionRemoveAll', USER_UPDATE: 'userUpdate', USER_NOTE_UPDATE: 'userNoteUpdate', PRESENCE_UPDATE: 'presenceUpdate', @@ -245,6 +246,7 @@ exports.WSEvents = { MESSAGE_DELETE_BULK: 'MESSAGE_DELETE_BULK', MESSAGE_REACTION_ADD: 'MESSAGE_REACTION_ADD', MESSAGE_REACTION_REMOVE: 'MESSAGE_REACTION_REMOVE', + MESSAGE_REACTION_REMOVE_ALL: 'MESSAGE_REACTION_REMOVE_ALL', USER_UPDATE: 'USER_UPDATE', USER_NOTE_UPDATE: 'USER_NOTE_UPDATE', PRESENCE_UPDATE: 'PRESENCE_UPDATE',