From b0047c424b5417287d2ea64ed84538931935b698 Mon Sep 17 00:00:00 2001 From: izexi <43889168+izexi@users.noreply.github.com> Date: Tue, 10 Sep 2019 15:09:06 +0100 Subject: [PATCH] =?UTF-8?q?feat(Partials):=20add=20DMChannel/MessageReacti?= =?UTF-8?q?on#fetch()=20and=20Parti=E2=80=A6=20(#3261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add DMChannel#fetch() & Action#getChannel({recipients}) * ref for MessageReaction partial * typings * add PartialTypes.REACTION * accommodate for fully removed reactions * fix incorrect wording and typo * typings: MessageReaction#count is nullable * typings: mark MessageReaction#partial as readonly Co-Authored-By: Vlad Frangu * fix(User): fetch dm channel if cached one is partial * docs: add missing comma Co-Authored-By: Antonio Román --- docs/topics/partials.md | 10 ++++--- src/client/actions/Action.js | 5 ++-- src/client/actions/MessageReactionAdd.js | 7 ++--- src/stores/ReactionStore.js | 22 +++++++++++++++ src/structures/DMChannel.js | 10 ++++++- src/structures/MessageReaction.js | 34 +++++++++++++++++++----- src/structures/User.js | 2 +- src/util/Constants.js | 2 ++ typings/index.d.ts | 8 ++++-- 9 files changed, 80 insertions(+), 20 deletions(-) diff --git a/docs/topics/partials.md b/docs/topics/partials.md index 2566f3def..c5c7200da 100644 --- a/docs/topics/partials.md +++ b/docs/topics/partials.md @@ -9,8 +9,8 @@ discard the event. With partials, you're able to receive the event, with a Messa Partials are opt-in, and you can enable them in the Client options by specifying [PartialTypes](../typedef/PartialType): ```js -// Accept partial messages and DM channels when emitting events -new Client({ partials: ['MESSAGE', 'CHANNEL'] }); +// Accept partial messages, DM channels, and reactions when emitting events +new Client({ partials: ['MESSAGE', 'CHANNEL', 'REACTION'] }); ``` ## Usage & warnings @@ -45,6 +45,10 @@ client.on('messageReactionAdd', async (reaction, user) => { if (reaction.message.partial) await reaction.message.fetch(); // Now the message has been cached and is fully available: console.log(`${reaction.message.author}'s message "${reaction.message.content}" gained a reaction!`); + // Fetches and caches the reaction itself, updating resources that were possibly defunct. + if (reaction.partial) await reaction.fetch(); + // Now the reaction is fully available and the properties will be reflected accurately: + console.log(`${reaction.count} user(s) have given the same reaction this message!`); }); ``` @@ -58,4 +62,4 @@ bot or any bot that relies on still receiving updates to resources you don't hav good example. Currently, the only type of channel that can be uncached is a DM channel, there is no reason why guild channels should -not be cached. \ No newline at end of file +not be cached. diff --git a/src/client/actions/Action.js b/src/client/actions/Action.js index 7f21318b4..bc9ed267a 100644 --- a/src/client/actions/Action.js +++ b/src/client/actions/Action.js @@ -36,6 +36,7 @@ class GenericAction { return data.channel || this.getPayload({ id, guild_id: data.guild_id, + recipients: [data.author || { id: data.user_id }], }, this.client.channels, id, PartialTypes.CHANNEL); } @@ -52,9 +53,9 @@ class GenericAction { const id = data.emoji.id || decodeURIComponent(data.emoji.name); return this.getPayload({ emoji: data.emoji, - count: 0, + count: message.partial ? null : 0, me: user.id === this.client.user.id, - }, message.reactions, id, PartialTypes.MESSAGE); + }, message.reactions, id, PartialTypes.REACTION); } getMember(data, guild) { diff --git a/src/client/actions/MessageReactionAdd.js b/src/client/actions/MessageReactionAdd.js index e7ae7e26a..721d9251d 100644 --- a/src/client/actions/MessageReactionAdd.js +++ b/src/client/actions/MessageReactionAdd.js @@ -26,11 +26,8 @@ class MessageReactionAdd extends Action { if (!message) return false; // Verify reaction - const reaction = message.reactions.add({ - emoji: data.emoji, - count: 0, - me: user.id === this.client.user.id, - }); + const reaction = this.getReaction(data, message, user); + if (!reaction) return false; reaction._add(user); /** * Emitted whenever a reaction is added to a cached message. diff --git a/src/stores/ReactionStore.js b/src/stores/ReactionStore.js index b3910ba4c..29b53849c 100644 --- a/src/stores/ReactionStore.js +++ b/src/stores/ReactionStore.js @@ -50,6 +50,28 @@ class ReactionStore extends DataStore { return this.client.api.channels(this.message.channel.id).messages(this.message.id).reactions.delete() .then(() => this.message); } + + _partial(emoji) { + const id = emoji.id || emoji.name; + const existing = this.get(id); + return !existing || existing.partial; + } + + async _fetchReaction(reactionEmoji, cache) { + const id = reactionEmoji.id || reactionEmoji.name; + const existing = this.get(id); + if (!this._partial(reactionEmoji)) return existing; + const data = await this.client.api.channels(this.message.channel.id).messages(this.message.id).get(); + if (!data.reactions || !data.reactions.some(r => (r.emoji.id || r.emoji.name) === id)) { + reactionEmoji.reaction._patch({ count: 0 }); + this.message.reactions.remove(id); + return existing; + } + for (const reaction of data.reactions) { + if (this._partial(reaction.emoji)) this.add(reaction, cache); + } + return existing; + } } module.exports = ReactionStore; diff --git a/src/structures/DMChannel.js b/src/structures/DMChannel.js index e172f2260..6c034ec92 100644 --- a/src/structures/DMChannel.js +++ b/src/structures/DMChannel.js @@ -56,7 +56,15 @@ class DMChannel extends Channel { * @readonly */ get partial() { - return !this.recipient; + return this.lastMessageID === undefined; + } + + /** + * Fetch this DMChannel. + * @returns {Promise} + */ + fetch() { + return this.recipient.createDM(); } /** diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index fe10e428f..cd2425952 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -27,12 +27,6 @@ class MessageReaction { */ this.me = data.me; - /** - * The number of people that have given the same reaction - * @type {number} - */ - this.count = data.count || 0; - /** * The users that have given this reaction, mapped by their ID * @type {ReactionUserStore} @@ -40,6 +34,16 @@ class MessageReaction { this.users = new ReactionUserStore(client, undefined, this); this._emoji = new ReactionEmoji(this, data.emoji); + + this._patch(data); + } + + _patch(data) { + /** + * The number of people that have given the same reaction + * @type {?number} + */ + this.count = typeof data.count === 'number' ? data.count : null; } /** @@ -63,18 +67,36 @@ class MessageReaction { return this._emoji; } + /** + * Whether or not this reaction is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return this.count === null; + } + + /** + * Fetch this reaction. + * @returns {Promise} + */ + fetch() { + return this.message.reactions._fetchReaction(this.emoji, true); + } toJSON() { return Util.flatten(this, { emoji: 'emojiID', message: 'messageID' }); } _add(user) { + if (this.partial) return; this.users.set(user.id, user); if (!this.me || user.id !== this.message.client.user.id || this.count === 0) this.count++; if (!this.me) this.me = user.id === this.message.client.user.id; } _remove(user) { + if (this.partial) return; this.users.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; diff --git a/src/structures/User.js b/src/structures/User.js index c2276c4f2..4425850f8 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -211,7 +211,7 @@ class User extends Base { */ async createDM() { const { dmChannel } = this; - if (dmChannel) return dmChannel; + if (dmChannel && !dmChannel.partial) return dmChannel; const data = await this.client.api.users(this.client.user.id).channels.post({ data: { recipient_id: this.id, } }); diff --git a/src/util/Constants.js b/src/util/Constants.js index 235913287..89b14c6a0 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -287,6 +287,7 @@ exports.ShardEvents = { * * CHANNEL (only affects DMChannels) * * GUILD_MEMBER * * MESSAGE + * * REACTION * Partials require you to put checks in place when handling data, read the Partials topic listed in the * sidebar for more information. * @typedef {string} PartialType @@ -296,6 +297,7 @@ exports.PartialTypes = keyMirror([ 'CHANNEL', 'GUILD_MEMBER', 'MESSAGE', + 'REACTION', ]); /** diff --git a/typings/index.d.ts b/typings/index.d.ts index af2400847..28946efa3 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -653,6 +653,7 @@ declare module 'discord.js' { public messages: MessageStore; public recipient: User; public readonly partial: boolean; + public fetch(): Promise; } export class Emoji extends Base { @@ -1091,11 +1092,13 @@ declare module 'discord.js' { constructor(client: Client, data: object, message: Message); private _emoji: GuildEmoji | ReactionEmoji; - public count: number; + public count: number | null; public readonly emoji: GuildEmoji | ReactionEmoji; public me: boolean; public message: Message; + public readonly partial: boolean; public users: ReactionUserStore; + public fetch(): Promise; public toJSON(): object; } @@ -2452,7 +2455,8 @@ declare module 'discord.js' { type PartialTypes = 'USER' | 'CHANNEL' | 'GUILD_MEMBER' - | 'MESSAGE'; + | 'MESSAGE' + | 'REACTION'; type PresenceStatus = ClientPresenceStatus | 'offline';