From 81059885a28c0f5871cfac8a29ca69af6579e71f Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 15:22:42 +0100 Subject: [PATCH] Start work on adding reaction support --- src/client/actions/ActionsManager.js | 2 + src/client/actions/MessageReactionAdd.js | 38 ++++++++++++++++ src/client/actions/MessageReactionRemove.js | 38 ++++++++++++++++ src/client/rest/RESTMethods.js | 31 +++++++++++++ .../packets/WebSocketPacketManager.js | 2 + .../packets/handlers/MessageReactionAdd.js | 11 +++++ .../packets/handlers/MessageReactionRemove.js | 11 +++++ src/structures/Message.js | 43 ++++++++++++++++++- src/structures/MessageReaction.js | 41 ++++++++++++++++++ src/util/Constants.js | 13 ++++++ src/util/parseEmoji.js | 11 +++++ test/random.js | 14 +++++- 12 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 src/client/actions/MessageReactionAdd.js create mode 100644 src/client/actions/MessageReactionRemove.js create mode 100644 src/client/websocket/packets/handlers/MessageReactionAdd.js create mode 100644 src/client/websocket/packets/handlers/MessageReactionRemove.js create mode 100644 src/structures/MessageReaction.js create mode 100644 src/util/parseEmoji.js diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js index 024db857c..dbfdc19dd 100644 --- a/src/client/actions/ActionsManager.js +++ b/src/client/actions/ActionsManager.js @@ -6,6 +6,8 @@ class ActionsManager { this.register('MessageDelete'); this.register('MessageDeleteBulk'); this.register('MessageUpdate'); + this.register('MessageReactionAdd'); + this.register('MessageReactionRemove'); this.register('ChannelCreate'); this.register('ChannelDelete'); this.register('ChannelUpdate'); diff --git a/src/client/actions/MessageReactionAdd.js b/src/client/actions/MessageReactionAdd.js new file mode 100644 index 000000000..c53af3828 --- /dev/null +++ b/src/client/actions/MessageReactionAdd.js @@ -0,0 +1,38 @@ +const Action = require('./Action'); +const Constants = require('../../util/Constants'); + +/* +{ user_id: 'id', + message_id: 'id', + emoji: { name: '�', id: null }, + channel_id: 'id' } } +*/ + +class MessageReactionAdd extends Action { + handle(data) { + const user = this.client.users.get(data.user_id); + if (!user) return false; + + 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; + + if (!data.emoji) return false; + + const reaction = message._addReaction(data.emoji, user); + + if (reaction) { + this.client.emit(Constants.Events.MESSAGE_REACTION_ADD, reaction, user); + } + + return { + message, + reaction, + user, + }; + } +} + +module.exports = MessageReactionAdd; diff --git a/src/client/actions/MessageReactionRemove.js b/src/client/actions/MessageReactionRemove.js new file mode 100644 index 000000000..ae626ea00 --- /dev/null +++ b/src/client/actions/MessageReactionRemove.js @@ -0,0 +1,38 @@ +const Action = require('./Action'); +const Constants = require('../../util/Constants'); + +/* +{ user_id: 'id', + message_id: 'id', + emoji: { name: '�', id: null }, + channel_id: 'id' } } +*/ + +class MessageReactionRemove extends Action { + handle(data) { + const user = this.client.users.get(data.user_id); + if (!user) return false; + + 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; + + if (!data.emoji) return false; + + const reaction = message._removeReaction(data.emoji, user); + + if (reaction) { + this.client.emit(Constants.Events.MESSAGE_REACTION_REMOVE, reaction, user); + } + + return { + message, + reaction, + user, + }; + } +} + +module.exports = MessageReactionRemove; diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 1555d4e95..a3f99d286 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -1,6 +1,7 @@ const Constants = require('../../util/Constants'); const Collection = require('../../util/Collection'); const splitMessage = require('../../util/SplitMessage'); +const parseEmoji = require('../../util/ParseEmoji'); const requireStructure = name => require(`../../structures/${name}`); const User = requireStructure('User'); @@ -720,6 +721,36 @@ class RESTMethods { .catch(reject); }); } + + addMessageReaction(channelID, messageID, emoji) { + return new Promise((resolve, reject) => { + this.rest.makeRequest('put', Constants.Endpoints.selfMessageReaction(channelID, messageID, emoji), true) + .then(() => { + resolve(this.rest.client.actions.MessageReactionAdd.handle({ + user_id: this.rest.client.user.id, + message_id: messageID, + emoji: parseEmoji(emoji), + channel_id: channelID, + }).reaction); + }) + .catch(reject); + }); + } + + removeMessageReaction(channelID, messageID, emoji) { + return new Promise((resolve, reject) => { + this.rest.makeRequest('delete', Constants.Endpoints.selfMessageReaction(channelID, messageID, emoji), true) + .then(() => { + resolve(this.rest.client.actions.MessageReactionRemove.handle({ + user_id: this.rest.client.user.id, + message_id: messageID, + emoji: parseEmoji(emoji), + channel_id: channelID, + }).reaction); + }) + .catch(reject); + }); + } } module.exports = RESTMethods; diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 6d49ee257..99ae348b7 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -44,6 +44,8 @@ class WebSocketPacketManager { this.register(Constants.WSEvents.GUILD_SYNC, 'GuildSync'); this.register(Constants.WSEvents.RELATIONSHIP_ADD, 'RelationshipAdd'); this.register(Constants.WSEvents.RELATIONSHIP_REMOVE, 'RelationshipRemove'); + this.register(Constants.WSEvents.MESSAGE_REACTION_ADD, 'MessageReactionAdd'); + this.register(Constants.WSEvents.MESSAGE_REACTION_REMOVE, 'MessageReactionRemove'); } get client() { diff --git a/src/client/websocket/packets/handlers/MessageReactionAdd.js b/src/client/websocket/packets/handlers/MessageReactionAdd.js new file mode 100644 index 000000000..6a5702efc --- /dev/null +++ b/src/client/websocket/packets/handlers/MessageReactionAdd.js @@ -0,0 +1,11 @@ +const AbstractHandler = require('./AbstractHandler'); + +class MessageReactionAddHandler extends AbstractHandler { + handle(packet) { + const client = this.packetManager.client; + const data = packet.d; + client.actions.MessageReactionAdd.handle(data); + } +} + +module.exports = MessageReactionAddHandler; diff --git a/src/client/websocket/packets/handlers/MessageReactionRemove.js b/src/client/websocket/packets/handlers/MessageReactionRemove.js new file mode 100644 index 000000000..2afaee703 --- /dev/null +++ b/src/client/websocket/packets/handlers/MessageReactionRemove.js @@ -0,0 +1,11 @@ +const AbstractHandler = require('./AbstractHandler'); + +class MessageReactionRemove extends AbstractHandler { + handle(packet) { + const client = this.packetManager.client; + const data = packet.d; + client.actions.MessageReactionRemove.handle(data); + } +} + +module.exports = MessageReactionRemove; diff --git a/src/structures/Message.js b/src/structures/Message.js index e9e093ac5..494c45779 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -3,6 +3,7 @@ const Embed = require('./MessageEmbed'); const Collection = require('../util/Collection'); const Constants = require('../util/Constants'); const escapeMarkdown = require('../util/EscapeMarkdown'); +const MessageReaction = require('./MessageReaction'); /** * Represents a Message on Discord @@ -148,6 +149,42 @@ class Message { } this._edits = []; + + /** + * A collection of Reactions to this Message, mapped by the reaction "id". + * @type {Collection} + */ + this.reactions = new Collection(); + } + + _addReaction(emoji, user) { + const emojiID = emoji.id || emoji; + let reaction; + if (this.reactions.has(emojiID)) { + reaction = this.reactions.get(emojiID); + } else { + reaction = new MessageReaction(this, emoji, 0); + this.reactions.set(emojiID, reaction); + } + if (!reaction.users.has(user.id)) { + reaction.users.set(user.id, user); + reaction.count++; + return reaction; + } + return null; + } + + _removeReaction(emoji, user) { + const emojiID = emoji.id || emoji; + if (this.reactions.has(emojiID)) { + const reaction = this.reactions.get(emojiID); + if (reaction.users.has(user.id)) { + reaction.users.delete(user.id); + reaction.count--; + return reaction; + } + } + return null; } patch(data) { // eslint-disable-line complexity @@ -266,7 +303,11 @@ class Message { }); } - /** + addReaction(emoji) { + return this.client.rest.methods.addMessageReaction(this.channel.id, this.id, emoji); + } + + /** * An array of cached versions of the message, including the current version. * Sorted from latest (first) to oldest (last). * @type {Message[]} diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js new file mode 100644 index 000000000..cab5bcd1f --- /dev/null +++ b/src/structures/MessageReaction.js @@ -0,0 +1,41 @@ +const Collection = require('../util/Collection'); +const parseEmoji = require('../util/ParseEmoji'); + +/** + * Represents a reaction to a message + */ +class MessageReaction { + constructor(message, emoji, count) { + /** + * The message that this reaction refers to + * @type {Message} + */ + this.message = message; + /** + * The emoji of this reaction, if this is a string it is a plain unicode emoji. + * @type {Emoji} + */ + this.emoji = emoji; + /** + * The number of people that have given the same reaction. + * @type {number} + */ + this.count = count || 0; + /** + * The users that have given this reaction, mapped by their ID. + * @type {Collection} + */ + this.users = new Collection(); + } + + /** + * If the client has given this reaction to a message, it is removed. + * @returns {Promise} + */ + remove() { + const message = this.message; + return message.client.rest.methods.removeMessageReaction(message.channel.id, message.id, parseEmoji(this.emoji)); + } +} + +module.exports = MessageReaction; diff --git a/src/util/Constants.js b/src/util/Constants.js index 60575cd61..28540cb8f 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -115,6 +115,15 @@ const Endpoints = exports.Endpoints = { channelMessage: (channelID, messageID) => `${Endpoints.channelMessages(channelID)}/${messageID}`, channelWebhooks: (channelID) => `${Endpoints.channel(channelID)}/webhooks`, + // message reactions + messageReactions: (channelID, messageID) => `${Endpoints.channelMessage(channelID, messageID)}/reactions`, + messageReaction: + (channel, msg, emoji, limit) => + `${Endpoints.messageReactions(channel, msg)}/${emoji}` + + `${limit ? `?limit=${limit}` : ''}`, + selfMessageReaction: (channel, msg, emoji, limit) => + `${Endpoints.messageReaction(channel, msg, emoji, limit)}/@me`, + // webhooks webhook: (webhookID, token) => `${API}/webhooks/${webhookID}${token ? `/${token}` : ''}`, }; @@ -187,6 +196,8 @@ exports.Events = { MESSAGE_DELETE: 'messageDelete', MESSAGE_UPDATE: 'messageUpdate', MESSAGE_BULK_DELETE: 'messageDeleteBulk', + MESSAGE_REACTION_ADD: 'messageReactionAdd', + MESSAGE_REACTION_REMOVE: 'messageReactionRemove', USER_UPDATE: 'userUpdate', PRESENCE_UPDATE: 'presenceUpdate', VOICE_STATE_UPDATE: 'voiceStateUpdate', @@ -222,6 +233,8 @@ exports.WSEvents = { MESSAGE_DELETE: 'MESSAGE_DELETE', MESSAGE_UPDATE: 'MESSAGE_UPDATE', MESSAGE_DELETE_BULK: 'MESSAGE_DELETE_BULK', + MESSAGE_REACTION_ADD: 'MESSAGE_REACTION_ADD', + MESSAGE_REACTION_REMOVE: 'MESSAGE_REACTION_REMOVE', USER_UPDATE: 'USER_UPDATE', PRESENCE_UPDATE: 'PRESENCE_UPDATE', VOICE_STATE_UPDATE: 'VOICE_STATE_UPDATE', diff --git a/src/util/parseEmoji.js b/src/util/parseEmoji.js new file mode 100644 index 000000000..4bc1d1145 --- /dev/null +++ b/src/util/parseEmoji.js @@ -0,0 +1,11 @@ +module.exports = function parseEmoji(text) { + if (text.includes(':')) { + const [name, id] = text.split(':'); + return { name, id }; + } else { + return { + name: text, + id: null, + }; + } +}; diff --git a/test/random.js b/test/random.js index efc8662f6..1e6df7642 100644 --- a/test/random.js +++ b/test/random.js @@ -20,7 +20,7 @@ client.on('userUpdate', (o, n) => { console.log(o.username, n.username); }); -client.on('guildMemberAdd', (g, m) => console.log(`${m.user.username} joined ${g.name}`)); +client.on('guildMemberAdd', m => console.log(`${m.user.username} joined ${m.guild.name}`)); client.on('channelCreate', channel => { console.log(`made ${channel.name}`); @@ -176,4 +176,14 @@ client.on('message', msg => { }) .catch(console.error); } -}) +}); + +client.on('messageReactionAdd', (reaction, user) => { + if (reaction.message.channel.id !== '222086648706498562') return; + reaction.message.channel.sendMessage(`${user.username} added reaction ${reaction.emoji}, count is now ${reaction.count}`); +}); + +client.on('messageReactionRemove', (reaction, user) => { + if (reaction.message.channel.id !== '222086648706498562') return; + reaction.message.channel.sendMessage(`${user.username} removed reaction ${reaction.emoji}, count is now ${reaction.count}`); +});