From dd31ee0c5fd04ec3436da93a9e76660499c0b211 Mon Sep 17 00:00:00 2001 From: isonmad Date: Thu, 27 Oct 2016 06:33:51 -0400 Subject: [PATCH 001/248] propagate errors in ClientManager.destroy (#844) If the promise returned by logout() rejects, previously it would be completely uncaught, and just return an eternally pending promise that never resolved. Related to pull requests #828 and #839. --- src/client/ClientManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js index 03d43b375..ef943b339 100644 --- a/src/client/ClientManager.js +++ b/src/client/ClientManager.js @@ -58,10 +58,10 @@ class ClientManager { } destroy() { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this.client.ws.destroy(); if (!this.client.user.bot) { - this.client.rest.methods.logout().then(resolve); + this.client.rest.methods.logout().then(resolve, reject); } else { resolve(); } From 81059885a28c0f5871cfac8a29ca69af6579e71f Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 15:22:42 +0100 Subject: [PATCH 002/248] 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}`); +}); From d129457624160962b61c612d10819c16f7f9b750 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 16:12:02 +0100 Subject: [PATCH 003/248] Improve emoji support --- src/structures/Emoji.js | 12 +++++ src/structures/Message.js | 10 +++- src/structures/MessageReaction.js | 82 ++++++++++++++++++++++++++++--- src/util/parseEmoji.js | 3 ++ 4 files changed, 99 insertions(+), 8 deletions(-) diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js index 0349b6f70..eb64b4b99 100644 --- a/src/structures/Emoji.js +++ b/src/structures/Emoji.js @@ -101,6 +101,18 @@ class Emoji { toString() { return this.requiresColons ? `<:${this.name}:${this.id}>` : this.name; } + + /** + * The identifier of this emoji, used for message reactions + * @readonly + * @type {string} + */ + get identifier() { + if (this.id) { + return `${this.name}:${this.id}`; + } + return encodeURIComponent(this.name); + } } module.exports = Emoji; diff --git a/src/structures/Message.js b/src/structures/Message.js index 494c45779..3c86f8821 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -158,7 +158,7 @@ class Message { } _addReaction(emoji, user) { - const emojiID = emoji.id || emoji; + const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name; let reaction; if (this.reactions.has(emojiID)) { reaction = this.reactions.get(emojiID); @@ -304,6 +304,14 @@ class Message { } addReaction(emoji) { + if (emoji.identifier) { + emoji = emoji.identifier; + } else if (typeof emoji === 'string') { + if (!emoji.includes('%')) emoji = encodeURIComponent(emoji); + } else { + return Promise.reject(`Emoji must be a string or an Emoji/ReactionEmoji`); + } + return this.client.rest.methods.addMessageReaction(this.channel.id, this.id, emoji); } diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index cab5bcd1f..e7f06ab0d 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -1,5 +1,55 @@ const Collection = require('../util/Collection'); -const parseEmoji = require('../util/ParseEmoji'); +const Emoji = require('./Emoji'); + +/** + * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis + * will use this class opposed to the Emoji class when the client doesn't know enough + * information about them. + */ +class ReactionEmoji { + constructor(reaction, name, id) { + /** + * The message reaction this emoji refers to + * @type {MessageReaction} + */ + this.reaction = reaction; + /** + * The name of this reaction emoji. + * @type {string} + */ + this.name = name; + /** + * The ID of this reaction emoji. + * @type {string} + */ + this.id = id; + } + + /** + * The identifier of this emoji, used for message reactions + * @readonly + * @type {string} + */ + get identifier() { + if (this.id) { + return `${this.name}:${this.id}`; + } + return encodeURIComponent(this.name); + } + + /** + * Creates the text required to form a graphical emoji on Discord. + * @example + * ```js + * // send the emoji used in a reaction to the channel the reaction is part of + * reaction.message.channel.sendMessage(`The emoji used is ${reaction.emoji}`); + * ``` + * @returns {string} + */ + toString() { + return this.id ? `<:${this.name}:${this.id}>` : this.name; + } +} /** * Represents a reaction to a message @@ -11,11 +61,8 @@ class MessageReaction { * @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; + + this._emoji = new ReactionEmoji(this, emoji.name, emoji.id); /** * The number of people that have given the same reaction. * @type {number} @@ -28,13 +75,34 @@ class MessageReaction { this.users = new Collection(); } + /** + * The emoji of this reaction, either an Emoji object for known custom emojis, or a ReactionEmoji which has fewer + * properties. Whatever the prototype of the emoji, it will still have `name`, `id`, `identifier` and `toString()` + * @type {Emoji|ReactionEmoji} + */ + get emoji() { + if (this._emoji instanceof Emoji) { + return this._emoji; + } + // check to see if the emoji has become known to the client + if (this._emoji.id) { + const emojis = this.message.client.emojis; + if (emojis.has(this._emoji.id)) { + const emoji = emojis.get(this._emoji.id); + this._emoji = emoji; + return emoji; + } + } + return this._emoji; + } + /** * 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)); + return message.client.rest.methods.removeMessageReaction(message.channel.id, message.id, this.emoji.identifier); } } diff --git a/src/util/parseEmoji.js b/src/util/parseEmoji.js index 4bc1d1145..d9f7b2212 100644 --- a/src/util/parseEmoji.js +++ b/src/util/parseEmoji.js @@ -1,4 +1,7 @@ module.exports = function parseEmoji(text) { + if (text.includes('%')) { + text = decodeURIComponent(text); + } if (text.includes(':')) { const [name, id] = text.split(':'); return { name, id }; From 8e505ed349d39d7fbcc29f70fe96761f5ad6bb42 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 16:30:02 +0100 Subject: [PATCH 004/248] Add Message Reaction me --- src/structures/Message.js | 22 +++++++++++++++++++++- src/structures/MessageReaction.js | 8 ++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 3c86f8821..da485713a 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -155,6 +155,13 @@ class Message { * @type {Collection} */ this.reactions = new Collection(); + + if (data.reactions && data.reactions.length > 0) { + for (const reaction of data.reactions) { + const id = reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name; + this.reactions.set(id, new MessageReaction(this, reaction.emoji, reaction.count, reaction.me)); + } + } } _addReaction(emoji, user) { @@ -162,8 +169,9 @@ class Message { let reaction; if (this.reactions.has(emojiID)) { reaction = this.reactions.get(emojiID); + if (!reaction.me) reaction.me = user.id === this.client.user.id; } else { - reaction = new MessageReaction(this, emoji, 0); + reaction = new MessageReaction(this, emoji, 0, user.id === this.client.user.id); this.reactions.set(emojiID, reaction); } if (!reaction.users.has(user.id)) { @@ -181,6 +189,9 @@ class Message { if (reaction.users.has(user.id)) { reaction.users.delete(user.id); reaction.count--; + if (user.id === this.client.user.id) { + reaction.me = false; + } return reaction; } } @@ -236,6 +247,15 @@ class Message { if (chan) this.mentions.channels.set(chan.id, chan); } } + if (data.reactions) { + this.reactions = new Collection(); + if (data.reactions.length > 0) { + for (const reaction of data.reactions) { + const id = reaction.emoji.id ? `${reaction.emoji.name}:${reaction.emoji.id}` : reaction.emoji.name; + this.reactions.set(id, new MessageReaction(this, data.emoji, data.count, data.me)); + } + } + } } /** diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index e7f06ab0d..77d6a1bd4 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -55,13 +55,17 @@ class ReactionEmoji { * Represents a reaction to a message */ class MessageReaction { - constructor(message, emoji, count) { + constructor(message, emoji, count, me) { /** * The message that this reaction refers to * @type {Message} */ this.message = message; - + /** + * Whether the client has given this reaction + * @type {boolean} + */ + this.me = me; this._emoji = new ReactionEmoji(this, emoji.name, emoji.id); /** * The number of people that have given the same reaction. From dd9c2915082211be67d851040f540a20d38d9e8c Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 16:58:06 +0100 Subject: [PATCH 005/248] Add reaction fetching of users --- src/client/rest/RESTMethods.js | 7 +++++++ src/structures/MessageReaction.js | 22 ++++++++++++++++++++++ test/random.js | 16 ++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index a3f99d286..601697644 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -751,6 +751,13 @@ class RESTMethods { .catch(reject); }); } + + getMessageReactionUsers(channelID, messageID, emoji, limit = 100) { + return new Promise((resolve, reject) => { + this.rest.makeRequest('get', Constants.Endpoints.messageReaction(channelID, messageID, emoji, limit), true) + .then(resolve, reject); + }); + } } module.exports = RESTMethods; diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index 77d6a1bd4..67f8b9ffb 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -108,6 +108,28 @@ class MessageReaction { const message = this.message; return message.client.rest.methods.removeMessageReaction(message.channel.id, message.id, this.emoji.identifier); } + + /** + * Fetch all the users that gave this reaction. Resolves with a collection of users, + * mapped by their IDs. + * @returns {Promise>} + */ + fetchUsers() { + const message = this.message; + return new Promise((resolve, reject) => { + message.client.rest.methods.getMessageReactionUsers(message.channel.id, message.id, this.emoji.identifier) + .then(users => { + this.users = new Collection(); + for (const rawUser of users) { + const user = this.message.client.dataManager.newUser(rawUser); + this.users.set(user.id, user); + } + this.count = this.users.size; + resolve(this.users); + }) + .catch(reject); + }); + } } module.exports = MessageReaction; diff --git a/test/random.js b/test/random.js index 1e6df7642..f3af346a7 100644 --- a/test/random.js +++ b/test/random.js @@ -187,3 +187,19 @@ 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}`); }); + +client.on('message', m => { + if (m.content.startsWith('#reactions')) { + const mID = m.content.split(' ')[1]; + m.channel.fetchMessage(mID).then(rM => { + for (const reaction of rM.reactions.values()) { + reaction.fetchUsers().then(users => { + m.channel.sendMessage( + `The following gave that message ${reaction.emoji}:\n` + + `${users.map(u => u.username).map(t => `- ${t}`).join('\n')}` + ); + }); + } + }); + } +}); \ No newline at end of file From 9cba1bc6d0d80d2a0f5547dd9423fa2d59847ff5 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 17:16:40 +0100 Subject: [PATCH 006/248] remove users from message reactions --- src/client/rest/RESTMethods.js | 10 +++++++--- src/structures/MessageReaction.js | 12 +++++++++--- src/util/Constants.js | 2 ++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 601697644..6395ec1a0 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -737,12 +737,16 @@ class RESTMethods { }); } - removeMessageReaction(channelID, messageID, emoji) { + removeMessageReaction(channelID, messageID, emoji, userID) { return new Promise((resolve, reject) => { - this.rest.makeRequest('delete', Constants.Endpoints.selfMessageReaction(channelID, messageID, emoji), true) + let endpoint = Constants.Endpoints.selfMessageReaction(channelID, messageID, emoji); + if (userID !== this.rest.client.user.id) { + endpoint = Constants.Endpoints.userMessageReaction(channelID, messageID, emoji, null, userID); + } + this.rest.makeRequest('delete', endpoint, true) .then(() => { resolve(this.rest.client.actions.MessageReactionRemove.handle({ - user_id: this.rest.client.user.id, + user_id: userID, message_id: messageID, emoji: parseEmoji(emoji), channel_id: channelID, diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index 67f8b9ffb..5a43c8f3e 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -101,12 +101,18 @@ class MessageReaction { } /** - * If the client has given this reaction to a message, it is removed. + * Removes a user from this reaction. + * @param {UserResolvable} [user] the user that you want to remove the reaction, defaults to the client. * @returns {Promise} */ - remove() { + remove(user = this.message.client.user) { const message = this.message; - return message.client.rest.methods.removeMessageReaction(message.channel.id, message.id, this.emoji.identifier); + user = this.message.client.resolver.resolveUserID(user); + + if (!user) return Promise.reject('A UserIDResolvable is required (string, user, member, message, guild)'); + + return message.client.rest.methods.removeMessageReaction( + message.channel.id, message.id, this.emoji.identifier, user); } /** diff --git a/src/util/Constants.js b/src/util/Constants.js index 28540cb8f..4d2070fa1 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -123,6 +123,8 @@ const Endpoints = exports.Endpoints = { `${limit ? `?limit=${limit}` : ''}`, selfMessageReaction: (channel, msg, emoji, limit) => `${Endpoints.messageReaction(channel, msg, emoji, limit)}/@me`, + userMessageReaction: (channel, msg, emoji, limit, id) => + `${Endpoints.messageReaction(channel, msg, emoji, limit)}/${id}`, // webhooks webhook: (webhookID, token) => `${API}/webhooks/${webhookID}${token ? `/${token}` : ''}`, From 756d7fc2c1d49036ec701991a433d5fbc6109c1c Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 17:22:11 +0100 Subject: [PATCH 007/248] Fix example --- src/structures/MessageReaction.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index 5a43c8f3e..2fe115736 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -40,10 +40,8 @@ class ReactionEmoji { /** * Creates the text required to form a graphical emoji on Discord. * @example - * ```js * // send the emoji used in a reaction to the channel the reaction is part of * reaction.message.channel.sendMessage(`The emoji used is ${reaction.emoji}`); - * ``` * @returns {string} */ toString() { From cd9b391e2a6d99bed4265140d9c56730ae82a0a1 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 17:25:17 +0100 Subject: [PATCH 008/248] Hide SecretKey in documentation --- src/client/voice/util/SecretKey.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/voice/util/SecretKey.js b/src/client/voice/util/SecretKey.js index 42a0da0cb..508a1baa0 100644 --- a/src/client/voice/util/SecretKey.js +++ b/src/client/voice/util/SecretKey.js @@ -1,5 +1,6 @@ /** * Represents a Secret Key used in encryption over voice + * @private */ class SecretKey { constructor(key) { From dfeafbf5fabed59f916fd26a22e3f134e3e6346c Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 17:32:23 +0100 Subject: [PATCH 009/248] Add the ADD_REACTIONS permission --- src/client/ClientDataResolver.js | 1 + src/util/Constants.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 60870d8c0..9ab4c1e5d 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -155,6 +155,7 @@ class ClientDataResolver { * "ADMINISTRATOR", * "MANAGE_CHANNELS", * "MANAGE_GUILD", + * "ADD_REACTIONS", // add reactions to messages * "READ_MESSAGES", * "SEND_MESSAGES", * "SEND_TTS_MESSAGES", diff --git a/src/util/Constants.js b/src/util/Constants.js index 4d2070fa1..05f999789 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -265,6 +265,7 @@ const PermissionFlags = exports.PermissionFlags = { ADMINISTRATOR: 1 << 3, MANAGE_CHANNELS: 1 << 4, MANAGE_GUILD: 1 << 5, + ADD_REACTIONS: 1 << 6, READ_MESSAGES: 1 << 10, SEND_MESSAGES: 1 << 11, From c4da8d10092b0d2482f2e67bf2abb060e2c2b48e Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 18:52:49 +0100 Subject: [PATCH 010/248] rename file fix --- src/util/{parseEmoji.js => ParseEmoji.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/util/{parseEmoji.js => ParseEmoji.js} (100%) diff --git a/src/util/parseEmoji.js b/src/util/ParseEmoji.js similarity index 100% rename from src/util/parseEmoji.js rename to src/util/ParseEmoji.js From 5dd76069f8c81e71267cd42a50f77f743fb46e6e Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 19:04:24 +0100 Subject: [PATCH 011/248] Simplify voice channel joining --- src/client/voice/ClientVoiceManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js index bd58989ee..e32eae4bf 100644 --- a/src/client/voice/ClientVoiceManager.js +++ b/src/client/voice/ClientVoiceManager.js @@ -80,7 +80,7 @@ class ClientVoiceManager { return new Promise((resolve, reject) => { if (this.pending.get(channel.guild.id)) throw new Error('Already connecting to this guild\'s voice server.'); - if (!channel.permissionsFor(this.client.user).hasPermission('CONNECT')) { + if (!channel.joinable) { throw new Error('You do not have permission to join this voice channel'); } From b15896e0a4febe59f4fe9e73f099a4d03c038f2a Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 27 Oct 2016 21:28:03 +0100 Subject: [PATCH 012/248] Add limit param to reaction.fetchUsers --- src/structures/MessageReaction.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index 2fe115736..aa82ec827 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -116,12 +116,13 @@ class MessageReaction { /** * Fetch all the users that gave this reaction. Resolves with a collection of users, * mapped by their IDs. + * @param {number} [limit=100] the maximum amount of users to fetch, defaults to 100 * @returns {Promise>} */ - fetchUsers() { + fetchUsers(limit = 100) { const message = this.message; return new Promise((resolve, reject) => { - message.client.rest.methods.getMessageReactionUsers(message.channel.id, message.id, this.emoji.identifier) + message.client.rest.methods.getMessageReactionUsers(message.channel.id, message.id, this.emoji.identifier, limit) .then(users => { this.users = new Collection(); for (const rawUser of users) { From 986b05442d6a0a183f079f894c67e679d5d1afee Mon Sep 17 00:00:00 2001 From: Hackzzila Date: Thu, 27 Oct 2016 15:49:09 -0500 Subject: [PATCH 013/248] Make errors great again (#850) --- src/util/Constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index 05f999789..aa9dfd38c 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -58,7 +58,7 @@ exports.DefaultOptions = { exports.Errors = { NO_TOKEN: 'Request to use token, but token was unavailable to the client.', - NO_BOT_ACCOUNT: 'You ideally should be using a bot account!', + NO_BOT_ACCOUNT: 'You have to use a bot account to use this method.', BAD_WS_MESSAGE: 'A bad message was received from the websocket; either bad compression, or not JSON.', TOOK_TOO_LONG: 'Something took too long to do.', NOT_A_PERMISSION: 'Invalid permission string or number.', From c9dbf1f7f032baa354c27f3268d731aa3589f267 Mon Sep 17 00:00:00 2001 From: Hackzzila Date: Thu, 27 Oct 2016 15:50:04 -0500 Subject: [PATCH 014/248] OAuth info stuff (#849) * OAuth info stuff * fix docs * oops --- src/client/Client.js | 9 ++++ src/client/rest/RESTMethods.js | 9 ++++ src/index.js | 2 + src/structures/ClientOAuth2App.js | 25 ++++++++++ src/structures/OAuth2App.js | 81 +++++++++++++++++++++++++++++++ src/util/Constants.js | 4 ++ 6 files changed, 130 insertions(+) create mode 100644 src/structures/ClientOAuth2App.js create mode 100644 src/structures/OAuth2App.js diff --git a/src/client/Client.js b/src/client/Client.js index 39a222154..98a9fa9f6 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -324,6 +324,15 @@ class Client extends EventEmitter { return messages; } + /** + * Get's the bot's OAuth2 app. Only usable by bot accounts + * @returns {Promise} + */ + getMyApp() { + if (!this.user.bot) throw new Error(Constants.Errors.NO_BOT_ACCOUNT); + return this.rest.methods.getMyApp(); + } + setTimeout(fn, ...params) { const timeout = setTimeout(() => { fn(); diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 6395ec1a0..c4903fd53 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -10,6 +10,7 @@ const Role = requireStructure('Role'); const Invite = requireStructure('Invite'); const Webhook = requireStructure('Webhook'); const UserProfile = requireStructure('UserProfile'); +const ClientOAuth2App = requireStructure('ClientOAuth2App'); class RESTMethods { constructor(restManager) { @@ -762,6 +763,14 @@ class RESTMethods { .then(resolve, reject); }); } + + getMyApp() { + return new Promise((resolve, reject) => { + this.rest.makeRequest('get', Constants.Endpoints.myApp, true) + .then(app => resolve(new ClientOAuth2App(this.rest.client, app))) + .catch(reject); + }); + } } module.exports = RESTMethods; diff --git a/src/index.js b/src/index.js index 2b8a19e6f..0c845cd4b 100644 --- a/src/index.js +++ b/src/index.js @@ -11,6 +11,7 @@ module.exports = { fetchRecommendedShards: require('./util/FetchRecommendedShards'), Channel: require('./structures/Channel'), + ClientOAuth2App: require('./structures/ClientOAuth2App'), ClientUser: require('./structures/ClientUser'), DMChannel: require('./structures/DMChannel'), Emoji: require('./structures/Emoji'), @@ -25,6 +26,7 @@ module.exports = { MessageAttachment: require('./structures/MessageAttachment'), MessageCollector: require('./structures/MessageCollector'), MessageEmbed: require('./structures/MessageEmbed'), + OAuth2App: require('./structures/OAuth2App'), PartialGuild: require('./structures/PartialGuild'), PartialGuildChannel: require('./structures/PartialGuildChannel'), PermissionOverwrites: require('./structures/PermissionOverwrites'), diff --git a/src/structures/ClientOAuth2App.js b/src/structures/ClientOAuth2App.js new file mode 100644 index 000000000..74521c40d --- /dev/null +++ b/src/structures/ClientOAuth2App.js @@ -0,0 +1,25 @@ +const User = require('./User'); +const OAuth2App = require('./OAuth2App'); + +/** + * Represents the client's OAuth2 Application + */ +class ClientOAuth2App extends OAuth2App { + setup(data) { + super.setup(data); + + /** + * The app's flags + * @type {int} + */ + this.flags = data.flags; + + /** + * The app's owner + * @type {User} + */ + this.owner = new User(this.client, data.owner); + } +} + +module.exports = ClientOAuth2App; diff --git a/src/structures/OAuth2App.js b/src/structures/OAuth2App.js new file mode 100644 index 000000000..89bcae805 --- /dev/null +++ b/src/structures/OAuth2App.js @@ -0,0 +1,81 @@ +/** + * Represents a OAuth2 Application + */ +class OAuth2App { + constructor(client, data) { + /** + * The client that instantiated the role + * @type {Client} + */ + this.client = client; + Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + + this.setup(data); + } + + setup(data) { + /** + * The ID of the app + * @type {string} + */ + this.id = data.id; + + /** + * The name of the app + * @type {string} + */ + this.name = data.name; + + /** + * The app's description + * @type {string} + */ + this.description = data.description; + + /** + * The app's icon hash + * @type {string} + */ + this.icon = data.icon; + + /** + * The app's icon URL + * @type {string} + */ + this.iconURL = `https://cdn.discordapp.com/app-icons/${this.id}/${this.icon}.jpg`; + + /** + * The app's RPC origins + * @type {Array} + */ + this.rpcOrigins = data.rpc_origins; + } + + /** + * The timestamp the app was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return (this.id / 4194304) + 1420070400000; + } + + /** + * The time the app was created + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * When concatenated with a string, this automatically concatenates the app name rather than the app object. + * @returns {string} + */ + toString() { + return this.name; + } +} + +module.exports = OAuth2App; diff --git a/src/util/Constants.js b/src/util/Constants.js index aa9dfd38c..8ce939b21 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -128,6 +128,10 @@ const Endpoints = exports.Endpoints = { // webhooks webhook: (webhookID, token) => `${API}/webhooks/${webhookID}${token ? `/${token}` : ''}`, + + // oauth + myApp: `${API}/oauth2/applications/@me`, + getApp: (id) => `${API}/oauth2/authorize?client_id=${id}`, }; exports.Status = { From 30105536a6ffe68d05b9e45c67cb10f40587df3f Mon Sep 17 00:00:00 2001 From: Hackzzila Date: Thu, 27 Oct 2016 16:19:20 -0500 Subject: [PATCH 015/248] ESLint warnings (#852) --- src/client/voice/VoiceWebSocket.js | 1 - src/structures/Guild.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/voice/VoiceWebSocket.js b/src/client/voice/VoiceWebSocket.js index 1c421a71b..ebc2a3103 100644 --- a/src/client/voice/VoiceWebSocket.js +++ b/src/client/voice/VoiceWebSocket.js @@ -142,7 +142,6 @@ class VoiceWebSocket extends EventEmitter { * Called whenever the connection to the WebSocket Server is lost */ onClose() { - // TODO see if the connection is open before reconnecting if (!this.dead) this.client.setTimeout(this.connect.bind(this), this.attempts * 1000); } diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 58b28cb8a..e78c204e0 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -650,7 +650,7 @@ class Guild { const updatedRoles = this.roles.array().map(r => ({ id: r.id, - position: r.id === role ? position : (r.position < position ? r.position : r.position + 1), + position: r.id === role ? position : r.position < position ? r.position : r.position + 1, })); return this.client.rest.methods.setRolePositions(this.id, updatedRoles); From e80f06a0594edefd276266c30e1b9978f1baed5c Mon Sep 17 00:00:00 2001 From: Hackzzila Date: Thu, 27 Oct 2016 16:19:32 -0500 Subject: [PATCH 016/248] 3 PRs in one day! (#851) --- src/structures/ClientOAuth2App.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/structures/ClientOAuth2App.js b/src/structures/ClientOAuth2App.js index 74521c40d..e3b1013b8 100644 --- a/src/structures/ClientOAuth2App.js +++ b/src/structures/ClientOAuth2App.js @@ -3,6 +3,7 @@ const OAuth2App = require('./OAuth2App'); /** * Represents the client's OAuth2 Application + * @extends {OAuth2App} */ class ClientOAuth2App extends OAuth2App { setup(data) { From 4bd19c94ba47cd53f9723a9349b9b29fc2646c01 Mon Sep 17 00:00:00 2001 From: isonmad Date: Thu, 27 Oct 2016 17:38:34 -0400 Subject: [PATCH 017/248] fix Client.destroy (#853) _timeouts and _intervals were changed to Set objects in commit 6ede7a32fd29bd44e65e344e1f102332d30c2129 a month ago. Like #844, this fix was reverted in 7d04863b66177c105a92b39ffc990be3b6bfe84f (#839) without explanation and was never included in the followup rewrite in commit 5e2ee2398ea5f7b30d8c1783be5cde05803e54fd. --- src/client/Client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 98a9fa9f6..17eddf5fd 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -236,8 +236,8 @@ class Client extends EventEmitter { destroy() { for (const t of this._timeouts) clearTimeout(t); for (const i of this._intervals) clearInterval(i); - this._timeouts = []; - this._intervals = []; + this._timeouts.clear(); + this._intervals.clear(); this.token = null; this.email = null; this.password = null; From 3f5e7451a7f0908c4c1090e404bc4d11ec548b91 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 27 Oct 2016 19:15:08 -0400 Subject: [PATCH 018/248] Fix linked whitespace :eyes: --- README.md | 28 +++++++--------------------- docs/custom/documents/welcome.md | 28 +++++++--------------------- 2 files changed, 14 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index f489c0503..a0909b7d6 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,16 @@ diff --git a/docs/custom/documents/welcome.md b/docs/custom/documents/welcome.md index 4b1cb445b..b5215178e 100644 --- a/docs/custom/documents/welcome.md +++ b/docs/custom/documents/welcome.md @@ -1,30 +1,16 @@ From 83bef4ca77548d42448936b76bf0e0fc7f2a1f0a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 27 Oct 2016 20:45:09 -0400 Subject: [PATCH 019/248] Teensy weensy cleanup --- src/client/voice/ClientVoiceManager.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js index e32eae4bf..e73e095d9 100644 --- a/src/client/voice/ClientVoiceManager.js +++ b/src/client/voice/ClientVoiceManager.js @@ -55,7 +55,7 @@ class ClientVoiceManager { throw new Error('There is no permission set for the client user in this channel - are they part of the guild?'); } if (!permissions.hasPermission('CONNECT')) { - throw new Error('You do not have permission to connect to this voice channel.'); + throw new Error('You do not have permission to join this voice channel.'); } options = mergeDefault({ @@ -79,10 +79,7 @@ class ClientVoiceManager { joinChannel(channel) { return new Promise((resolve, reject) => { if (this.pending.get(channel.guild.id)) throw new Error('Already connecting to this guild\'s voice server.'); - - if (!channel.joinable) { - throw new Error('You do not have permission to join this voice channel'); - } + if (!channel.joinable) throw new Error('You do not have permission to join this voice channel.'); const existingConnection = this.connections.get(channel.guild.id); if (existingConnection) { From 4e6b632d2339151c50ed1f190c8524ca7f0fe8b7 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 27 Oct 2016 20:51:19 -0400 Subject: [PATCH 020/248] Fix VoiceConnection.speaking not emitting --- src/client/voice/VoiceConnection.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index afb405789..cac004f80 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -20,6 +20,7 @@ class VoiceConnection extends EventEmitter { constructor(pendingConnection) { super(); + /** * The Voice Manager that instantiated this connection * @type {ClientVoiceManager} @@ -77,6 +78,13 @@ class VoiceConnection extends EventEmitter { */ this.ssrcMap = new Map(); + /** + * Whether this connection is ready + * @type {boolean} + * @private + */ + this.ready = false; + /** * Object that wraps contains the `ws` and `udp` sockets of this voice connection * @type {object} @@ -161,6 +169,7 @@ class VoiceConnection extends EventEmitter { * @event VoiceConnection#ready */ this.emit('ready'); + this.ready = true; }); this.sockets.ws.on('speaking', data => { const guild = this.channel.guild; From cf04b444547b75c87c8b33c2d641ca0922016165 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 27 Oct 2016 21:38:48 -0400 Subject: [PATCH 021/248] Clean up some more --- src/client/voice/VoiceConnection.js | 1 - src/client/voice/VoiceUDPClient.js | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index cac004f80..03e0a3f75 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -17,7 +17,6 @@ const fs = require('fs'); * @extends {EventEmitter} */ class VoiceConnection extends EventEmitter { - constructor(pendingConnection) { super(); diff --git a/src/client/voice/VoiceUDPClient.js b/src/client/voice/VoiceUDPClient.js index 8246478c3..b7b0c0cfe 100644 --- a/src/client/voice/VoiceUDPClient.js +++ b/src/client/voice/VoiceUDPClient.js @@ -3,18 +3,6 @@ const dns = require('dns'); const Constants = require('../../util/Constants'); const EventEmitter = require('events').EventEmitter; -function parseLocalPacket(message) { - try { - const packet = new Buffer(message); - let address = ''; - for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]); - const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10); - return { address, port }; - } catch (error) { - return { error }; - } -} - /** * Represents a UDP Client for a Voice Connection * @extends {EventEmitter} @@ -142,4 +130,16 @@ class VoiceConnectionUDPClient extends EventEmitter { } } +function parseLocalPacket(message) { + try { + const packet = new Buffer(message); + let address = ''; + for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]); + const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10); + return { address, port }; + } catch (error) { + return { error }; + } +} + module.exports = VoiceConnectionUDPClient; From c42e7a15aad9472f67de31b35c74646f82299f44 Mon Sep 17 00:00:00 2001 From: Programmix Date: Sat, 29 Oct 2016 21:08:32 -0700 Subject: [PATCH 022/248] Update documentation (add missing typedefs) (#861) --- src/structures/Guild.js | 17 +++++++++++++++-- src/structures/Role.js | 21 ++++++++++++++++----- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index e78c204e0..f581daecd 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -344,6 +344,19 @@ class Guild { }); } + /** + * The data for editing a guild + * @typedef {Object} GuildEditData + * @property {string} name The name of the guild + * @property {string} region The region of the guild + * @property {number} verificationLevel The verification level of the guild + * @property {GuildChannelResolvable} afkChannel The AFK channel of the guild + * @property {number} afkTimeout The AFK timeout of the guild + * @property {Base64Resolvable} icon The icon of the guild + * @property {GuildMemberResolvable} owner The owner of the guild + * @property {Base64Resolvable} splash The splash screen of the guild + */ + /** * Updates the Guild with new information - e.g. a new name. * @param {GuildEditData} data The data to update the guild with @@ -377,7 +390,7 @@ class Guild { /** * Edit the region of the Guild. - * @param {Region} region The new region of the guild. + * @param {string} region The new region of the guild. * @returns {Promise} * @example * // edit the guild region @@ -391,7 +404,7 @@ class Guild { /** * Edit the verification level of the Guild. - * @param {VerificationLevel} verificationLevel The new verification level of the guild + * @param {number} verificationLevel The new verification level of the guild * @returns {Promise} * @example * // edit the guild verification level diff --git a/src/structures/Role.js b/src/structures/Role.js index 4bbb6c55b..122183f46 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -163,6 +163,17 @@ class Role { return this.constructor.comparePositions(this, role); } + /** + * The data for a role + * @typedef {Object} RoleData + * @property {string} name The name of the role + * @property {number|string} color The color of the role, either a hex string or a base 10 number + * @property {boolean} hoist Whether or not the role should be hoisted + * @property {number} position The position of the role + * @property {string[]} permissions The permissions of the role + * @property {boolean} mentionable Whether or not the role should be mentionable + */ + /** * Edits the role * @param {RoleData} data The new data for the role @@ -188,7 +199,7 @@ class Role { * .catch(console.error); */ setName(name) { - return this.client.rest.methods.updateGuildRole(this, { name }); + return this.edit({ name }); } /** @@ -202,7 +213,7 @@ class Role { * .catch(console.error); */ setColor(color) { - return this.client.rest.methods.updateGuildRole(this, { color }); + return this.edit({ color }); } /** @@ -216,7 +227,7 @@ class Role { * .catch(console.error); */ setHoist(hoist) { - return this.client.rest.methods.updateGuildRole(this, { hoist }); + return this.edit({ hoist }); } /** @@ -244,7 +255,7 @@ class Role { * .catch(console.error); */ setPermissions(permissions) { - return this.client.rest.methods.updateGuildRole(this, { permissions }); + return this.edit({ permissions }); } /** @@ -258,7 +269,7 @@ class Role { * .catch(console.error); */ setMentionable(mentionable) { - return this.client.rest.methods.updateGuildRole(this, { mentionable }); + return this.edit({ mentionable }); } /** From 8306d50bd8d2042154e5e1889b32f0e7512f1286 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 12:47:09 -0400 Subject: [PATCH 023/248] Clean up a bunch of promise stuff --- src/client/ClientManager.js | 6 +- src/client/rest/RESTMethods.js | 173 +++++++----------- src/client/voice/VoiceConnection.js | 6 +- .../voice/dispatcher/StreamDispatcher.js | 2 +- src/client/websocket/WebSocketManager.js | 2 +- src/sharding/Shard.js | 14 +- src/sharding/ShardingManager.js | 2 +- src/structures/Message.js | 16 +- src/structures/MessageReaction.js | 19 +- src/structures/TextChannel.js | 6 +- src/structures/Webhook.js | 17 +- src/structures/interface/TextBasedChannel.js | 10 +- 12 files changed, 112 insertions(+), 161 deletions(-) diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js index ef943b339..11c5aba54 100644 --- a/src/client/ClientManager.js +++ b/src/client/ClientManager.js @@ -40,7 +40,7 @@ class ClientManager { resolve(token); this.client.clearTimeout(timeout); }); - }).catch(reject); + }, reject); } /** @@ -58,10 +58,10 @@ class ClientManager { } destroy() { - return new Promise((resolve, reject) => { + return new Promise(resolve => { this.client.ws.destroy(); if (!this.client.user.bot) { - this.client.rest.methods.logout().then(resolve, reject); + resolve(this.client.rest.methods.logout()); } else { resolve(); } diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index c4903fd53..807c93bf6 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -18,8 +18,8 @@ class RESTMethods { } loginToken(token = this.rest.client.token) { - token = token.replace(/^Bot\s*/i, ''); return new Promise((resolve, reject) => { + token = token.replace(/^Bot\s*/i, ''); this.rest.client.manager.connectToWebSocket(token, resolve, reject); }); } @@ -32,8 +32,7 @@ class RESTMethods { this.rest.makeRequest('post', Constants.Endpoints.login, false, { email, password }) .then(data => { resolve(this.loginToken(data.token)); - }) - .catch(reject); + }, reject); }); } @@ -47,8 +46,7 @@ class RESTMethods { .then(res => { this.rest.client.ws.gateway = `${res.url}/?encoding=json&v=${Constants.PROTOCOL_VERSION}`; resolve(this.rest.client.ws.gateway); - }) - .catch(reject); + }, reject); }); } @@ -71,7 +69,7 @@ class RESTMethods { if (channel instanceof User || channel instanceof GuildMember) { this.createDM(channel).then(chan => { this._sendMessageRequest(chan, content, file, tts, nonce, resolve, reject); - }).catch(reject); + }, reject); } else { this._sendMessageRequest(channel, content, file, tts, nonce, resolve, reject); } @@ -93,20 +91,19 @@ class RESTMethods { return this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, { content: content[i2], tts, nonce, }, file); - }).catch(reject); + }, reject); } else { promise.then(data => { datas.push(data); resolve(this.rest.client.actions.MessageCreate.handle(datas).messages); - }).catch(reject); + }, reject); } } } else { this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, { content, tts, nonce, }, file) - .then(data => resolve(this.rest.client.actions.MessageCreate.handle(data).message)) - .catch(reject); + .then(data => resolve(this.rest.client.actions.MessageCreate.handle(data).message), reject); } } @@ -118,8 +115,7 @@ class RESTMethods { id: message.id, channel_id: message.channel.id, }).message); - }) - .catch(reject); + }, reject); }); } @@ -132,8 +128,7 @@ class RESTMethods { channel_id: channel.id, ids: messages, }).messages); - }) - .catch(reject); + }, reject); }); } @@ -145,7 +140,7 @@ class RESTMethods { content, }).then(data => { resolve(this.rest.client.actions.MessageUpdate.handle(data).updated); - }).catch(reject); + }, reject); }); } @@ -156,7 +151,7 @@ class RESTMethods { type: channelType, }).then(data => { resolve(this.rest.client.actions.ChannelCreate.handle(data).channel); - }).catch(reject); + }, reject); }); } @@ -172,7 +167,7 @@ class RESTMethods { if (dmChannel) return resolve(dmChannel); return this.rest.makeRequest('post', Constants.Endpoints.userChannels(this.rest.client.user.id), true, { recipient_id: recipient.id, - }).then(data => resolve(this.rest.client.actions.ChannelCreate.handle(data).channel)).catch(reject); + }).then(data => resolve(this.rest.client.actions.ChannelCreate.handle(data).channel), reject); }); } @@ -182,7 +177,7 @@ class RESTMethods { this.rest.makeRequest('del', Constants.Endpoints.channel(channel.id), true).then(data => { data.id = channel.id; resolve(this.rest.client.actions.ChannelDelete.handle(data).channel); - }).catch(reject); + }, reject); }); } @@ -195,7 +190,7 @@ class RESTMethods { this.rest.makeRequest('patch', Constants.Endpoints.channel(channel.id), true, data).then(newData => { resolve(this.rest.client.actions.ChannelUpdate.handle(newData).updated); - }).catch(reject); + }, reject); }); } @@ -204,7 +199,7 @@ class RESTMethods { return new Promise((resolve, reject) => { this.rest.makeRequest('del', Constants.Endpoints.meGuild(guild.id), true).then(() => { resolve(this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild); - }).catch(reject); + }, reject); }); } @@ -212,8 +207,7 @@ class RESTMethods { options.icon = this.rest.client.resolver.resolveBase64(options.icon) || null; options.region = options.region || 'us-central'; return new Promise((resolve, reject) => { - this.rest.makeRequest('post', Constants.Endpoints.guilds, true, options) - .then(data => { + this.rest.makeRequest('post', Constants.Endpoints.guilds, true, options).then(data => { if (this.rest.client.guilds.has(data.id)) resolve(this.rest.client.guilds.get(data.id)); const handleGuild = guild => { if (guild.id === data.id) resolve(guild); @@ -224,7 +218,7 @@ class RESTMethods { this.rest.client.removeListener('guildCreate', handleGuild); reject(new Error('Took too long to receive guild data')); }, 10000); - }).catch(reject); + }, reject); }); } @@ -233,15 +227,15 @@ class RESTMethods { return new Promise((resolve, reject) => { this.rest.makeRequest('del', Constants.Endpoints.guild(guild.id), true).then(() => { resolve(this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild); - }).catch(reject); + }, reject); }); } getUser(userID) { return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then((data) => { + this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then(data => { resolve(this.rest.client.actions.UserGet.handle(data).user); - }).catch(reject); + }, reject); }); } @@ -259,8 +253,7 @@ class RESTMethods { } this.rest.makeRequest('patch', Constants.Endpoints.me, true, data) - .then(newData => resolve(this.rest.client.actions.UserUpdate.handle(newData).updated)) - .catch(reject); + .then(newData => resolve(this.rest.client.actions.UserUpdate.handle(newData).updated), reject); }); } @@ -277,8 +270,7 @@ class RESTMethods { if (_data.splash) data.splash = this.rest.client.resolver.resolveBase64(_data.splash); this.rest.makeRequest('patch', Constants.Endpoints.guild(guild.id), true, data) - .then(newData => resolve(this.rest.client.actions.GuildUpdate.handle(newData).updated)) - .catch(reject); + .then(newData => resolve(this.rest.client.actions.GuildUpdate.handle(newData).updated), reject); }); } @@ -289,7 +281,7 @@ class RESTMethods { guild_id: guild.id, user: member.user, }).member); - }).catch(reject); + }, reject); }); } @@ -300,7 +292,7 @@ class RESTMethods { guild_id: guild.id, role, }).role); - }).catch(reject); + }, reject); }); } @@ -311,24 +303,21 @@ class RESTMethods { guild_id: role.guild.id, role_id: role.id, }).role); - }).catch(reject); + }, reject); }); } setChannelOverwrite(channel, payload) { return new Promise((resolve, reject) => { this.rest.makeRequest('put', `${Constants.Endpoints.channelPermissions(channel.id)}/${payload.id}`, true, payload) - .then(resolve) - .catch(reject); + .then(resolve, reject); }); } deletePermissionOverwrites(overwrite) { return new Promise((resolve, reject) => { const endpoint = `${Constants.Endpoints.channelPermissions(overwrite.channel.id)}/${overwrite.id}`; - this.rest.makeRequest('del', endpoint, true) - .then(() => resolve(overwrite)) - .catch(reject); + this.rest.makeRequest('del', endpoint, true).then(() => resolve(overwrite), reject); }); } @@ -342,9 +331,7 @@ class RESTMethods { let endpoint = Constants.Endpoints.channelMessages(channel.id); if (params.length > 0) endpoint += `?${params.join('&')}`; - this.rest.makeRequest('get', endpoint, true) - .then(resolve) - .catch(reject); + this.rest.makeRequest('get', endpoint, true).then(resolve, reject); }); } @@ -354,17 +341,15 @@ class RESTMethods { if (msg) return resolve(msg); const endpoint = Constants.Endpoints.channelMessage(channel.id, messageID); - return this.rest.makeRequest('get', endpoint, true) - .then(resolve) - .catch(reject); + return this.rest.makeRequest('get', endpoint, true).then(resolve, reject); }); } getGuildMember(guild, user) { return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.guildMember(guild.id, user.id), true).then((data) => { + this.rest.makeRequest('get', Constants.Endpoints.guildMember(guild.id, user.id), true).then(data => { resolve(this.rest.client.actions.GuildMemberGet.handle(guild, data).member); - }).catch(reject); + }, reject); }); } @@ -382,17 +367,12 @@ class RESTMethods { } this.rest.makeRequest('patch', endpoint, true, data) - .then(resData => resolve(member.guild._updateMember(member, resData).mem)) - .catch(reject); + .then(resData => resolve(member.guild._updateMember(member, resData).mem), reject); }); } sendTyping(channelID) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('post', `${Constants.Endpoints.channel(channelID)}/typing`, true) - .then(resolve) - .catch(reject); - }); + return this.rest.makeRequest('post', `${Constants.Endpoints.channel(channelID)}/typing`, true); } banGuildMember(guild, member, deleteDays = 0) { @@ -415,14 +395,14 @@ class RESTMethods { return; } resolve(id); - }).catch(reject); + }, reject); }); } unbanGuildMember(guild, member) { return new Promise((resolve, reject) => { const id = this.rest.client.resolver.resolveUserID(member); - if (!id) throw new Error('Couldn\'t resolve the user ID to ban.'); + if (!id) throw new Error('Couldn\'t resolve the user ID to unban.'); const listener = (eGuild, eUser) => { if (eGuild.id === guild.id && eUser.id === id) { @@ -444,7 +424,7 @@ class RESTMethods { bannedUsers.set(user.id, user); } resolve(bannedUsers); - }).catch(reject); + }, reject); }); } @@ -476,23 +456,21 @@ class RESTMethods { role: _role, guild_id: role.guild.id, }).updated); - }).catch(reject); + }, reject); }); } pinMessage(message) { return new Promise((resolve, reject) => { this.rest.makeRequest('put', `${Constants.Endpoints.channel(message.channel.id)}/pins/${message.id}`, true) - .then(() => resolve(message)) - .catch(reject); + .then(() => resolve(message), reject); }); } unpinMessage(message) { return new Promise((resolve, reject) => { this.rest.makeRequest('del', `${Constants.Endpoints.channel(message.channel.id)}/pins/${message.id}`, true) - .then(() => resolve(message)) - .catch(reject); + .then(() => resolve(message), reject); }); } @@ -508,24 +486,21 @@ class RESTMethods { payload.max_uses = options.maxUses; this.rest.makeRequest('post', `${Constants.Endpoints.channelInvites(channel.id)}`, true, payload) - .then(invite => resolve(new Invite(this.rest.client, invite))) - .catch(reject); + .then(invite => resolve(new Invite(this.rest.client, invite)), reject); }); } deleteInvite(invite) { return new Promise((resolve, reject) => { this.rest.makeRequest('del', Constants.Endpoints.invite(invite.code), true) - .then(() => resolve(invite)) - .catch(reject); + .then(() => resolve(invite), reject); }); } getInvite(code) { return new Promise((resolve, reject) => { this.rest.makeRequest('get', Constants.Endpoints.invite(code), true) - .then(invite => resolve(new Invite(this.rest.client, invite))) - .catch(reject); + .then(invite => resolve(new Invite(this.rest.client, invite)), reject); }); } @@ -538,16 +513,14 @@ class RESTMethods { invites.set(invite.code, invite); } resolve(invites); - }).catch(reject); + }, reject); }); } pruneGuildMembers(guild, days, dry) { return new Promise((resolve, reject) => { this.rest.makeRequest(dry ? 'get' : 'post', `${Constants.Endpoints.guildPrune(guild.id)}?days=${days}`, true) - .then(data => { - resolve(data.pruned); - }).catch(reject); + .then(data => resolve(data.pruned), reject); }); } @@ -556,7 +529,7 @@ class RESTMethods { this.rest.makeRequest('post', `${Constants.Endpoints.guildEmojis(guild.id)}`, true, { name: name, image: image }) .then(data => { resolve(this.rest.client.actions.EmojiCreate.handle(data, guild).emoji); - }).catch(reject); + }, reject); }); } @@ -565,7 +538,7 @@ class RESTMethods { this.rest.makeRequest('delete', `${Constants.Endpoints.guildEmojis(emoji.guild.id)}/${emoji.id}`, true) .then(() => { resolve(this.rest.client.actions.EmojiDelete.handle(emoji).data); - }).catch(reject); + }, reject); }); } @@ -574,7 +547,7 @@ class RESTMethods { this.rest.makeRequest('get', Constants.Endpoints.webhook(id, token), require('util').isUndefined(token)) .then(data => { resolve(new Webhook(this.rest.client, data)); - }).catch(reject); + }, reject); }); } @@ -587,7 +560,7 @@ class RESTMethods { hooks.set(hook.id, new Webhook(this.rest.client, hook)); } resolve(hooks); - }).catch(reject); + }, reject); }); } @@ -600,7 +573,7 @@ class RESTMethods { hooks.set(hook.id, new Webhook(this.rest.client, hook)); } resolve(hooks); - }).catch(reject); + }, reject); }); } @@ -609,10 +582,7 @@ class RESTMethods { this.rest.makeRequest('post', Constants.Endpoints.channelWebhooks(channel.id), true, { name, avatar, - }) - .then(data => { - resolve(new Webhook(this.rest.client, data)); - }).catch(reject); + }).then(data => resolve(new Webhook(this.rest.client, data)), reject); }); } @@ -625,7 +595,7 @@ class RESTMethods { webhook.name = data.name; webhook.avatar = data.avatar; resolve(webhook); - }).catch(reject); + }, reject); }); } @@ -643,10 +613,7 @@ class RESTMethods { this.rest.makeRequest('post', `${Constants.Endpoints.webhook(webhook.id, webhook.token)}?wait=true`, false, { content: content, username: webhook.name, avatar_url: avatarURL, tts: tts, file: file, embeds: embeds, - }) - .then(data => { - resolve(data); - }).catch(reject); + }).then(data => resolve(data), reject); }); } @@ -657,9 +624,7 @@ class RESTMethods { `${Constants.Endpoints.webhook(webhook.id, webhook.token)}/slack?wait=true`, false, body - ).then(data => { - resolve(data); - }).catch(reject); + ).then(data => resolve(data), reject); }); } @@ -668,45 +633,35 @@ class RESTMethods { this.rest.makeRequest('post', Constants.Endpoints.relationships('@me'), true, { discriminator: user.discriminator, username: user.username, - }).then(() => { - resolve(user); - }).catch(reject); + }).then(() => resolve(user), reject); }); } removeFriend(user) { return new Promise((resolve, reject) => { this.rest.makeRequest('delete', `${Constants.Endpoints.relationships('@me')}/${user.id}`, true) - .then(() => { - resolve(user); - }).catch(reject); + .then(() => resolve(user), reject); }); } fetchUserProfile(user) { return new Promise((resolve, reject) => { this.rest.makeRequest('get', Constants.Endpoints.userProfile(user.id), true) - .then(data => { - resolve(new UserProfile(user, data)); - }).catch(reject); + .then(data => resolve(new UserProfile(user, data)), reject); }); } blockUser(user) { return new Promise((resolve, reject) => { this.rest.makeRequest('put', `${Constants.Endpoints.relationships('@me')}/${user.id}`, true, { type: 2 }) - .then(() => { - resolve(user); - }).catch(reject); + .then(() => resolve(user), reject); }); } unblockUser(user) { return new Promise((resolve, reject) => { this.rest.makeRequest('delete', `${Constants.Endpoints.relationships('@me')}/${user.id}`, true) - .then(() => { - resolve(user); - }).catch(reject); + .then(() => resolve(user), reject); }); } @@ -718,8 +673,7 @@ class RESTMethods { guild_id: guildID, roles, }).guild); - }) - .catch(reject); + }, reject); }); } @@ -733,8 +687,7 @@ class RESTMethods { emoji: parseEmoji(emoji), channel_id: channelID, }).reaction); - }) - .catch(reject); + }, reject); }); } @@ -752,8 +705,7 @@ class RESTMethods { emoji: parseEmoji(emoji), channel_id: channelID, }).reaction); - }) - .catch(reject); + }, reject); }); } @@ -767,8 +719,7 @@ class RESTMethods { getMyApp() { return new Promise((resolve, reject) => { this.rest.makeRequest('get', Constants.Endpoints.myApp, true) - .then(app => resolve(new ClientOAuth2App(this.rest.client, app))) - .catch(reject); + .then(app => resolve(new ClientOAuth2App(this.rest.client, app)), reject); }); } } diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 03e0a3f75..19a586548 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -107,8 +107,7 @@ class VoiceConnection extends EventEmitter { speaking: true, delay: 0, }, - }) - .catch(e => { + }).catch(e => { this.emit('debug', e); }); } @@ -156,8 +155,7 @@ class VoiceConnection extends EventEmitter { this.sockets.udp.findEndpointAddress() .then(address => { this.sockets.udp.createUDPSocket(address); - }) - .catch(e => this.emit('error', e)); + }, e => this.emit('error', e)); }); this.sockets.ws.once('sessionDescription', (mode, secret) => { this.authentication.encryptionMode = mode; diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index 961ad97f4..0a4dbe493 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -136,7 +136,7 @@ class StreamDispatcher extends EventEmitter { const packet = this._createPacket(sequence, timestamp, this.player.opusEncoder.encode(buffer)); while (repeats--) { this.player.voiceConnection.sockets.udp.send(packet) - .catch(e => this.emit('debug', `failed to send a packet ${e}`)); + .catch(e => this.emit('debug', `Failed to send a packet ${e}`)); } } diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 8f7110613..40d714f4d 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -264,7 +264,7 @@ class WebSocketManager extends EventEmitter { this.status = Constants.Status.NEARLY; if (this.client.options.fetchAllMembers) { const promises = this.client.guilds.map(g => g.fetchMembers()); - Promise.all(promises).then(() => this._emitReady()).catch(e => { + Promise.all(promises).then(() => this._emitReady(), e => { this.client.emit(Constants.Events.WARN, 'Error in pre-ready guild member fetching'); this.client.emit(Constants.Events.ERROR, e); this._emitReady(); diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index bafa0a383..ea48f4234 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -134,17 +134,19 @@ class Shard { if (message) { // Shard is requesting a property fetch if (message._sFetchProp) { - this.manager.fetchClientValues(message._sFetchProp) - .then(results => this.send({ _sFetchProp: message._sFetchProp, _result: results })) - .catch(err => this.send({ _sFetchProp: message._sFetchProp, _error: makePlainError(err) })); + this.manager.fetchClientValues(message._sFetchProp).then( + results => this.send({ _sFetchProp: message._sFetchProp, _result: results }), + err => this.send({ _sFetchProp: message._sFetchProp, _error: makePlainError(err) }) + ); return; } // Shard is requesting an eval broadcast if (message._sEval) { - this.manager.broadcastEval(message._sEval) - .then(results => this.send({ _sEval: message._sEval, _result: results })) - .catch(err => this.send({ _sEval: message._sEval, _error: makePlainError(err) })); + this.manager.broadcastEval(message._sEval).then( + results => this.send({ _sEval: message._sEval, _result: results }), + err => this.send({ _sEval: message._sEval, _error: makePlainError(err) }) + ); return; } } diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index 66814babb..a85f11f11 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -110,7 +110,7 @@ class ShardingManager extends EventEmitter { fetchRecommendedShards(this.token).then(count => { this.totalShards = count; resolve(this._spawn(count, delay)); - }).catch(reject); + }, reject); } else { if (typeof amount !== 'number' || isNaN(amount)) throw new TypeError('Amount of shards must be a number.'); if (amount < 1) throw new RangeError('Amount of shards must be at least 1.'); diff --git a/src/structures/Message.js b/src/structures/Message.js index da485713a..0bcd14f46 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -438,13 +438,15 @@ class Message { * .catch(console.error); */ delete(timeout = 0) { - return new Promise((resolve, reject) => { - this.client.setTimeout(() => { - this.client.rest.methods.deleteMessage(this) - .then(resolve) - .catch(reject); - }, timeout); - }); + if (timeout <= 0) { + return this.client.rest.methods.deleteMessage(this); + } else { + return new Promise(resolve => { + this.client.setTimeout(() => { + resolve(this.delete()); + }, timeout); + }); + } } /** diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index aa82ec827..822e5e2fe 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -123,16 +123,15 @@ class MessageReaction { const message = this.message; return new Promise((resolve, reject) => { message.client.rest.methods.getMessageReactionUsers(message.channel.id, message.id, this.emoji.identifier, limit) - .then(users => { - this.users = new Collection(); - for (const rawUser of users) { - const user = this.message.client.dataManager.newUser(rawUser); - this.users.set(user.id, user); - } - this.count = this.users.size; - resolve(this.users); - }) - .catch(reject); + .then(users => { + this.users = new Collection(); + for (const rawUser of users) { + const user = this.message.client.dataManager.newUser(rawUser); + this.users.set(user.id, user); + } + this.count = this.users.size; + resolve(this.users); + }, reject); }); } } diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 4b69f91a6..0a1dee4e2 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -66,10 +66,10 @@ class TextChannel extends GuildChannel { this.client.resolver.resolveFile(avatar).then(file => { let base64 = new Buffer(file, 'binary').toString('base64'); let dataURI = `data:;base64,${base64}`; - this.client.rest.methods.createWebhook(this, name, dataURI).then(resolve).catch(reject); - }).catch(reject); + resolve(this.client.rest.methods.createWebhook(this, name, dataURI)); + }, reject); } else { - this.client.rest.methods.createWebhook(this, name).then(resolve).catch(reject); + resolve(this.client.rest.methods.createWebhook(this, name)); } }); } diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 98f44447d..d2a549d08 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -145,11 +145,11 @@ class Webhook { } return new Promise((resolve, reject) => { this.client.resolver.resolveFile(attachment).then(file => { - this.client.rest.methods.sendWebhookMessage(this, content, options, { + resolve(this.client.rest.methods.sendWebhookMessage(this, content, options, { file, name: fileName, - }).then(resolve).catch(reject); - }).catch(reject); + })); + }, reject); }); } @@ -181,14 +181,13 @@ class Webhook { if (avatar) { this.client.resolver.resolveFile(avatar).then(file => { const dataURI = this.client.resolver.resolveBase64(file); - this.client.rest.methods.editWebhook(this, name, dataURI) - .then(resolve).catch(reject); - }).catch(reject); + resolve(this.client.rest.methods.editWebhook(this, name, dataURI)); + }, reject); } else { - this.client.rest.methods.editWebhook(this, name) - .then(data => { + this.client.rest.methods.editWebhook(this, name).then(data => { this.setup(data); - }).catch(reject); + resolve(this); + }, reject); } }); } diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index 12f31cead..9797827bd 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -97,8 +97,8 @@ class TextBasedChannel { this.client.rest.methods.sendMessage(this, content, options, { file, name: fileName, - }).then(resolve).catch(reject); - }).catch(reject); + }).then(resolve, reject); + }, reject); }); } @@ -138,7 +138,7 @@ class TextBasedChannel { this._cacheMessage(msg); resolve(msg); - }).catch(reject); + }, reject); }); } @@ -172,7 +172,7 @@ class TextBasedChannel { this._cacheMessage(msg); } resolve(messages); - }).catch(reject); + }, reject); }); } @@ -190,7 +190,7 @@ class TextBasedChannel { this._cacheMessage(msg); } resolve(messages); - }).catch(reject); + }, reject); }); } From 60e0d507f065883ddbeaf135541315dcafc16fb6 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 16:27:28 -0400 Subject: [PATCH 024/248] Clean up nearly all promises to utilise chaining, other small fixes --- src/client/Client.js | 5 +- src/client/ClientDataResolver.js | 8 +- src/client/rest/RESTMethods.js | 750 ++++++++----------- src/sharding/ShardingManager.js | 24 +- src/structures/ClientUser.js | 36 +- src/structures/Guild.js | 13 +- src/structures/MessageReaction.js | 134 ++-- src/structures/TextChannel.js | 19 +- src/structures/Webhook.js | 35 +- src/structures/interface/TextBasedChannel.js | 78 +- 10 files changed, 482 insertions(+), 620 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 17eddf5fd..baf444ddb 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -282,10 +282,11 @@ class Client extends EventEmitter { /** * Fetch a webhook by ID. * @param {string} id ID of the webhook + * @param {string} [token] Token for the webhook * @returns {Promise} */ - fetchWebhook(id) { - return this.rest.methods.getWebhook(id); + fetchWebhook(id, token) { + return this.rest.methods.getWebhook(id, token); } /** diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 56274aa65..e729454bd 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -91,11 +91,9 @@ class ClientDataResolver { */ resolveGuildMember(guild, user) { if (user instanceof GuildMember) return user; - guild = this.resolveGuild(guild); user = this.resolveUser(user); if (!guild || !user) return null; - return guild.members.get(user.id) || null; } @@ -136,7 +134,6 @@ class ClientDataResolver { resolveInviteCode(data) { const inviteRegex = /discord(?:app)?\.(?:gg|com\/invite)\/([a-z0-9]{5})/i; const match = inviteRegex.exec(data); - if (match && match[1]) return match[1]; return data; } @@ -240,6 +237,8 @@ class ClientDataResolver { * @returns {Promise} */ resolveFile(resource) { + if (resource instanceof Buffer) return Promise.resolve(resource); + if (typeof resource === 'string') { return new Promise((resolve, reject) => { if (/^https?:\/\//.test(resource)) { @@ -259,8 +258,7 @@ class ClientDataResolver { }); } - if (resource instanceof Buffer) return Promise.resolve(resource); - return Promise.reject(new TypeError('Resource must be a string or Buffer.')); + return Promise.reject(new TypeError('The resource must be a string or Buffer.')); } } diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 807c93bf6..1ca113289 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -25,15 +25,12 @@ class RESTMethods { } loginEmailPassword(email, password) { - return new Promise((resolve, reject) => { - this.rest.client.emit('warn', 'Client launched using email and password - should use token instead'); - this.rest.client.email = email; - this.rest.client.password = password; - this.rest.makeRequest('post', Constants.Endpoints.login, false, { email, password }) - .then(data => { - resolve(this.loginToken(data.token)); - }, reject); - }); + this.rest.client.emit('warn', 'Client launched using email and password - should use token instead'); + this.rest.client.email = email; + this.rest.client.password = password; + return this.rest.makeRequest('post', Constants.Endpoints.login, false, { email, password }).then(data => + this.loginToken(data.token) + ); } logout() { @@ -41,12 +38,9 @@ class RESTMethods { } getGateway() { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.gateway, true) - .then(res => { - this.rest.client.ws.gateway = `${res.url}/?encoding=json&v=${Constants.PROTOCOL_VERSION}`; - resolve(this.rest.client.ws.gateway); - }, reject); + return this.rest.makeRequest('get', Constants.Endpoints.gateway, true).then(res => { + this.rest.client.ws.gateway = `${res.url}/?encoding=json&v=${Constants.PROTOCOL_VERSION}`; + return this.rest.client.ws.gateway; }); } @@ -108,99 +102,78 @@ class RESTMethods { } deleteMessage(message) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('del', Constants.Endpoints.channelMessage(message.channel.id, message.id), true) - .then(() => { - resolve(this.rest.client.actions.MessageDelete.handle({ - id: message.id, - channel_id: message.channel.id, - }).message); - }, reject); - }); + return this.rest.makeRequest('del', Constants.Endpoints.channelMessage(message.channel.id, message.id), true) + .then(() => + this.rest.client.actions.MessageDelete.handle({ + id: message.id, + channel_id: message.channel.id, + }).message + ); } bulkDeleteMessages(channel, messages) { - return new Promise((resolve, reject) => { - const options = { messages }; - this.rest.makeRequest('post', `${Constants.Endpoints.channelMessages(channel.id)}/bulk_delete`, true, options) - .then(() => { - resolve(this.rest.client.actions.MessageDeleteBulk.handle({ - channel_id: channel.id, - ids: messages, - }).messages); - }, reject); - }); + return this.rest.makeRequest('post', `${Constants.Endpoints.channelMessages(channel.id)}/bulk_delete`, true, { + messages, + }).then(() => + this.rest.client.actions.MessageDeleteBulk.handle({ + channel_id: channel.id, + ids: messages, + }).messages + ); } updateMessage(message, content) { - return new Promise((resolve, reject) => { - content = this.rest.client.resolver.resolveString(content); - - this.rest.makeRequest('patch', Constants.Endpoints.channelMessage(message.channel.id, message.id), true, { - content, - }).then(data => { - resolve(this.rest.client.actions.MessageUpdate.handle(data).updated); - }, reject); - }); + content = this.rest.client.resolver.resolveString(content); + return this.rest.makeRequest('patch', Constants.Endpoints.channelMessage(message.channel.id, message.id), true, { + content, + }).then(data => this.rest.client.actions.MessageUpdate.handle(data).updated); } createChannel(guild, channelName, channelType) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('post', Constants.Endpoints.guildChannels(guild.id), true, { - name: channelName, - type: channelType, - }).then(data => { - resolve(this.rest.client.actions.ChannelCreate.handle(data).channel); - }, reject); - }); - } - - getExistingDM(recipient) { - return this.rest.client.channels.filter(channel => - channel.recipient && channel.recipient.id === recipient.id - ).first(); + return this.rest.makeRequest('post', Constants.Endpoints.guildChannels(guild.id), true, { + name: channelName, + type: channelType, + }).then(data => this.rest.client.actions.ChannelCreate.handle(data).channel); } createDM(recipient) { - return new Promise((resolve, reject) => { - const dmChannel = this.getExistingDM(recipient); - if (dmChannel) return resolve(dmChannel); - return this.rest.makeRequest('post', Constants.Endpoints.userChannels(this.rest.client.user.id), true, { - recipient_id: recipient.id, - }).then(data => resolve(this.rest.client.actions.ChannelCreate.handle(data).channel), reject); - }); + const dmChannel = this.getExistingDM(recipient); + if (dmChannel) return Promise.resolve(dmChannel); + return this.rest.makeRequest('post', Constants.Endpoints.userChannels(this.rest.client.user.id), true, { + recipient_id: recipient.id, + }).then(data => this.rest.client.actions.ChannelCreate.handle(data).channel); + } + + getExistingDM(recipient) { + return this.rest.client.channels.find(channel => + channel.recipient && channel.recipient.id === recipient.id + ); } deleteChannel(channel) { - return new Promise((resolve, reject) => { - if (channel instanceof User || channel instanceof GuildMember) channel = this.getExistingDM(channel); - this.rest.makeRequest('del', Constants.Endpoints.channel(channel.id), true).then(data => { - data.id = channel.id; - resolve(this.rest.client.actions.ChannelDelete.handle(data).channel); - }, reject); + if (channel instanceof User || channel instanceof GuildMember) channel = this.getExistingDM(channel); + if (!channel) return Promise.reject(new Error('No channel to delete.')); + return this.rest.makeRequest('del', Constants.Endpoints.channel(channel.id), true).then(data => { + data.id = channel.id; + return this.rest.client.actions.ChannelDelete.handle(data).channel; }); } updateChannel(channel, data) { - return new Promise((resolve, reject) => { - data.name = (data.name || channel.name).trim(); - data.topic = data.topic || channel.topic; - data.position = data.position || channel.position; - data.bitrate = data.bitrate || channel.bitrate; - - this.rest.makeRequest('patch', Constants.Endpoints.channel(channel.id), true, data).then(newData => { - resolve(this.rest.client.actions.ChannelUpdate.handle(newData).updated); - }, reject); - }); + data.name = (data.name || channel.name).trim(); + data.topic = data.topic || channel.topic; + data.position = data.position || channel.position; + data.bitrate = data.bitrate || channel.bitrate; + return this.rest.makeRequest('patch', Constants.Endpoints.channel(channel.id), true, data).then(newData => + this.rest.client.actions.ChannelUpdate.handle(newData).updated + ); } leaveGuild(guild) { - if (guild.ownerID === this.rest.client.user.id) return this.deleteGuild(guild); - return new Promise((resolve, reject) => { - this.rest.makeRequest('del', Constants.Endpoints.meGuild(guild.id), true).then(() => { - resolve(this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild); - }, reject); - }); + if (guild.ownerID === this.rest.client.user.id) return Promise.reject(new Error('Guild is owned by the client.')); + return this.rest.makeRequest('del', Constants.Endpoints.meGuild(guild.id), true).then(() => + this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild + ); } createGuild(options) { @@ -208,15 +181,23 @@ class RESTMethods { options.region = options.region || 'us-central'; return new Promise((resolve, reject) => { this.rest.makeRequest('post', Constants.Endpoints.guilds, true, options).then(data => { - if (this.rest.client.guilds.has(data.id)) resolve(this.rest.client.guilds.get(data.id)); + if (this.rest.client.guilds.has(data.id)) { + resolve(this.rest.client.guilds.get(data.id)); + return; + } + const handleGuild = guild => { - if (guild.id === data.id) resolve(guild); - this.rest.client.removeListener('guildCreate', handleGuild); + if (guild.id === data.id) { + this.rest.client.removeListener('guildCreate', handleGuild); + this.rest.client.clearTimeout(timeout); + resolve(guild); + } }; this.rest.client.on('guildCreate', handleGuild); - this.rest.client.setTimeout(() => { + + const timeout = this.rest.client.setTimeout(() => { this.rest.client.removeListener('guildCreate', handleGuild); - reject(new Error('Took too long to receive guild data')); + reject(new Error('Took too long to receive guild data.')); }, 10000); }, reject); }); @@ -224,151 +205,126 @@ class RESTMethods { // untested but probably will work deleteGuild(guild) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('del', Constants.Endpoints.guild(guild.id), true).then(() => { - resolve(this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild); - }, reject); - }); + return this.rest.makeRequest('del', Constants.Endpoints.guild(guild.id), true).then(() => + this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild + ); } getUser(userID) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then(data => { - resolve(this.rest.client.actions.UserGet.handle(data).user); - }, reject); - }); + return this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then(data => + this.rest.client.actions.UserGet.handle(data).user + ); } updateCurrentUser(_data) { - return new Promise((resolve, reject) => { - const user = this.rest.client.user; - - const data = {}; - data.username = _data.username || user.username; - data.avatar = this.rest.client.resolver.resolveBase64(_data.avatar) || user.avatar; - if (!user.bot) { - data.email = _data.email || user.email; - data.password = this.rest.client.password; - if (_data.new_password) data.new_password = _data.newPassword; - } - - this.rest.makeRequest('patch', Constants.Endpoints.me, true, data) - .then(newData => resolve(this.rest.client.actions.UserUpdate.handle(newData).updated), reject); - }); + const user = this.rest.client.user; + const data = {}; + data.username = _data.username || user.username; + data.avatar = this.rest.client.resolver.resolveBase64(_data.avatar) || user.avatar; + if (!user.bot) { + data.email = _data.email || user.email; + data.password = this.rest.client.password; + if (_data.new_password) data.new_password = _data.newPassword; + } + return this.rest.makeRequest('patch', Constants.Endpoints.me, true, data).then(newData => + this.rest.client.actions.UserUpdate.handle(newData).updated + ); } updateGuild(guild, _data) { - return new Promise((resolve, reject) => { - const data = {}; - if (_data.name) data.name = _data.name; - if (_data.region) data.region = _data.region; - if (_data.verificationLevel) data.verification_level = Number(_data.verificationLevel); - if (_data.afkChannel) data.afk_channel_id = this.rest.client.resolver.resolveChannel(_data.afkChannel).id; - if (_data.afkTimeout) data.afk_timeout = Number(_data.afkTimeout); - if (_data.icon) data.icon = this.rest.client.resolver.resolveBase64(_data.icon); - if (_data.owner) data.owner_id = this.rest.client.resolver.resolveUser(_data.owner).id; - if (_data.splash) data.splash = this.rest.client.resolver.resolveBase64(_data.splash); - - this.rest.makeRequest('patch', Constants.Endpoints.guild(guild.id), true, data) - .then(newData => resolve(this.rest.client.actions.GuildUpdate.handle(newData).updated), reject); - }); + const data = {}; + if (_data.name) data.name = _data.name; + if (_data.region) data.region = _data.region; + if (_data.verificationLevel) data.verification_level = Number(_data.verificationLevel); + if (_data.afkChannel) data.afk_channel_id = this.rest.client.resolver.resolveChannel(_data.afkChannel).id; + if (_data.afkTimeout) data.afk_timeout = Number(_data.afkTimeout); + if (_data.icon) data.icon = this.rest.client.resolver.resolveBase64(_data.icon); + if (_data.owner) data.owner_id = this.rest.client.resolver.resolveUser(_data.owner).id; + if (_data.splash) data.splash = this.rest.client.resolver.resolveBase64(_data.splash); + return this.rest.makeRequest('patch', Constants.Endpoints.guild(guild.id), true, data).then(newData => + this.rest.client.actions.GuildUpdate.handle(newData).updated + ); } kickGuildMember(guild, member) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('del', Constants.Endpoints.guildMember(guild.id, member.id), true).then(() => { - resolve(this.rest.client.actions.GuildMemberRemove.handle({ - guild_id: guild.id, - user: member.user, - }).member); - }, reject); - }); + return this.rest.makeRequest('del', Constants.Endpoints.guildMember(guild.id, member.id), true).then(() => + this.rest.client.actions.GuildMemberRemove.handle({ + guild_id: guild.id, + user: member.user, + }).member + ); } createGuildRole(guild) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('post', Constants.Endpoints.guildRoles(guild.id), true).then(role => { - resolve(this.rest.client.actions.GuildRoleCreate.handle({ - guild_id: guild.id, - role, - }).role); - }, reject); - }); + return this.rest.makeRequest('post', Constants.Endpoints.guildRoles(guild.id), true).then(role => + this.rest.client.actions.GuildRoleCreate.handle({ + guild_id: guild.id, + role, + }).role + ); } deleteGuildRole(role) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('del', Constants.Endpoints.guildRole(role.guild.id, role.id), true).then(() => { - resolve(this.rest.client.actions.GuildRoleDelete.handle({ - guild_id: role.guild.id, - role_id: role.id, - }).role); - }, reject); - }); + return this.rest.makeRequest('del', Constants.Endpoints.guildRole(role.guild.id, role.id), true).then(() => + this.rest.client.actions.GuildRoleDelete.handle({ + guild_id: role.guild.id, + role_id: role.id, + }).role + ); } setChannelOverwrite(channel, payload) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('put', `${Constants.Endpoints.channelPermissions(channel.id)}/${payload.id}`, true, payload) - .then(resolve, reject); - }); + return this.rest.makeRequest( + 'put', `${Constants.Endpoints.channelPermissions(channel.id)}/${payload.id}`, true, payload + ); } deletePermissionOverwrites(overwrite) { - return new Promise((resolve, reject) => { - const endpoint = `${Constants.Endpoints.channelPermissions(overwrite.channel.id)}/${overwrite.id}`; - this.rest.makeRequest('del', endpoint, true).then(() => resolve(overwrite), reject); - }); + return this.rest.makeRequest( + 'del', `${Constants.Endpoints.channelPermissions(overwrite.channel.id)}/${overwrite.id}`, true + ).then(() => overwrite); } getChannelMessages(channel, payload = {}) { - return new Promise((resolve, reject) => { - const params = []; - if (payload.limit) params.push(`limit=${payload.limit}`); - if (payload.around) params.push(`around=${payload.around}`); - else if (payload.before) params.push(`before=${payload.before}`); - else if (payload.after) params.push(`after=${payload.after}`); + const params = []; + if (payload.limit) params.push(`limit=${payload.limit}`); + if (payload.around) params.push(`around=${payload.around}`); + else if (payload.before) params.push(`before=${payload.before}`); + else if (payload.after) params.push(`after=${payload.after}`); - let endpoint = Constants.Endpoints.channelMessages(channel.id); - if (params.length > 0) endpoint += `?${params.join('&')}`; - this.rest.makeRequest('get', endpoint, true).then(resolve, reject); - }); + let endpoint = Constants.Endpoints.channelMessages(channel.id); + if (params.length > 0) endpoint += `?${params.join('&')}`; + return this.rest.makeRequest('get', endpoint, true); } getChannelMessage(channel, messageID) { - return new Promise((resolve, reject) => { - const msg = channel.messages.get(messageID); - if (msg) return resolve(msg); - - const endpoint = Constants.Endpoints.channelMessage(channel.id, messageID); - return this.rest.makeRequest('get', endpoint, true).then(resolve, reject); - }); + const msg = channel.messages.get(messageID); + if (msg) return Promise.resolve(msg); + return this.rest.makeRequest('get', Constants.Endpoints.channelMessage(channel.id, messageID), true); } getGuildMember(guild, user) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.guildMember(guild.id, user.id), true).then(data => { - resolve(this.rest.client.actions.GuildMemberGet.handle(guild, data).member); - }, reject); - }); + return this.rest.makeRequest('get', Constants.Endpoints.guildMember(guild.id, user.id), true).then(data => + this.rest.client.actions.GuildMemberGet.handle(guild, data).member + ); } updateGuildMember(member, data) { - return new Promise((resolve, reject) => { - if (data.channel) data.channel_id = this.rest.client.resolver.resolveChannel(data.channel).id; - if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role); + if (data.channel) data.channel_id = this.rest.client.resolver.resolveChannel(data.channel).id; + if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role); - let endpoint = Constants.Endpoints.guildMember(member.guild.id, member.id); - // fix your endpoints, discord ;-; - if (member.id === this.rest.client.user.id) { - if (Object.keys(data).length === 1 && Object.keys(data)[0] === 'nick') { - endpoint = Constants.Endpoints.stupidInconsistentGuildEndpoint(member.guild.id); - } + let endpoint = Constants.Endpoints.guildMember(member.guild.id, member.id); + // fix your endpoints, discord ;-; + if (member.id === this.rest.client.user.id) { + const keys = Object.keys(data); + if (keys.length === 1 && keys[0] === 'nick') { + endpoint = Constants.Endpoints.stupidInconsistentGuildEndpoint(member.guild.id); } + } - this.rest.makeRequest('patch', endpoint, true, data) - .then(resData => resolve(member.guild._updateMember(member, resData).mem), reject); - }); + return this.rest.makeRequest('patch', endpoint, true, data).then(newData => + member.guild._updateMember(member, newData).mem + ); } sendTyping(channelID) { @@ -376,26 +332,20 @@ class RESTMethods { } banGuildMember(guild, member, deleteDays = 0) { - return new Promise((resolve, reject) => { - const id = this.rest.client.resolver.resolveUserID(member); - if (!id) throw new Error('Couldn\'t resolve the user ID to ban.'); - - this.rest.makeRequest('put', - `${Constants.Endpoints.guildBans(guild.id)}/${id}?delete-message-days=${deleteDays}`, true, { - 'delete-message-days': deleteDays, - }).then(() => { - if (member instanceof GuildMember) { - resolve(member); - return; - } - const user = this.rest.client.resolver.resolveUser(id); - if (user) { - member = this.rest.client.resolver.resolveGuildMember(guild, user); - resolve(member || user); - return; - } - resolve(id); - }, reject); + const id = this.rest.client.resolver.resolveUserID(member); + if (!id) return Promise.reject(new Error('Couldn\'t resolve the user ID to ban.')); + return this.rest.makeRequest( + 'put', `${Constants.Endpoints.guildBans(guild.id)}/${id}?delete-message-days=${deleteDays}`, true, { + 'delete-message-days': deleteDays, + } + ).then(() => { + if (member instanceof GuildMember) return member; + const user = this.rest.client.resolver.resolveUser(id); + if (user) { + member = this.rest.client.resolver.resolveGuildMember(guild, user); + return member || user; + } + return id; }); } @@ -407,71 +357,76 @@ class RESTMethods { const listener = (eGuild, eUser) => { if (eGuild.id === guild.id && eUser.id === id) { this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); + this.rest.client.clearTimeout(timeout); resolve(eUser); } }; this.rest.client.on(Constants.Events.GUILD_BAN_REMOVE, listener); - this.rest.makeRequest('del', `${Constants.Endpoints.guildBans(guild.id)}/${id}`, true).catch(reject); + + const timeout = this.rest.client.setTimeout(() => { + this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); + reject(new Error('Took too long to receive the ban remove event.')); + }, 10000); + + this.rest.makeRequest('del', `${Constants.Endpoints.guildBans(guild.id)}/${id}`, true).catch(err => { + this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); + this.rest.client.clearTimeout(timeout); + reject(err); + }); }); } getGuildBans(guild) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.guildBans(guild.id), true).then(banItems => { - const bannedUsers = new Collection(); - for (const banItem of banItems) { - const user = this.rest.client.dataManager.newUser(banItem.user); - bannedUsers.set(user.id, user); - } - resolve(bannedUsers); - }, reject); + return this.rest.makeRequest('get', Constants.Endpoints.guildBans(guild.id), true).then(banItems => { + const bannedUsers = new Collection(); + for (const banItem of banItems) { + const user = this.rest.client.dataManager.newUser(banItem.user); + bannedUsers.set(user.id, user); + } + return bannedUsers; }); } updateGuildRole(role, _data) { - return new Promise((resolve, reject) => { - const data = {}; - data.name = _data.name || role.name; - data.position = typeof _data.position !== 'undefined' ? _data.position : role.position; - data.color = _data.color || role.color; - if (typeof data.color === 'string' && data.color.startsWith('#')) { - data.color = parseInt(data.color.replace('#', ''), 16); - } - data.hoist = typeof _data.hoist !== 'undefined' ? _data.hoist : role.hoist; - data.mentionable = typeof _data.mentionable !== 'undefined' ? _data.mentionable : role.mentionable; + const data = {}; + data.name = _data.name || role.name; + data.position = typeof _data.position !== 'undefined' ? _data.position : role.position; + data.color = _data.color || role.color; + if (typeof data.color === 'string' && data.color.startsWith('#')) { + data.color = parseInt(data.color.replace('#', ''), 16); + } + data.hoist = typeof _data.hoist !== 'undefined' ? _data.hoist : role.hoist; + data.mentionable = typeof _data.mentionable !== 'undefined' ? _data.mentionable : role.mentionable; - if (_data.permissions) { - let perms = 0; - for (let perm of _data.permissions) { - if (typeof perm === 'string') perm = Constants.PermissionFlags[perm]; - perms |= perm; - } - data.permissions = perms; - } else { - data.permissions = role.permissions; + if (_data.permissions) { + let perms = 0; + for (let perm of _data.permissions) { + if (typeof perm === 'string') perm = Constants.PermissionFlags[perm]; + perms |= perm; } + data.permissions = perms; + } else { + data.permissions = role.permissions; + } - this.rest.makeRequest('patch', Constants.Endpoints.guildRole(role.guild.id, role.id), true, data).then(_role => { - resolve(this.rest.client.actions.GuildRoleUpdate.handle({ - role: _role, - guild_id: role.guild.id, - }).updated); - }, reject); - }); + return this.rest.makeRequest( + 'patch', Constants.Endpoints.guildRole(role.guild.id, role.id), true, data + ).then(_role => + this.rest.client.actions.GuildRoleUpdate.handle({ + role: _role, + guild_id: role.guild.id, + }).updated + ); } pinMessage(message) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('put', `${Constants.Endpoints.channel(message.channel.id)}/pins/${message.id}`, true) - .then(() => resolve(message), reject); - }); + return this.rest.makeRequest('put', `${Constants.Endpoints.channel(message.channel.id)}/pins/${message.id}`, true) + .then(() => message); } unpinMessage(message) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('del', `${Constants.Endpoints.channel(message.channel.id)}/pins/${message.id}`, true) - .then(() => resolve(message), reject); - }); + return this.rest.makeRequest('del', `${Constants.Endpoints.channel(message.channel.id)}/pins/${message.id}`, true) + .then(() => message); } getChannelPinnedMessages(channel) { @@ -479,123 +434,85 @@ class RESTMethods { } createChannelInvite(channel, options) { - return new Promise((resolve, reject) => { - const payload = {}; - payload.temporary = options.temporary; - payload.max_age = options.maxAge; - payload.max_uses = options.maxUses; - - this.rest.makeRequest('post', `${Constants.Endpoints.channelInvites(channel.id)}`, true, payload) - .then(invite => resolve(new Invite(this.rest.client, invite)), reject); - }); + const payload = {}; + payload.temporary = options.temporary; + payload.max_age = options.maxAge; + payload.max_uses = options.maxUses; + return this.rest.makeRequest('post', `${Constants.Endpoints.channelInvites(channel.id)}`, true, payload) + .then(invite => new Invite(this.rest.client, invite)); } deleteInvite(invite) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('del', Constants.Endpoints.invite(invite.code), true) - .then(() => resolve(invite), reject); - }); + return this.rest.makeRequest('del', Constants.Endpoints.invite(invite.code), true).then(() => invite); } getInvite(code) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.invite(code), true) - .then(invite => resolve(new Invite(this.rest.client, invite)), reject); - }); + return this.rest.makeRequest('get', Constants.Endpoints.invite(code), true).then(invite => + new Invite(this.rest.client, invite) + ); } getGuildInvites(guild) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.guildInvites(guild.id), true).then(inviteItems => { - const invites = new Collection(); - for (const inviteItem of inviteItems) { - const invite = new Invite(this.rest.client, inviteItem); - invites.set(invite.code, invite); - } - resolve(invites); - }, reject); + return this.rest.makeRequest('get', Constants.Endpoints.guildInvites(guild.id), true).then(inviteItems => { + const invites = new Collection(); + for (const inviteItem of inviteItems) { + const invite = new Invite(this.rest.client, inviteItem); + invites.set(invite.code, invite); + } + return invites; }); } pruneGuildMembers(guild, days, dry) { - return new Promise((resolve, reject) => { - this.rest.makeRequest(dry ? 'get' : 'post', `${Constants.Endpoints.guildPrune(guild.id)}?days=${days}`, true) - .then(data => resolve(data.pruned), reject); - }); + return this.rest.makeRequest(dry ? 'get' : 'post', `${Constants.Endpoints.guildPrune(guild.id)}?days=${days}`, true) + .then(data => data.pruned); } createEmoji(guild, image, name) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('post', `${Constants.Endpoints.guildEmojis(guild.id)}`, true, { name: name, image: image }) - .then(data => { - resolve(this.rest.client.actions.EmojiCreate.handle(data, guild).emoji); - }, reject); - }); + return this.rest.makeRequest('post', `${Constants.Endpoints.guildEmojis(guild.id)}`, true, { name, image }) + .then(data => this.rest.client.actions.EmojiCreate.handle(data, guild).emoji); } deleteEmoji(emoji) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('delete', `${Constants.Endpoints.guildEmojis(emoji.guild.id)}/${emoji.id}`, true) - .then(() => { - resolve(this.rest.client.actions.EmojiDelete.handle(emoji).data); - }, reject); - }); + return this.rest.makeRequest('delete', `${Constants.Endpoints.guildEmojis(emoji.guild.id)}/${emoji.id}`, true) + .then(() => this.rest.client.actions.EmojiDelete.handle(emoji).data); } getWebhook(id, token) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.webhook(id, token), require('util').isUndefined(token)) - .then(data => { - resolve(new Webhook(this.rest.client, data)); - }, reject); - }); + return this.rest.makeRequest('get', Constants.Endpoints.webhook(id, token), !token).then(data => + new Webhook(this.rest.client, data) + ); } getGuildWebhooks(guild) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.guildWebhooks(guild.id), true) - .then(data => { - const hooks = new Collection(); - for (const hook of data) { - hooks.set(hook.id, new Webhook(this.rest.client, hook)); - } - resolve(hooks); - }, reject); + return this.rest.makeRequest('get', Constants.Endpoints.guildWebhooks(guild.id), true).then(data => { + const hooks = new Collection(); + for (const hook of data) hooks.set(hook.id, new Webhook(this.rest.client, hook)); + return hooks; }); } getChannelWebhooks(channel) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.channelWebhooks(channel.id), true) - .then(data => { - const hooks = new Collection(); - for (const hook of data) { - hooks.set(hook.id, new Webhook(this.rest.client, hook)); - } - resolve(hooks); - }, reject); + return this.rest.makeRequest('get', Constants.Endpoints.channelWebhooks(channel.id), true).then(data => { + const hooks = new Collection(); + for (const hook of data) hooks.set(hook.id, new Webhook(this.rest.client, hook)); + return hooks; }); } createWebhook(channel, name, avatar) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('post', Constants.Endpoints.channelWebhooks(channel.id), true, { - name, - avatar, - }).then(data => resolve(new Webhook(this.rest.client, data)), reject); - }); + return this.rest.makeRequest('post', Constants.Endpoints.channelWebhooks(channel.id), true, { name, avatar }) + .then(data => new Webhook(this.rest.client, data)); } editWebhook(webhook, name, avatar) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('patch', Constants.Endpoints.webhook(webhook.id, webhook.token), false, { - name, - avatar, - }).then(data => { - webhook.name = data.name; - webhook.avatar = data.avatar; - resolve(webhook); - }, reject); + return this.rest.makeRequest('patch', Constants.Endpoints.webhook(webhook.id, webhook.token), false, { + name, + avatar, + }).then(data => { + webhook.name = data.name; + webhook.avatar = data.avatar; + return webhook; }); } @@ -604,123 +521,100 @@ class RESTMethods { } sendWebhookMessage(webhook, content, { avatarURL, tts, disableEveryone, embeds } = {}, file = null) { - return new Promise((resolve, reject) => { - if (typeof content !== 'undefined') content = this.rest.client.resolver.resolveString(content); - + if (typeof content !== 'undefined') content = this.rest.client.resolver.resolveString(content); + if (content) { if (disableEveryone || (typeof disableEveryone === 'undefined' && this.rest.client.options.disableEveryone)) { - content = content.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere'); + content = content.replace(/@(everyone|here)/g, '@\u200b$1'); } - - this.rest.makeRequest('post', `${Constants.Endpoints.webhook(webhook.id, webhook.token)}?wait=true`, false, { - content: content, username: webhook.name, avatar_url: avatarURL, tts: tts, file: file, embeds: embeds, - }).then(data => resolve(data), reject); + } + return this.rest.makeRequest('post', `${Constants.Endpoints.webhook(webhook.id, webhook.token)}?wait=true`, false, { + username: webhook.name, + avatar_url: avatarURL, + content, + tts, + file, + embeds, }); } sendSlackWebhookMessage(webhook, body) { - return new Promise((resolve, reject) => { - this.rest.makeRequest( - 'post', - `${Constants.Endpoints.webhook(webhook.id, webhook.token)}/slack?wait=true`, - false, - body - ).then(data => resolve(data), reject); - }); - } - - addFriend(user) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('post', Constants.Endpoints.relationships('@me'), true, { - discriminator: user.discriminator, - username: user.username, - }).then(() => resolve(user), reject); - }); - } - - removeFriend(user) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('delete', `${Constants.Endpoints.relationships('@me')}/${user.id}`, true) - .then(() => resolve(user), reject); - }); + return this.rest.makeRequest( + 'post', `${Constants.Endpoints.webhook(webhook.id, webhook.token)}/slack?wait=true`, false, body + ); } fetchUserProfile(user) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.userProfile(user.id), true) - .then(data => resolve(new UserProfile(user, data)), reject); - }); + return this.rest.makeRequest('get', Constants.Endpoints.userProfile(user.id), true).then(data => + new UserProfile(user, data) + ); + } + + addFriend(user) { + return this.rest.makeRequest('post', Constants.Endpoints.relationships('@me'), true, { + username: user.username, + discriminator: user.discriminator, + }).then(() => user); + } + + removeFriend(user) { + return this.rest.makeRequest('delete', `${Constants.Endpoints.relationships('@me')}/${user.id}`, true) + .then(() => user); } blockUser(user) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('put', `${Constants.Endpoints.relationships('@me')}/${user.id}`, true, { type: 2 }) - .then(() => resolve(user), reject); - }); + return this.rest.makeRequest('put', `${Constants.Endpoints.relationships('@me')}/${user.id}`, true, { type: 2 }) + .then(() => user); } unblockUser(user) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('delete', `${Constants.Endpoints.relationships('@me')}/${user.id}`, true) - .then(() => resolve(user), reject); - }); + return this.rest.makeRequest('delete', `${Constants.Endpoints.relationships('@me')}/${user.id}`, true) + .then(() => user); } setRolePositions(guildID, roles) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('patch', Constants.Endpoints.guildRoles(guildID), true, roles) - .then(() => { - resolve(this.rest.client.actions.GuildRolesPositionUpdate.handle({ - guild_id: guildID, - roles, - }).guild); - }, reject); - }); + return this.rest.makeRequest('patch', Constants.Endpoints.guildRoles(guildID), true, roles).then(() => + this.rest.client.actions.GuildRolesPositionUpdate.handle({ + guild_id: guildID, + roles, + }).guild + ); } 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); - }, reject); - }); + return this.rest.makeRequest('put', Constants.Endpoints.selfMessageReaction(channelID, messageID, emoji), true) + .then(() => + this.rest.client.actions.MessageReactionAdd.handle({ + user_id: this.rest.client.user.id, + message_id: messageID, + emoji: parseEmoji(emoji), + channel_id: channelID, + }).reaction + ); } removeMessageReaction(channelID, messageID, emoji, userID) { - return new Promise((resolve, reject) => { - let endpoint = Constants.Endpoints.selfMessageReaction(channelID, messageID, emoji); - if (userID !== this.rest.client.user.id) { - endpoint = Constants.Endpoints.userMessageReaction(channelID, messageID, emoji, null, userID); - } - this.rest.makeRequest('delete', endpoint, true) - .then(() => { - resolve(this.rest.client.actions.MessageReactionRemove.handle({ - user_id: userID, - message_id: messageID, - emoji: parseEmoji(emoji), - channel_id: channelID, - }).reaction); - }, reject); - }); + let endpoint = Constants.Endpoints.selfMessageReaction(channelID, messageID, emoji); + if (userID !== this.rest.client.user.id) { + endpoint = Constants.Endpoints.userMessageReaction(channelID, messageID, emoji, null, userID); + } + return this.rest.makeRequest('delete', endpoint, true).then(() => + this.rest.client.actions.MessageReactionRemove.handle({ + user_id: userID, + message_id: messageID, + emoji: parseEmoji(emoji), + channel_id: channelID, + }).reaction + ); } getMessageReactionUsers(channelID, messageID, emoji, limit = 100) { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.messageReaction(channelID, messageID, emoji, limit), true) - .then(resolve, reject); - }); + return this.rest.makeRequest('get', Constants.Endpoints.messageReaction(channelID, messageID, emoji, limit), true); } getMyApp() { - return new Promise((resolve, reject) => { - this.rest.makeRequest('get', Constants.Endpoints.myApp, true) - .then(app => resolve(new ClientOAuth2App(this.rest.client, app)), reject); - }); + return this.rest.makeRequest('get', Constants.Endpoints.myApp, true).then(app => + new ClientOAuth2App(this.rest.client, app) + ); } } diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index a85f11f11..dcaa1cc97 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -105,19 +105,17 @@ class ShardingManager extends EventEmitter { * @returns {Promise>} */ spawn(amount = this.totalShards, delay = 5500) { - return new Promise((resolve, reject) => { - if (amount === 'auto') { - fetchRecommendedShards(this.token).then(count => { - this.totalShards = count; - resolve(this._spawn(count, delay)); - }, reject); - } else { - if (typeof amount !== 'number' || isNaN(amount)) throw new TypeError('Amount of shards must be a number.'); - if (amount < 1) throw new RangeError('Amount of shards must be at least 1.'); - if (amount !== Math.floor(amount)) throw new TypeError('Amount of shards must be an integer.'); - resolve(this._spawn(amount, delay)); - } - }); + if (amount === 'auto') { + return fetchRecommendedShards(this.token).then(count => { + this.totalShards = count; + return this._spawn(count, delay); + }); + } else { + if (typeof amount !== 'number' || isNaN(amount)) throw new TypeError('Amount of shards must be a number.'); + if (amount < 1) throw new RangeError('Amount of shards must be at least 1.'); + if (amount !== Math.floor(amount)) throw new TypeError('Amount of shards must be an integer.'); + return this._spawn(amount, delay); + } } /** diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index cd7c3891a..b9877f741 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -90,7 +90,7 @@ class ClientUser extends User { /** * Set the avatar of the logged in Client. - * @param {FileResolvable|Base64Resolveable} avatar The new avatar + * @param {FileResolvable|Base64Resolvable} avatar The new avatar * @returns {Promise} * @example * // set avatar @@ -99,15 +99,13 @@ class ClientUser extends User { * .catch(console.error); */ setAvatar(avatar) { - return new Promise(resolve => { - if (avatar.startsWith('data:')) { - resolve(this.client.rest.methods.updateCurrentUser({ avatar })); - } else { - this.client.resolver.resolveFile(avatar).then(data => { - resolve(this.client.rest.methods.updateCurrentUser({ avatar: data })); - }); - } - }); + if (avatar.startsWith('data:')) { + return this.client.rest.methods.updateCurrentUser({ avatar }); + } else { + return this.client.resolver.resolveFile(avatar).then(data => + this.client.rest.methods.updateCurrentUser({ avatar: data }) + ); + } } /** @@ -172,16 +170,14 @@ class ClientUser extends User { * @returns {Promise} The guild that was created */ createGuild(name, region, icon = null) { - return new Promise(resolve => { - if (!icon) resolve(this.client.rest.methods.createGuild({ name, icon, region })); - if (icon.startsWith('data:')) { - resolve(this.client.rest.methods.createGuild({ name, icon, region })); - } else { - this.client.resolver.resolveFile(icon).then(data => { - resolve(this.client.rest.methods.createGuild({ name, icon: data, region })); - }); - } - }); + if (!icon) return this.client.rest.methods.createGuild({ name, icon, region }); + if (icon.startsWith('data:')) { + return this.client.rest.methods.createGuild({ name, icon, region }); + } else { + return this.client.resolver.resolveFile(icon).then(data => + this.client.rest.methods.createGuild({ name, icon: data, region }) + ); + } } /** diff --git a/src/structures/Guild.js b/src/structures/Guild.js index f581daecd..1eabea56d 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -582,7 +582,7 @@ class Guild { /** * Creates a new custom emoji in the guild. - * @param {FileResolveable} attachment The image for the emoji. + * @param {FileResolvable} attachment The image for the emoji. * @param {string} name The name for the emoji. * @returns {Promise} The created emoji. * @example @@ -597,13 +597,10 @@ class Guild { * .catch(console.error); */ createEmoji(attachment, name) { - return new Promise((resolve, reject) => { - this.client.resolver.resolveFile(attachment).then(file => { - let base64 = new Buffer(file, 'binary').toString('base64'); - let dataURI = `data:;base64,${base64}`; - this.client.rest.methods.createEmoji(this, dataURI, name) - .then(resolve).catch(reject); - }).catch(reject); + return this.client.resolver.resolveFile(attachment).then(file => { + let base64 = new Buffer(file, 'binary').toString('base64'); + let dataURI = `data:;base64,${base64}`; + return this.client.rest.methods.createEmoji(this, dataURI, name); }); } diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index 822e5e2fe..2598e2217 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -1,54 +1,6 @@ const Collection = require('../util/Collection'); const Emoji = require('./Emoji'); -/** - * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis - * will use this class opposed to the Emoji class when the client doesn't know enough - * information about them. - */ -class ReactionEmoji { - constructor(reaction, name, id) { - /** - * The message reaction this emoji refers to - * @type {MessageReaction} - */ - this.reaction = reaction; - /** - * The name of this reaction emoji. - * @type {string} - */ - this.name = name; - /** - * The ID of this reaction emoji. - * @type {string} - */ - this.id = id; - } - - /** - * The identifier of this emoji, used for message reactions - * @readonly - * @type {string} - */ - get identifier() { - if (this.id) { - return `${this.name}:${this.id}`; - } - return encodeURIComponent(this.name); - } - - /** - * Creates the text required to form a graphical emoji on Discord. - * @example - * // send the emoji used in a reaction to the channel the reaction is part of - * reaction.message.channel.sendMessage(`The emoji used is ${reaction.emoji}`); - * @returns {string} - */ - toString() { - return this.id ? `<:${this.name}:${this.id}>` : this.name; - } -} - /** * Represents a reaction to a message */ @@ -59,22 +11,26 @@ class MessageReaction { * @type {Message} */ this.message = message; + /** * Whether the client has given this reaction * @type {boolean} */ this.me = me; - this._emoji = new ReactionEmoji(this, emoji.name, emoji.id); + /** * 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(); + + this._emoji = new ReactionEmoji(this, emoji.name, emoji.id); } /** @@ -83,9 +39,7 @@ class MessageReaction { * @type {Emoji|ReactionEmoji} */ get emoji() { - if (this._emoji instanceof Emoji) { - return this._emoji; - } + if (this._emoji instanceof Emoji) return this._emoji; // check to see if the emoji has become known to the client if (this._emoji.id) { const emojis = this.message.client.emojis; @@ -106,11 +60,10 @@ class MessageReaction { remove(user = this.message.client.user) { const message = this.message; user = this.message.client.resolver.resolveUserID(user); - - if (!user) return Promise.reject('A UserIDResolvable is required (string, user, member, message, guild)'); - + 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.channel.id, message.id, this.emoji.identifier, user + ); } /** @@ -121,19 +74,66 @@ class MessageReaction { */ fetchUsers(limit = 100) { const message = this.message; - return new Promise((resolve, reject) => { - message.client.rest.methods.getMessageReactionUsers(message.channel.id, message.id, this.emoji.identifier, limit) - .then(users => { - this.users = new Collection(); - for (const rawUser of users) { - const user = this.message.client.dataManager.newUser(rawUser); - this.users.set(user.id, user); - } - this.count = this.users.size; - resolve(this.users); - }, reject); + return message.client.rest.methods.getMessageReactionUsers( + message.channel.id, message.id, this.emoji.identifier, limit + ).then(users => { + this.users = new Collection(); + for (const rawUser of users) { + const user = this.message.client.dataManager.newUser(rawUser); + this.users.set(user.id, user); + } + this.count = this.users.size; + return users; }); } } +/** + * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis + * will use this class opposed to the Emoji class when the client doesn't know enough + * information about them. + */ +class ReactionEmoji { + constructor(reaction, name, id) { + /** + * The message reaction this emoji refers to + * @type {MessageReaction} + */ + this.reaction = reaction; + + /** + * The name of this reaction emoji. + * @type {string} + */ + this.name = name; + + /** + * The ID of this reaction emoji. + * @type {string} + */ + this.id = id; + } + + /** + * The identifier of this emoji, used for message reactions + * @readonly + * @type {string} + */ + get identifier() { + if (this.id) return `${this.name}:${this.id}`; + return encodeURIComponent(this.name); + } + + /** + * Creates the text required to form a graphical emoji on Discord. + * @example + * // send the emoji used in a reaction to the channel the reaction is part of + * reaction.message.channel.sendMessage(`The emoji used is ${reaction.emoji}`); + * @returns {string} + */ + toString() { + return this.id ? `<:${this.name}:${this.id}>` : this.name; + } +} + module.exports = MessageReaction; diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 0a1dee4e2..08439c268 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -61,17 +61,14 @@ class TextChannel extends GuildChannel { * .catch(console.error) */ createWebhook(name, avatar) { - return new Promise((resolve, reject) => { - if (avatar) { - this.client.resolver.resolveFile(avatar).then(file => { - let base64 = new Buffer(file, 'binary').toString('base64'); - let dataURI = `data:;base64,${base64}`; - resolve(this.client.rest.methods.createWebhook(this, name, dataURI)); - }, reject); - } else { - resolve(this.client.rest.methods.createWebhook(this, name)); - } - }); + if (avatar) { + return this.client.resolver.resolveFile(avatar).then(file => { + let base64 = new Buffer(file, 'binary').toString('base64'); + let dataURI = `data:;base64,${base64}`; + return this.client.rest.methods.createWebhook(this, name, dataURI); + }); + } + return this.client.rest.methods.createWebhook(this, name); } // These are here only for documentation purposes - they are implemented by TextBasedChannel diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index d2a549d08..1791e2c9d 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -143,14 +143,12 @@ class Webhook { fileName = 'file.jpg'; } } - return new Promise((resolve, reject) => { - this.client.resolver.resolveFile(attachment).then(file => { - resolve(this.client.rest.methods.sendWebhookMessage(this, content, options, { - file, - name: fileName, - })); - }, reject); - }); + return this.client.resolver.resolveFile(attachment).then(file => + this.client.rest.methods.sendWebhookMessage(this, content, options, { + file, + name: fileName, + }) + ); } /** @@ -177,18 +175,15 @@ class Webhook { * @returns {Promise} */ edit(name = this.name, avatar) { - return new Promise((resolve, reject) => { - if (avatar) { - this.client.resolver.resolveFile(avatar).then(file => { - const dataURI = this.client.resolver.resolveBase64(file); - resolve(this.client.rest.methods.editWebhook(this, name, dataURI)); - }, reject); - } else { - this.client.rest.methods.editWebhook(this, name).then(data => { - this.setup(data); - resolve(this); - }, reject); - } + if (avatar) { + return this.client.resolver.resolveFile(avatar).then(file => { + const dataURI = this.client.resolver.resolveBase64(file); + return this.client.rest.methods.editWebhook(this, name, dataURI); + }); + } + return this.client.rest.methods.editWebhook(this, name).then(data => { + this.setup(data); + return this; }); } diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index 9797827bd..f6c0e7078 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -92,14 +92,12 @@ class TextBasedChannel { fileName = 'file.jpg'; } } - return new Promise((resolve, reject) => { - this.client.resolver.resolveFile(attachment).then(file => { - this.client.rest.methods.sendMessage(this, content, options, { - file, - name: fileName, - }).then(resolve, reject); - }, reject); - }); + return this.client.resolver.resolveFile(attachment).then(file => + this.client.rest.methods.sendMessage(this, content, options, { + file, + name: fileName, + }) + ); } /** @@ -131,14 +129,10 @@ class TextBasedChannel { * .catch(console.error); */ fetchMessage(messageID) { - return new Promise((resolve, reject) => { - this.client.rest.methods.getChannelMessage(this, messageID).then(data => { - let msg = data; - if (!(msg instanceof Message)) msg = new Message(this, data, this.client); - - this._cacheMessage(msg); - resolve(msg); - }, reject); + return this.client.rest.methods.getChannelMessage(this, messageID).then(data => { + const msg = data instanceof Message ? data : new Message(this, data, this.client); + this._cacheMessage(msg); + return msg; }); } @@ -163,16 +157,14 @@ class TextBasedChannel { * .catch(console.error); */ fetchMessages(options = {}) { - return new Promise((resolve, reject) => { - this.client.rest.methods.getChannelMessages(this, options).then(data => { - const messages = new Collection(); - for (const message of data) { - const msg = new Message(this, message, this.client); - messages.set(message.id, msg); - this._cacheMessage(msg); - } - resolve(messages); - }, reject); + return this.client.rest.methods.getChannelMessages(this, options).then(data => { + const messages = new Collection(); + for (const message of data) { + const msg = new Message(this, message, this.client); + messages.set(message.id, msg); + this._cacheMessage(msg); + } + return messages; }); } @@ -181,16 +173,14 @@ class TextBasedChannel { * @returns {Promise>} */ fetchPinnedMessages() { - return new Promise((resolve, reject) => { - this.client.rest.methods.getChannelPinnedMessages(this).then(data => { - const messages = new Collection(); - for (const message of data) { - const msg = new Message(this, message, this.client); - messages.set(message.id, msg); - this._cacheMessage(msg); - } - resolve(messages); - }, reject); + return this.client.rest.methods.getChannelPinnedMessages(this).then(data => { + const messages = new Collection(); + for (const message of data) { + const msg = new Message(this, message, this.client); + messages.set(message.id, msg); + this._cacheMessage(msg); + } + return messages; }); } @@ -317,16 +307,12 @@ class TextBasedChannel { * @returns {Promise>} Deleted messages */ bulkDelete(messages) { - return new Promise((resolve, reject) => { - if (!isNaN(messages)) { - this.fetchMessages({ limit: messages }).then(msgs => resolve(this.bulkDelete(msgs))); - } else if (messages instanceof Array || messages instanceof Collection) { - const messageIDs = messages instanceof Collection ? messages.keyArray() : messages.map(m => m.id); - resolve(this.client.rest.methods.bulkDeleteMessages(this, messageIDs)); - } else { - reject(new TypeError('Messages must be an Array, Collection, or number.')); - } - }); + if (!isNaN(messages)) return this.fetchMessages({ limit: messages }).then(msgs => this.bulkDelete(msgs)); + if (messages instanceof Array || messages instanceof Collection) { + const messageIDs = messages instanceof Collection ? messages.keyArray() : messages.map(m => m.id); + return this.client.rest.methods.bulkDeleteMessages(this, messageIDs); + } + throw new TypeError('The messages must be an Array, Collection, or number.'); } _cacheMessage(message) { From 5fa9e3548bc63eed7e85622ddd1ab95c7e42b5d3 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 16:29:56 -0400 Subject: [PATCH 025/248] Rename ClientDataResolver.resolveFile -> resolveBuffer --- src/client/ClientDataResolver.js | 8 ++++---- src/structures/ClientUser.js | 8 ++++---- src/structures/Guild.js | 4 ++-- src/structures/TextChannel.js | 4 ++-- src/structures/Webhook.js | 8 ++++---- src/structures/interface/TextBasedChannel.js | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index e729454bd..7888cad11 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -228,15 +228,15 @@ class ClientDataResolver { * * A Buffer * * The path to a local file * * A URL - * @typedef {string|Buffer} FileResolvable + * @typedef {string|Buffer} BufferResolvable */ /** - * Resolves a FileResolvable to a Buffer - * @param {FileResolvable} resource The file resolvable to resolve + * Resolves a BufferResolvable to a Buffer + * @param {BufferResolvable} resource The buffer resolvable to resolve * @returns {Promise} */ - resolveFile(resource) { + resolveBuffer(resource) { if (resource instanceof Buffer) return Promise.resolve(resource); if (typeof resource === 'string') { diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index b9877f741..69f37e5b9 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -90,7 +90,7 @@ class ClientUser extends User { /** * Set the avatar of the logged in Client. - * @param {FileResolvable|Base64Resolvable} avatar The new avatar + * @param {BufferResolvable|Base64Resolvable} avatar The new avatar * @returns {Promise} * @example * // set avatar @@ -102,7 +102,7 @@ class ClientUser extends User { if (avatar.startsWith('data:')) { return this.client.rest.methods.updateCurrentUser({ avatar }); } else { - return this.client.resolver.resolveFile(avatar).then(data => + return this.client.resolver.resolveBuffer(avatar).then(data => this.client.rest.methods.updateCurrentUser({ avatar: data }) ); } @@ -166,7 +166,7 @@ class ClientUser extends User { * This is only available for user accounts, not bot accounts! * @param {string} name The name of the guild * @param {string} region The region for the server - * @param {FileResolvable|Base64Resolvable} [icon=null] The icon for the guild + * @param {BufferResolvable|Base64Resolvable} [icon=null] The icon for the guild * @returns {Promise} The guild that was created */ createGuild(name, region, icon = null) { @@ -174,7 +174,7 @@ class ClientUser extends User { if (icon.startsWith('data:')) { return this.client.rest.methods.createGuild({ name, icon, region }); } else { - return this.client.resolver.resolveFile(icon).then(data => + return this.client.resolver.resolveBuffer(icon).then(data => this.client.rest.methods.createGuild({ name, icon: data, region }) ); } diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 1eabea56d..1aa99d53a 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -582,7 +582,7 @@ class Guild { /** * Creates a new custom emoji in the guild. - * @param {FileResolvable} attachment The image for the emoji. + * @param {BufferResolvable} attachment The image for the emoji. * @param {string} name The name for the emoji. * @returns {Promise} The created emoji. * @example @@ -597,7 +597,7 @@ class Guild { * .catch(console.error); */ createEmoji(attachment, name) { - return this.client.resolver.resolveFile(attachment).then(file => { + return this.client.resolver.resolveBuffer(attachment).then(file => { let base64 = new Buffer(file, 'binary').toString('base64'); let dataURI = `data:;base64,${base64}`; return this.client.rest.methods.createEmoji(this, dataURI, name); diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 08439c268..620a229b7 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -53,7 +53,7 @@ class TextChannel extends GuildChannel { /** * Create a webhook for the channel. * @param {string} name The name of the webhook. - * @param {FileResolvable} avatar The avatar for the webhook. + * @param {BufferResolvable} avatar The avatar for the webhook. * @returns {Promise} webhook The created webhook. * @example * channel.createWebhook('Snek', 'http://snek.s3.amazonaws.com/topSnek.png') @@ -62,7 +62,7 @@ class TextChannel extends GuildChannel { */ createWebhook(name, avatar) { if (avatar) { - return this.client.resolver.resolveFile(avatar).then(file => { + return this.client.resolver.resolveBuffer(avatar).then(file => { let base64 = new Buffer(file, 'binary').toString('base64'); let dataURI = `data:;base64,${base64}`; return this.client.rest.methods.createWebhook(this, name, dataURI); diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 1791e2c9d..85daa06c6 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -127,7 +127,7 @@ class Webhook { /** * Send a file with this webhook - * @param {FileResolvable} attachment The file to send + * @param {BufferResolvable} attachment The file to send * @param {string} [fileName="file.jpg"] The name and extension of the file * @param {StringResolvable} [content] Text message to send with the attachment * @param {WebhookMessageOptions} [options] The options to provide @@ -143,7 +143,7 @@ class Webhook { fileName = 'file.jpg'; } } - return this.client.resolver.resolveFile(attachment).then(file => + return this.client.resolver.resolveBuffer(attachment).then(file => this.client.rest.methods.sendWebhookMessage(this, content, options, { file, name: fileName, @@ -171,12 +171,12 @@ class Webhook { /** * Edit the Webhook. * @param {string} name The new name for the Webhook - * @param {FileResolvable} avatar The new avatar for the Webhook. + * @param {BufferResolvable} avatar The new avatar for the Webhook. * @returns {Promise} */ edit(name = this.name, avatar) { if (avatar) { - return this.client.resolver.resolveFile(avatar).then(file => { + return this.client.resolver.resolveBuffer(avatar).then(file => { const dataURI = this.client.resolver.resolveBase64(file); return this.client.rest.methods.editWebhook(this, name, dataURI); }); diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index f6c0e7078..35ba29ba5 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -76,7 +76,7 @@ class TextBasedChannel { /** * Send a file to this channel - * @param {FileResolvable} attachment The file to send + * @param {BufferResolvable} attachment The file to send * @param {string} [fileName="file.jpg"] The name and extension of the file * @param {StringResolvable} [content] Text message to send with the attachment * @param {MessageOptions} [options] The options to provide @@ -92,7 +92,7 @@ class TextBasedChannel { fileName = 'file.jpg'; } } - return this.client.resolver.resolveFile(attachment).then(file => + return this.client.resolver.resolveBuffer(attachment).then(file => this.client.rest.methods.sendMessage(this, content, options, { file, name: fileName, From 589c44327a3a61444bb0908e3f2deec87c4deb03 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 16:41:39 -0400 Subject: [PATCH 026/248] Make bot/user account notices consistent --- src/client/Client.js | 10 ++++++---- src/structures/ClientUser.js | 10 +++++----- src/structures/Guild.js | 3 ++- src/structures/interface/TextBasedChannel.js | 4 ++-- src/util/Constants.js | 3 ++- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index baf444ddb..c1b088c36 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -109,7 +109,7 @@ class Client extends EventEmitter { /** * A Collection of presences for friends of the logged in user. - * This is only present for user accounts, not bot accounts! + * This is only filled for user accounts, not bot accounts. * @type {Collection} */ this.presences = new Collection(); @@ -246,7 +246,8 @@ class Client extends EventEmitter { /** * This shouldn't really be necessary to most developers as it is automatically invoked every 30 seconds, however - * if you wish to force a sync of Guild data, you can use this. Only applicable to user accounts. + * if you wish to force a sync of Guild data, you can use this. + * This is only applicable to user accounts. * @param {Guild[]|Collection} [guilds=this.guilds] An array or collection of guilds to sync */ syncGuilds(guilds = this.guilds) { @@ -260,7 +261,7 @@ class Client extends EventEmitter { /** * Caches a user, or obtains it from the cache if it's already cached. - * If the user isn't already cached, it will only be obtainable by OAuth bot accounts. + * This is only available to bot accounts. * @param {string} id The ID of the user to obtain * @returns {Promise} */ @@ -326,7 +327,8 @@ class Client extends EventEmitter { } /** - * Get's the bot's OAuth2 app. Only usable by bot accounts + * Get's the bot's OAuth2 app. + * This is only available for bot accounts. * @returns {Promise} */ getMyApp() { diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 69f37e5b9..1a2e6466b 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -25,14 +25,14 @@ class ClientUser extends User { /** * A Collection of friends for the logged in user. - * This is only filled for user accounts, not bot accounts! + * This is only filled for user accounts, not bot accounts. * @type {Collection} */ this.friends = new Collection(); /** * A Collection of blocked users for the logged in user. - * This is only filled for user accounts, not bot accounts! + * This is only filled for user accounts, not bot accounts. * @type {Collection} */ this.blocked = new Collection(); @@ -141,7 +141,7 @@ class ClientUser extends User { /** * Send a friend request - * This is only available for user accounts, not bot accounts! + * This is only available for user accounts, not bot accounts. * @param {UserResolvable} user The user to send the friend request to. * @returns {Promise} The user the friend request was sent to. */ @@ -152,7 +152,7 @@ class ClientUser extends User { /** * Remove a friend - * This is only available for user accounts, not bot accounts! + * This is only available for user accounts, not bot accounts. * @param {UserResolvable} user The user to remove from your friends * @returns {Promise} The user that was removed */ @@ -163,7 +163,7 @@ class ClientUser extends User { /** * Creates a guild - * This is only available for user accounts, not bot accounts! + * This is only available for user accounts, not bot accounts. * @param {string} name The name of the guild * @param {string} region The region for the server * @param {BufferResolvable|Base64Resolvable} [icon=null] The icon for the guild diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 1aa99d53a..5521bbcbd 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -538,7 +538,8 @@ class Guild { } /** - * Syncs this guild (already done automatically every 30 seconds). Only applicable to user accounts. + * Syncs this guild (already done automatically every 30 seconds). + * This is only applicable to user accounts. */ sync() { if (!this.client.user.bot) this.client.syncGuilds([this]); diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index 35ba29ba5..fd7684232 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -119,7 +119,7 @@ class TextBasedChannel { /** * Gets a single message from this channel, regardless of it being cached or not. - * Only OAuth bot accounts can use this method. + * This is only available for bot accounts. * @param {string} messageID The ID of the message to get * @returns {Promise} * @example @@ -302,7 +302,7 @@ class TextBasedChannel { /** * Bulk delete given messages. - * Only OAuth Bot accounts may use this method. + * This is only available for bot accounts. * @param {Collection|Message[]|number} messages Messages to delete, or number of messages to delete * @returns {Promise>} Deleted messages */ diff --git a/src/util/Constants.js b/src/util/Constants.js index 8ce939b21..d8015a73e 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -58,7 +58,8 @@ exports.DefaultOptions = { exports.Errors = { NO_TOKEN: 'Request to use token, but token was unavailable to the client.', - NO_BOT_ACCOUNT: 'You have to use a bot account to use this method.', + NO_BOT_ACCOUNT: 'Only bot accounts are able to make use of this feature.', + NO_USER_ACCOUNT: 'Only user accounts are able to make use of this feature.', BAD_WS_MESSAGE: 'A bad message was received from the websocket; either bad compression, or not JSON.', TOOK_TOO_LONG: 'Something took too long to do.', NOT_A_PERMISSION: 'Invalid permission string or number.', From 85330769a7b2625a96da431aca4e6c111c0a4273 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 16:55:08 -0400 Subject: [PATCH 027/248] Refactor OAuth application stuff --- src/client/Client.js | 6 +++--- src/client/rest/RESTMethods.js | 8 ++++---- src/index.js | 4 ++-- .../{ClientOAuth2App.js => ClientOAuth2Application.js} | 8 ++++---- src/structures/{OAuth2App.js => OAuth2Application.js} | 8 ++++---- src/util/Constants.js | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) rename src/structures/{ClientOAuth2App.js => ClientOAuth2Application.js} (63%) rename src/structures/{OAuth2App.js => OAuth2Application.js} (90%) diff --git a/src/client/Client.js b/src/client/Client.js index c1b088c36..03189c7d4 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -327,13 +327,13 @@ class Client extends EventEmitter { } /** - * Get's the bot's OAuth2 app. + * Gets the bot's OAuth2 application. * This is only available for bot accounts. * @returns {Promise} */ - getMyApp() { + fetchApplication() { if (!this.user.bot) throw new Error(Constants.Errors.NO_BOT_ACCOUNT); - return this.rest.methods.getMyApp(); + return this.rest.methods.getMyApplication(); } setTimeout(fn, ...params) { diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 1ca113289..52ec23205 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -10,7 +10,7 @@ const Role = requireStructure('Role'); const Invite = requireStructure('Invite'); const Webhook = requireStructure('Webhook'); const UserProfile = requireStructure('UserProfile'); -const ClientOAuth2App = requireStructure('ClientOAuth2App'); +const ClientOAuth2Application = requireStructure('ClientOAuth2Application'); class RESTMethods { constructor(restManager) { @@ -611,9 +611,9 @@ class RESTMethods { return this.rest.makeRequest('get', Constants.Endpoints.messageReaction(channelID, messageID, emoji, limit), true); } - getMyApp() { - return this.rest.makeRequest('get', Constants.Endpoints.myApp, true).then(app => - new ClientOAuth2App(this.rest.client, app) + getMyApplication() { + return this.rest.makeRequest('get', Constants.Endpoints.myApplication, true).then(app => + new ClientOAuth2Application(this.rest.client, app) ); } } diff --git a/src/index.js b/src/index.js index 0c845cd4b..cca40367a 100644 --- a/src/index.js +++ b/src/index.js @@ -11,7 +11,7 @@ module.exports = { fetchRecommendedShards: require('./util/FetchRecommendedShards'), Channel: require('./structures/Channel'), - ClientOAuth2App: require('./structures/ClientOAuth2App'), + ClientOAuth2Application: require('./structures/ClientOAuth2Application'), ClientUser: require('./structures/ClientUser'), DMChannel: require('./structures/DMChannel'), Emoji: require('./structures/Emoji'), @@ -26,7 +26,7 @@ module.exports = { MessageAttachment: require('./structures/MessageAttachment'), MessageCollector: require('./structures/MessageCollector'), MessageEmbed: require('./structures/MessageEmbed'), - OAuth2App: require('./structures/OAuth2App'), + OAuth2Application: require('./structures/OAuth2Application'), PartialGuild: require('./structures/PartialGuild'), PartialGuildChannel: require('./structures/PartialGuildChannel'), PermissionOverwrites: require('./structures/PermissionOverwrites'), diff --git a/src/structures/ClientOAuth2App.js b/src/structures/ClientOAuth2Application.js similarity index 63% rename from src/structures/ClientOAuth2App.js rename to src/structures/ClientOAuth2Application.js index e3b1013b8..158d71a4f 100644 --- a/src/structures/ClientOAuth2App.js +++ b/src/structures/ClientOAuth2Application.js @@ -1,11 +1,11 @@ const User = require('./User'); -const OAuth2App = require('./OAuth2App'); +const OAuth2Application = require('./OAuth2Application'); /** * Represents the client's OAuth2 Application - * @extends {OAuth2App} + * @extends {OAuth2Application} */ -class ClientOAuth2App extends OAuth2App { +class ClientOAuth2Application extends OAuth2Application { setup(data) { super.setup(data); @@ -23,4 +23,4 @@ class ClientOAuth2App extends OAuth2App { } } -module.exports = ClientOAuth2App; +module.exports = ClientOAuth2Application; diff --git a/src/structures/OAuth2App.js b/src/structures/OAuth2Application.js similarity index 90% rename from src/structures/OAuth2App.js rename to src/structures/OAuth2Application.js index 89bcae805..9969c8ed6 100644 --- a/src/structures/OAuth2App.js +++ b/src/structures/OAuth2Application.js @@ -1,10 +1,10 @@ /** - * Represents a OAuth2 Application + * Represents an OAuth2 Application */ -class OAuth2App { +class OAuth2Application { constructor(client, data) { /** - * The client that instantiated the role + * The client that instantiated the application * @type {Client} */ this.client = client; @@ -78,4 +78,4 @@ class OAuth2App { } } -module.exports = OAuth2App; +module.exports = OAuth2Application; diff --git a/src/util/Constants.js b/src/util/Constants.js index d8015a73e..f21141b82 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -131,7 +131,7 @@ const Endpoints = exports.Endpoints = { webhook: (webhookID, token) => `${API}/webhooks/${webhookID}${token ? `/${token}` : ''}`, // oauth - myApp: `${API}/oauth2/applications/@me`, + myApplication: `${API}/oauth2/applications/@me`, getApp: (id) => `${API}/oauth2/authorize?client_id=${id}`, }; From f2496070d36df1d6fde1a5b080add58a7fd33965 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 17:02:06 -0400 Subject: [PATCH 028/248] Document client timeout/interval stuff --- src/client/Client.js | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 03189c7d4..3e3a32172 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -336,26 +336,48 @@ class Client extends EventEmitter { return this.rest.methods.getMyApplication(); } - setTimeout(fn, ...params) { + /** + * Sets a timeout that will be automatically cancelled if the client is destroyed. + * @param {function} fn Function to execute + * @param {number} delay Time to wait before executing (in milliseconds) + * @param {args} args Arguments for the function (not an array, but an infinite argument) + * @returns {Timeout} + */ + setTimeout(fn, delay, ...args) { const timeout = setTimeout(() => { fn(); this._timeouts.delete(timeout); - }, ...params); + }, delay, ...args); this._timeouts.add(timeout); return timeout; } + /** + * Clears a timeout + * @param {Timeout} timeout Timeout to cancel + */ clearTimeout(timeout) { clearTimeout(timeout); this._timeouts.delete(timeout); } - setInterval(...params) { - const interval = setInterval(...params); + /** + * Sets an interval that will be automatically cancelled if the client is destroyed. + * @param {function} fn Function to execute + * @param {number} delay Time to wait before executing (in milliseconds) + * @param {args} args Arguments for the function (not an array, but an infinite argument) + * @returns {Timeout} + */ + setInterval(fn, delay, ...args) { + const interval = setInterval(fn, delay, ...args); this._intervals.add(interval); return interval; } + /** + * Clears an interval + * @param {Timeout} interval Interval to cancel + */ clearInterval(interval) { clearInterval(interval); this._intervals.delete(interval); From d6f55adf52eb7d365e7125e0134ad7451d3d46b3 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 17:05:18 -0400 Subject: [PATCH 029/248] Add missing exports for reaction stuff --- src/index.js | 2 ++ src/structures/MessageReaction.js | 49 +------------------------------ src/structures/ReactionEmoji.js | 49 +++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 48 deletions(-) create mode 100644 src/structures/ReactionEmoji.js diff --git a/src/index.js b/src/index.js index cca40367a..3eea67ea1 100644 --- a/src/index.js +++ b/src/index.js @@ -26,11 +26,13 @@ module.exports = { MessageAttachment: require('./structures/MessageAttachment'), MessageCollector: require('./structures/MessageCollector'), MessageEmbed: require('./structures/MessageEmbed'), + MessageReaction: require('./structures/MessageReaction'), OAuth2Application: require('./structures/OAuth2Application'), PartialGuild: require('./structures/PartialGuild'), PartialGuildChannel: require('./structures/PartialGuildChannel'), PermissionOverwrites: require('./structures/PermissionOverwrites'), Presence: require('./structures/Presence').Presence, + ReactionEmoji: require('./structures/ReactionEmoji'), Role: require('./structures/Role'), TextChannel: require('./structures/TextChannel'), User: require('./structures/User'), diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index 2598e2217..693b9dc92 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -1,5 +1,6 @@ const Collection = require('../util/Collection'); const Emoji = require('./Emoji'); +const ReactionEmoji = require('./ReactionEmoji'); /** * Represents a reaction to a message @@ -88,52 +89,4 @@ class MessageReaction { } } -/** - * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis - * will use this class opposed to the Emoji class when the client doesn't know enough - * information about them. - */ -class ReactionEmoji { - constructor(reaction, name, id) { - /** - * The message reaction this emoji refers to - * @type {MessageReaction} - */ - this.reaction = reaction; - - /** - * The name of this reaction emoji. - * @type {string} - */ - this.name = name; - - /** - * The ID of this reaction emoji. - * @type {string} - */ - this.id = id; - } - - /** - * The identifier of this emoji, used for message reactions - * @readonly - * @type {string} - */ - get identifier() { - if (this.id) return `${this.name}:${this.id}`; - return encodeURIComponent(this.name); - } - - /** - * Creates the text required to form a graphical emoji on Discord. - * @example - * // send the emoji used in a reaction to the channel the reaction is part of - * reaction.message.channel.sendMessage(`The emoji used is ${reaction.emoji}`); - * @returns {string} - */ - toString() { - return this.id ? `<:${this.name}:${this.id}>` : this.name; - } -} - module.exports = MessageReaction; diff --git a/src/structures/ReactionEmoji.js b/src/structures/ReactionEmoji.js new file mode 100644 index 000000000..b6d2cdbd6 --- /dev/null +++ b/src/structures/ReactionEmoji.js @@ -0,0 +1,49 @@ +/** + * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis + * will use this class opposed to the Emoji class when the client doesn't know enough + * information about them. + */ +class ReactionEmoji { + constructor(reaction, name, id) { + /** + * The message reaction this emoji refers to + * @type {MessageReaction} + */ + this.reaction = reaction; + + /** + * The name of this reaction emoji. + * @type {string} + */ + this.name = name; + + /** + * The ID of this reaction emoji. + * @type {string} + */ + this.id = id; + } + + /** + * The identifier of this emoji, used for message reactions + * @readonly + * @type {string} + */ + get identifier() { + if (this.id) return `${this.name}:${this.id}`; + return encodeURIComponent(this.name); + } + + /** + * Creates the text required to form a graphical emoji on Discord. + * @example + * // send the emoji used in a reaction to the channel the reaction is part of + * reaction.message.channel.sendMessage(`The emoji used is ${reaction.emoji}`); + * @returns {string} + */ + toString() { + return this.id ? `<:${this.name}:${this.id}>` : this.name; + } +} + +module.exports = ReactionEmoji; From 73261646fc49a81e418791bb38a6401e43a5a1e2 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 17:23:39 -0400 Subject: [PATCH 030/248] Fix ESLint warnings --- .eslintrc.json | 2 ++ package.json | 4 ++-- src/client/voice/dispatcher/StreamDispatcher.js | 2 +- src/structures/GuildChannel.js | 2 +- src/structures/GuildMember.js | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 90f35dc06..2e3021ca0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -80,6 +80,7 @@ "consistent-this": ["error", "$this"], "eol-last": "error", "func-names": "error", + "func-name-matching": "error", "func-style": ["error", "declaration", { "allowArrowFunctions": true }], "indent": ["error", 2, { "SwitchCase": 1 }], "key-spacing": "error", @@ -122,6 +123,7 @@ "no-useless-computed-key": "error", "no-useless-constructor": "error", "prefer-arrow-callback": "error", + "prefer-numeric-literals": "error", "prefer-rest-params": "error", "prefer-spread": "error", "prefer-template": "error", diff --git a/package.json b/package.json index 6e92756f8..978a47b96 100644 --- a/package.json +++ b/package.json @@ -31,11 +31,11 @@ "ws": "^1.1.1" }, "peerDependencies": { - "node-opus": "^0.2.1", + "node-opus": "^0.2.2", "opusscript": "^0.0.1" }, "devDependencies": { - "eslint": "^3.8.0", + "eslint": "^3.9.0", "fs-extra": "^0.30.0", "jsdoc-to-markdown": "^2.0.0" }, diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index 0a4dbe493..f69fa0436 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -223,7 +223,7 @@ class StreamDispatcher extends EventEmitter { buffer = this._applyVolume(buffer); data.count++; - data.sequence = (data.sequence + 1) < (65536) ? data.sequence + 1 : 0; + data.sequence = (data.sequence + 1) < 65536 ? data.sequence + 1 : 0; data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0; this._sendBuffer(buffer, data.sequence, data.timestamp); diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 7d220cf6c..59b9879f4 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -70,7 +70,7 @@ class GuildChannel extends Channel { permissions |= overwrite.allowData; } - const admin = Boolean(permissions & (Constants.PermissionFlags.ADMINISTRATOR)); + const admin = Boolean(permissions & Constants.PermissionFlags.ADMINISTRATOR); if (admin) permissions = Constants.ALL_PERMISSIONS; return new EvaluatedPermissions(member, permissions); diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 408efedc8..e972df1c8 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -187,7 +187,7 @@ class GuildMember { const roles = this.roles; for (const role of roles.values()) permissions |= role.permissions; - const admin = Boolean(permissions & (Constants.PermissionFlags.ADMINISTRATOR)); + const admin = Boolean(permissions & Constants.PermissionFlags.ADMINISTRATOR); if (admin) permissions = Constants.ALL_PERMISSIONS; return new EvaluatedPermissions(this, permissions); From d7e1e1c0c9228baaaf022b59ee420f32d307c3b7 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 22:22:16 -0400 Subject: [PATCH 031/248] Add warnings for Collection.find/exists --- src/client/Client.js | 4 ++-- src/util/Collection.js | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 3e3a32172..c19c01e67 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -340,7 +340,7 @@ class Client extends EventEmitter { * Sets a timeout that will be automatically cancelled if the client is destroyed. * @param {function} fn Function to execute * @param {number} delay Time to wait before executing (in milliseconds) - * @param {args} args Arguments for the function (not an array, but an infinite argument) + * @param {args} args Arguments for the function (infinite/rest argument, not an array) * @returns {Timeout} */ setTimeout(fn, delay, ...args) { @@ -365,7 +365,7 @@ class Client extends EventEmitter { * Sets an interval that will be automatically cancelled if the client is destroyed. * @param {function} fn Function to execute * @param {number} delay Time to wait before executing (in milliseconds) - * @param {args} args Arguments for the function (not an array, but an infinite argument) + * @param {args} args Arguments for the function (infinite/rest argument, not an array) * @returns {Timeout} */ setInterval(fn, delay, ...args) { diff --git a/src/util/Collection.js b/src/util/Collection.js index 53299023a..7dc8a10da 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -125,6 +125,8 @@ class Collection extends Map { * Returns a single item where `item[prop] === value`, or the given function returns `true`. * In the latter case, this is identical to * [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). + * Do not use this to obtain an item by its ID. Instead, use `collection.get(id)`. See + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) for details. * @param {string|function} propOrFn The property to test against, or the function to test with * @param {*} [value] The expected value - only applicable and required if using a property for the first argument * @returns {*} @@ -183,6 +185,8 @@ class Collection extends Map { /** * Returns true if the collection has an item where `item[prop] === value` + * Do not use this to check for an item by its ID. Instead, use `collection.has(id)`. See + * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) for details. * @param {string} prop The property to test against * @param {*} value The expected value * @returns {boolean} @@ -288,7 +292,7 @@ class Collection extends Map { /** * Combines this collection with others into a new collection. None of the source collections are modified. - * @param {Collection} collections Collections to merge + * @param {Collection} collections Collections to merge (infinite/rest argument, not an array) * @returns {Collection} * @example const newColl = someColl.concat(someOtherColl, anotherColl, ohBoyAColl); */ From a673a97441acccb4246fc9a02dc37f0dee2eef6d Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 30 Oct 2016 22:28:06 -0400 Subject: [PATCH 032/248] Rephrase Collection.find/exists slightly --- src/util/Collection.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/Collection.js b/src/util/Collection.js index 7dc8a10da..a6b7bf856 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -122,7 +122,7 @@ class Collection extends Map { } /** - * Returns a single item where `item[prop] === value`, or the given function returns `true`. + * Searches for a single item where `item[prop] === value`, or the given function returns `true`. * In the latter case, this is identical to * [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). * Do not use this to obtain an item by its ID. Instead, use `collection.get(id)`. See @@ -154,7 +154,7 @@ class Collection extends Map { /* eslint-disable max-len */ /** - * Returns the key of the item where `item[prop] === value`, or the given function returns `true`. + * Searches for the key of an item where `item[prop] === value`, or the given function returns `true`. * In the latter case, this is identical to * [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). * @param {string|function} propOrFn The property to test against, or the function to test with @@ -184,7 +184,7 @@ class Collection extends Map { } /** - * Returns true if the collection has an item where `item[prop] === value` + * Searches for an item where `item[prop] === value`, and returns `true` if one is found. * Do not use this to check for an item by its ID. Instead, use `collection.has(id)`. See * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) for details. * @param {string} prop The property to test against From 6dc95cd0846b8e473d6b3b8dac9f8a74e2225c9a Mon Sep 17 00:00:00 2001 From: Programmix Date: Sun, 30 Oct 2016 20:06:09 -0700 Subject: [PATCH 033/248] Add support for notes (#860) * Add support for notes * Ensure consistency with notes from ready payload * Add getter method for users * Minor tweaks * Update warning messages * More minor fixes --- src/client/actions/ActionsManager.js | 1 + src/client/actions/UserNoteUpdate.js | 30 +++++++++++++++++++ src/client/rest/RESTMethods.js | 5 ++++ .../packets/WebSocketPacketManager.js | 1 + .../websocket/packets/handlers/Ready.js | 9 ++++++ .../packets/handlers/UserNoteUpdate.js | 12 ++++++++ src/structures/ClientUser.js | 7 +++++ src/structures/User.js | 24 +++++++++++++++ src/util/Constants.js | 3 ++ 9 files changed, 92 insertions(+) create mode 100644 src/client/actions/UserNoteUpdate.js create mode 100644 src/client/websocket/packets/handlers/UserNoteUpdate.js diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js index dbfdc19dd..e4545fd73 100644 --- a/src/client/actions/ActionsManager.js +++ b/src/client/actions/ActionsManager.js @@ -21,6 +21,7 @@ class ActionsManager { this.register('GuildRoleUpdate'); this.register('UserGet'); this.register('UserUpdate'); + this.register('UserNoteUpdate'); this.register('GuildSync'); this.register('GuildEmojiCreate'); this.register('GuildEmojiDelete'); diff --git a/src/client/actions/UserNoteUpdate.js b/src/client/actions/UserNoteUpdate.js new file mode 100644 index 000000000..4c2cc2187 --- /dev/null +++ b/src/client/actions/UserNoteUpdate.js @@ -0,0 +1,30 @@ +const Action = require('./Action'); +const Constants = require('../../util/Constants'); + +class UserNoteUpdateAction extends Action { + handle(data) { + const client = this.client; + + const oldNote = client.user.notes.get(data.id); + const note = data.note.length ? data.note : null; + + client.user.notes.set(data.id, note); + + client.emit(Constants.Events.USER_NOTE_UPDATE, data.id, oldNote, note); + + return { + old: oldNote, + updated: note, + }; + } +} + +/** + * Emitted whenever a note is updated. + * @event Client#userNoteUpdate + * @param {User} user The user the note belongs to + * @param {string} oldNote The note content before the update + * @param {string} newNote The note content after the update + */ + +module.exports = UserNoteUpdateAction; diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 52ec23205..0c86c6db5 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -616,6 +616,11 @@ class RESTMethods { new ClientOAuth2Application(this.rest.client, app) ); } + + setNote(user, note) { + return this.rest.makeRequest('put', Constants.Endpoints.note(user.id), true, { note }) + .then(() => user); + } } module.exports = RESTMethods; diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 99ae348b7..ecff0eec4 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -33,6 +33,7 @@ class WebSocketPacketManager { this.register(Constants.WSEvents.CHANNEL_UPDATE, 'ChannelUpdate'); this.register(Constants.WSEvents.PRESENCE_UPDATE, 'PresenceUpdate'); this.register(Constants.WSEvents.USER_UPDATE, 'UserUpdate'); + this.register(Constants.WSEvents.USER_NOTE_UPDATE, 'UserNoteUpdate'); this.register(Constants.WSEvents.VOICE_STATE_UPDATE, 'VoiceStateUpdate'); this.register(Constants.WSEvents.TYPING_START, 'TypingStart'); this.register(Constants.WSEvents.MESSAGE_CREATE, 'MessageCreate'); diff --git a/src/client/websocket/packets/handlers/Ready.js b/src/client/websocket/packets/handlers/Ready.js index bb35ea47b..96d8557d7 100644 --- a/src/client/websocket/packets/handlers/Ready.js +++ b/src/client/websocket/packets/handlers/Ready.js @@ -31,6 +31,15 @@ class ReadyHandler extends AbstractHandler { client._setPresence(presence.user.id, presence); } + if (data.notes) { + for (const user in data.notes) { + let note = data.notes[user]; + if (!note.length) note = null; + + client.user.notes.set(user, note); + } + } + if (!client.user.bot && client.options.sync) client.setInterval(client.syncGuilds.bind(client), 30000); client.once('ready', client.syncGuilds.bind(client)); diff --git a/src/client/websocket/packets/handlers/UserNoteUpdate.js b/src/client/websocket/packets/handlers/UserNoteUpdate.js new file mode 100644 index 000000000..1e4777a39 --- /dev/null +++ b/src/client/websocket/packets/handlers/UserNoteUpdate.js @@ -0,0 +1,12 @@ +const AbstractHandler = require('./AbstractHandler'); + +class UserNoteUpdateHandler extends AbstractHandler { + handle(packet) { + const client = this.packetManager.client; + const data = packet.d; + + client.actions.UserNoteUpdate.handle(data); + } +} + +module.exports = UserNoteUpdateHandler; diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 1a2e6466b..69e128581 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -36,6 +36,13 @@ class ClientUser extends User { * @type {Collection} */ this.blocked = new Collection(); + + /** + * A Collection of notes for the logged in user. + * This is only filled for user accounts, not bot accounts. + * @type {Collection} + */ + this.notes = new Collection(); } edit(data) { diff --git a/src/structures/User.js b/src/structures/User.js index b9ac5b522..3dcd9da79 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -97,6 +97,16 @@ class User { return Constants.Endpoints.avatar(this.id, this.avatar); } + /** + * The note that is set for the user + * This is only available for user accounts. + * @type {?string} + * @readonly + */ + get note() { + return this.client.user.notes.get(this.id) || null; + } + /** * Check whether the user is typing in a channel. * @param {ChannelResolvable} channel The channel to check in @@ -137,6 +147,7 @@ class User { /** * Sends a friend request to the user + * This is only available for user accounts. * @returns {Promise} */ addFriend() { @@ -145,6 +156,7 @@ class User { /** * Removes the user from your friends + * This is only available for user accounts. * @returns {Promise} */ removeFriend() { @@ -153,6 +165,7 @@ class User { /** * Blocks the user + * This is only available for user accounts. * @returns {Promise} */ block() { @@ -161,6 +174,7 @@ class User { /** * Unblocks the user + * This is only available for user accounts. * @returns {Promise} */ unblock() { @@ -175,6 +189,16 @@ class User { return this.client.rest.methods.fetchUserProfile(this); } + /** + * Sets a note for the user + * This is only available for user accounts. + * @param {string} note The note to set for the user + * @returns {Promise} + */ + setNote(note) { + return this.client.rest.methods.setNote(this, note); + } + /** * Checks if the user is equal to another. It compares username, ID, discriminator, status and the game being played. * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties. diff --git a/src/util/Constants.js b/src/util/Constants.js index f21141b82..3fa18b869 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -88,6 +88,7 @@ const Endpoints = exports.Endpoints = { me: `${API}/users/@me`, meGuild: (guildID) => `${Endpoints.me}/guilds/${guildID}`, relationships: (userID) => `${Endpoints.user(userID)}/relationships`, + note: (userID) => `${Endpoints.me}/notes/${userID}`, // guilds guilds: `${API}/guilds`, @@ -206,6 +207,7 @@ exports.Events = { MESSAGE_REACTION_ADD: 'messageReactionAdd', MESSAGE_REACTION_REMOVE: 'messageReactionRemove', USER_UPDATE: 'userUpdate', + USER_NOTE_UPDATE: 'userNoteUpdate', PRESENCE_UPDATE: 'presenceUpdate', VOICE_STATE_UPDATE: 'voiceStateUpdate', TYPING_START: 'typingStart', @@ -243,6 +245,7 @@ exports.WSEvents = { MESSAGE_REACTION_ADD: 'MESSAGE_REACTION_ADD', MESSAGE_REACTION_REMOVE: 'MESSAGE_REACTION_REMOVE', USER_UPDATE: 'USER_UPDATE', + USER_NOTE_UPDATE: 'USER_NOTE_UPDATE', PRESENCE_UPDATE: 'PRESENCE_UPDATE', VOICE_STATE_UPDATE: 'VOICE_STATE_UPDATE', TYPING_START: 'TYPING_START', From 2a50dad852bba37a860deae5ca9b530f5327c2cd Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 1 Nov 2016 01:39:53 -0400 Subject: [PATCH 034/248] Update docs for ezmode node-opus Windows building --- README.md | 2 +- docs/custom/documents/faq.md | 5 +++-- docs/custom/documents/welcome.md | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a0909b7d6..ef776560c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus. -Using opusscript is only recommended for development on Windows, since getting node-opus to build there can be a bit of a challenge. +Using opusscript is only recommended for development environments where node-opus is tough to get working. For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers. ## Example Usage diff --git a/docs/custom/documents/faq.md b/docs/custom/documents/faq.md index 82a94f656..6993620a7 100644 --- a/docs/custom/documents/faq.md +++ b/docs/custom/documents/faq.md @@ -9,7 +9,7 @@ Update to Node.js 6.0.0 or newer. ## How do I get voice working? - Install FFMPEG. - Install either the `node-opus` package or the `opusscript` package. - node-opus is greatly preferred, but is tougher to get working on Windows. + node-opus is greatly preferred, due to it having significantly better performance. ## How do I install FFMPEG? - **Ubuntu 16.04:** `sudo apt install ffpmeg` @@ -18,4 +18,5 @@ Update to Node.js 6.0.0 or newer. ## How do I set up node-opus? - **Ubuntu:** Simply run `npm install node-opus`, and it's done. Congrats! -- **Windows:** See [AoDude's guide](https://github.com/bdistin/OhGodMusicBot/blob/master/README.md). Good luck. +- **Windows:** Run `npm install --global --production windows-build-tools` in an admin command prompt or PowerShell. + Then, running `npm install node-opus` in your bot's directory should successfully build it. Woo! diff --git a/docs/custom/documents/welcome.md b/docs/custom/documents/welcome.md index b5215178e..b8f7b3c5e 100644 --- a/docs/custom/documents/welcome.md +++ b/docs/custom/documents/welcome.md @@ -31,7 +31,7 @@ With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus. -Using opusscript is only recommended for development on Windows, since getting node-opus to build there can be a bit of a challenge. +Using opusscript is only recommended for development environments where node-opus is tough to get working. For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers. ## Guides From 9a61de1493f51d01c28b088f5eb46aaed4596c82 Mon Sep 17 00:00:00 2001 From: Programmix Date: Mon, 31 Oct 2016 22:42:05 -0700 Subject: [PATCH 035/248] Document GuildChannel.edit, add VoiceChannel.setUserLimit, fix typo (#866) --- src/client/rest/RESTMethods.js | 12 +++++++----- src/structures/Guild.js | 16 ++++++++-------- src/structures/GuildChannel.js | 22 +++++++++++++++++++++- src/structures/Role.js | 12 ++++++------ src/structures/VoiceChannel.js | 16 +++++++++++++++- 5 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 0c86c6db5..5f0e063ab 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -159,11 +159,13 @@ class RESTMethods { }); } - updateChannel(channel, data) { - data.name = (data.name || channel.name).trim(); - data.topic = data.topic || channel.topic; - data.position = data.position || channel.position; - data.bitrate = data.bitrate || channel.bitrate; + updateChannel(channel, _data) { + const data = {}; + data.name = (_data.name || channel.name).trim(); + data.topic = _data.topic || channel.topic; + data.position = _data.position || channel.position; + data.bitrate = _data.bitrate || channel.bitrate; + data.user_limit = _data.userLimit || channel.userLimit; return this.rest.makeRequest('patch', Constants.Endpoints.channel(channel.id), true, data).then(newData => this.rest.client.actions.ChannelUpdate.handle(newData).updated ); diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 5521bbcbd..f3616969f 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -347,14 +347,14 @@ class Guild { /** * The data for editing a guild * @typedef {Object} GuildEditData - * @property {string} name The name of the guild - * @property {string} region The region of the guild - * @property {number} verificationLevel The verification level of the guild - * @property {GuildChannelResolvable} afkChannel The AFK channel of the guild - * @property {number} afkTimeout The AFK timeout of the guild - * @property {Base64Resolvable} icon The icon of the guild - * @property {GuildMemberResolvable} owner The owner of the guild - * @property {Base64Resolvable} splash The splash screen of the guild + * @property {string} [name] The name of the guild + * @property {string} [region] The region of the guild + * @property {number} [verificationLevel] The verification level of the guild + * @property {GuildChannelResolvable} [afkChannel] The AFK channel of the guild + * @property {number} [afkTimeout] The AFK timeout of the guild + * @property {Base64Resolvable} [icon] The icon of the guild + * @property {GuildMemberResolvable} [owner] The owner of the guild + * @property {Base64Resolvable} [splash] The splash screen of the guild */ /** diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 59b9879f4..7e3c7c304 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -161,6 +161,26 @@ class GuildChannel extends Channel { return this.client.rest.methods.setChannelOverwrite(this, payload); } + /** + * The data for a guild channel + * @typedef {Object} ChannelData + * @property {string} [name] The name of the channel + * @property {number} [position] The position of the channel + * @property {string} [topic] The topic of the text channel + * @property {number} [bitrate] The bitrate of the voice channel + * @property {number} [userLimit] The user limit of the channel + */ + + /** + * Edits the channel + * @param {ChannelData} data The new data for the channel + * @returns {Promise} + * @example + * // edit a channel + * channel.edit({name: 'new-channel'}) + * .then(c => console.log(`Edited channel ${c}`)) + * .catch(console.error); + */ edit(data) { return this.client.rest.methods.updateChannel(this, data); } @@ -176,7 +196,7 @@ class GuildChannel extends Channel { * .catch(console.error); */ setName(name) { - return this.client.rest.methods.updateChannel(this, { name }); + return this.edit({ name }); } /** diff --git a/src/structures/Role.js b/src/structures/Role.js index 122183f46..4790a5c90 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -166,12 +166,12 @@ class Role { /** * The data for a role * @typedef {Object} RoleData - * @property {string} name The name of the role - * @property {number|string} color The color of the role, either a hex string or a base 10 number - * @property {boolean} hoist Whether or not the role should be hoisted - * @property {number} position The position of the role - * @property {string[]} permissions The permissions of the role - * @property {boolean} mentionable Whether or not the role should be mentionable + * @property {string} [name] The name of the role + * @property {number|string} [color] The color of the role, either a hex string or a base 10 number + * @property {boolean} [hoist] Whether or not the role should be hoisted + * @property {number} [position] The position of the role + * @property {string[]} [permissions] The permissions of the role + * @property {boolean} [mentionable] Whether or not the role should be mentionable */ /** diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js index 2bc565d8b..d190e6a15 100644 --- a/src/structures/VoiceChannel.js +++ b/src/structures/VoiceChannel.js @@ -72,7 +72,21 @@ class VoiceChannel extends GuildChannel { * .catch(console.error); */ setBitrate(bitrate) { - return this.rest.client.rest.methods.updateChannel(this, { bitrate }); + return this.edit({ bitrate }); + } + + /** + * Sets the user limit of the channel + * @param {number} userLimit The new user limit + * @returns {Promise} + * @example + * // set the user limit of a voice channel + * voiceChannel.setUserLimit(42) + * .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`)) + * .catch(console.error); + */ + setUserLimit(userLimit) { + return this.edit({ userLimit }); } /** From b91590d3a80483f454cfa0420a74ea8fa78972ae Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 1 Nov 2016 02:24:37 -0400 Subject: [PATCH 036/248] Fix mispositioned line in ClientOptions doc --- src/util/Constants.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index 3fa18b869..d9b72e934 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -8,8 +8,9 @@ exports.Package = require('../../package.json'); * @property {number} [shardId=0] The ID of this shard * @property {number} [shardCount=0] The number of shards * @property {number} [messageCacheMaxSize=200] Maximum number of messages to cache per channel + * (-1 or Infinity for unlimited - don't do this without message sweeping, otherwise memory usage will climb + * indefinitely) * @property {boolean} [sync=false] Whether to periodically sync guilds - * (-1 for unlimited - don't do this without message sweeping, otherwise memory usage will climb indefinitely) * @property {number} [messageCacheLifetime=0] How long until a message should be uncached by the message sweeping * (in seconds, 0 for forever) * @property {number} [messageSweepInterval=0] How frequently to remove messages from the cache that are older than From 93e6c69bd1c7c306c494878524373332a5fe786e Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 1 Nov 2016 02:28:23 -0400 Subject: [PATCH 037/248] Update sync option docs --- src/util/Constants.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index d9b72e934..167cd13f5 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -10,7 +10,6 @@ exports.Package = require('../../package.json'); * @property {number} [messageCacheMaxSize=200] Maximum number of messages to cache per channel * (-1 or Infinity for unlimited - don't do this without message sweeping, otherwise memory usage will climb * indefinitely) - * @property {boolean} [sync=false] Whether to periodically sync guilds * @property {number} [messageCacheLifetime=0] How long until a message should be uncached by the message sweeping * (in seconds, 0 for forever) * @property {number} [messageSweepInterval=0] How frequently to remove messages from the cache that are older than @@ -18,6 +17,7 @@ exports.Package = require('../../package.json'); * @property {boolean} [fetchAllMembers=false] Whether to cache all guild members and users upon startup, as well as * upon joining a guild * @property {boolean} [disableEveryone=false] Default value for MessageOptions.disableEveryone + * @property {boolean} [sync=false] Whether to periodically sync guilds (for userbots) * @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their * corresponding websocket events * @property {string[]} [disabledEvents] An array of disabled websocket events. Events in this array will not be @@ -34,9 +34,9 @@ exports.DefaultOptions = { messageSweepInterval: 0, fetchAllMembers: false, disableEveryone: false, + sync: false, restWsBridgeTimeout: 5000, disabledEvents: [], - sync: false, /** * Websocket options. These are left as snake_case to match the API. From fe3914658a4284917f820bf7db8230cbcc8d5f33 Mon Sep 17 00:00:00 2001 From: Programmix Date: Sat, 5 Nov 2016 16:57:34 -0700 Subject: [PATCH 038/248] Grammar cleanup (#875) This commit: * fixes inconsistencies (primarily regarding capitalization) * fixes non-proper nouns that were improperly capitalized * fixes reminents from not-so-meticulous copy+paste jobs --- src/client/Client.js | 12 ++-- src/client/ClientDataManager.js | 2 +- src/client/ClientDataResolver.js | 20 +++---- src/client/voice/ClientVoiceManager.js | 2 +- src/client/voice/VoiceConnection.js | 2 +- .../packets/handlers/ChannelCreate.js | 2 +- .../packets/handlers/ChannelDelete.js | 2 +- .../packets/handlers/ChannelPinsUpdate.js | 2 +- .../websocket/packets/handlers/GuildDelete.js | 2 +- .../packets/handlers/GuildMembersChunk.js | 2 +- .../packets/handlers/PresenceUpdate.js | 2 +- src/structures/Channel.js | 2 +- src/structures/ClientUser.js | 2 +- src/structures/DMChannel.js | 2 +- src/structures/Emoji.js | 8 +-- src/structures/GroupDMChannel.js | 2 +- src/structures/Guild.js | 56 +++++++++---------- src/structures/GuildChannel.js | 16 +++--- src/structures/GuildMember.js | 32 +++++------ src/structures/Invite.js | 12 ++-- src/structures/Message.js | 10 ++-- src/structures/MessageAttachment.js | 4 +- src/structures/MessageCollector.js | 6 +- src/structures/MessageEmbed.js | 6 +- src/structures/MessageReaction.js | 5 +- src/structures/PartialGuild.js | 4 +- src/structures/PartialGuildChannel.js | 10 ++-- src/structures/PermissionOverwrites.js | 4 +- src/structures/Presence.js | 4 +- src/structures/Role.js | 4 +- src/structures/TextChannel.js | 4 +- src/structures/User.js | 14 ++--- src/structures/UserConnection.js | 2 +- src/structures/UserProfile.js | 4 +- src/structures/VoiceChannel.js | 6 +- src/structures/Webhook.js | 22 ++++---- src/structures/interface/TextBasedChannel.js | 10 ++-- 37 files changed, 151 insertions(+), 150 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index c19c01e67..322b227d2 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -90,25 +90,25 @@ class Client extends EventEmitter { this.shard = process.send ? ShardClientUtil.singleton(this) : null; /** - * A Collection of the Client's stored users + * A collection of the Client's stored users * @type {Collection} */ this.users = new Collection(); /** - * A Collection of the Client's stored guilds + * A collection of the Client's stored guilds * @type {Collection} */ this.guilds = new Collection(); /** - * A Collection of the Client's stored channels + * A collection of the Client's stored channels * @type {Collection} */ this.channels = new Collection(); /** - * A Collection of presences for friends of the logged in user. + * A collection of presences for friends of the logged in user. * This is only filled for user accounts, not bot accounts. * @type {Collection} */ @@ -175,7 +175,7 @@ class Client extends EventEmitter { } /** - * Returns a Collection, mapping Guild ID to Voice Connections. + * Returns a collection, mapping guild ID to voice connections. * @type {Collection} * @readonly */ @@ -246,7 +246,7 @@ class Client extends EventEmitter { /** * This shouldn't really be necessary to most developers as it is automatically invoked every 30 seconds, however - * if you wish to force a sync of Guild data, you can use this. + * if you wish to force a sync of guild data, you can use this. * This is only applicable to user accounts. * @param {Guild[]|Collection} [guilds=this.guilds] An array or collection of guilds to sync */ diff --git a/src/client/ClientDataManager.js b/src/client/ClientDataManager.js index 7d837d970..b8a73d67c 100644 --- a/src/client/ClientDataManager.js +++ b/src/client/ClientDataManager.js @@ -24,7 +24,7 @@ class ClientDataManager { this.client.guilds.set(guild.id, guild); if (this.pastReady && !already) { /** - * Emitted whenever the client joins a Guild. + * Emitted whenever the client joins a guild. * @event Client#guildCreate * @param {Guild} guild The created guild */ diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 7888cad11..76fa85a8d 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -25,10 +25,10 @@ class ClientDataResolver { /** * Data that resolves to give a User object. This can be: * * A User object - * * A User ID - * * A Message (resolves to the message author) - * * A Guild (owner of the guild) - * * A Guild Member + * * A user ID + * * A Message object (resolves to the message author) + * * A Guild object (owner of the guild) + * * A GuildMember object * @typedef {User|string|Message|Guild|GuildMember} UserResolvable */ @@ -99,10 +99,10 @@ class ClientDataResolver { /** * Data that can be resolved to give a Channel. This can be: - * * An instance of a Channel - * * An instance of a Message (the channel the message was sent in) - * * An instance of a Guild (the #general channel) - * * An ID of a Channel + * * A Channel object + * * A Message object (the channel the message was sent in) + * * A Guild object (the #general channel) + * * A channel ID * @typedef {Channel|Guild|Message|string} ChannelResolvable */ @@ -190,7 +190,7 @@ class ClientDataResolver { /** * Data that can be resolved to give a string. This can be: * * A string - * * An Array (joined with a new line delimiter to give a string) + * * An array (joined with a new line delimiter to give a string) * * Any value * @typedef {string|Array|*} StringResolvable */ @@ -209,7 +209,7 @@ class ClientDataResolver { /** * Data that resolves to give a Base64 string, typically for image uploading. This can be: * * A Buffer - * * A Base64 string + * * A base64 string * @typedef {Buffer|string} Base64Resolvable */ diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js index e73e095d9..09e9982a1 100644 --- a/src/client/voice/ClientVoiceManager.js +++ b/src/client/voice/ClientVoiceManager.js @@ -23,7 +23,7 @@ class ClientVoiceManager { this.connections = new Collection(); /** - * Pending connection attempts, maps Guild ID to VoiceChannel + * Pending connection attempts, maps guild ID to VoiceChannel * @type {Collection} */ this.pending = new Collection(); diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 19a586548..d5fb3147c 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -7,7 +7,7 @@ const EventEmitter = require('events').EventEmitter; const fs = require('fs'); /** - * Represents a connection to a Voice Channel in Discord. + * Represents a connection to a voice channel in Discord. * ```js * // obtained using: * voiceChannel.join().then(connection => { diff --git a/src/client/websocket/packets/handlers/ChannelCreate.js b/src/client/websocket/packets/handlers/ChannelCreate.js index d0488d7ec..04cb2985a 100644 --- a/src/client/websocket/packets/handlers/ChannelCreate.js +++ b/src/client/websocket/packets/handlers/ChannelCreate.js @@ -9,7 +9,7 @@ class ChannelCreateHandler extends AbstractHandler { } /** - * Emitted whenever a Channel is created. + * Emitted whenever a channel is created. * @event Client#channelCreate * @param {Channel} channel The channel that was created */ diff --git a/src/client/websocket/packets/handlers/ChannelDelete.js b/src/client/websocket/packets/handlers/ChannelDelete.js index ec49df0d5..b25f585df 100644 --- a/src/client/websocket/packets/handlers/ChannelDelete.js +++ b/src/client/websocket/packets/handlers/ChannelDelete.js @@ -12,7 +12,7 @@ class ChannelDeleteHandler extends AbstractHandler { } /** - * Emitted whenever a Channel is deleted. + * Emitted whenever a channel is deleted. * @event Client#channelDelete * @param {Channel} channel The channel that was deleted */ diff --git a/src/client/websocket/packets/handlers/ChannelPinsUpdate.js b/src/client/websocket/packets/handlers/ChannelPinsUpdate.js index 65d862959..636df81e5 100644 --- a/src/client/websocket/packets/handlers/ChannelPinsUpdate.js +++ b/src/client/websocket/packets/handlers/ChannelPinsUpdate.js @@ -21,7 +21,7 @@ class ChannelPinsUpdate extends AbstractHandler { } /** - * Emitted whenever the pins of a Channel are updated. Due to the nature of the WebSocket event, not much information + * Emitted whenever the pins of a channel are updated. Due to the nature of the WebSocket event, not much information * can be provided easily here - you need to manually check the pins yourself. * @event Client#channelPinsUpdate * @param {Channel} channel The channel that the pins update occured in diff --git a/src/client/websocket/packets/handlers/GuildDelete.js b/src/client/websocket/packets/handlers/GuildDelete.js index 9b74d56f6..35e3c53d8 100644 --- a/src/client/websocket/packets/handlers/GuildDelete.js +++ b/src/client/websocket/packets/handlers/GuildDelete.js @@ -11,7 +11,7 @@ class GuildDeleteHandler extends AbstractHandler { } /** - * Emitted whenever a Guild is deleted/left. + * Emitted whenever a guild is deleted/left. * @event Client#guildDelete * @param {Guild} guild The guild that was deleted */ diff --git a/src/client/websocket/packets/handlers/GuildMembersChunk.js b/src/client/websocket/packets/handlers/GuildMembersChunk.js index 1a58e1cea..5dda5ad27 100644 --- a/src/client/websocket/packets/handlers/GuildMembersChunk.js +++ b/src/client/websocket/packets/handlers/GuildMembersChunk.js @@ -20,7 +20,7 @@ class GuildMembersChunkHandler extends AbstractHandler { } /** - * Emitted whenever a chunk of Guild members is received (all members come from the same guild) + * Emitted whenever a chunk of guild members is received (all members come from the same guild) * @event Client#guildMembersChunk * @param {GuildMember[]} members The members in the chunk */ diff --git a/src/client/websocket/packets/handlers/PresenceUpdate.js b/src/client/websocket/packets/handlers/PresenceUpdate.js index 9edacd76a..09d78a01b 100644 --- a/src/client/websocket/packets/handlers/PresenceUpdate.js +++ b/src/client/websocket/packets/handlers/PresenceUpdate.js @@ -64,7 +64,7 @@ class PresenceUpdateHandler extends AbstractHandler { */ /** - * Emitted whenever a member becomes available in a large Guild + * Emitted whenever a member becomes available in a large guild * @event Client#guildMemberAvailable * @param {GuildMember} member The member that became available */ diff --git a/src/structures/Channel.js b/src/structures/Channel.js index 2cdab15de..644748891 100644 --- a/src/structures/Channel.js +++ b/src/structures/Channel.js @@ -1,5 +1,5 @@ /** - * Represents any Channel on Discord + * Represents any channel on Discord */ class Channel { constructor(client, data) { diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 69e128581..0fd704849 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -2,7 +2,7 @@ const User = require('./User'); const Collection = require('../util/Collection'); /** - * Represents the logged in client's Discord User + * Represents the logged in client's Discord user * @extends {User} */ class ClientUser extends User { diff --git a/src/structures/DMChannel.js b/src/structures/DMChannel.js index f7f53c322..09213c977 100644 --- a/src/structures/DMChannel.js +++ b/src/structures/DMChannel.js @@ -3,7 +3,7 @@ const TextBasedChannel = require('./interface/TextBasedChannel'); const Collection = require('../util/Collection'); /** - * Represents a Direct Message Channel between two users. + * Represents a direct message channel between two users. * @extends {Channel} * @implements {TextBasedChannel} */ diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js index eb64b4b99..a3f12a867 100644 --- a/src/structures/Emoji.js +++ b/src/structures/Emoji.js @@ -2,7 +2,7 @@ const Constants = require('../util/Constants'); const Collection = require('../util/Collection'); /** - * Represents a Custom Emoji + * Represents a custom emoji */ class Emoji { constructor(guild, data) { @@ -14,7 +14,7 @@ class Emoji { Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); /** - * The Guild this emoji is part of + * The guild this emoji is part of * @type {Guild} */ this.guild = guild; @@ -24,13 +24,13 @@ class Emoji { setup(data) { /** - * The ID of the Emoji + * The ID of the emoji * @type {string} */ this.id = data.id; /** - * The name of the Emoji + * The name of the emoji * @type {string} */ this.name = data.name; diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js index cfeea2555..1e81865ae 100644 --- a/src/structures/GroupDMChannel.js +++ b/src/structures/GroupDMChannel.js @@ -110,7 +110,7 @@ class GroupDMChannel extends Channel { } /** - * When concatenated with a string, this automatically concatenates the Channel's name instead of the Channel object. + * When concatenated with a string, this automatically concatenates the channel's name instead of the Channel object. * @returns {string} * @example * // logs: Hello from My Group DM! diff --git a/src/structures/Guild.js b/src/structures/Guild.js index f3616969f..73b3e45df 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -9,7 +9,7 @@ const cloneObject = require('../util/CloneObject'); const arraysEqual = require('../util/ArraysEqual'); /** - * Represents a Guild (or a Server) on Discord. + * Represents a guild (or a server) on Discord. * It's recommended to see if a guild is available before performing operations or reading data from it. You can * check this with `guild.available`. */ @@ -23,19 +23,19 @@ class Guild { Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); /** - * A Collection of members that are in this Guild. The key is the member's ID, the value is the member. + * A collection of members that are in this guild. The key is the member's ID, the value is the member. * @type {Collection} */ this.members = new Collection(); /** - * A Collection of channels that are in this Guild. The key is the channel's ID, the value is the channel. + * A collection of channels that are in this guild. The key is the channel's ID, the value is the channel. * @type {Collection} */ this.channels = new Collection(); /** - * A Collection of roles that are in this Guild. The key is the role's ID, the value is the role. + * A collection of roles that are in this guild. The key is the role's ID, the value is the role. * @type {Collection} */ this.roles = new Collection(); @@ -43,7 +43,7 @@ class Guild { if (!data) return; if (data.unavailable) { /** - * Whether the Guild is available to access. If it is not available, it indicates a server outage. + * Whether the guild is available to access. If it is not available, it indicates a server outage. * @type {boolean} */ this.available = false; @@ -90,7 +90,7 @@ class Guild { this.region = data.region; /** - * The full amount of members in this Guild as of `READY` + * The full amount of members in this guild as of `READY` * @type {number} */ this.memberCount = data.member_count || this.memberCount; @@ -102,7 +102,7 @@ class Guild { this.large = data.large || this.large; /** - * A collection of presences in this Guild + * A collection of presences in this guild * @type {Collection} */ this.presences = new Collection(); @@ -114,7 +114,7 @@ class Guild { this.features = data.features; /** - * A Collection of emojis that are in this Guild. The key is the emoji's ID, the value is the emoji. + * A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji. * @type {Collection} */ this.emojis = new Collection(); @@ -242,7 +242,7 @@ class Guild { } /** - * The owner of the Guild + * The owner of the guild * @type {GuildMember} * @readonly */ @@ -269,7 +269,7 @@ class Guild { } /** - * Returns the GuildMember form of a User object, if the User is present in the guild. + * Returns the GuildMember form of a User object, if the user is present in the guild. * @param {UserResolvable} user The user that you want to obtain the GuildMember of * @returns {?GuildMember} * @example @@ -281,7 +281,7 @@ class Guild { } /** - * Fetch a Collection of banned users in this Guild. + * Fetch a collection of banned users in this guild. * @returns {Promise>} */ fetchBans() { @@ -289,7 +289,7 @@ class Guild { } /** - * Fetch a Collection of invites to this Guild. Resolves with a Collection mapping invites by their codes. + * Fetch a collection of invites to this guild. Resolves with a collection mapping invites by their codes. * @returns {Promise>} */ fetchInvites() { @@ -318,7 +318,7 @@ class Guild { } /** - * Fetches all the members in the Guild, even if they are offline. If the Guild has less than 250 members, + * Fetches all the members in the guild, even if they are offline. If the guild has less than 250 members, * this should not be necessary. * @param {string} [query=''] An optional query to provide when fetching members * @returns {Promise} @@ -375,8 +375,8 @@ class Guild { } /** - * Edit the name of the Guild. - * @param {string} name The new name of the Guild + * Edit the name of the guild. + * @param {string} name The new name of the guild * @returns {Promise} * @example * // edit the guild name @@ -389,7 +389,7 @@ class Guild { } /** - * Edit the region of the Guild. + * Edit the region of the guild. * @param {string} region The new region of the guild. * @returns {Promise} * @example @@ -403,7 +403,7 @@ class Guild { } /** - * Edit the verification level of the Guild. + * Edit the verification level of the guild. * @param {number} verificationLevel The new verification level of the guild * @returns {Promise} * @example @@ -417,7 +417,7 @@ class Guild { } /** - * Edit the AFK channel of the Guild. + * Edit the AFK channel of the guild. * @param {GuildChannelResolvable} afkChannel The new AFK channel * @returns {Promise} * @example @@ -431,7 +431,7 @@ class Guild { } /** - * Edit the AFK timeout of the Guild. + * Edit the AFK timeout of the guild. * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK * @returns {Promise} * @example @@ -445,7 +445,7 @@ class Guild { } /** - * Set a new Guild Icon. + * Set a new guild icon. * @param {Base64Resolvable} icon The new icon of the guild * @returns {Promise} * @example @@ -459,8 +459,8 @@ class Guild { } /** - * Sets a new owner of the Guild. - * @param {GuildMemberResolvable} owner The new owner of the Guild + * Sets a new owner of the guild. + * @param {GuildMemberResolvable} owner The new owner of the guild * @returns {Promise} * @example * // edit the guild owner @@ -473,7 +473,7 @@ class Guild { } /** - * Set a new Guild Splash Logo. + * Set a new guild splash screen. * @param {Base64Resolvable} splash The new splash screen of the guild * @returns {Promise} * @example @@ -503,7 +503,7 @@ class Guild { } /** - * Unbans a user from the Guild. + * Unbans a user from the guild. * @param {UserResolvable} user The user to unban * @returns {Promise} * @example @@ -546,7 +546,7 @@ class Guild { } /** - * Creates a new Channel in the Guild. + * Creates a new channel in the guild. * @param {string} name The name of the new channel * @param {string} type The type of the new channel, either `text` or `voice` * @returns {Promise} @@ -702,7 +702,7 @@ class Guild { } /** - * When concatenated with a string, this automatically concatenates the Guild's name instead of the Guild object. + * When concatenated with a string, this automatically concatenates the guild's name instead of the Guild object. * @returns {string} * @example * // logs: Hello from My Guild! @@ -757,7 +757,7 @@ class Guild { if (this.client.ws.status === Constants.Status.READY && notSame) { /** - * Emitted whenever a Guild Member changes - i.e. new role, removed role, nickname + * Emitted whenever a guild member changes - i.e. new role, removed role, nickname * @event Client#guildMemberUpdate * @param {GuildMember} oldMember The member before the update * @param {GuildMember} newMember The member after the update @@ -781,7 +781,7 @@ class Guild { if (member && member.speaking !== speaking) { member.speaking = speaking; /** - * Emitted once a Guild Member starts/stops speaking + * Emitted once a guild member starts/stops speaking * @event Client#guildMemberSpeaking * @param {GuildMember} member The member that started/stopped speaking * @param {boolean} speaking Whether or not the member is speaking diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 7e3c7c304..19d7bf3d6 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -7,7 +7,7 @@ const Collection = require('../util/Collection'); const arraysEqual = require('../util/ArraysEqual'); /** - * Represents a Guild Channel (i.e. Text Channels and Voice Channels) + * Represents a guild channel (i.e. text channels and voice channels) * @extends {Channel} */ class GuildChannel extends Channel { @@ -25,7 +25,7 @@ class GuildChannel extends Channel { super.setup(data); /** - * The name of the Guild Channel + * The name of the guild channel * @type {string} */ this.name = data.name; @@ -186,7 +186,7 @@ class GuildChannel extends Channel { } /** - * Set a new name for the Guild Channel + * Set a new name for the guild channel * @param {string} name The new name for the guild channel * @returns {Promise} * @example @@ -200,7 +200,7 @@ class GuildChannel extends Channel { } /** - * Set a new position for the Guild Channel + * Set a new position for the guild channel * @param {number} position The new position for the guild channel * @returns {Promise} * @example @@ -214,7 +214,7 @@ class GuildChannel extends Channel { } /** - * Set a new topic for the Guild Channel + * Set a new topic for the guild channel * @param {string} topic The new topic for the guild channel * @returns {Promise} * @example @@ -228,7 +228,7 @@ class GuildChannel extends Channel { } /** - * Options given when creating a Guild Channel Invite + * Options given when creating a guild channel invite * @typedef {Object} InviteOptions * @property {boolean} [temporary=false] Whether the invite should kick users after 24hrs if they are not given a role * @property {number} [maxAge=0] Time in seconds the invite expires in @@ -236,7 +236,7 @@ class GuildChannel extends Channel { */ /** - * Create an invite to this Guild Channel + * Create an invite to this guild channel * @param {InviteOptions} [options={}] The options for the invite * @returns {Promise} */ @@ -272,7 +272,7 @@ class GuildChannel extends Channel { } /** - * When concatenated with a string, this automatically returns the Channel's mention instead of the Channel object. + * When concatenated with a string, this automatically returns the channel's mention instead of the Channel object. * @returns {string} * @example * // Outputs: Hello from #general diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index e972df1c8..1bf0f8aa9 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -6,13 +6,13 @@ const Collection = require('../util/Collection'); const Presence = require('./Presence').Presence; /** - * Represents a Member of a Guild on Discord + * Represents a member of a guild on Discord * @implements {TextBasedChannel} */ class GuildMember { constructor(guild, data) { /** - * The client that instantiated this GuildMember + * The Client that instantiated this GuildMember * @type {Client} */ this.client = guild.client; @@ -78,7 +78,7 @@ class GuildMember { this.speaking = false; /** - * The nickname of this Guild Member, if they have one + * The nickname of this guild member, if they have one * @type {?string} */ this.nickname = data.nick || null; @@ -103,7 +103,7 @@ class GuildMember { } /** - * The presence of this Guild Member + * The presence of this guild member * @type {Presence} * @readonly */ @@ -167,7 +167,7 @@ class GuildMember { } /** - * The ID of this User + * The ID of this user * @type {string} * @readonly */ @@ -263,7 +263,7 @@ class GuildMember { } /** - * Edit a Guild Member + * Edit a guild member * @param {GuildmemberEditData} data The data to edit the member with * @returns {Promise} */ @@ -290,7 +290,7 @@ class GuildMember { } /** - * Moves the Guild Member to the given channel. + * Moves the guild member to the given channel. * @param {ChannelResolvable} channel The channel to move the member to * @returns {Promise} */ @@ -299,7 +299,7 @@ class GuildMember { } /** - * Sets the Roles applied to the member. + * Sets the roles applied to the member. * @param {Collection|Role[]|string[]} roles The roles or role IDs to apply * @returns {Promise} */ @@ -308,7 +308,7 @@ class GuildMember { } /** - * Adds a single Role to the member. + * Adds a single role to the member. * @param {Role|string} role The role or ID of the role to add * @returns {Promise} */ @@ -333,7 +333,7 @@ class GuildMember { } /** - * Removes a single Role from the member. + * Removes a single role from the member. * @param {Role|string} role The role or ID of the role to remove * @returns {Promise} */ @@ -363,8 +363,8 @@ class GuildMember { } /** - * Set the nickname for the Guild Member - * @param {string} nick The nickname for the Guild Member + * Set the nickname for the guild member + * @param {string} nick The nickname for the guild member * @returns {Promise} */ setNickname(nick) { @@ -372,7 +372,7 @@ class GuildMember { } /** - * Deletes any DMs with this Guild Member + * Deletes any DMs with this guild member * @returns {Promise} */ deleteDM() { @@ -380,7 +380,7 @@ class GuildMember { } /** - * Kick this member from the Guild + * Kick this member from the guild * @returns {Promise} */ kick() { @@ -388,7 +388,7 @@ class GuildMember { } /** - * Ban this Guild Member + * Ban this guild member * @param {number} [deleteDays=0] The amount of days worth of messages from this member that should * also be deleted. Between `0` and `7`. * @returns {Promise} @@ -401,7 +401,7 @@ class GuildMember { } /** - * When concatenated with a string, this automatically concatenates the User's mention instead of the Member object. + * When concatenated with a string, this automatically concatenates the user's mention instead of the Member object. * @returns {string} * @example * // logs: Hello from <@123456789>! diff --git a/src/structures/Invite.js b/src/structures/Invite.js index bcf165182..7da9bc93f 100644 --- a/src/structures/Invite.js +++ b/src/structures/Invite.js @@ -24,7 +24,7 @@ const Constants = require('../util/Constants'); */ /** - * Represents an Invitation to a Guild Channel. + * Represents an invitation to a guild channel. * The only guaranteed properties are `code`, `guild` and `channel`. Other properties can be missing. */ class Invite { @@ -41,8 +41,8 @@ class Invite { setup(data) { /** - * The Guild the invite is for. If this Guild is already known, this will be a Guild object. If the Guild is - * unknown, this will be a Partial Guild. + * The guild the invite is for. If this guild is already known, this will be a Guild object. If the guild is + * unknown, this will be a PartialGuild object. * @type {Guild|PartialGuild} */ this.guild = this.client.guilds.get(data.guild.id) || new PartialGuild(this.client, data.guild); @@ -86,8 +86,8 @@ class Invite { } /** - * The Channel the invite is for. If this Channel is already known, this will be a GuildChannel object. - * If the Channel is unknown, this will be a Partial Guild Channel. + * The channel the invite is for. If this channel is already known, this will be a GuildChannel object. + * If the channel is unknown, this will be a PartialGuildChannel object. * @type {GuildChannel|PartialGuildChannel} */ this.channel = this.client.channels.get(data.channel.id) || new PartialGuildChannel(this.client, data.channel); @@ -144,7 +144,7 @@ class Invite { } /** - * When concatenated with a string, this automatically concatenates the Invite's URL instead of the object. + * When concatenated with a string, this automatically concatenates the invite's URL instead of the object. * @returns {string} * @example * // logs: Invite: https://discord.gg/A1b2C3 diff --git a/src/structures/Message.js b/src/structures/Message.js index 0bcd14f46..bd372b4eb 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -6,12 +6,12 @@ const escapeMarkdown = require('../util/EscapeMarkdown'); const MessageReaction = require('./MessageReaction'); /** - * Represents a Message on Discord + * Represents a message on Discord */ class Message { constructor(channel, data, client) { /** - * The client that instantiated the Message + * The Client that instantiated the Message * @type {Client} */ this.client = client; @@ -52,7 +52,7 @@ class Message { this.author = this.client.dataManager.newUser(data.author); /** - * Represents the Author of the message as a Guild Member. Only available if the message comes from a Guild + * Represents the author of the message as a guild member. Only available if the message comes from a guild * where the author is still a member. * @type {GuildMember} */ @@ -151,7 +151,7 @@ class Message { this._edits = []; /** - * A collection of Reactions to this Message, mapped by the reaction "id". + * A collection of reactions to this message, mapped by the reaction "id". * @type {Collection} */ this.reactions = new Collection(); @@ -504,7 +504,7 @@ class Message { } /** - * When concatenated with a string, this automatically concatenates the Message's content instead of the object. + * When concatenated with a string, this automatically concatenates the message's content instead of the object. * @returns {string} * @example * // logs: Message: This is a message! diff --git a/src/structures/MessageAttachment.js b/src/structures/MessageAttachment.js index e9573a7be..0c87d3a75 100644 --- a/src/structures/MessageAttachment.js +++ b/src/structures/MessageAttachment.js @@ -1,10 +1,10 @@ /** - * Represents an Attachment in a Message + * Represents an attachment in a message */ class MessageAttachment { constructor(message, data) { /** - * The Client that instantiated this Message. + * The Client that instantiated this MessageAttachment. * @type {Client} */ this.client = message.client; diff --git a/src/structures/MessageCollector.js b/src/structures/MessageCollector.js index 375bc845a..72567d731 100644 --- a/src/structures/MessageCollector.js +++ b/src/structures/MessageCollector.js @@ -54,7 +54,7 @@ class MessageCollector extends EventEmitter { this.options = options; /** - * Whether this collector has stopped collecting Messages. + * Whether this collector has stopped collecting messages. * @type {boolean} */ this.ended = false; @@ -81,7 +81,7 @@ class MessageCollector extends EventEmitter { if (this.filter(message, this)) { this.collected.set(message.id, message); /** - * Emitted whenever the Collector receives a Message that passes the filter test. + * Emitted whenever the collector receives a message that passes the filter test. * @param {Message} message The received message * @param {MessageCollector} collector The collector the message passed through * @event MessageCollector#message @@ -138,7 +138,7 @@ class MessageCollector extends EventEmitter { /** * Emitted when the Collector stops collecting. * @param {Collection} collection A collection of messages collected - * during the lifetime of the Collector, mapped by the ID of the Messages. + * during the lifetime of the collector, mapped by the ID of the messages. * @param {string} reason The reason for the end of the collector. If it ended because it reached the specified time * limit, this would be `time`. If you invoke `.stop()` without specifying a reason, this would be `user`. If it * ended because it reached its message limit, it will be `limit`. diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js index 850e443d8..f119f561c 100644 --- a/src/structures/MessageEmbed.js +++ b/src/structures/MessageEmbed.js @@ -65,7 +65,7 @@ class MessageEmbed { } /** - * Represents a thumbnail for a Message embed + * Represents a thumbnail for a message embed */ class MessageEmbedThumbnail { constructor(embed, data) { @@ -106,7 +106,7 @@ class MessageEmbedThumbnail { } /** - * Represents a Provider for a Message embed + * Represents a provider for a message embed */ class MessageEmbedProvider { constructor(embed, data) { @@ -135,7 +135,7 @@ class MessageEmbedProvider { } /** - * Represents a Author for a Message embed + * Represents a author for a message embed */ class MessageEmbedAuthor { constructor(embed, data) { diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index 693b9dc92..5f6a215a0 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -35,8 +35,9 @@ class MessageReaction { } /** - * The emoji of this reaction, either an Emoji object for known custom emojis, or a ReactionEmoji which has fewer - * properties. Whatever the prototype of the emoji, it will still have `name`, `id`, `identifier` and `toString()` + * The emoji of this reaction, either an Emoji object for known custom emojis, or a ReactionEmoji + * object which has fewer properties. Whatever the prototype of the emoji, it will still have + * `name`, `id`, `identifier` and `toString()` * @type {Emoji|ReactionEmoji} */ get emoji() { diff --git a/src/structures/PartialGuild.js b/src/structures/PartialGuild.js index d605cc274..aff53cd84 100644 --- a/src/structures/PartialGuild.js +++ b/src/structures/PartialGuild.js @@ -6,12 +6,12 @@ */ /** - * Represents a Guild that the client only has limited information for - e.g. from invites. + * Represents a guild that the client only has limited information for - e.g. from invites. */ class PartialGuild { constructor(client, data) { /** - * The client that instantiated this PartialGuild + * The Client that instantiated this PartialGuild * @type {Client} */ this.client = client; diff --git a/src/structures/PartialGuildChannel.js b/src/structures/PartialGuildChannel.js index 47e33884d..87d54f540 100644 --- a/src/structures/PartialGuildChannel.js +++ b/src/structures/PartialGuildChannel.js @@ -5,12 +5,12 @@ const Constants = require('../util/Constants'); */ /** - * Represents a Guild Channel that the client only has limited information for - e.g. from invites. + * Represents a guild channel that the client only has limited information for - e.g. from invites. */ class PartialGuildChannel { constructor(client, data) { /** - * The client that instantiated this PartialGuildChannel + * The Client that instantiated this PartialGuildChannel * @type {Client} */ this.client = client; @@ -21,19 +21,19 @@ class PartialGuildChannel { setup(data) { /** - * The ID of this Guild Channel + * The ID of this guild channel * @type {string} */ this.id = data.id; /** - * The name of this Guild Channel + * The name of this guild channel * @type {string} */ this.name = data.name; /** - * The type of this Guild Channel - `text` or `voice` + * The type of this guild channel - `text` or `voice` * @type {string} */ this.type = Constants.ChannelTypes.text === data.type ? 'text' : 'voice'; diff --git a/src/structures/PermissionOverwrites.js b/src/structures/PermissionOverwrites.js index b1b2944fc..0ecc3cd83 100644 --- a/src/structures/PermissionOverwrites.js +++ b/src/structures/PermissionOverwrites.js @@ -1,5 +1,5 @@ /** - * Represents a permission overwrite for a Role or Member in a Guild Channel. + * Represents a permission overwrite for a role or member in a guild channel. */ class PermissionOverwrites { constructor(guildChannel, data) { @@ -14,7 +14,7 @@ class PermissionOverwrites { setup(data) { /** - * The ID of this overwrite, either a User ID or a Role ID + * The ID of this overwrite, either a user ID or a role ID * @type {string} */ this.id = data.id; diff --git a/src/structures/Presence.js b/src/structures/Presence.js index d84a4c9bc..372f83072 100644 --- a/src/structures/Presence.js +++ b/src/structures/Presence.js @@ -1,5 +1,5 @@ /** - * Represents a User's presence + * Represents a user's presence */ class Presence { constructor(data) { @@ -44,7 +44,7 @@ class Presence { } /** - * Represents a Game that is part of a User's presence. + * Represents a game that is part of a user's presence. */ class Game { constructor(data) { diff --git a/src/structures/Role.js b/src/structures/Role.js index 4790a5c90..685904219 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -1,7 +1,7 @@ const Constants = require('../util/Constants'); /** - * Represents a Role on Discord + * Represents a role on Discord */ class Role { constructor(guild, data) { @@ -304,7 +304,7 @@ class Role { } /** - * When concatenated with a string, this automatically concatenates the Role mention rather than the Role object. + * When concatenated with a string, this automatically concatenates the role mention rather than the Role object. * @returns {string} */ toString() { diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 620a229b7..cd067714f 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -3,7 +3,7 @@ const TextBasedChannel = require('./interface/TextBasedChannel'); const Collection = require('../util/Collection'); /** - * Represents a Server Text Channel on Discord. + * Represents a guild text channel on Discord. * @extends {GuildChannel} * @implements {TextBasedChannel} */ @@ -19,7 +19,7 @@ class TextChannel extends GuildChannel { super.setup(data); /** - * The topic of the Text Channel, if there is one. + * The topic of the text channel, if there is one. * @type {?string} */ this.topic = data.topic; diff --git a/src/structures/User.js b/src/structures/User.js index 3dcd9da79..bb02003c7 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -3,7 +3,7 @@ const Constants = require('../util/Constants'); const Presence = require('./Presence').Presence; /** - * Represents a User on Discord. + * Represents a user on Discord. * @implements {TextBasedChannel} */ class User { @@ -20,19 +20,19 @@ class User { setup(data) { /** - * The ID of the User + * The ID of the user * @type {string} */ this.id = data.id; /** - * The username of the User + * The username of the user * @type {string} */ this.username = data.username; /** - * A discriminator based on username for the User + * A discriminator based on username for the user * @type {string} */ this.discriminator = data.discriminator; @@ -44,7 +44,7 @@ class User { this.avatar = data.avatar; /** - * Whether or not the User is a Bot. + * Whether or not the user is a bot. * @type {boolean} */ this.bot = Boolean(data.bot); @@ -138,7 +138,7 @@ class User { } /** - * Deletes a DM Channel (if one exists) between the Client and the User. Resolves with the Channel if successful. + * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful. * @returns {Promise} */ deleteDM() { @@ -217,7 +217,7 @@ class User { } /** - * When concatenated with a string, this automatically concatenates the User's mention instead of the User object. + * When concatenated with a string, this automatically concatenates the user's mention instead of the User object. * @returns {string} * @example * // logs: Hello from <@123456789>! diff --git a/src/structures/UserConnection.js b/src/structures/UserConnection.js index d25df0300..70814de01 100644 --- a/src/structures/UserConnection.js +++ b/src/structures/UserConnection.js @@ -1,5 +1,5 @@ /** - * Represents a User Connection object (or "platform identity") + * Represents a user connection (or "platform identity") */ class UserConnection { constructor(user, data) { diff --git a/src/structures/UserProfile.js b/src/structures/UserProfile.js index 4150b3a72..d47ac922f 100644 --- a/src/structures/UserProfile.js +++ b/src/structures/UserProfile.js @@ -13,14 +13,14 @@ class UserProfile { this.user = user; /** - * The Client that created the instance of the the User. + * The Client that created the instance of the the UserProfile. * @type {Client} */ this.client = this.user.client; Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); /** - * Guilds that the ClientUser and the User share + * Guilds that the client user and the user share * @type {Collection} */ this.mutualGuilds = new Collection(); diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js index d190e6a15..4ba788460 100644 --- a/src/structures/VoiceChannel.js +++ b/src/structures/VoiceChannel.js @@ -2,7 +2,7 @@ const GuildChannel = require('./GuildChannel'); const Collection = require('../util/Collection'); /** - * Represents a Server Voice Channel on Discord. + * Represents a guild voice channel on Discord. * @extends {GuildChannel} */ class VoiceChannel extends GuildChannel { @@ -10,7 +10,7 @@ class VoiceChannel extends GuildChannel { super(guild, data); /** - * The members in this Voice Channel. + * The members in this voice channel. * @type {Collection} */ this.members = new Collection(); @@ -90,7 +90,7 @@ class VoiceChannel extends GuildChannel { } /** - * Attempts to join this Voice Channel + * Attempts to join this voice channel * @returns {Promise} * @example * // join a voice channel diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 85daa06c6..47b52acb2 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -2,13 +2,13 @@ const path = require('path'); const escapeMarkdown = require('../util/EscapeMarkdown'); /** - * Represents a Webhook + * Represents a webhook */ class Webhook { constructor(client, dataOrID, token) { if (client) { /** - * The client that instantiated the Channel + * The Client that instantiated the Webhook * @type {Client} */ this.client = client; @@ -23,43 +23,43 @@ class Webhook { setup(data) { /** - * The name of the Webhook + * The name of the webhook * @type {string} */ this.name = data.name; /** - * The token for the Webhook + * The token for the webhook * @type {string} */ this.token = data.token; /** - * The avatar for the Webhook + * The avatar for the webhook * @type {string} */ this.avatar = data.avatar; /** - * The ID of the Webhook + * The ID of the webhook * @type {string} */ this.id = data.id; /** - * The guild the Webhook belongs to + * The guild the webhook belongs to * @type {string} */ this.guildID = data.guild_id; /** - * The channel the Webhook belongs to + * The channel the webhook belongs to * @type {string} */ this.channelID = data.channel_id; /** - * The owner of the Webhook + * The owner of the webhook * @type {User} */ if (data.user) this.owner = data.user; @@ -169,7 +169,7 @@ class Webhook { } /** - * Edit the Webhook. + * Edit the webhook. * @param {string} name The new name for the Webhook * @param {BufferResolvable} avatar The new avatar for the Webhook. * @returns {Promise} @@ -188,7 +188,7 @@ class Webhook { } /** - * Delete the Webhook + * Delete the webhook * @returns {Promise} */ delete() { diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index fd7684232..725ef5ef1 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -11,7 +11,7 @@ const escapeMarkdown = require('../../util/EscapeMarkdown'); class TextBasedChannel { constructor() { /** - * A Collection containing the messages sent to this channel. + * A collection containing the messages sent to this channel. * @type {Collection} */ this.messages = new Collection(); @@ -147,7 +147,7 @@ class TextBasedChannel { */ /** - * Gets the past messages sent in this channel. Resolves with a Collection mapping message ID's to Message objects. + * Gets the past messages sent in this channel. Resolves with a collection mapping message ID's to Message objects. * @param {ChannelLogsQueryOptions} [options={}] The query parameters to pass in * @returns {Promise>} * @example @@ -169,7 +169,7 @@ class TextBasedChannel { } /** - * Fetches the pinned messages of this Channel and returns a Collection of them. + * Fetches the pinned messages of this channel and returns a collection of them. * @returns {Promise>} */ fetchPinnedMessages() { @@ -210,7 +210,7 @@ class TextBasedChannel { /** * Stops the typing indicator in the channel. * The indicator will only stop if this is called as many times as startTyping(). - * It can take a few seconds for the Client User to stop typing. + * It can take a few seconds for the client user to stop typing. * @param {boolean} [force=false] Whether or not to reset the call count and force the indicator to stop * @example * // stop typing in a channel @@ -274,7 +274,7 @@ class TextBasedChannel { */ /** - * Similar to createCollector but in Promise form. Resolves with a Collection of messages that pass the specified + * Similar to createCollector but in promise form. Resolves with a collection of messages that pass the specified * filter. * @param {CollectorFilterFunction} filter The filter function to use * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector From 5dc30d68123fd46d8da3cb8b1a4e7122d8a0f2a2 Mon Sep 17 00:00:00 2001 From: Slamakans Date: Sun, 6 Nov 2016 11:43:31 +0100 Subject: [PATCH 039/248] Nullable permission overwrites (#869) Made it possible to pass null to GuildChannel.overwritePermissions's PermissionOverwriteOptions to blank the permission out. --- src/structures/GuildChannel.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 19d7bf3d6..c384279ff 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -155,6 +155,9 @@ class GuildChannel extends Channel { } else if (options[perm] === false) { payload.allow &= ~(Constants.PermissionFlags[perm] || 0); payload.deny |= Constants.PermissionFlags[perm] || 0; + } else if (options[perm] === null) { + payload.allow &= ~(Constants.PermissionFlags[perm] || 0); + payload.deny &= ~(Constants.PermissionFlags[perm] || 0); } } From 7d02e73a260e8ed67d5cbd341a4da3f244d9e211 Mon Sep 17 00:00:00 2001 From: HyperCoder Date: Sun, 6 Nov 2016 11:43:39 -0500 Subject: [PATCH 040/248] Add .manageable (#878) --- src/structures/Role.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/structures/Role.js b/src/structures/Role.js index 685904219..891b8c92d 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -284,6 +284,18 @@ class Role { delete() { return this.client.rest.methods.deleteGuildRole(this); } + + /** + * Whether the role is managable by the client user. + * @type {boolean} + * @readonly + */ + get manageable() { + if (this.managed) return false; + const clientMember = this.guild.member(this.client.user); + if (!clientMember.hasPermission(Constants.PermissionFlags.MANAGE_ROLES_OR_PERMISSIONS)) return false; + return clientMember.highestRole.comparePositionTo(this) > 0; + } /** * Whether this role equals another role. It compares all properties, so for most operations From 544540fb0230301383d94ed61dfad92cc9a2292b Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sun, 6 Nov 2016 16:49:14 +0000 Subject: [PATCH 041/248] Change Role.manageable to Role.editable --- src/structures/Role.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/Role.js b/src/structures/Role.js index 891b8c92d..164996cb3 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -284,13 +284,13 @@ class Role { delete() { return this.client.rest.methods.deleteGuildRole(this); } - + /** * Whether the role is managable by the client user. * @type {boolean} * @readonly */ - get manageable() { + get editable() { if (this.managed) return false; const clientMember = this.guild.member(this.client.user); if (!clientMember.hasPermission(Constants.PermissionFlags.MANAGE_ROLES_OR_PERMISSIONS)) return false; From ce132d5f54b4cc8d75c271bb77f960568693f547 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 17:15:17 -0500 Subject: [PATCH 042/248] Make bot/user account warnings MOAR CONSISTENT!!one! --- src/client/Client.js | 19 +++++++++---------- src/structures/ClientUser.js | 12 ++++++------ src/structures/Guild.js | 2 +- src/structures/User.js | 13 +++++++------ src/structures/interface/TextBasedChannel.js | 4 ++-- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 322b227d2..0b4cf17e5 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -109,7 +109,7 @@ class Client extends EventEmitter { /** * A collection of presences for friends of the logged in user. - * This is only filled for user accounts, not bot accounts. + * This is only filled when using a user account. * @type {Collection} */ this.presences = new Collection(); @@ -247,21 +247,20 @@ class Client extends EventEmitter { /** * This shouldn't really be necessary to most developers as it is automatically invoked every 30 seconds, however * if you wish to force a sync of guild data, you can use this. - * This is only applicable to user accounts. + * This is only available when using a user account. * @param {Guild[]|Collection} [guilds=this.guilds] An array or collection of guilds to sync */ syncGuilds(guilds = this.guilds) { - if (!this.user.bot) { - this.ws.send({ - op: 12, - d: guilds instanceof Collection ? guilds.keyArray() : guilds.map(g => g.id), - }); - } + if (this.user.bot) return; + this.ws.send({ + op: 12, + d: guilds instanceof Collection ? guilds.keyArray() : guilds.map(g => g.id), + }); } /** * Caches a user, or obtains it from the cache if it's already cached. - * This is only available to bot accounts. + * This is only available when using a bot account. * @param {string} id The ID of the user to obtain * @returns {Promise} */ @@ -328,7 +327,7 @@ class Client extends EventEmitter { /** * Gets the bot's OAuth2 application. - * This is only available for bot accounts. + * This is only available when using a bot account. * @returns {Promise} */ fetchApplication() { diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 0fd704849..5aa547300 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -25,21 +25,21 @@ class ClientUser extends User { /** * A Collection of friends for the logged in user. - * This is only filled for user accounts, not bot accounts. + * This is only filled when using a user account. * @type {Collection} */ this.friends = new Collection(); /** * A Collection of blocked users for the logged in user. - * This is only filled for user accounts, not bot accounts. + * This is only filled when using a user account. * @type {Collection} */ this.blocked = new Collection(); /** * A Collection of notes for the logged in user. - * This is only filled for user accounts, not bot accounts. + * This is only filled when using a user account. * @type {Collection} */ this.notes = new Collection(); @@ -148,7 +148,7 @@ class ClientUser extends User { /** * Send a friend request - * This is only available for user accounts, not bot accounts. + * This is only available when using a user account. * @param {UserResolvable} user The user to send the friend request to. * @returns {Promise} The user the friend request was sent to. */ @@ -159,7 +159,7 @@ class ClientUser extends User { /** * Remove a friend - * This is only available for user accounts, not bot accounts. + * This is only available when using a user account. * @param {UserResolvable} user The user to remove from your friends * @returns {Promise} The user that was removed */ @@ -170,7 +170,7 @@ class ClientUser extends User { /** * Creates a guild - * This is only available for user accounts, not bot accounts. + * This is only available when using a user account. * @param {string} name The name of the guild * @param {string} region The region for the server * @param {BufferResolvable|Base64Resolvable} [icon=null] The icon for the guild diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 73b3e45df..ffbe580ca 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -539,7 +539,7 @@ class Guild { /** * Syncs this guild (already done automatically every 30 seconds). - * This is only applicable to user accounts. + * This is only available when using a user account. */ sync() { if (!this.client.user.bot) this.client.syncGuilds([this]); diff --git a/src/structures/User.js b/src/structures/User.js index bb02003c7..28e8f73b5 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -99,7 +99,7 @@ class User { /** * The note that is set for the user - * This is only available for user accounts. + * This is only available when using a user account. * @type {?string} * @readonly */ @@ -147,7 +147,7 @@ class User { /** * Sends a friend request to the user - * This is only available for user accounts. + * This is only available when using a user account. * @returns {Promise} */ addFriend() { @@ -156,7 +156,7 @@ class User { /** * Removes the user from your friends - * This is only available for user accounts. + * This is only available when using a user account. * @returns {Promise} */ removeFriend() { @@ -165,7 +165,7 @@ class User { /** * Blocks the user - * This is only available for user accounts. + * This is only available when using a user account. * @returns {Promise} */ block() { @@ -174,7 +174,7 @@ class User { /** * Unblocks the user - * This is only available for user accounts. + * This is only available when using a user account. * @returns {Promise} */ unblock() { @@ -183,6 +183,7 @@ class User { /** * Get the profile of the user + * This is only available when using a user account. * @returns {Promise} */ fetchProfile() { @@ -191,7 +192,7 @@ class User { /** * Sets a note for the user - * This is only available for user accounts. + * This is only available when using a user account. * @param {string} note The note to set for the user * @returns {Promise} */ diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index 725ef5ef1..fba4442b1 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -119,7 +119,7 @@ class TextBasedChannel { /** * Gets a single message from this channel, regardless of it being cached or not. - * This is only available for bot accounts. + * This is only available when using a bot account. * @param {string} messageID The ID of the message to get * @returns {Promise} * @example @@ -302,7 +302,7 @@ class TextBasedChannel { /** * Bulk delete given messages. - * This is only available for bot accounts. + * This is only available when using a bot account. * @param {Collection|Message[]|number} messages Messages to delete, or number of messages to delete * @returns {Promise>} Deleted messages */ From 3230d90a58d64856890157841f9ea4ecc617bc62 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 17:23:54 -0500 Subject: [PATCH 043/248] Remove fs-extra dependency --- docs/generator/generator.js | 2 +- package.json | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/generator/generator.js b/docs/generator/generator.js index f9c314ec6..bd6786e89 100644 --- a/docs/generator/generator.js +++ b/docs/generator/generator.js @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -const fs = require('fs-extra'); +const fs = require('fs'); const zlib = require('zlib'); const jsdoc2md = require('jsdoc-to-markdown'); const Documentation = require('./documentation'); diff --git a/package.json b/package.json index 978a47b96..c5b4c712c 100644 --- a/package.json +++ b/package.json @@ -27,16 +27,15 @@ "homepage": "https://github.com/hydrabolt/discord.js#readme", "dependencies": { "superagent": "^2.3.0", - "tweetnacl": "^0.14.3", - "ws": "^1.1.1" + "tweetnacl": "^0.14.0", + "ws": "^1.1.0" }, "peerDependencies": { - "node-opus": "^0.2.2", + "node-opus": "^0.2.0", "opusscript": "^0.0.1" }, "devDependencies": { - "eslint": "^3.9.0", - "fs-extra": "^0.30.0", + "eslint": "^3.10.0", "jsdoc-to-markdown": "^2.0.0" }, "engines": { From 305070dded3dca0579e5df2a632cbd363e956b8b Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 17:30:07 -0500 Subject: [PATCH 044/248] Remove experimental warning on sharding manager --- src/sharding/ShardingManager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index dcaa1cc97..671b5d7b9 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -10,7 +10,6 @@ const fetchRecommendedShards = require('../util/FetchRecommendedShards'); * This is a utility class that can be used to help you spawn shards of your Client. Each shard is completely separate * from the other. The Shard Manager takes a path to a file and spawns it under the specified amount of shards safely. * If you do not select an amount of shards, the manager will automatically decide the best amount. - * The Sharding Manager is still experimental * @extends {EventEmitter} */ class ShardingManager extends EventEmitter { From 318bb52c36a2f2c76f6eb86dc06e2626ea29c34a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 17:46:11 -0500 Subject: [PATCH 045/248] Clean up presence constructor --- src/structures/Presence.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/structures/Presence.js b/src/structures/Presence.js index 372f83072..05db21aaa 100644 --- a/src/structures/Presence.js +++ b/src/structures/Presence.js @@ -2,10 +2,7 @@ * Represents a user's presence */ class Presence { - constructor(data) { - if (!data) { - data = {}; - } + constructor(data = {}) { /** * The status of the presence: * From 90304aa7d6b99cf086720fadd4008efdae26916a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 20:52:37 -0500 Subject: [PATCH 046/248] Fix loads of JSDoc type issues --- .eslintrc.json | 8 +++++--- src/client/Client.js | 4 ++-- src/client/voice/VoiceConnection.js | 2 +- src/sharding/Shard.js | 2 +- src/sharding/ShardClientUtil.js | 2 +- src/structures/ClientOAuth2Application.js | 2 +- src/structures/EvaluatedPermissions.js | 2 +- src/structures/Guild.js | 2 +- src/structures/GuildChannel.js | 2 +- src/structures/GuildMember.js | 2 +- src/structures/Message.js | 2 +- src/structures/OAuth2Application.js | 2 +- src/util/Collection.js | 4 ++-- 13 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 2e3021ca0..f2babc118 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,17 +14,19 @@ "valid-jsdoc": ["error", { "requireReturn": false, "requireReturnDescription": false, + "prefer": { + "return": "returns", + "arg": "param" + }, "preferType": { "String": "string", "Number": "number", "Boolean": "boolean", "Function": "function", "object": "Object", + "array": "Array", "date": "Date", "error": "Error" - }, - "prefer": { - "return": "returns" } }], diff --git a/src/client/Client.js b/src/client/Client.js index 0b4cf17e5..b33fb2ab3 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -85,7 +85,7 @@ class Client extends EventEmitter { /** * The shard helpers for the client (only if the process was spawned as a child, such as from a ShardingManager) - * @type {?ShardUtil} + * @type {?ShardClientUtil} */ this.shard = process.send ? ShardClientUtil.singleton(this) : null; @@ -328,7 +328,7 @@ class Client extends EventEmitter { /** * Gets the bot's OAuth2 application. * This is only available when using a bot account. - * @returns {Promise} + * @returns {Promise} */ fetchApplication() { if (!this.user.bot) throw new Error(Constants.Errors.NO_BOT_ACCOUNT); diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 611ef6829..3bd88fafe 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -70,7 +70,7 @@ class VoiceConnection extends EventEmitter { /** * Warning info from the connection * @event VoiceConnection#warn - * @param {string|error} warning the warning + * @param {string|Error} warning the warning */ this.emit('warn', e); this.player.cleanup(); diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index ea48f4234..31cbb814d 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -10,7 +10,7 @@ class Shard { /** * @param {ShardingManager} manager The sharding manager * @param {number} id The ID of this shard - * @param {array} [args=[]] Command line arguments to pass to the script + * @param {Array} [args=[]] Command line arguments to pass to the script */ constructor(manager, id, args = []) { /** diff --git a/src/sharding/ShardClientUtil.js b/src/sharding/ShardClientUtil.js index 51c16fa8f..295b5fbcd 100644 --- a/src/sharding/ShardClientUtil.js +++ b/src/sharding/ShardClientUtil.js @@ -127,7 +127,7 @@ class ShardClientUtil { /** * Creates/gets the singleton of this class * @param {Client} client Client to use - * @returns {ShardUtil} + * @returns {ShardClientUtil} */ static singleton(client) { if (!this._singleton) { diff --git a/src/structures/ClientOAuth2Application.js b/src/structures/ClientOAuth2Application.js index 158d71a4f..46e125040 100644 --- a/src/structures/ClientOAuth2Application.js +++ b/src/structures/ClientOAuth2Application.js @@ -11,7 +11,7 @@ class ClientOAuth2Application extends OAuth2Application { /** * The app's flags - * @type {int} + * @type {number} */ this.flags = data.flags; diff --git a/src/structures/EvaluatedPermissions.js b/src/structures/EvaluatedPermissions.js index de92c10d7..ae8a643ce 100644 --- a/src/structures/EvaluatedPermissions.js +++ b/src/structures/EvaluatedPermissions.js @@ -57,7 +57,7 @@ class EvaluatedPermissions { * Checks whether the user has all specified permissions, and lists any missing permissions. * @param {PermissionResolvable[]} permissions The permissions to check for * @param {boolean} [explicit=false] Whether to require the user to explicitly have the exact permissions - * @returns {array} + * @returns {PermissionResolvable[]} */ missingPermissions(permissions, explicit = false) { return permissions.filter(p => !this.hasPermission(p, explicit)); diff --git a/src/structures/Guild.js b/src/structures/Guild.js index ffbe580ca..9c58f82b9 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -418,7 +418,7 @@ class Guild { /** * Edit the AFK channel of the guild. - * @param {GuildChannelResolvable} afkChannel The new AFK channel + * @param {ChannelResolvable} afkChannel The new AFK channel * @returns {Promise} * @example * // edit the guild AFK channel diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index c384279ff..4a30d033c 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -235,7 +235,7 @@ class GuildChannel extends Channel { * @typedef {Object} InviteOptions * @property {boolean} [temporary=false] Whether the invite should kick users after 24hrs if they are not given a role * @property {number} [maxAge=0] Time in seconds the invite expires in - * @property {maxUses} [maxUses=0] Maximum amount of uses for this invite + * @property {number} [maxUses=0] Maximum amount of uses for this invite */ /** diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 1bf0f8aa9..d0513031f 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -256,7 +256,7 @@ class GuildMember { * Checks whether the roles of the member allows them to perform specific actions, and lists any missing permissions. * @param {PermissionResolvable[]} permissions The permissions to check for * @param {boolean} [explicit=false] Whether to require the member to explicitly have the exact permissions - * @returns {array} + * @returns {PermissionResolvable[]} */ missingPermissions(permissions, explicit = false) { return permissions.filter(p => !this.hasPermission(p, explicit)); diff --git a/src/structures/Message.js b/src/structures/Message.js index bd372b4eb..769787728 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -84,7 +84,7 @@ class Message { /** * A list of embeds in the message - e.g. YouTube Player - * @type {Embed[]} + * @type {MessageEmbed[]} */ this.embeds = data.embeds.map(e => new Embed(this, e)); diff --git a/src/structures/OAuth2Application.js b/src/structures/OAuth2Application.js index 9969c8ed6..1bd740b1c 100644 --- a/src/structures/OAuth2Application.js +++ b/src/structures/OAuth2Application.js @@ -46,7 +46,7 @@ class OAuth2Application { /** * The app's RPC origins - * @type {Array} + * @type {Array} */ this.rpcOrigins = data.rpc_origins; } diff --git a/src/util/Collection.js b/src/util/Collection.js index a6b7bf856..b031f896c 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -107,7 +107,7 @@ class Collection extends Map { * Returns an array of items where `item[prop] === value` of the collection * @param {string} prop The property to test against * @param {*} value The expected value - * @returns {array} + * @returns {Array} * @example * collection.findAll('username', 'Bob'); */ @@ -237,7 +237,7 @@ class Collection extends Map { * [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). * @param {function} fn Function that produces an element of the new array, taking three arguments * @param {*} [thisArg] Value to use as `this` when executing function - * @returns {array} + * @returns {Array} */ map(fn, thisArg) { if (thisArg) fn = fn.bind(thisArg); From b07a31d44efa20f77a2aca6537c8a78512d75250 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 21:07:22 -0500 Subject: [PATCH 047/248] Change case of "function" --- .eslintrc.json | 2 +- src/client/Client.js | 4 ++-- src/client/ClientManager.js | 4 ++-- src/util/Collection.js | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index f2babc118..8c94d1128 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,8 +22,8 @@ "String": "string", "Number": "number", "Boolean": "boolean", - "Function": "function", "object": "Object", + "function": "Function", "array": "Array", "date": "Date", "error": "Error" diff --git a/src/client/Client.js b/src/client/Client.js index b33fb2ab3..293af9d24 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -337,7 +337,7 @@ class Client extends EventEmitter { /** * Sets a timeout that will be automatically cancelled if the client is destroyed. - * @param {function} fn Function to execute + * @param {Function} fn Function to execute * @param {number} delay Time to wait before executing (in milliseconds) * @param {args} args Arguments for the function (infinite/rest argument, not an array) * @returns {Timeout} @@ -362,7 +362,7 @@ class Client extends EventEmitter { /** * Sets an interval that will be automatically cancelled if the client is destroyed. - * @param {function} fn Function to execute + * @param {Function} fn Function to execute * @param {number} delay Time to wait before executing (in milliseconds) * @param {args} args Arguments for the function (infinite/rest argument, not an array) * @returns {Timeout} diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js index 11c5aba54..2d1ae7ef1 100644 --- a/src/client/ClientManager.js +++ b/src/client/ClientManager.js @@ -22,8 +22,8 @@ class ClientManager { /** * Connects the Client to the WebSocket * @param {string} token The authorization token - * @param {function} resolve Function to run when connection is successful - * @param {function} reject Function to run when connection fails + * @param {Function} resolve Function to run when connection is successful + * @param {Function} reject Function to run when connection fails */ connectToWebSocket(token, resolve, reject) { this.client.emit(Constants.Events.DEBUG, `Authenticated using token ${token}`); diff --git a/src/util/Collection.js b/src/util/Collection.js index b031f896c..cd1226d21 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -127,7 +127,7 @@ class Collection extends Map { * [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). * Do not use this to obtain an item by its ID. Instead, use `collection.get(id)`. See * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) for details. - * @param {string|function} propOrFn The property to test against, or the function to test with + * @param {string|Function} propOrFn The property to test against, or the function to test with * @param {*} [value] The expected value - only applicable and required if using a property for the first argument * @returns {*} * @example @@ -203,7 +203,7 @@ class Collection extends Map { * Identical to * [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), * but returns a Collection instead of an Array. - * @param {function} fn Function used to test (should return a boolean) + * @param {Function} fn Function used to test (should return a boolean) * @param {Object} [thisArg] Value to use as `this` when executing function * @returns {Collection} */ @@ -219,7 +219,7 @@ class Collection extends Map { /** * Identical to * [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter). - * @param {function} fn Function used to test (should return a boolean) + * @param {Function} fn Function used to test (should return a boolean) * @param {Object} [thisArg] Value to use as `this` when executing function * @returns {Collection} */ @@ -235,7 +235,7 @@ class Collection extends Map { /** * Identical to * [Array.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). - * @param {function} fn Function that produces an element of the new array, taking three arguments + * @param {Function} fn Function that produces an element of the new array, taking three arguments * @param {*} [thisArg] Value to use as `this` when executing function * @returns {Array} */ @@ -250,7 +250,7 @@ class Collection extends Map { /** * Identical to * [Array.some()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some). - * @param {function} fn Function used to test (should return a boolean) + * @param {Function} fn Function used to test (should return a boolean) * @param {Object} [thisArg] Value to use as `this` when executing function * @returns {boolean} */ @@ -265,7 +265,7 @@ class Collection extends Map { /** * Identical to * [Array.every()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every). - * @param {function} fn Function used to test (should return a boolean) + * @param {Function} fn Function used to test (should return a boolean) * @param {Object} [thisArg] Value to use as `this` when executing function * @returns {boolean} */ @@ -280,7 +280,7 @@ class Collection extends Map { /** * Identical to * [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). - * @param {function} fn Function used to reduce + * @param {Function} fn Function used to reduce * @param {*} [startVal] The starting value * @returns {*} */ From c65d7a10ec8407216383400a6be49f2479e4c10d Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 21:10:28 -0500 Subject: [PATCH 048/248] Fix another JSDoc type issue --- .eslintrc.json | 3 ++- src/structures/Guild.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8c94d1128..e477d36c6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,7 +26,8 @@ "function": "Function", "array": "Array", "date": "Date", - "error": "Error" + "error": "Error", + "null": "void" } }], diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 9c58f82b9..43deeb8c2 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -350,7 +350,7 @@ class Guild { * @property {string} [name] The name of the guild * @property {string} [region] The region of the guild * @property {number} [verificationLevel] The verification level of the guild - * @property {GuildChannelResolvable} [afkChannel] The AFK channel of the guild + * @property {ChannelResolvable} [afkChannel] The AFK channel of the guild * @property {number} [afkTimeout] The AFK timeout of the guild * @property {Base64Resolvable} [icon] The icon of the guild * @property {GuildMemberResolvable} [owner] The owner of the guild From 7b26e70a2eb9d95a669bc60daea7da429a52268f Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 21:14:36 -0500 Subject: [PATCH 049/248] Possibly change default return type for function docs --- docs/generator/types/DocumentedFunction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generator/types/DocumentedFunction.js b/docs/generator/types/DocumentedFunction.js index 698fb16b5..17438f92b 100644 --- a/docs/generator/types/DocumentedFunction.js +++ b/docs/generator/types/DocumentedFunction.js @@ -56,7 +56,7 @@ class DocumentedFunction extends DocumentedItem { this.directData = data; this.directData.meta = new DocumentedItemMeta(this, data.meta); this.directData.returns = new DocumentedVarType(this, data.returns ? data.returns[0].type : { - names: ['null'], + names: ['void'], }); const newParams = []; for (const param of data.params) { From 962cb8f8d0f56ee495e3a08c0b21d040177e7b1f Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 21:44:57 -0500 Subject: [PATCH 050/248] Add default value support to docgen --- docs/generator/types/DocumentedParam.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/generator/types/DocumentedParam.js b/docs/generator/types/DocumentedParam.js index bb94d7e37..18d44aeac 100644 --- a/docs/generator/types/DocumentedParam.js +++ b/docs/generator/types/DocumentedParam.js @@ -23,11 +23,12 @@ class DocumentedParam extends DocumentedItem { serialize() { super.serialize(); - const { name, description, type, optional } = this.directData; + const { name, description, type, optional, defaultvalue } = this.directData; return { name, description, optional, + default: defaultvalue, type: type.serialize(), }; } From 8482abed7ba850de5fea20d99783bd0270e2f345 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 21:45:05 -0500 Subject: [PATCH 051/248] Document generator v14 --- docs/generator/config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/generator/config.json b/docs/generator/config.json index 72bda7152..0efc2dc71 100644 --- a/docs/generator/config.json +++ b/docs/generator/config.json @@ -1,4 +1,4 @@ { - "GEN_VERSION": 13, + "GEN_VERSION": 14, "COMPRESS": false -} \ No newline at end of file +} From 2b7c7bfd8f72692be2d057012112c5331b048212 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 21:50:05 -0500 Subject: [PATCH 052/248] Dummy commit to force a travis build --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c5b4c712c..e5e89894b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "discord.js", "version": "10.0.1", - "description": "A powerful library for interacting with the Discord API", + "description": "A powerful library for interacting with the Discord API-", "main": "./src/index", "scripts": { "test": "eslint src/ && node docs/generator/generator.js silent", From acdf2d14c22bf75f5f8e3d341165771dba4452c3 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 21:50:17 -0500 Subject: [PATCH 053/248] Undo that dummy commit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e5e89894b..c5b4c712c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "discord.js", "version": "10.0.1", - "description": "A powerful library for interacting with the Discord API-", + "description": "A powerful library for interacting with the Discord API", "main": "./src/index", "scripts": { "test": "eslint src/ && node docs/generator/generator.js silent", From 99b8d8f031fb63a01e6fc2cbf176905deaf00221 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 22:07:17 -0500 Subject: [PATCH 054/248] Clean up docgen code and some reaction stuff --- docs/generator/types/DocumentedClass.js | 5 +- docs/generator/types/DocumentedConstructor.js | 6 +- docs/generator/types/DocumentedEvent.js | 6 +- docs/generator/types/DocumentedFunction.js | 5 +- docs/generator/types/DocumentedItemMeta.js | 8 +- docs/generator/types/DocumentedMember.js | 2 - docs/generator/types/DocumentedParam.js | 1 - docs/generator/types/DocumentedTypeDef.js | 10 +- docs/generator/types/DocumentedVarType.js | 39 ++++---- src/structures/Message.js | 97 ++++++++++--------- 10 files changed, 76 insertions(+), 103 deletions(-) diff --git a/docs/generator/types/DocumentedClass.js b/docs/generator/types/DocumentedClass.js index 323afafba..9f321b5fb 100644 --- a/docs/generator/types/DocumentedClass.js +++ b/docs/generator/types/DocumentedClass.js @@ -21,7 +21,6 @@ const DocumentedEvent = require('./DocumentedEvent'); */ class DocumentedClass extends DocumentedItem { - constructor(docParent, data) { super(docParent, data); this.props = new Map(); @@ -70,9 +69,7 @@ class DocumentedClass extends DocumentedItem { extends: augments, access, }; - if (this.classConstructor) { - serialized.classConstructor = this.classConstructor.serialize(); - } + if (this.classConstructor) serialized.classConstructor = this.classConstructor.serialize(); serialized.methods = Array.from(this.methods.values()).map(m => m.serialize()); serialized.properties = Array.from(this.props.values()).map(p => p.serialize()); serialized.events = Array.from(this.events.values()).map(e => e.serialize()); diff --git a/docs/generator/types/DocumentedConstructor.js b/docs/generator/types/DocumentedConstructor.js index e539bf642..06b21d6b0 100644 --- a/docs/generator/types/DocumentedConstructor.js +++ b/docs/generator/types/DocumentedConstructor.js @@ -17,14 +17,11 @@ const DocumentedParam = require('./DocumentedParam'); */ class DocumentedConstructor extends DocumentedItem { - registerMetaInfo(data) { super.registerMetaInfo(data); this.directData = data; const newParams = []; - for (const param of data.params) { - newParams.push(new DocumentedParam(this, param)); - } + for (const param of data.params) newParams.push(new DocumentedParam(this, param)); this.directData.params = newParams; } @@ -40,7 +37,6 @@ class DocumentedConstructor extends DocumentedItem { params: params.map(p => p.serialize()), }; } - } module.exports = DocumentedConstructor; diff --git a/docs/generator/types/DocumentedEvent.js b/docs/generator/types/DocumentedEvent.js index 0641b23b8..52791980d 100644 --- a/docs/generator/types/DocumentedEvent.js +++ b/docs/generator/types/DocumentedEvent.js @@ -50,15 +50,12 @@ const DocumentedParam = require('./DocumentedParam'); */ class DocumentedEvent extends DocumentedItem { - registerMetaInfo(data) { this.directData = data; this.directData.meta = new DocumentedItemMeta(this, data.meta); const newParams = []; data.params = data.params || []; - for (const param of data.params) { - newParams.push(new DocumentedParam(this, param)); - } + for (const param of data.params) newParams.push(new DocumentedParam(this, param)); this.directData.params = newParams; } @@ -74,7 +71,6 @@ class DocumentedEvent extends DocumentedItem { params: params.map(p => p.serialize()), }; } - } module.exports = DocumentedEvent; diff --git a/docs/generator/types/DocumentedFunction.js b/docs/generator/types/DocumentedFunction.js index 17438f92b..04ba6760d 100644 --- a/docs/generator/types/DocumentedFunction.js +++ b/docs/generator/types/DocumentedFunction.js @@ -50,7 +50,6 @@ const DocumentedParam = require('./DocumentedParam'); */ class DocumentedFunction extends DocumentedItem { - registerMetaInfo(data) { super.registerMetaInfo(data); this.directData = data; @@ -59,9 +58,7 @@ class DocumentedFunction extends DocumentedItem { names: ['void'], }); const newParams = []; - for (const param of data.params) { - newParams.push(new DocumentedParam(this, param)); - } + for (const param of data.params) newParams.push(new DocumentedParam(this, param)); this.directData.params = newParams; } diff --git a/docs/generator/types/DocumentedItemMeta.js b/docs/generator/types/DocumentedItemMeta.js index 7b44941b6..3bc6fbae1 100644 --- a/docs/generator/types/DocumentedItemMeta.js +++ b/docs/generator/types/DocumentedItemMeta.js @@ -1,8 +1,8 @@ -const cwd = (`${process.cwd()}\\`).replace(/\\/g, '/'); -const backToForward = /\\/g; - const DocumentedItem = require('./DocumentedItem'); +const cwd = `${process.cwd()}\\`.replace(/\\/g, '/'); +const backToForward = /\\/g; + /* { lineno: 7, filename: 'VoiceChannel.js', @@ -10,7 +10,6 @@ const DocumentedItem = require('./DocumentedItem'); */ class DocumentedItemMeta extends DocumentedItem { - registerMetaInfo(data) { super.registerMetaInfo(data); this.directData.line = data.lineno; @@ -23,7 +22,6 @@ class DocumentedItemMeta extends DocumentedItem { const { line, file, path } = this.directData; return { line, file, path }; } - } module.exports = DocumentedItemMeta; diff --git a/docs/generator/types/DocumentedMember.js b/docs/generator/types/DocumentedMember.js index 3eaddc24e..5b66e7b65 100644 --- a/docs/generator/types/DocumentedMember.js +++ b/docs/generator/types/DocumentedMember.js @@ -21,7 +21,6 @@ const DocumentedParam = require('./DocumentedParam'); */ class DocumentedMember extends DocumentedItem { - registerMetaInfo(data) { super.registerMetaInfo(data); this.directData = data; @@ -52,7 +51,6 @@ class DocumentedMember extends DocumentedItem { props: properties.map(p => p.serialize()), }; } - } module.exports = DocumentedMember; diff --git a/docs/generator/types/DocumentedParam.js b/docs/generator/types/DocumentedParam.js index 18d44aeac..d869078d4 100644 --- a/docs/generator/types/DocumentedParam.js +++ b/docs/generator/types/DocumentedParam.js @@ -14,7 +14,6 @@ const DocumentedVarType = require('./DocumentedVarType'); */ class DocumentedParam extends DocumentedItem { - registerMetaInfo(data) { super.registerMetaInfo(data); this.directData = data; diff --git a/docs/generator/types/DocumentedTypeDef.js b/docs/generator/types/DocumentedTypeDef.js index dfabc0dfa..0571868ec 100644 --- a/docs/generator/types/DocumentedTypeDef.js +++ b/docs/generator/types/DocumentedTypeDef.js @@ -19,11 +19,6 @@ const DocumentedParam = require('./DocumentedParam'); */ class DocumentedTypeDef extends DocumentedItem { - - constructor(...args) { - super(...args); - } - registerMetaInfo(data) { super.registerMetaInfo(data); this.props = new Map(); @@ -31,9 +26,7 @@ class DocumentedTypeDef extends DocumentedItem { this.directData.meta = new DocumentedItemMeta(this, data.meta); this.directData.type = new DocumentedVarType(this, data.type); data.properties = data.properties || []; - for (const prop of data.properties) { - this.props.set(prop.name, new DocumentedParam(this, prop)); - } + for (const prop of data.properties) this.props.set(prop.name, new DocumentedParam(this, prop)); } serialize() { @@ -50,7 +43,6 @@ class DocumentedTypeDef extends DocumentedItem { serialized.properties = Array.from(this.props.values()).map(p => p.serialize()); return serialized; } - } module.exports = DocumentedTypeDef; diff --git a/docs/generator/types/DocumentedVarType.js b/docs/generator/types/DocumentedVarType.js index 209431daf..fcbb1b548 100644 --- a/docs/generator/types/DocumentedVarType.js +++ b/docs/generator/types/DocumentedVarType.js @@ -11,10 +11,24 @@ const DocumentedItem = require('./DocumentedItem'); const regex = /([\w]+)([^\w]+)/; const regexG = /([\w]+)([^\w]+)/g; -function splitVarName(str) { - if (str === '*') { - return ['*', '']; +class DocumentedVarType extends DocumentedItem { + registerMetaInfo(data) { + super.registerMetaInfo(data); + this.directData = data; } + + serialize() { + super.serialize(); + const names = []; + for (const name of this.directData.names) names.push(splitVarName(name)); + return { + types: names, + }; + } +} + +function splitVarName(str) { + if (str === '*') return ['*', '']; const matches = str.match(regexG); const output = []; if (matches) { @@ -28,23 +42,4 @@ function splitVarName(str) { return output; } -class DocumentedVarType extends DocumentedItem { - - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.directData = data; - } - - serialize() { - super.serialize(); - const names = []; - for (const name of this.directData.names) { - names.push(splitVarName(name)); - } - return { - types: names, - }; - } -} - module.exports = DocumentedVarType; diff --git a/src/structures/Message.js b/src/structures/Message.js index 769787728..d7ad13484 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -164,40 +164,6 @@ class Message { } } - _addReaction(emoji, user) { - const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name; - let reaction; - if (this.reactions.has(emojiID)) { - reaction = this.reactions.get(emojiID); - if (!reaction.me) reaction.me = user.id === this.client.user.id; - } else { - reaction = new MessageReaction(this, emoji, 0, user.id === this.client.user.id); - 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--; - if (user.id === this.client.user.id) { - reaction.me = false; - } - return reaction; - } - } - return null; - } - patch(data) { // eslint-disable-line complexity if (data.author) { this.author = this.client.users.get(data.author.id); @@ -323,18 +289,6 @@ class Message { }); } - addReaction(emoji) { - if (emoji.identifier) { - emoji = emoji.identifier; - } else if (typeof emoji === 'string') { - if (!emoji.includes('%')) emoji = encodeURIComponent(emoji); - } else { - return Promise.reject(`Emoji must be a string or an Emoji/ReactionEmoji`); - } - - 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). @@ -427,6 +381,23 @@ class Message { return this.client.rest.methods.unpinMessage(this); } + /** + * Adds a reaction to the message + * @param {Emoji|ReactionEmoji|string} emoji Emoji to react with + * @returns {Promise} + */ + addReaction(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.'); + } + + return this.client.rest.methods.addMessageReaction(this.channel.id, this.id, emoji); + } + /** * Deletes the message * @param {number} [timeout=0] How long to wait to delete the message in milliseconds @@ -513,6 +484,40 @@ class Message { toString() { return this.content; } + + _addReaction(emoji, user) { + const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name; + let reaction; + if (this.reactions.has(emojiID)) { + reaction = this.reactions.get(emojiID); + if (!reaction.me) reaction.me = user.id === this.client.user.id; + } else { + reaction = new MessageReaction(this, emoji, 0, user.id === this.client.user.id); + 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--; + if (user.id === this.client.user.id) { + reaction.me = false; + } + return reaction; + } + } + return null; + } } module.exports = Message; From 2c76f5437bf43c9ccb6626274afd22d40be2d140 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 22:08:43 -0500 Subject: [PATCH 055/248] Inline a line, woohoo --- src/structures/Message.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index d7ad13484..7c43f4e47 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -510,9 +510,7 @@ class Message { if (reaction.users.has(user.id)) { reaction.users.delete(user.id); reaction.count--; - if (user.id === this.client.user.id) { - reaction.me = false; - } + if (user.id === this.client.user.id) reaction.me = false; return reaction; } } From c50de74310254725c3a8901b7b04e3071520f352 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 22:12:26 -0500 Subject: [PATCH 056/248] Rename Message.addReaction -> Message.react --- src/structures/Message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 7c43f4e47..45d7681cc 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -386,7 +386,7 @@ class Message { * @param {Emoji|ReactionEmoji|string} emoji Emoji to react with * @returns {Promise} */ - addReaction(emoji) { + react(emoji) { if (emoji.identifier) { emoji = emoji.identifier; } else if (typeof emoji === 'string') { From 9d0fcb3936b7c04351eb0e4f24c29aab26ddc0f4 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 22:15:47 -0500 Subject: [PATCH 057/248] Fix Message.reply example --- src/structures/Message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 45d7681cc..0f053f324 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -427,7 +427,7 @@ class Message { * @returns {Promise} * @example * // reply to a message - * message.reply('Hey, I'm a reply!') + * message.reply('Hey, I\'m a reply!') * .then(msg => console.log(`Sent a reply to ${msg.author}`)) * .catch(console.error); */ From 5d3123d405cd70b2fc8a1b3a422688363b5dc971 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 23:50:06 -0500 Subject: [PATCH 058/248] Add branch info to contributing guide --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4d1c83166..8b56f1c1a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ is a great boon to your coding process. ## Setup To get ready to work on the codebase, please do the following: -1. Fork & clone the repository +1. Fork & clone the repository, and make sure you're on the **indev** branch 2. Run `npm install` 3. If you're working on voice, also run `npm install node-opus` or `npm install opusscript` 4. Code your heart out! From af5e07fb338e1487131643f7e0764699c4af83ac Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 12 Nov 2016 23:58:20 -0500 Subject: [PATCH 059/248] Change sentence structure slightly --- README.md | 2 +- docs/custom/documents/welcome.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef776560c..60a988455 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ ## About discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily. It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend. -Usability and performance are key focuses of discord.js. It also has nearly 100% coverage of the Discord API. +Usability and performance are key focuses of discord.js, and it also has nearly 100% coverage of the Discord API. ## Installation **Node.js 6.0.0 or newer is required.** diff --git a/docs/custom/documents/welcome.md b/docs/custom/documents/welcome.md index b8f7b3c5e..6360b34c6 100644 --- a/docs/custom/documents/welcome.md +++ b/docs/custom/documents/welcome.md @@ -21,7 +21,7 @@ v10 is just a more consistent and stable iteration over v9, and contains loads o ## About discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily. It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend. -Usability and performance are key focuses of discord.js. It also has nearly 100% coverage of the Discord API. +Usability and performance are key focuses of discord.js, and it also has nearly 100% coverage of the Discord API. ## Installation **Node.js 6.0.0 or newer is required.** From ee3a03f7074ff2fe94dc7a74f46596d3b08d4ce5 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 13 Nov 2016 00:27:56 -0500 Subject: [PATCH 060/248] Make Collection.find/exists error when using with IDs --- src/util/Collection.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/Collection.js b/src/util/Collection.js index cd1226d21..42c272a37 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -138,6 +138,7 @@ class Collection extends Map { find(propOrFn, value) { if (typeof propOrFn === 'string') { if (typeof value === 'undefined') throw new Error('Value must be specified.'); + if (propOrFn === 'id') throw new RangeError('Don\'t use .find() with IDs. Instead, use .get(id).'); for (const item of this.values()) { if (item[propOrFn] === value) return item; } @@ -196,6 +197,7 @@ class Collection extends Map { * } */ exists(prop, value) { + if (prop === 'id') throw new RangeError('Don\'t use .find() with IDs. Instead, use .get(id).'); return Boolean(this.find(prop, value)); } From 27270a3bad580a1863b2ac198b2672b41f4eddd5 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 13 Nov 2016 00:05:13 -0600 Subject: [PATCH 061/248] add embed support! (#894) * add embed support! * document message embeds * make gawdl3y happy * make edit great again * make docs better * Update Message.js * Update TextBasedChannel.js * Update TextBasedChannel.js --- src/client/rest/RESTMethods.js | 16 ++++++++-------- src/structures/Message.js | 11 +++++++++-- src/structures/interface/TextBasedChannel.js | 2 ++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 5f0e063ab..b9898016d 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -48,7 +48,7 @@ class RESTMethods { return this.rest.makeRequest('get', Constants.Endpoints.botGateway, true); } - sendMessage(channel, content, { tts, nonce, disableEveryone, split } = {}, file = null) { + sendMessage(channel, content, { tts, nonce, embed, disableEveryone, split } = {}, file = null) { return new Promise((resolve, reject) => { if (typeof content !== 'undefined') content = this.rest.client.resolver.resolveString(content); @@ -62,15 +62,15 @@ class RESTMethods { if (channel instanceof User || channel instanceof GuildMember) { this.createDM(channel).then(chan => { - this._sendMessageRequest(chan, content, file, tts, nonce, resolve, reject); + this._sendMessageRequest(chan, content, file, tts, nonce, embed, resolve, reject); }, reject); } else { - this._sendMessageRequest(channel, content, file, tts, nonce, resolve, reject); + this._sendMessageRequest(channel, content, file, tts, nonce, embed, resolve, reject); } }); } - _sendMessageRequest(channel, content, file, tts, nonce, resolve, reject) { + _sendMessageRequest(channel, content, file, tts, nonce, embed, resolve, reject) { if (content instanceof Array) { const datas = []; let promise = this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, { @@ -83,7 +83,7 @@ class RESTMethods { promise = promise.then(data => { datas.push(data); return this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, { - content: content[i2], tts, nonce, + content: content[i2], tts, nonce, embed, }, file); }, reject); } else { @@ -95,7 +95,7 @@ class RESTMethods { } } else { this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, { - content, tts, nonce, + content, tts, nonce, embed, }, file) .then(data => resolve(this.rest.client.actions.MessageCreate.handle(data).message), reject); } @@ -122,10 +122,10 @@ class RESTMethods { ); } - updateMessage(message, content) { + updateMessage(message, content, { embed } = {}) { content = this.rest.client.resolver.resolveString(content); return this.rest.makeRequest('patch', Constants.Endpoints.channelMessage(message.channel.id, message.id), true, { - content, + content, embed, }).then(data => this.rest.client.actions.MessageUpdate.handle(data).updated); } diff --git a/src/structures/Message.js b/src/structures/Message.js index 0f053f324..8831d3f4d 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -340,9 +340,16 @@ class Message { return this.mentions.users.has(data) || this.mentions.channels.has(data) || this.mentions.roles.has(data); } + /** + * Options that can be passed into editMessage + * @typedef {Object} MessageEditOptions + * @property {Object} [embed] An embed to be added/edited + */ + /** * Edit the content of the message * @param {StringResolvable} content The new content for the message + * @param {MessageEditOptions} [options={}] The options to provide * @returns {Promise} * @example * // update the content of a message @@ -350,8 +357,8 @@ class Message { * .then(msg => console.log(`Updated the content of a message from ${msg.author}`)) * .catch(console.error); */ - edit(content) { - return this.client.rest.methods.updateMessage(this, content); + edit(content, options = {}) { + return this.client.rest.methods.updateMessage(this, content, options); } /** diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index fba4442b1..d622dcabe 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -28,6 +28,8 @@ class TextBasedChannel { * @typedef {Object} MessageOptions * @property {boolean} [tts=false] Whether or not the message should be spoken aloud * @property {string} [nonce=''] The nonce for the message + * @property {Object} [embed] An embed for the message + * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details) * @property {boolean} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here * should be replaced with plain-text * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if From da9d1a3daf5d050413c20795c8f07f76e301fbdf Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 13 Nov 2016 01:07:45 -0500 Subject: [PATCH 062/248] Don't mind me --- src/util/Collection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Collection.js b/src/util/Collection.js index 42c272a37..a5bf0b5f4 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -197,7 +197,7 @@ class Collection extends Map { * } */ exists(prop, value) { - if (prop === 'id') throw new RangeError('Don\'t use .find() with IDs. Instead, use .get(id).'); + if (prop === 'id') throw new RangeError('Don\'t use .find() with IDs. Instead, use .has(id).'); return Boolean(this.find(prop, value)); } From bb3b709d6e315d0facb1c5c701d1e12fd796c4c8 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 13 Nov 2016 01:13:13 -0500 Subject: [PATCH 063/248] Nothing to see here :eyes: --- src/util/Collection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Collection.js b/src/util/Collection.js index a5bf0b5f4..9d36e5efa 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -197,7 +197,7 @@ class Collection extends Map { * } */ exists(prop, value) { - if (prop === 'id') throw new RangeError('Don\'t use .find() with IDs. Instead, use .has(id).'); + if (prop === 'id') throw new RangeError('Don\'t use .exists() with IDs. Instead, use .has(id).'); return Boolean(this.find(prop, value)); } From c041b1bc23e8df9075d984af0b754eeb50a8aaaf Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 13 Nov 2016 01:05:55 -0600 Subject: [PATCH 064/248] fix these things (#895) * fix these things * fix enormous stupid --- src/structures/Guild.js | 12 ++++++++---- src/structures/TextChannel.js | 17 +++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 43deeb8c2..f073132f0 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -598,10 +598,14 @@ class Guild { * .catch(console.error); */ createEmoji(attachment, name) { - return this.client.resolver.resolveBuffer(attachment).then(file => { - let base64 = new Buffer(file, 'binary').toString('base64'); - let dataURI = `data:;base64,${base64}`; - return this.client.rest.methods.createEmoji(this, dataURI, name); + return new Promise(resolve => { + if (attachment.startsWith('data:')) { + resolve(this.client.rest.methods.createEmoji(this, attachment, name)); + } else { + this.client.resolver.resolveBuffer(attachment).then(data => + resolve(this.client.rest.methods.createEmoji(this, data, name)) + ); + } }); } diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index cd067714f..74c0ff2d0 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -61,14 +61,15 @@ class TextChannel extends GuildChannel { * .catch(console.error) */ createWebhook(name, avatar) { - if (avatar) { - return this.client.resolver.resolveBuffer(avatar).then(file => { - let base64 = new Buffer(file, 'binary').toString('base64'); - let dataURI = `data:;base64,${base64}`; - return this.client.rest.methods.createWebhook(this, name, dataURI); - }); - } - return this.client.rest.methods.createWebhook(this, name); + return new Promise(resolve => { + if (avatar.startsWith('data:')) { + resolve(this.client.rest.methods.createWebhook(this, name, avatar)); + } else { + this.client.resolver.resolveBuffer(avatar).then(data => + resolve(this.client.rest.methods.createWebhook(this, name, data)) + ); + } + }); } // These are here only for documentation purposes - they are implemented by TextBasedChannel From a359f344d891e0166ed18f49c61a84d0ea4f99cd Mon Sep 17 00:00:00 2001 From: Steffen Date: Sun, 13 Nov 2016 08:07:51 +0100 Subject: [PATCH 065/248] UnhandledPromiseRejectionWarning caused by resolveBuffer on empty resource body (#886) * Fix for UnhandledPromiseRejectionWarning in resolveBuffer * code simplification * reject with TypeError if body is not a Buffer --- src/client/ClientDataResolver.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 76fa85a8d..11f7ef23e 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -244,7 +244,11 @@ class ClientDataResolver { if (/^https?:\/\//.test(resource)) { request.get(resource) .set('Content-Type', 'blob') - .end((err, res) => err ? reject(err) : resolve(res.body)); + .end((err, res) => { + if (err) return reject(err); + if (!(res.body instanceof Buffer)) return reject(new TypeError('Body is not a Buffer')); + return resolve(res.body); + }); } else { const file = path.resolve(resource); fs.stat(file, (err, stats) => { From 5ed8098af88e5c6a4f27b6db5391023e72037766 Mon Sep 17 00:00:00 2001 From: Programmix Date: Sat, 12 Nov 2016 23:29:26 -0800 Subject: [PATCH 066/248] Clean up reactions, add remove all reactions (#890) * Clean up reactions, add remove all reactions * Reorganize reactions * Re-add Gawdl3y's precious little inline * Update Message.js --- src/client/ClientDataResolver.js | 23 ++++++++++++++ src/client/actions/ActionsManager.js | 1 + .../actions/MessageReactionRemoveAll.js | 21 +++++++++++++ src/client/rest/RESTMethods.js | 31 +++++++++++-------- .../packets/WebSocketPacketManager.js | 1 + .../handlers/MessageReactionRemoveAll.js | 11 +++++++ src/structures/Message.js | 29 ++++++++++------- src/structures/MessageReaction.js | 4 +-- src/util/Constants.js | 2 ++ 9 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 src/client/actions/MessageReactionRemoveAll.js create mode 100644 src/client/websocket/packets/handlers/MessageReactionRemoveAll.js 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', From 1833a83664fcf23a970bafd970f675194144183a Mon Sep 17 00:00:00 2001 From: York Date: Thu, 17 Nov 2016 07:18:03 +0000 Subject: [PATCH 067/248] Documented reaction events (#905) --- src/client/actions/MessageReactionAdd.js | 7 ++++++- src/client/actions/MessageReactionRemove.js | 7 ++++++- src/client/actions/MessageReactionRemoveAll.js | 6 +++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/client/actions/MessageReactionAdd.js b/src/client/actions/MessageReactionAdd.js index c53af3828..75f4cde5e 100644 --- a/src/client/actions/MessageReactionAdd.js +++ b/src/client/actions/MessageReactionAdd.js @@ -34,5 +34,10 @@ class MessageReactionAdd extends Action { }; } } - +/** + * Emitted whenever a reaction is added to a message. + * @event Client#messageReactionAdd. + * @param {MessageReaction} messageReaction The reaction object. + * @param {User} user The user that applied the emoji or reaction emoji. + */ module.exports = MessageReactionAdd; diff --git a/src/client/actions/MessageReactionRemove.js b/src/client/actions/MessageReactionRemove.js index ae626ea00..ab9cec2bf 100644 --- a/src/client/actions/MessageReactionRemove.js +++ b/src/client/actions/MessageReactionRemove.js @@ -34,5 +34,10 @@ class MessageReactionRemove extends Action { }; } } - +/** + * Emitted whenever a reaction is removed from a message. + * @event Client#messageReactionRemove. + * @param {MessageReaction} messageReaction The reaction object. + * @param {User} user The user that removed the emoji or reaction emoji. + */ module.exports = MessageReactionRemove; diff --git a/src/client/actions/MessageReactionRemoveAll.js b/src/client/actions/MessageReactionRemoveAll.js index 54f38a6c4..3aefe69f1 100644 --- a/src/client/actions/MessageReactionRemoveAll.js +++ b/src/client/actions/MessageReactionRemoveAll.js @@ -17,5 +17,9 @@ class MessageReactionRemoveAll extends Action { }; } } - +/** + * Emitted whenever all reactions are removed from a message. + * @event Client#messageReactionRemoveAll. + * @param {MessageReaction} messageReaction The reaction object. + */ module.exports = MessageReactionRemoveAll; From b2bc844ed77cc29000e48d807d3b75a88795cb21 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Thu, 17 Nov 2016 01:42:50 -0600 Subject: [PATCH 068/248] Add new MessageEmbed stuff (#898) * fix * Update MessageEmbed.js * man * Update MessageCreate.js * Update MessageEmbed.js * Update MessageEmbed.js * clean up, add class * my dreams are slowly becoming memes * aghhh * safety * Update MessageEmbed.js * Update MessageEmbed.js * Update MessageEmbed.js * dammit --- src/client/actions/MessageCreate.js | 2 + src/structures/MessageEmbed.js | 103 +++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/client/actions/MessageCreate.js b/src/client/actions/MessageCreate.js index 1280d991a..d3ac7f22d 100644 --- a/src/client/actions/MessageCreate.js +++ b/src/client/actions/MessageCreate.js @@ -12,11 +12,13 @@ class MessageCreateAction extends Action { for (let i = 0; i < data.length; i++) { messages[i] = channel._cacheMessage(new Message(channel, data[i], client)); } + channel.lastMessageID = messages[messages.length - 1].id; return { messages, }; } else { const message = channel._cacheMessage(new Message(channel, data, client)); + channel.lastMessageID = data.id; return { message, }; diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js index f119f561c..0cd071408 100644 --- a/src/structures/MessageEmbed.js +++ b/src/structures/MessageEmbed.js @@ -1,5 +1,5 @@ /** - * Represents an embed in an image - e.g. preview of image + * Represents an embed in a message (image/video preview, rich embed, etc.) */ class MessageEmbed { constructor(message, data) { @@ -44,6 +44,19 @@ class MessageEmbed { */ this.url = data.url; + /** + * The fields of this embed + * @type {MessageEmbedField[]} + */ + this.fields = []; + if (data.fields) for (const field of data.fields) this.fields.push(new MessageEmbedField(field, field)); + + /** + * The timestamp of this embed + * @type {number} + */ + this.createdTimestamp = data.timestamp; + /** * The thumbnail of this embed, if there is one * @type {MessageEmbedThumbnail} @@ -61,6 +74,20 @@ class MessageEmbed { * @type {MessageEmbedProvider} */ this.provider = data.provider ? new MessageEmbedProvider(this, data.provider) : null; + + /** + * The footer of this embed + * @type {MessageEmbedFooter} + */ + this.footer = data.footer ? new MessageEmbedFooter(data.footer) : null; + } + + /** + * The date this embed was created + * @type {Date} + */ + get createdAt() { + return new Date(this.createdTimestamp); } } @@ -135,7 +162,7 @@ class MessageEmbedProvider { } /** - * Represents a author for a message embed + * Represents an author for a message embed */ class MessageEmbedAuthor { constructor(embed, data) { @@ -163,8 +190,80 @@ class MessageEmbedAuthor { } } +/** + * Represents a field for a message embed + */ +class MessageEmbedField { + constructor(embed, data) { + /** + * The embed this footer is part of + * @type {MessageEmbed} + */ + this.embed = embed; + + this.setup(data); + } + + setup(data) { + /** + * The name of this field + * @type {string} + */ + this.name = data.name; + + /** + * The value of this field + * @type {string} + */ + this.value = data.value; + + /** + * If this field is displayed inline + * @type {boolean} + */ + this.inline = data.inline; + } +} + +/** + * Represents the footer of a message embed + */ +class MessageEmbedFooter { + constructor(embed, data) { + /** + * The embed this footer is part of + * @type {MessageEmbed} + */ + this.embed = embed; + + this.setup(data); + } + + setup(data) { + /** + * The text in this footer + * @type {string} + */ + this.text = data.text; + + /** + * The icon URL of this footer + * @type {string} + */ + this.iconUrl = data.icon_url; + + /** + * The proxy icon URL of this footer + * @type {string} + */ + this.proxyIconUrl = data.proxy_icon_url; + } +} + MessageEmbed.Thumbnail = MessageEmbedThumbnail; MessageEmbed.Provider = MessageEmbedProvider; MessageEmbed.Author = MessageEmbedAuthor; +MessageEmbed.Field = MessageEmbedField; +MessageEmbed.Footer = MessageEmbedFooter; module.exports = MessageEmbed; From 49fdc331a7f9f5ce1f93135c6acb048e6bf00d3b Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 17 Nov 2016 02:49:24 -0500 Subject: [PATCH 069/248] Fix formatting --- src/client/rest/RESTMethods.js | 26 ++++++++++++++------------ src/structures/MessageEmbed.js | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index cadaee76a..be35d7d20 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -583,15 +583,16 @@ class RESTMethods { } 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: message.id, - emoji: parseEmoji(emoji), - channel_id: message.channel.id, - }).reaction - ); + 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: message.id, + emoji: parseEmoji(emoji), + channel_id: message.channel.id, + }).reaction + ); } removeMessageReaction(message, emoji, user) { @@ -615,7 +616,9 @@ class RESTMethods { } getMessageReactionUsers(message, emoji, limit = 100) { - return this.rest.makeRequest('get', Constants.Endpoints.messageReaction(message.channel.id, message.id, emoji, limit), true); + return this.rest.makeRequest( + 'get', Constants.Endpoints.messageReaction(message.channel.id, message.id, emoji, limit), true + ); } getMyApplication() { @@ -625,8 +628,7 @@ class RESTMethods { } setNote(user, note) { - return this.rest.makeRequest('put', Constants.Endpoints.note(user.id), true, { note }) - .then(() => user); + return this.rest.makeRequest('put', Constants.Endpoints.note(user.id), true, { note }).then(() => user); } } diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js index 0cd071408..43469c350 100644 --- a/src/structures/MessageEmbed.js +++ b/src/structures/MessageEmbed.js @@ -251,7 +251,7 @@ class MessageEmbedFooter { * @type {string} */ this.iconUrl = data.icon_url; - + /** * The proxy icon URL of this footer * @type {string} From 93948328b4a41bd922221844fa1ef810fed60c52 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 17 Nov 2016 02:51:46 -0500 Subject: [PATCH 070/248] Fix some Gus code --- src/structures/MessageEmbed.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js index 43469c350..2790a679c 100644 --- a/src/structures/MessageEmbed.js +++ b/src/structures/MessageEmbed.js @@ -49,7 +49,7 @@ class MessageEmbed { * @type {MessageEmbedField[]} */ this.fields = []; - if (data.fields) for (const field of data.fields) this.fields.push(new MessageEmbedField(field, field)); + if (data.fields) for (const field of data.fields) this.fields.push(new MessageEmbedField(this, field)); /** * The timestamp of this embed @@ -79,7 +79,7 @@ class MessageEmbed { * The footer of this embed * @type {MessageEmbedFooter} */ - this.footer = data.footer ? new MessageEmbedFooter(data.footer) : null; + this.footer = data.footer ? new MessageEmbedFooter(this, data.footer) : null; } /** From 3fd3588ef2e45b679964e7ea6a9643aad15ad228 Mon Sep 17 00:00:00 2001 From: Crawl Date: Thu, 17 Nov 2016 09:03:51 +0100 Subject: [PATCH 071/248] Fix reaction event names (#906) --- src/client/actions/MessageReactionAdd.js | 2 +- src/client/actions/MessageReactionRemove.js | 2 +- src/client/actions/MessageReactionRemoveAll.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/actions/MessageReactionAdd.js b/src/client/actions/MessageReactionAdd.js index 75f4cde5e..f57ec2e27 100644 --- a/src/client/actions/MessageReactionAdd.js +++ b/src/client/actions/MessageReactionAdd.js @@ -36,7 +36,7 @@ class MessageReactionAdd extends Action { } /** * Emitted whenever a reaction is added to a message. - * @event Client#messageReactionAdd. + * @event Client#messageReactionAdd * @param {MessageReaction} messageReaction The reaction object. * @param {User} user The user that applied the emoji or reaction emoji. */ diff --git a/src/client/actions/MessageReactionRemove.js b/src/client/actions/MessageReactionRemove.js index ab9cec2bf..98a958d15 100644 --- a/src/client/actions/MessageReactionRemove.js +++ b/src/client/actions/MessageReactionRemove.js @@ -36,7 +36,7 @@ class MessageReactionRemove extends Action { } /** * Emitted whenever a reaction is removed from a message. - * @event Client#messageReactionRemove. + * @event Client#messageReactionRemove * @param {MessageReaction} messageReaction The reaction object. * @param {User} user The user that removed the emoji or reaction emoji. */ diff --git a/src/client/actions/MessageReactionRemoveAll.js b/src/client/actions/MessageReactionRemoveAll.js index 3aefe69f1..f35b78503 100644 --- a/src/client/actions/MessageReactionRemoveAll.js +++ b/src/client/actions/MessageReactionRemoveAll.js @@ -19,7 +19,7 @@ class MessageReactionRemoveAll extends Action { } /** * Emitted whenever all reactions are removed from a message. - * @event Client#messageReactionRemoveAll. + * @event Client#messageReactionRemoveAll * @param {MessageReaction} messageReaction The reaction object. */ module.exports = MessageReactionRemoveAll; From 213c45323fb376ec09276ac964cdbee78cbb8b32 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 19 Nov 2016 21:21:49 -0500 Subject: [PATCH 072/248] Switch to a non-absurdly-sized logo --- README.md | 2 +- docs/custom/documents/welcome.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 60a988455..73471ae83 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- discord.js + discord.js

Discord server diff --git a/docs/custom/documents/welcome.md b/docs/custom/documents/welcome.md index 6360b34c6..98f0597bf 100644 --- a/docs/custom/documents/welcome.md +++ b/docs/custom/documents/welcome.md @@ -1,6 +1,6 @@

- discord.js + discord.js

Discord server From 34548e3ecc751d3eed978eb09bed4647eb6b7810 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 19 Nov 2016 21:23:16 -0500 Subject: [PATCH 073/248] Fix webhook example --- docs/custom/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/custom/index.js b/docs/custom/index.js index a076c4619..49e594bc0 100644 --- a/docs/custom/index.js +++ b/docs/custom/index.js @@ -4,14 +4,13 @@ const files = [ require('./faq'), require('./ping_pong'), require('./avatar'), + require('./webhook'), ]; const categories = {}; for (const file of files) { file.category = file.category.toLowerCase(); - if (!categories[file.category]) { - categories[file.category] = []; - } + if (!categories[file.category]) categories[file.category] = []; categories[file.category].push(file); } From c29eb7ab2097fc893e4d1323d705cb838b5c0d66 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 00:19:41 -0500 Subject: [PATCH 074/248] Update links --- README.md | 6 +++--- docs/README.md | 2 +- docs/custom/documents/welcome.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 73471ae83..12509a471 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- discord.js + discord.js

Discord server @@ -54,7 +54,7 @@ A bot template using discord.js can be generated using [generator-discordbot](ht * [Website](http://discord.js.org/) * [Discord.js server](https://discord.gg/bRCvFy9) * [Discord API server](https://discord.gg/rV4BwdK) -* [Documentation](http://discord.js.org/#!/docs) +* [Documentation](http://discord.js.org/#/docs) * [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html) * [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/custom/examples) * [GitHub](https://github.com/hydrabolt/discord.js) @@ -63,7 +63,7 @@ A bot template using discord.js can be generated using [generator-discordbot](ht ## Contributing Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the -[documentation](http://discord.js.org/#!/docs). +[documentation](http://discord.js.org/#/docs). See [the contributing guide](CONTRIBUTING.md) if you'd like to submit a PR. ## Help diff --git a/docs/README.md b/docs/README.md index 6369fd9e6..7092346cf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,2 +1,2 @@ # discord.js docs -[View documentation here](http://discord.js.org/#!/docs) +[View documentation here](http://discord.js.org/#/docs) diff --git a/docs/custom/documents/welcome.md b/docs/custom/documents/welcome.md index 98f0597bf..dde170889 100644 --- a/docs/custom/documents/welcome.md +++ b/docs/custom/documents/welcome.md @@ -1,6 +1,6 @@

- discord.js + discord.js

Discord server @@ -42,7 +42,7 @@ For production bots, using node-opus should be considered a necessity, especiall * [Website](http://discord.js.org/) * [Discord.js server](https://discord.gg/bRCvFy9) * [Discord API server](https://discord.gg/rV4BwdK) -* [Documentation](http://discord.js.org/#!/docs) +* [Documentation](http://discord.js.org/#/docs) * [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html) * [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/custom/examples) * [GitHub](https://github.com/hydrabolt/discord.js) From 74ab72fdea926c2d2462fa4aea6ef2bb844ef39a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 17:39:55 -0500 Subject: [PATCH 075/248] Update Collection docs --- src/util/Collection.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/util/Collection.js b/src/util/Collection.js index 9d36e5efa..8c5bbdc0b 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -5,7 +5,19 @@ class Collection extends Map { constructor(iterable) { super(iterable); + + /** + * Cached array for the `array()` method - will be reset to `null` whenever `set()` or `delete()` are called. + * @type {?Array} + * @private + */ this._array = null; + + /** + * Cached array for the `keyArray()` method - will be reset to `null` whenever `set()` or `delete()` are called. + * @type {?Array} + * @private + */ this._keyArray = null; } @@ -23,11 +35,9 @@ class Collection extends Map { /** * Creates an ordered array of the values of this collection, and caches it internally. The array will only be - * reconstructed if an item is added to or removed from the collection, or if you add/remove elements on the array. + * reconstructed if an item is added to or removed from the collection, or if you change the length of the array + * itself. If you don't want this caching behaviour, use `Array.from(collection.values())` instead. * @returns {Array} - * @example - * // identical to: - * Array.from(collection.values()); */ array() { if (!this._array || this._array.length !== this.size) this._array = Array.from(this.values()); @@ -36,11 +46,9 @@ class Collection extends Map { /** * Creates an ordered array of the keys of this collection, and caches it internally. The array will only be - * reconstructed if an item is added to or removed from the collection, or if you add/remove elements on the array. + * reconstructed if an item is added to or removed from the collection, or if you change the length of the array + * itself. If you don't want this caching behaviour, use `Array.from(collection.keys())` instead. * @returns {Array} - * @example - * // identical to: - * Array.from(collection.keys()); */ keyArray() { if (!this._keyArray || this._keyArray.length !== this.size) this._keyArray = Array.from(this.keys()); @@ -158,7 +166,7 @@ class Collection extends Map { * Searches for the key of an item where `item[prop] === value`, or the given function returns `true`. * In the latter case, this is identical to * [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). - * @param {string|function} propOrFn The property to test against, or the function to test with + * @param {string|Function} propOrFn The property to test against, or the function to test with * @param {*} [value] The expected value - only applicable and required if using a property for the first argument * @returns {*} * @example @@ -308,8 +316,7 @@ class Collection extends Map { } /** - * If the items in this collection have a delete method (e.g. messages), invoke - * the delete method. Returns an array of promises + * Calls the `delete()` method on all items that have it. * @returns {Promise[]} */ deleteAll() { From a80a64f8ce1dbfc0d2b964599ef2cd65c708a743 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 17:59:54 -0500 Subject: [PATCH 076/248] Update Collection docs some more --- src/util/Collection.js | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/util/Collection.js b/src/util/Collection.js index 8c5bbdc0b..79ee0f3a2 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -56,7 +56,7 @@ class Collection extends Map { } /** - * Returns the first item in this collection. + * Obtains the first item in this collection. * @returns {*} */ first() { @@ -64,7 +64,7 @@ class Collection extends Map { } /** - * Returns the first key in this collection. + * Obtains the first key in this collection. * @returns {*} */ firstKey() { @@ -72,8 +72,8 @@ class Collection extends Map { } /** - * Returns the last item in this collection. This is a relatively slow operation, - * since an array copy of the values must be made to find the last element. + * Obtains the last item in this collection. This relies on the `array()` method, and thus the caching mechanism + * applies here as well. * @returns {*} */ last() { @@ -82,8 +82,8 @@ class Collection extends Map { } /** - * Returns the last key in this collection. This is a relatively slow operation, - * since an array copy of the keys must be made to find the last element. + * Obtains the last key in this collection. This relies on the `keyArray()` method, and thus the caching mechanism + * applies here as well. * @returns {*} */ lastKey() { @@ -92,8 +92,8 @@ class Collection extends Map { } /** - * Returns a random item from this collection. This is a relatively slow operation, - * since an array copy of the values must be made to find a random element. + * Obtains a random item from this collection. This relies on the `array()` method, and thus the caching mechanism + * applies here as well. * @returns {*} */ random() { @@ -102,8 +102,8 @@ class Collection extends Map { } /** - * Returns a random key from this collection. This is a relatively slow operation, - * since an array copy of the keys must be made to find a random element. + * Obtains a random key from this collection. This relies on the `keyArray()` method, and thus the caching mechanism + * applies here as well. * @returns {*} */ randomKey() { @@ -112,7 +112,8 @@ class Collection extends Map { } /** - * Returns an array of items where `item[prop] === value` of the collection + * Searches for all items where their specified property's value is identical to the given value + * (`item[prop] === value`). * @param {string} prop The property to test against * @param {*} value The expected value * @returns {Array} @@ -130,8 +131,8 @@ class Collection extends Map { } /** - * Searches for a single item where `item[prop] === value`, or the given function returns `true`. - * In the latter case, this is identical to + * Searches for a single item where its specified property's value is identical to the given value + * (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is identical to * [Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). * Do not use this to obtain an item by its ID. Instead, use `collection.get(id)`. See * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) for details. @@ -163,8 +164,8 @@ class Collection extends Map { /* eslint-disable max-len */ /** - * Searches for the key of an item where `item[prop] === value`, or the given function returns `true`. - * In the latter case, this is identical to + * Searches for the key of a single item where its specified property's value is identical to the given value + * (`item[prop] === value`), or the given function returns a truthy value. In the latter case, this is identical to * [Array.findIndex()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex). * @param {string|Function} propOrFn The property to test against, or the function to test with * @param {*} [value] The expected value - only applicable and required if using a property for the first argument @@ -193,7 +194,8 @@ class Collection extends Map { } /** - * Searches for an item where `item[prop] === value`, and returns `true` if one is found. + * Searches for the existence of a single item where its specified property's value is identical to the given value + * (`item[prop] === value`). * Do not use this to check for an item by its ID. Instead, use `collection.has(id)`. See * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) for details. * @param {string} prop The property to test against @@ -231,7 +233,7 @@ class Collection extends Map { * [Array.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter). * @param {Function} fn Function used to test (should return a boolean) * @param {Object} [thisArg] Value to use as `this` when executing function - * @returns {Collection} + * @returns {Array} */ filterArray(fn, thisArg) { if (thisArg) fn = fn.bind(thisArg); From 2bb5aa1fda2df0c5a92db4d8a4813a5d12404a9d Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 18:05:01 -0500 Subject: [PATCH 077/248] Change Collection description --- src/util/Collection.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/Collection.js b/src/util/Collection.js index 79ee0f3a2..4de19f57d 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -1,5 +1,6 @@ /** - * A utility class to help make it easier to access the data stores + * A Map with additional utility methods. This is used throughout discord.js rather than Arrays for anything that has + * an ID, for significantly improved performance and ease-of-use. * @extends {Map} */ class Collection extends Map { From 686cf297d282b7ba96e1ae3f09cf39e54d305319 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 18:19:58 -0500 Subject: [PATCH 078/248] Clean up various script things --- docs/deploy/deploy.sh | 2 +- docs/generator/{generator.js => index.js} | 10 +++++++--- package.json | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) rename docs/generator/{generator.js => index.js} (86%) diff --git a/docs/deploy/deploy.sh b/docs/deploy/deploy.sh index fa0cdfa70..4b71f8f5c 100644 --- a/docs/deploy/deploy.sh +++ b/docs/deploy/deploy.sh @@ -4,7 +4,7 @@ set -e function build { - node docs/generator/generator.js + node docs/generator } # Ignore Travis checking PRs diff --git a/docs/generator/generator.js b/docs/generator/index.js similarity index 86% rename from docs/generator/generator.js rename to docs/generator/index.js index bd6786e89..ce8e06b6d 100644 --- a/docs/generator/generator.js +++ b/docs/generator/index.js @@ -1,6 +1,6 @@ +#!/usr/bin/env node /* eslint-disable no-console */ const fs = require('fs'); -const zlib = require('zlib'); const jsdoc2md = require('jsdoc-to-markdown'); const Documentation = require('./documentation'); const custom = require('../custom/index'); @@ -14,16 +14,20 @@ console.log('Parsing JSDocs in source files...'); jsdoc2md.getTemplateData({ files: [`./src/*.js`, `./src/**/*.js`] }).then(data => { console.log(`${data.length} items found.`); const documentation = new Documentation(data, custom); + console.log('Serializing...'); let output = JSON.stringify(documentation.serialize(), null, 0); + if (config.compress) { console.log('Compressing...'); - output = zlib.deflateSync(output).toString('utf8'); + output = require('zlib').deflateSync(output).toString('utf8'); } - if (!process.argv.slice(2).includes('silent')) { + + if (!process.argv.slice(2).includes('test')) { console.log('Writing to docs.json...'); fs.writeFileSync('./docs/docs.json', output); } + console.log('Done!'); process.exit(0); }).catch(console.error); diff --git a/package.json b/package.json index c5b4c712c..7a4c0e72f 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,10 @@ "description": "A powerful library for interacting with the Discord API", "main": "./src/index", "scripts": { - "test": "eslint src/ && node docs/generator/generator.js silent", - "docs": "node docs/generator/generator.js" + "test": "eslint src && node docs/generator test", + "docs": "node docs/generator", + "test-docs": "node docs/generator test", + "lint": "eslint src" }, "repository": { "type": "git", From b3e795d0b0722d5664c28457c68d00a8619abaf5 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 18:21:23 -0500 Subject: [PATCH 079/248] Add newlines around logo --- README.md | 2 ++ docs/custom/documents/welcome.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 12509a471..ce4258a48 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@

+

discord.js

+

Discord server NPM version diff --git a/docs/custom/documents/welcome.md b/docs/custom/documents/welcome.md index dde170889..6e3f1090c 100644 --- a/docs/custom/documents/welcome.md +++ b/docs/custom/documents/welcome.md @@ -1,7 +1,9 @@

+

discord.js

+

Discord server NPM version From 2440a4a2c838a589328af87aaa85fb76f0dbc1e3 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 20 Nov 2016 18:38:16 -0600 Subject: [PATCH 080/248] Add webpack building (#907) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * friggin webpack tho * probably important * add all the stuff to the package.json * add minify builds and a nice package.json script to run it all * clean up * use uglify harmony branch so we can actually run minify builds that work * update build system * make test better * clean up * fix issues with compression * ‮ * c++ requirements in a node lib? whaaaaat? * fix travis yml? * put railings on voice connections * 🖕🏻 * aaaaaa * handle arraybuffers in the unlikely event one is sent * support arraybuffers in resolvebuffer * this needs to be fixed at some point * this was fixed * disable filename versioning if env VERSIONED is set to false * Update ClientDataResolver.js * Update ClientVoiceManager.js * Update WebSocketManager.js * Update ConvertArrayBuffer.js * Update webpack.html * enable compression for browser and fix ws error handler * Update WebSocketManager.js * everything will be okay gawdl3y * compression is slower in browser, so rip the last three hours of my life * Update Constants.js * Update .gitignore --- .gitignore | 2 + .travis.yml | 8 +++- package.json | 16 +++++-- src/client/Client.js | 6 +++ src/client/ClientDataResolver.js | 31 +++++++------ src/client/rest/RESTMethods.js | 15 +++---- src/client/voice/ClientVoiceManager.js | 1 + src/client/websocket/WebSocketManager.js | 20 ++++++--- .../websocket/packets/handlers/Ready.js | 3 +- src/index.js | 2 + src/util/Constants.js | 5 ++- src/util/ConvertArrayBuffer.js | 18 ++++++++ test/webpack.html | 30 +++++++++++++ webpack.config.js | 43 +++++++++++++++++++ 14 files changed, 162 insertions(+), 38 deletions(-) create mode 100644 src/util/ConvertArrayBuffer.js create mode 100644 test/webpack.html create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore index dee8a5889..80a6efd9c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ logs/ # Authentication test/auth.json +test/auth.js docs/deploy/deploy_key docs/deploy/deploy_key.pub @@ -15,3 +16,4 @@ docs/deploy/deploy_key.pub .tmp/ .vscode/ docs/docs.json +webpack/ diff --git a/.travis.yml b/.travis.yml index 310a7c4b1..ef31870bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,10 @@ env: global: - ENCRYPTION_LABEL: "af862fa96d3e" - COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com" - + - CXX=g++-4.9 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.9 diff --git a/package.json b/package.json index 7a4c0e72f..4720038fe 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "test": "eslint src && node docs/generator test", "docs": "node docs/generator", "test-docs": "node docs/generator test", - "lint": "eslint src" + "lint": "eslint src", + "web-dist": "npm install && ./node_modules/parallel-webpack/bin/run.js" }, "repository": { "type": "git", @@ -29,16 +30,23 @@ "homepage": "https://github.com/hydrabolt/discord.js#readme", "dependencies": { "superagent": "^2.3.0", - "tweetnacl": "^0.14.0", - "ws": "^1.1.0" + "tweetnacl": "^0.14.3", + "ws": "^1.1.1" }, "peerDependencies": { "node-opus": "^0.2.0", "opusscript": "^0.0.1" }, "devDependencies": { + "bufferutil": "^1.2.1", "eslint": "^3.10.0", - "jsdoc-to-markdown": "^2.0.0" + "jsdoc-to-markdown": "^2.0.0", + "json-loader": "^0.5.4", + "parallel-webpack": "^1.5.0", + "uglify-js": "github:mishoo/UglifyJS2#harmony", + "utf-8-validate": "^1.2.1", + "webpack": "^1.13.3", + "zlibjs": "github:imaya/zlib.js" }, "engines": { "node": ">=6.0.0" diff --git a/src/client/Client.js b/src/client/Client.js index 293af9d24..fd5c029e0 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -23,6 +23,12 @@ class Client extends EventEmitter { constructor(options = {}) { super(); + /** + * Whether the client is in a browser environment + * @type {boolean} + */ + this.browser = typeof window !== 'undefined'; + // Obtain shard details from environment if (!options.shardId && 'SHARD_ID' in process.env) options.shardId = Number(process.env.SHARD_ID); if (!options.shardCount && 'SHARD_COUNT' in process.env) options.shardCount = Number(process.env.SHARD_COUNT); diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index dfdc7ae2e..2a668d9f3 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -3,13 +3,14 @@ const fs = require('fs'); const request = require('superagent'); const Constants = require('../util/Constants'); -const User = require(`../structures/User`); -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`); +const convertArrayBuffer = require('../util/ConvertArrayBuffer'); +const User = require('../structures/User'); +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. @@ -240,17 +241,19 @@ class ClientDataResolver { */ resolveBuffer(resource) { if (resource instanceof Buffer) return Promise.resolve(resource); + if (this.client.browser && resource instanceof ArrayBuffer) return Promise.resolve(convertArrayBuffer(resource)); if (typeof resource === 'string') { return new Promise((resolve, reject) => { if (/^https?:\/\//.test(resource)) { - request.get(resource) - .set('Content-Type', 'blob') - .end((err, res) => { - if (err) return reject(err); - if (!(res.body instanceof Buffer)) return reject(new TypeError('Body is not a Buffer')); - return resolve(res.body); - }); + const req = request.get(resource).set('Content-Type', 'blob'); + if (this.client.browser) req.responseType('arraybuffer'); + req.end((err, res) => { + if (err) return reject(err); + if (this.client.browser) return resolve(convertArrayBuffer(res.xhr.response)); + if (!(res.body instanceof Buffer)) return reject(new TypeError('Body is not a Buffer')); + return resolve(res.body); + }); } else { const file = path.resolve(resource); fs.stat(file, (err, stats) => { diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index be35d7d20..cfa35eea4 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -3,14 +3,13 @@ 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'); -const GuildMember = requireStructure('GuildMember'); -const Role = requireStructure('Role'); -const Invite = requireStructure('Invite'); -const Webhook = requireStructure('Webhook'); -const UserProfile = requireStructure('UserProfile'); -const ClientOAuth2Application = requireStructure('ClientOAuth2Application'); +const User = require('../../structures/User'); +const GuildMember = require('../../structures/GuildMember'); +const Role = require('../../structures/Role'); +const Invite = require('../../structures/Invite'); +const Webhook = require('../../structures/Webhook'); +const UserProfile = require('../../structures/UserProfile'); +const ClientOAuth2Application = require('../../structures/ClientOAuth2Application'); class RESTMethods { constructor(restManager) { diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js index 09e9982a1..d65384505 100644 --- a/src/client/voice/ClientVoiceManager.js +++ b/src/client/voice/ClientVoiceManager.js @@ -78,6 +78,7 @@ class ClientVoiceManager { */ joinChannel(channel) { return new Promise((resolve, reject) => { + if (this.client.browser) throw new Error('Voice connections are not available in browsers.'); if (this.pending.get(channel.guild.id)) throw new Error('Already connecting to this guild\'s voice server.'); if (!channel.joinable) throw new Error('You do not have permission to join this voice channel.'); diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 40d714f4d..002411161 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -1,8 +1,10 @@ -const WebSocket = require('ws'); +const browser = typeof window !== 'undefined'; +const WebSocket = browser ? window.WebSocket : require('ws'); // eslint-disable-line no-undef const EventEmitter = require('events').EventEmitter; const Constants = require('../../util/Constants'); -const zlib = require('zlib'); +const inflate = browser ? require('zlibjs').inflateSync : require('zlib').inflateSync; const PacketManager = require('./packets/WebSocketPacketManager'); +const convertArrayBuffer = require('../../util/ConvertArrayBuffer'); /** * The WebSocket Manager of the Client @@ -78,6 +80,7 @@ class WebSocketManager extends EventEmitter { this.normalReady = false; if (this.status !== Constants.Status.RECONNECTING) this.status = Constants.Status.CONNECTING; this.ws = new WebSocket(gateway); + if (browser) this.ws.binaryType = 'arraybuffer'; this.ws.onopen = () => this.eventOpen(); this.ws.onclose = (d) => this.eventClose(d); this.ws.onmessage = (e) => this.eventMessage(e); @@ -193,11 +196,11 @@ class WebSocketManager extends EventEmitter { */ eventClose(event) { this.emit('close', event); + this.client.clearInterval(this.client.manager.heartbeatInterval); /** * Emitted whenever the client websocket is disconnected * @event Client#disconnect */ - clearInterval(this.client.manager.heartbeatInterval); if (!this.reconnecting) this.client.emit(Constants.Events.DISCONNECT); if (event.code === 4004) return; if (event.code === 4010) return; @@ -211,10 +214,13 @@ class WebSocketManager extends EventEmitter { * @returns {boolean} */ eventMessage(event) { - let packet; + let packet = event.data; try { - if (event.binary) event.data = zlib.inflateSync(event.data).toString(); - packet = JSON.parse(event.data); + if (typeof packet !== 'string') { + if (packet instanceof ArrayBuffer) packet = convertArrayBuffer(packet); + packet = inflate(packet).toString(); + } + packet = JSON.parse(packet); } catch (e) { return this.eventError(new Error(Constants.Errors.BAD_WS_MESSAGE)); } @@ -236,7 +242,7 @@ class WebSocketManager extends EventEmitter { * @param {Error} error The encountered error */ if (this.client.listenerCount('error') > 0) this.client.emit('error', err); - this.tryReconnect(); + this.ws.close(); } _emitReady(normal = true) { diff --git a/src/client/websocket/packets/handlers/Ready.js b/src/client/websocket/packets/handlers/Ready.js index 96d8557d7..aed766bf0 100644 --- a/src/client/websocket/packets/handlers/Ready.js +++ b/src/client/websocket/packets/handlers/Ready.js @@ -1,7 +1,6 @@ const AbstractHandler = require('./AbstractHandler'); -const getStructure = name => require(`../../../../structures/${name}`); -const ClientUser = getStructure('ClientUser'); +const ClientUser = require('../../../../structures/ClientUser'); class ReadyHandler extends AbstractHandler { handle(packet) { diff --git a/src/index.js b/src/index.js index 3eea67ea1..c1d262a7b 100644 --- a/src/index.js +++ b/src/index.js @@ -41,3 +41,5 @@ module.exports = { version: require('../package').version, }; + +if (typeof window !== 'undefined') window.Discord = module.exports; // eslint-disable-line no-undef diff --git a/src/util/Constants.js b/src/util/Constants.js index 7efe40690..ac2f3be2c 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -42,11 +42,12 @@ exports.DefaultOptions = { * Websocket options. These are left as snake_case to match the API. * @typedef {Object} WebsocketOptions * @property {number} [large_threshold=250] Number of members in a guild to be considered large - * @property {boolean} [compress=true] Whether to compress data sent on the connection + * @property {boolean} [compress=true] Whether to compress data sent on the connection. + * Defaults to `false` for browsers. */ ws: { large_threshold: 250, - compress: true, + compress: typeof window === 'undefined', properties: { $os: process ? process.platform : 'discord.js', $browser: 'discord.js', diff --git a/src/util/ConvertArrayBuffer.js b/src/util/ConvertArrayBuffer.js new file mode 100644 index 000000000..26b1cc8b7 --- /dev/null +++ b/src/util/ConvertArrayBuffer.js @@ -0,0 +1,18 @@ +function arrayBufferToBuffer(ab) { + const buffer = new Buffer(ab.byteLength); + const view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) buffer[i] = view[i]; + return buffer; +} + +function str2ab(str) { + const buffer = new ArrayBuffer(str.length * 2); + const view = new Uint16Array(buffer); + for (var i = 0, strLen = str.length; i < strLen; i++) view[i] = str.charCodeAt(i); + return buffer; +} + +module.exports = function convertArrayBuffer(x) { + if (typeof x === 'string') x = str2ab(x); + return arrayBufferToBuffer(x); +}; diff --git a/test/webpack.html b/test/webpack.html new file mode 100644 index 000000000..006ea774a --- /dev/null +++ b/test/webpack.html @@ -0,0 +1,30 @@ + + + + discord.js Webpack test + + + + + + + + diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 000000000..906df9147 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,43 @@ +/* + ONLY RUN BUILDS WITH `npm run web-dist`! + DO NOT USE NORMAL WEBPACK! IT WILL NOT WORK! +*/ + +const webpack = require('webpack'); +const createVariants = require('parallel-webpack').createVariants; +const version = require('./package.json').version; + +const createConfig = (options) => { + const plugins = [ + new webpack.DefinePlugin({ 'global.GENTLY': false }), + ]; + + if (options.minify) plugins.push(new webpack.optimize.UglifyJsPlugin({ minimize: true })); + + const filename = `./webpack/discord${process.env.VERSIONED === 'false' ? '' : '.' + version}${options.minify ? '.min' : ''}.js`; // eslint-disable-line + + return { + entry: './src/index.js', + output: { + path: __dirname, + filename, + }, + module: { + loaders: [ + { test: /\.json$/, loader: 'json-loader' }, + { test: /\.md$/, loader: 'ignore-loader' }, + ], + }, + node: { + fs: 'empty', + dns: 'mock', + tls: 'mock', + child_process: 'empty', + dgram: 'empty', + __dirname: true, + }, + plugins, + }; +}; + +module.exports = createVariants({}, { minify: [false, true] }, createConfig); From 176859e7da74cccae4416ddda72f0e2b165c7ed8 Mon Sep 17 00:00:00 2001 From: meew0 Date: Mon, 21 Nov 2016 01:57:24 +0100 Subject: [PATCH 081/248] Add webpack building, ESLint on PRs, and tag building to Travis (#911) * Make Travis run ESLint before deploying * Fix Travis never building docs for tags * Update deploy.sh to also build the webpack * Update deploy.sh --- .travis.yml | 4 ++- docs/deploy/deploy.sh | 62 +++++++++++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index ef31870bc..c31f0f431 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,9 @@ cache: directories: - node_modules install: npm install -script: bash ./docs/deploy/deploy.sh +script: + - npm run test + - bash ./docs/deploy/deploy.sh env: global: - ENCRYPTION_LABEL: "af862fa96d3e" diff --git a/docs/deploy/deploy.sh b/docs/deploy/deploy.sh index 4b71f8f5c..ad15f839e 100644 --- a/docs/deploy/deploy.sh +++ b/docs/deploy/deploy.sh @@ -4,7 +4,11 @@ set -e function build { + # Build docs node docs/generator + + # Build the webpack + VERSIONED=false npm run web-dist } # Ignore Travis checking PRs @@ -15,7 +19,9 @@ if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then fi # Ignore travis checking other branches irrelevant to users -if [ "$TRAVIS_BRANCH" != "master" -a "$TRAVIS_BRANCH" != "indev" ]; then +# Apparently Travis considers tag builds as separate branches so we need to +# check for that separately +if [ "$TRAVIS_BRANCH" != "master" -a "$TRAVIS_BRANCH" != "indev" -a "$TRAVIS_BRANCH" != "$TRAVIS_TAG" ]; then echo "deploy.sh: Ignoring push to another branch than master/indev" build exit 0 @@ -29,19 +35,27 @@ if [ -n "$TRAVIS_TAG" ]; then SOURCE=$TRAVIS_TAG fi +# Initialise some useful variables REPO=`git config remote.origin.url` SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:} SHA=`git rev-parse --verify HEAD` -TARGET_BRANCH="docs" +# Decrypt and add the ssh key +ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key" +ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv" +ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR} +ENCRYPTED_IV=${!ENCRYPTED_IV_VAR} +openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in docs/deploy/deploy_key.enc -out deploy_key -d +chmod 600 deploy_key +eval `ssh-agent -s` +ssh-add deploy_key + +# Build everything +build # Checkout the repo in the target branch so we can build docs and push to it +TARGET_BRANCH="docs" git clone $REPO out -b $TARGET_BRANCH -cd out -cd .. - -# Build the docs -build # Move the generated JSON file to the newly-checked-out repo, to be committed # and pushed @@ -49,20 +63,28 @@ mv docs/docs.json out/$SOURCE.json # Commit and push cd out +git add . git config user.name "Travis CI" git config user.email "$COMMIT_AUTHOR_EMAIL" - -git add . git commit -m "Docs build: ${SHA}" - -ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key" -ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv" -ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR} -ENCRYPTED_IV=${!ENCRYPTED_IV_VAR} -openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in ../docs/deploy/deploy_key.enc -out deploy_key -d -chmod 600 deploy_key -eval `ssh-agent -s` -ssh-add deploy_key - -# Now that we're all set up, we can push. +git push $SSH_REPO $TARGET_BRANCH + +# Clean up... +cd .. +rm -rf out + +# ...then do the same once more for the webpack +TARGET_BRANCH="webpack" +git clone $REPO out -b $TARGET_BRANCH + +# Move the generated webpack over +mv webpack/discord.js out/discord.$SOURCE.js +mv webpack/discord.min.js out/discord.$SOURCE.min.js + +# Commit and push +cd out +git add . +git config user.name "Travis CI" +git config user.email "$COMMIT_AUTHOR_EMAIL" +git commit -m "Webpack build: ${SHA}" git push $SSH_REPO $TARGET_BRANCH From 6383d42eb55c45ff673ea036daedf08cf264beb1 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 20:08:59 -0500 Subject: [PATCH 082/248] Update to superagent 3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4720038fe..a4bbff247 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/hydrabolt/discord.js#readme", "dependencies": { - "superagent": "^2.3.0", + "superagent": "^3.0.0", "tweetnacl": "^0.14.3", "ws": "^1.1.1" }, From 411c9bd32c94c3164a08cfc196463cb969c9d47b Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 20:19:43 -0500 Subject: [PATCH 083/248] Update to Webpack 2.1 beta 27 --- package.json | 4 ++-- webpack.config.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a4bbff247..4fc847026 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "docs": "node docs/generator", "test-docs": "node docs/generator test", "lint": "eslint src", - "web-dist": "npm install && ./node_modules/parallel-webpack/bin/run.js" + "web-dist": "npm install && node ./node_modules/parallel-webpack/bin/run.js" }, "repository": { "type": "git", @@ -45,7 +45,7 @@ "parallel-webpack": "^1.5.0", "uglify-js": "github:mishoo/UglifyJS2#harmony", "utf-8-validate": "^1.2.1", - "webpack": "^1.13.3", + "webpack": "2.1.0-beta.27", "zlibjs": "github:imaya/zlib.js" }, "engines": { diff --git a/webpack.config.js b/webpack.config.js index 906df9147..822cbdd6e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -23,7 +23,7 @@ const createConfig = (options) => { filename, }, module: { - loaders: [ + rules: [ { test: /\.json$/, loader: 'json-loader' }, { test: /\.md$/, loader: 'ignore-loader' }, ], From 3ef16f97c44f6444168aa1fb1218cd0f300ac53a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 20:52:39 -0500 Subject: [PATCH 084/248] Remove dynamic requires --- src/client/actions/ActionsManager.js | 57 ++++++++------- .../packets/WebSocketPacketManager.js | 69 +++++++++---------- 2 files changed, 62 insertions(+), 64 deletions(-) diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js index 1986f5a77..ac95aa7fb 100644 --- a/src/client/actions/ActionsManager.js +++ b/src/client/actions/ActionsManager.js @@ -2,37 +2,36 @@ class ActionsManager { constructor(client) { this.client = client; - this.register('MessageCreate'); - this.register('MessageDelete'); - this.register('MessageDeleteBulk'); - this.register('MessageUpdate'); - this.register('MessageReactionAdd'); - this.register('MessageReactionRemove'); - this.register('MessageReactionRemoveAll'); - this.register('ChannelCreate'); - this.register('ChannelDelete'); - this.register('ChannelUpdate'); - this.register('GuildDelete'); - this.register('GuildUpdate'); - this.register('GuildMemberGet'); - this.register('GuildMemberRemove'); - this.register('GuildBanRemove'); - this.register('GuildRoleCreate'); - this.register('GuildRoleDelete'); - this.register('GuildRoleUpdate'); - this.register('UserGet'); - this.register('UserUpdate'); - this.register('UserNoteUpdate'); - this.register('GuildSync'); - this.register('GuildEmojiCreate'); - this.register('GuildEmojiDelete'); - this.register('GuildEmojiUpdate'); - this.register('GuildRolesPositionUpdate'); + this.register(require('./MessageCreate')); + this.register(require('./MessageDelete')); + this.register(require('./MessageDeleteBulk')); + this.register(require('./MessageUpdate')); + this.register(require('./MessageReactionAdd')); + this.register(require('./MessageReactionRemove')); + this.register(require('./MessageReactionRemoveAll')); + this.register(require('./ChannelCreate')); + this.register(require('./ChannelDelete')); + this.register(require('./ChannelUpdate')); + this.register(require('./GuildDelete')); + this.register(require('./GuildUpdate')); + this.register(require('./GuildMemberGet')); + this.register(require('./GuildMemberRemove')); + this.register(require('./GuildBanRemove')); + this.register(require('./GuildRoleCreate')); + this.register(require('./GuildRoleDelete')); + this.register(require('./GuildRoleUpdate')); + this.register(require('./UserGet')); + this.register(require('./UserUpdate')); + this.register(require('./UserNoteUpdate')); + this.register(require('./GuildSync')); + this.register(require('./GuildEmojiCreate')); + this.register(require('./GuildEmojiDelete')); + this.register(require('./GuildEmojiUpdate')); + this.register(require('./GuildRolesPositionUpdate')); } - register(name) { - const Action = require(`./${name}`); - this[name] = new Action(this.client); + register(Action) { + this[Action.name.replace(/Action$/, '')] = new Action(this.client); } } diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index e2bdcf3f8..9b3f04d31 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -15,47 +15,46 @@ class WebSocketPacketManager { this.handlers = {}; this.queue = []; - this.register(Constants.WSEvents.READY, 'Ready'); - this.register(Constants.WSEvents.GUILD_CREATE, 'GuildCreate'); - this.register(Constants.WSEvents.GUILD_DELETE, 'GuildDelete'); - this.register(Constants.WSEvents.GUILD_UPDATE, 'GuildUpdate'); - this.register(Constants.WSEvents.GUILD_BAN_ADD, 'GuildBanAdd'); - this.register(Constants.WSEvents.GUILD_BAN_REMOVE, 'GuildBanRemove'); - this.register(Constants.WSEvents.GUILD_MEMBER_ADD, 'GuildMemberAdd'); - this.register(Constants.WSEvents.GUILD_MEMBER_REMOVE, 'GuildMemberRemove'); - this.register(Constants.WSEvents.GUILD_MEMBER_UPDATE, 'GuildMemberUpdate'); - this.register(Constants.WSEvents.GUILD_ROLE_CREATE, 'GuildRoleCreate'); - this.register(Constants.WSEvents.GUILD_ROLE_DELETE, 'GuildRoleDelete'); - this.register(Constants.WSEvents.GUILD_ROLE_UPDATE, 'GuildRoleUpdate'); - this.register(Constants.WSEvents.GUILD_MEMBERS_CHUNK, 'GuildMembersChunk'); - this.register(Constants.WSEvents.CHANNEL_CREATE, 'ChannelCreate'); - this.register(Constants.WSEvents.CHANNEL_DELETE, 'ChannelDelete'); - this.register(Constants.WSEvents.CHANNEL_UPDATE, 'ChannelUpdate'); - this.register(Constants.WSEvents.PRESENCE_UPDATE, 'PresenceUpdate'); - this.register(Constants.WSEvents.USER_UPDATE, 'UserUpdate'); - this.register(Constants.WSEvents.USER_NOTE_UPDATE, 'UserNoteUpdate'); - this.register(Constants.WSEvents.VOICE_STATE_UPDATE, 'VoiceStateUpdate'); - this.register(Constants.WSEvents.TYPING_START, 'TypingStart'); - this.register(Constants.WSEvents.MESSAGE_CREATE, 'MessageCreate'); - this.register(Constants.WSEvents.MESSAGE_DELETE, 'MessageDelete'); - this.register(Constants.WSEvents.MESSAGE_UPDATE, 'MessageUpdate'); - this.register(Constants.WSEvents.VOICE_SERVER_UPDATE, 'VoiceServerUpdate'); - this.register(Constants.WSEvents.MESSAGE_DELETE_BULK, 'MessageDeleteBulk'); - this.register(Constants.WSEvents.CHANNEL_PINS_UPDATE, 'ChannelPinsUpdate'); - 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'); - this.register(Constants.WSEvents.MESSAGE_REACTION_REMOVE_ALL, 'MessageReactionRemoveAll'); + this.register(Constants.WSEvents.READY, require('./handlers/Ready')); + this.register(Constants.WSEvents.GUILD_CREATE, require('./handlers/GuildCreate')); + this.register(Constants.WSEvents.GUILD_DELETE, require('./handlers/GuildDelete')); + this.register(Constants.WSEvents.GUILD_UPDATE, require('./handlers/GuildUpdate')); + this.register(Constants.WSEvents.GUILD_BAN_ADD, require('./handlers/GuildBanAdd')); + this.register(Constants.WSEvents.GUILD_BAN_REMOVE, require('./handlers/GuildBanRemove')); + this.register(Constants.WSEvents.GUILD_MEMBER_ADD, require('./handlers/GuildMemberAdd')); + this.register(Constants.WSEvents.GUILD_MEMBER_REMOVE, require('./handlers/GuildMemberRemove')); + this.register(Constants.WSEvents.GUILD_MEMBER_UPDATE, require('./handlers/GuildMemberUpdate')); + this.register(Constants.WSEvents.GUILD_ROLE_CREATE, require('./handlers/GuildRoleCreate')); + this.register(Constants.WSEvents.GUILD_ROLE_DELETE, require('./handlers/GuildRoleDelete')); + this.register(Constants.WSEvents.GUILD_ROLE_UPDATE, require('./handlers/GuildRoleUpdate')); + this.register(Constants.WSEvents.GUILD_MEMBERS_CHUNK, require('./handlers/GuildMembersChunk')); + this.register(Constants.WSEvents.CHANNEL_CREATE, require('./handlers/ChannelCreate')); + this.register(Constants.WSEvents.CHANNEL_DELETE, require('./handlers/ChannelDelete')); + this.register(Constants.WSEvents.CHANNEL_UPDATE, require('./handlers/ChannelUpdate')); + this.register(Constants.WSEvents.CHANNEL_PINS_UPDATE, require('./handlers/ChannelPinsUpdate')); + this.register(Constants.WSEvents.PRESENCE_UPDATE, require('./handlers/PresenceUpdate')); + this.register(Constants.WSEvents.USER_UPDATE, require('./handlers/UserUpdate')); + this.register(Constants.WSEvents.USER_NOTE_UPDATE, require('./handlers/UserNoteUpdate')); + this.register(Constants.WSEvents.VOICE_STATE_UPDATE, require('./handlers/VoiceStateUpdate')); + this.register(Constants.WSEvents.TYPING_START, require('./handlers/TypingStart')); + this.register(Constants.WSEvents.MESSAGE_CREATE, require('./handlers/MessageCreate')); + this.register(Constants.WSEvents.MESSAGE_DELETE, require('./handlers/MessageDelete')); + this.register(Constants.WSEvents.MESSAGE_UPDATE, require('./handlers/MessageUpdate')); + this.register(Constants.WSEvents.MESSAGE_DELETE_BULK, require('./handlers/MessageDeleteBulk')); + this.register(Constants.WSEvents.VOICE_SERVER_UPDATE, require('./handlers/VoiceServerUpdate')); + this.register(Constants.WSEvents.GUILD_SYNC, require('./handlers/GuildSync')); + this.register(Constants.WSEvents.RELATIONSHIP_ADD, require('./handlers/RelationshipAdd')); + this.register(Constants.WSEvents.RELATIONSHIP_REMOVE, require('./handlers/RelationshipRemove')); + this.register(Constants.WSEvents.MESSAGE_REACTION_ADD, require('./handlers/MessageReactionAdd')); + this.register(Constants.WSEvents.MESSAGE_REACTION_REMOVE, require('./handlers/MessageReactionRemove')); + this.register(Constants.WSEvents.MESSAGE_REACTION_REMOVE_ALL, require('./handlers/MessageReactionRemoveAll')); } get client() { return this.ws.client; } - register(event, handle) { - const Handler = require(`./handlers/${handle}`); + register(event, Handler) { this.handlers[event] = new Handler(this); } From 049ab42eb44085b13c31973ce62d96234fcb4b20 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 20:54:40 -0500 Subject: [PATCH 085/248] Do stuff slightly better --- package.json | 2 +- test/webpack.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4fc847026..d3f0271d7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "docs": "node docs/generator", "test-docs": "node docs/generator test", "lint": "eslint src", - "web-dist": "npm install && node ./node_modules/parallel-webpack/bin/run.js" + "web-dist": "node ./node_modules/parallel-webpack/bin/run.js" }, "repository": { "type": "git", diff --git a/test/webpack.html b/test/webpack.html index 006ea774a..967712e33 100644 --- a/test/webpack.html +++ b/test/webpack.html @@ -23,7 +23,7 @@ console.log(message.author.username, message.author.id, message.content); }); - client.login(window.token || prompt('token', 'abcdef123456')); + client.login(window.token || prompt('token pls', 'abcdef123456')); })(); From 18dc95e1bdd6dc02c6520d2ee1b4432d4ce4d2d6 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 21:17:07 -0500 Subject: [PATCH 086/248] Ensure opusscript and node-opus don't get built --- webpack.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webpack.config.js b/webpack.config.js index 822cbdd6e..a3d2d0121 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -28,6 +28,10 @@ const createConfig = (options) => { { test: /\.md$/, loader: 'ignore-loader' }, ], }, + externals: { + opusscript: { commonjs: 'opusscript' }, + 'node-opus': { commonjs: 'node-opus' }, + }, node: { fs: 'empty', dns: 'mock', From 697fa152789feb0f68e0ed18cb0200c0693e8edc Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 21:42:39 -0500 Subject: [PATCH 087/248] Add web dist info to readme --- README.md | 5 +++++ docs/custom/documents/welcome.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index ce4258a48..615a0439f 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,11 @@ client.login('your token'); A bot template using discord.js can be generated using [generator-discordbot](https://www.npmjs.com/package/generator-discordbot). +## Web distributions +Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack). +These are built by [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`, +the entire `Discord` object is available as a global (on the `window` object). Any voice-related functionality is unavailable in these builds. + ## Links * [Website](http://discord.js.org/) * [Discord.js server](https://discord.gg/bRCvFy9) diff --git a/docs/custom/documents/welcome.md b/docs/custom/documents/welcome.md index 6e3f1090c..ad16fd5de 100644 --- a/docs/custom/documents/welcome.md +++ b/docs/custom/documents/welcome.md @@ -36,6 +36,11 @@ The preferred audio engine is node-opus, as it performs significantly better tha Using opusscript is only recommended for development environments where node-opus is tough to get working. For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers. +## Web distributions +Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack). +These are built by [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`, +the entire `Discord` object is available as a global (on the `window` object). Any voice-related functionality is unavailable in these builds. + ## Guides * [LuckyEvie's general guide](https://eslachance.gitbooks.io/discord-js-bot-guide/content/) * [York's v9 upgrade guide](https://yorkaargh.wordpress.com/2016/09/03/updating-discord-js-bots/) From 66845e2f681412619f4badc4a8f51df78207d60d Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 21:46:35 -0500 Subject: [PATCH 088/248] Ensure ws doesn't get built --- webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack.config.js b/webpack.config.js index a3d2d0121..d7bf01789 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -31,6 +31,7 @@ const createConfig = (options) => { externals: { opusscript: { commonjs: 'opusscript' }, 'node-opus': { commonjs: 'node-opus' }, + ws: { commonjs: 'ws' }, }, node: { fs: 'empty', From f38944c8f67da531f8164a6edd092101d8093307 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 21:50:36 -0500 Subject: [PATCH 089/248] Add ShardingManager web dist info --- README.md | 3 ++- docs/custom/documents/welcome.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 615a0439f..e8392968c 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ A bot template using discord.js can be generated using [generator-discordbot](ht ## Web distributions Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack). These are built by [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`, -the entire `Discord` object is available as a global (on the `window` object). Any voice-related functionality is unavailable in these builds. +the entire `Discord` object is available as a global (on the `window` object). +The ShardingManager and any voice-related functionality is unavailable in these builds. ## Links * [Website](http://discord.js.org/) diff --git a/docs/custom/documents/welcome.md b/docs/custom/documents/welcome.md index ad16fd5de..0d2b4aad3 100644 --- a/docs/custom/documents/welcome.md +++ b/docs/custom/documents/welcome.md @@ -39,7 +39,8 @@ For production bots, using node-opus should be considered a necessity, especiall ## Web distributions Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack). These are built by [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`, -the entire `Discord` object is available as a global (on the `window` object). Any voice-related functionality is unavailable in these builds. +the entire `Discord` object is available as a global (on the `window` object). +The ShardingManager and any voice-related functionality is unavailable in these builds. ## Guides * [LuckyEvie's general guide](https://eslachance.gitbooks.io/discord-js-bot-guide/content/) From 7e69475d114898b85c117f693fc649f40c7aac95 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 22:06:38 -0500 Subject: [PATCH 090/248] Just to please Gus --- webpack.config.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index d7bf01789..75e28e7e7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,6 +22,9 @@ const createConfig = (options) => { path: __dirname, filename, }, + resolve: { + descriptionFiles: ['package.json'], + }, module: { rules: [ { test: /\.json$/, loader: 'json-loader' }, @@ -29,8 +32,8 @@ const createConfig = (options) => { ], }, externals: { - opusscript: { commonjs: 'opusscript' }, 'node-opus': { commonjs: 'node-opus' }, + opusscript: { commonjs: 'opusscript' }, ws: { commonjs: 'ws' }, }, node: { From f6a60581c49fad6116e3bcc2a6ff2631281cb2e8 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 22:40:06 -0500 Subject: [PATCH 091/248] Remove even more stuff from web dists --- package.json | 23 +++++++++++++++++++++++ src/client/Client.js | 7 ++++--- src/client/voice/ClientVoiceManager.js | 1 - src/structures/Guild.js | 1 + src/structures/VoiceChannel.js | 3 +++ webpack.config.js | 9 ++++----- 6 files changed, 35 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index d3f0271d7..b463179ae 100644 --- a/package.json +++ b/package.json @@ -50,5 +50,28 @@ }, "engines": { "node": ">=6.0.0" + }, + "browser": { + "src/sharding/Shard.js": false, + "src/sharding/ShardClientUtil.js": false, + "src/sharding/ShardingManager.js": false, + "src/client/voice/dispatcher/StreamDispatcher.js": false, + "src/client/voice/opus/BaseOpusEngine.js": false, + "src/client/voice/opus/NodeOpusEngine.js": false, + "src/client/voice/opus/OpusEngineList.js": false, + "src/client/voice/opus/OpusScriptEngine.js": false, + "src/client/voice/pcm/ConverterEngine.js": false, + "src/client/voice/pcm/ConverterEngineList.js": false, + "src/client/voice/pcm/FfmpegConverterEngine.js": false, + "src/client/voice/player/AudioPlayer.js": false, + "src/client/voice/player/BasePlayer.js": false, + "src/client/voice/player/DefaultPlayer.js": false, + "src/client/voice/receiver/VoiceReadable.js": false, + "src/client/voice/receiver/VoiceReceiver.js": false, + "src/client/voice/util/SecretKey.js": false, + "src/client/voice/ClientVoiceManager.js": false, + "src/client/voice/VoiceConnection.js": false, + "src/client/voice/VoiceUDPClient.js": false, + "src/client/voice/VoiceWebSocket.js": false } } diff --git a/src/client/Client.js b/src/client/Client.js index fd5c029e0..bf784a7ab 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -83,11 +83,11 @@ class Client extends EventEmitter { this.actions = new ActionsManager(this); /** - * The Voice Manager of the Client - * @type {ClientVoiceManager} + * The Voice Manager of the Client (`null` in browsers) + * @type {?ClientVoiceManager} * @private */ - this.voice = new ClientVoiceManager(this); + this.voice = !this.browser ? new ClientVoiceManager(this) : null; /** * The shard helpers for the client (only if the process was spawned as a child, such as from a ShardingManager) @@ -186,6 +186,7 @@ class Client extends EventEmitter { * @readonly */ get voiceConnections() { + if (this.browser) return new Collection(); return this.voice.connections; } diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js index d65384505..09e9982a1 100644 --- a/src/client/voice/ClientVoiceManager.js +++ b/src/client/voice/ClientVoiceManager.js @@ -78,7 +78,6 @@ class ClientVoiceManager { */ joinChannel(channel) { return new Promise((resolve, reject) => { - if (this.client.browser) throw new Error('Voice connections are not available in browsers.'); if (this.pending.get(channel.guild.id)) throw new Error('Already connecting to this guild\'s voice server.'); if (!channel.joinable) throw new Error('You do not have permission to join this voice channel.'); diff --git a/src/structures/Guild.js b/src/structures/Guild.js index f073132f0..bfa8406f8 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -256,6 +256,7 @@ class Guild { * @readonly */ get voiceConnection() { + if (this.client.browser) return null; return this.client.voice.connections.get(this.id) || null; } diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js index 4ba788460..848a6d513 100644 --- a/src/structures/VoiceChannel.js +++ b/src/structures/VoiceChannel.js @@ -50,6 +50,7 @@ class VoiceChannel extends GuildChannel { * @type {boolean} */ get joinable() { + if (this.client.browser) return false; return this.permissionsFor(this.client.user).hasPermission('CONNECT'); } @@ -99,6 +100,7 @@ class VoiceChannel extends GuildChannel { * .catch(console.error); */ join() { + if (this.client.browser) return Promise.reject(new Error('Voice connections are not available in browsers.')); return this.client.voice.joinChannel(this); } @@ -109,6 +111,7 @@ class VoiceChannel extends GuildChannel { * voiceChannel.leave(); */ leave() { + if (this.client.browser) return; const connection = this.client.voice.connections.get(this.guild.id); if (connection && connection.channel.id === this.id) connection.disconnect(); } diff --git a/webpack.config.js b/webpack.config.js index 75e28e7e7..fe9ae4742 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,9 +22,6 @@ const createConfig = (options) => { path: __dirname, filename, }, - resolve: { - descriptionFiles: ['package.json'], - }, module: { rules: [ { test: /\.json$/, loader: 'json-loader' }, @@ -32,9 +29,10 @@ const createConfig = (options) => { ], }, externals: { - 'node-opus': { commonjs: 'node-opus' }, - opusscript: { commonjs: 'opusscript' }, ws: { commonjs: 'ws' }, + opusscript: { commonjs: 'opusscript' }, + 'node-opus': { commonjs: 'node-opus' }, + 'tweet-nacl': { commonjs: 'tweet-nacl' }, }, node: { fs: 'empty', @@ -42,6 +40,7 @@ const createConfig = (options) => { tls: 'mock', child_process: 'empty', dgram: 'empty', + zlib: 'empty', __dirname: true, }, plugins, From ee4a8bb3b6819f8638e7715049d4953784aa3e45 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 22:45:59 -0500 Subject: [PATCH 092/248] Made Client.browser a getter --- src/client/Client.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index bf784a7ab..e39b2c4bd 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -23,12 +23,6 @@ class Client extends EventEmitter { constructor(options = {}) { super(); - /** - * Whether the client is in a browser environment - * @type {boolean} - */ - this.browser = typeof window !== 'undefined'; - // Obtain shard details from environment if (!options.shardId && 'SHARD_ID' in process.env) options.shardId = Number(process.env.SHARD_ID); if (!options.shardCount && 'SHARD_COUNT' in process.env) options.shardCount = Number(process.env.SHARD_COUNT); @@ -212,6 +206,15 @@ class Client extends EventEmitter { return this.readyAt ? this.readyAt.getTime() : null; } + /** + * Whether the client is in a browser environment + * @type {boolean} + * @readonly + */ + get browser() { + return typeof window !== 'undefined'; + } + /** * Logs the client in. If successful, resolves with the account's token. If you're making a bot, it's * much better to use a bot account rather than a user account. From 0d754d1fbb85a4c188b19403322c6506910fce27 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 23:19:53 -0500 Subject: [PATCH 093/248] Change the way modules are excluded from webpack --- package.json | 4 ++++ webpack.config.js | 6 ------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b463179ae..d8a478b7f 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,10 @@ "node": ">=6.0.0" }, "browser": { + "ws": false, + "opusscript": false, + "node-opus": false, + "tweet-nacl": false, "src/sharding/Shard.js": false, "src/sharding/ShardClientUtil.js": false, "src/sharding/ShardingManager.js": false, diff --git a/webpack.config.js b/webpack.config.js index fe9ae4742..23e9f1c0c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -28,12 +28,6 @@ const createConfig = (options) => { { test: /\.md$/, loader: 'ignore-loader' }, ], }, - externals: { - ws: { commonjs: 'ws' }, - opusscript: { commonjs: 'opusscript' }, - 'node-opus': { commonjs: 'node-opus' }, - 'tweet-nacl': { commonjs: 'tweet-nacl' }, - }, node: { fs: 'empty', dns: 'mock', From a4193553e2fc5cf0218403afbfa2ace3725490c7 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 20 Nov 2016 23:39:40 -0500 Subject: [PATCH 094/248] Optimise websocket events --- src/client/websocket/WebSocketManager.js | 49 ++++++++++++++++-------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 002411161..cdaa4b29a 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -129,9 +129,7 @@ class WebSocketManager extends EventEmitter { const item = this._queue[0]; if (this.ws.readyState === WebSocket.OPEN && item) { if (this._remaining === 0) { - this.client.setTimeout(() => { - this.doQueue(); - }, 1000); + this.client.setTimeout(this.doQueue.bind(this), 1000); return; } this._remaining--; @@ -214,21 +212,42 @@ class WebSocketManager extends EventEmitter { * @returns {boolean} */ eventMessage(event) { - let packet = event.data; - try { - if (typeof packet !== 'string') { - if (packet instanceof ArrayBuffer) packet = convertArrayBuffer(packet); - packet = inflate(packet).toString(); - } - packet = JSON.parse(packet); - } catch (e) { - return this.eventError(new Error(Constants.Errors.BAD_WS_MESSAGE)); + const data = this.tryParseEventData(event.data); + if (data === null) { + this.eventError(new Error(Constants.Errors.BAD_WS_MESSAGE)); + return false; } - this.client.emit('raw', packet); + this.client.emit('raw', data); - if (packet.op === Constants.OPCodes.HELLO) this.client.manager.setupKeepAlive(packet.d.heartbeat_interval); - return this.packetManager.handle(packet); + if (data.op === Constants.OPCodes.HELLO) this.client.manager.setupKeepAlive(data.d.heartbeat_interval); + return this.packetManager.handle(data); + } + + /** + * Parses the raw data from a websocket event, inflating it if necessary + * @param {*} data Event data + * @returns {Object} + */ + parseEventData(data) { + if (typeof data !== 'string') { + if (data instanceof ArrayBuffer) data = convertArrayBuffer(data); + data = inflate(data).toString(); + } + return JSON.parse(data); + } + + /** + * Tries to call `parseEventData()` and return its result, or returns `null` upon thrown errors. + * @param {*} data Event data + * @returns {?Object} + */ + tryParseEventData(data) { + try { + return this.parseEventData(data); + } catch (err) { + return null; + } } /** From c2b5eb291a3a4af04ba11938a636ca62b193fe61 Mon Sep 17 00:00:00 2001 From: meew0 Date: Mon, 21 Nov 2016 18:09:49 +0100 Subject: [PATCH 095/248] Ignore commit failures due to no changes being made (#912) --- docs/deploy/deploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/deploy/deploy.sh b/docs/deploy/deploy.sh index ad15f839e..d20038b87 100644 --- a/docs/deploy/deploy.sh +++ b/docs/deploy/deploy.sh @@ -66,7 +66,7 @@ cd out git add . git config user.name "Travis CI" git config user.email "$COMMIT_AUTHOR_EMAIL" -git commit -m "Docs build: ${SHA}" +git commit -m "Docs build: ${SHA}" || true git push $SSH_REPO $TARGET_BRANCH # Clean up... @@ -86,5 +86,5 @@ cd out git add . git config user.name "Travis CI" git config user.email "$COMMIT_AUTHOR_EMAIL" -git commit -m "Webpack build: ${SHA}" +git commit -m "Webpack build: ${SHA}" || true git push $SSH_REPO $TARGET_BRANCH From c8858de71e144c43709f3065bb9c5a38c27928fc Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 22 Nov 2016 01:05:53 -0500 Subject: [PATCH 096/248] Change to zlibjs release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8a478b7f..f4fd7046a 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "uglify-js": "github:mishoo/UglifyJS2#harmony", "utf-8-validate": "^1.2.1", "webpack": "2.1.0-beta.27", - "zlibjs": "github:imaya/zlib.js" + "zlibjs": "^0.2.0" }, "engines": { "node": ">=6.0.0" From 7c97244854d2901c3b98ea891176be291d5a3575 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 22 Nov 2016 18:24:29 -0500 Subject: [PATCH 097/248] Move to new docgen --- .travis.yml | 2 +- README.md | 6 +- {docs/deploy => deploy}/deploy.sh | 2 +- {docs/deploy => deploy}/deploy_key.enc | Bin docs/README.md | 3 +- docs/custom/avatar.js | 10 - docs/custom/faq.js | 7 - docs/custom/index.js | 17 -- docs/custom/ping_pong.js | 10 - docs/custom/updating.js | 7 - docs/custom/webhook.js | 10 - docs/custom/welcome.js | 7 - docs/{custom => }/examples/avatar.js | 0 docs/{custom => }/examples/ping_pong.js | 0 docs/{custom => }/examples/webhook.js | 0 docs/{custom/documents => general}/faq.md | 0 .../{custom/documents => general}/updating.md | 256 +++++++++--------- docs/{custom/documents => general}/welcome.md | 124 ++++----- docs/generator/config.json | 4 - docs/generator/documentation.js | 101 ------- docs/generator/index.js | 33 --- docs/generator/types/DocumentedClass.js | 80 ------ docs/generator/types/DocumentedConstructor.js | 42 --- docs/generator/types/DocumentedEvent.js | 76 ------ docs/generator/types/DocumentedFunction.js | 88 ------ docs/generator/types/DocumentedInterface.js | 32 --- docs/generator/types/DocumentedItem.js | 17 -- docs/generator/types/DocumentedItemMeta.js | 27 -- docs/generator/types/DocumentedMember.js | 56 ---- docs/generator/types/DocumentedParam.js | 36 --- docs/generator/types/DocumentedTypeDef.js | 48 ---- docs/generator/types/DocumentedVarType.js | 45 --- package.json | 7 +- 33 files changed, 200 insertions(+), 953 deletions(-) rename {docs/deploy => deploy}/deploy.sh (99%) rename {docs/deploy => deploy}/deploy_key.enc (100%) delete mode 100644 docs/custom/avatar.js delete mode 100644 docs/custom/faq.js delete mode 100644 docs/custom/index.js delete mode 100644 docs/custom/ping_pong.js delete mode 100644 docs/custom/updating.js delete mode 100644 docs/custom/webhook.js delete mode 100644 docs/custom/welcome.js rename docs/{custom => }/examples/avatar.js (100%) rename docs/{custom => }/examples/ping_pong.js (100%) rename docs/{custom => }/examples/webhook.js (100%) rename docs/{custom/documents => general}/faq.md (100%) rename docs/{custom/documents => general}/updating.md (97%) rename docs/{custom/documents => general}/welcome.md (96%) delete mode 100644 docs/generator/config.json delete mode 100644 docs/generator/documentation.js delete mode 100644 docs/generator/index.js delete mode 100644 docs/generator/types/DocumentedClass.js delete mode 100644 docs/generator/types/DocumentedConstructor.js delete mode 100644 docs/generator/types/DocumentedEvent.js delete mode 100644 docs/generator/types/DocumentedFunction.js delete mode 100644 docs/generator/types/DocumentedInterface.js delete mode 100644 docs/generator/types/DocumentedItem.js delete mode 100644 docs/generator/types/DocumentedItemMeta.js delete mode 100644 docs/generator/types/DocumentedMember.js delete mode 100644 docs/generator/types/DocumentedParam.js delete mode 100644 docs/generator/types/DocumentedTypeDef.js delete mode 100644 docs/generator/types/DocumentedVarType.js diff --git a/.travis.yml b/.travis.yml index c31f0f431..de3f3d9dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ cache: install: npm install script: - npm run test - - bash ./docs/deploy/deploy.sh + - bash ./deploy/deploy.sh env: global: - ENCRYPTION_LABEL: "af862fa96d3e" diff --git a/README.md b/README.md index e8392968c..c305a51d3 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,10 @@ the entire `Discord` object is available as a global (on the `window` object). The ShardingManager and any voice-related functionality is unavailable in these builds. ## Links -* [Website](http://discord.js.org/) +* [Website](https://discord.js.org/) * [Discord.js server](https://discord.gg/bRCvFy9) * [Discord API server](https://discord.gg/rV4BwdK) -* [Documentation](http://discord.js.org/#/docs) +* [Documentation](https://discord.js.org/#/docs) * [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html) * [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/custom/examples) * [GitHub](https://github.com/hydrabolt/discord.js) @@ -71,7 +71,7 @@ The ShardingManager and any voice-related functionality is unavailable in these ## Contributing Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the -[documentation](http://discord.js.org/#/docs). +[documentation](https://discord.js.org/#/docs). See [the contributing guide](CONTRIBUTING.md) if you'd like to submit a PR. ## Help diff --git a/docs/deploy/deploy.sh b/deploy/deploy.sh similarity index 99% rename from docs/deploy/deploy.sh rename to deploy/deploy.sh index d20038b87..eee2336bb 100644 --- a/docs/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -5,7 +5,7 @@ set -e function build { # Build docs - node docs/generator + npm run docs # Build the webpack VERSIONED=false npm run web-dist diff --git a/docs/deploy/deploy_key.enc b/deploy/deploy_key.enc similarity index 100% rename from docs/deploy/deploy_key.enc rename to deploy/deploy_key.enc diff --git a/docs/README.md b/docs/README.md index 7092346cf..d41af8eaf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,2 +1 @@ -# discord.js docs -[View documentation here](http://discord.js.org/#/docs) +## [View the documentation here.](https://discord.js.org/#/docs) diff --git a/docs/custom/avatar.js b/docs/custom/avatar.js deleted file mode 100644 index b3a77ced7..000000000 --- a/docs/custom/avatar.js +++ /dev/null @@ -1,10 +0,0 @@ -const fs = require('fs'); - -module.exports = { - category: 'Examples', - name: 'Avatars', - data: -`\`\`\`js -${fs.readFileSync('./docs/custom/examples/avatar.js').toString('utf-8')} -\`\`\``, -}; diff --git a/docs/custom/faq.js b/docs/custom/faq.js deleted file mode 100644 index abb06946a..000000000 --- a/docs/custom/faq.js +++ /dev/null @@ -1,7 +0,0 @@ -const fs = require('fs'); - -module.exports = { - category: 'General', - name: 'FAQ', - data: fs.readFileSync('./docs/custom/documents/faq.md').toString('utf-8'), -}; diff --git a/docs/custom/index.js b/docs/custom/index.js deleted file mode 100644 index 49e594bc0..000000000 --- a/docs/custom/index.js +++ /dev/null @@ -1,17 +0,0 @@ -const files = [ - require('./welcome'), - require('./updating'), - require('./faq'), - require('./ping_pong'), - require('./avatar'), - require('./webhook'), -]; - -const categories = {}; -for (const file of files) { - file.category = file.category.toLowerCase(); - if (!categories[file.category]) categories[file.category] = []; - categories[file.category].push(file); -} - -module.exports = categories; diff --git a/docs/custom/ping_pong.js b/docs/custom/ping_pong.js deleted file mode 100644 index c6952ad6c..000000000 --- a/docs/custom/ping_pong.js +++ /dev/null @@ -1,10 +0,0 @@ -const fs = require('fs'); - -module.exports = { - category: 'Examples', - name: 'Ping Pong', - data: -`\`\`\`js -${fs.readFileSync('./docs/custom/examples/ping_pong.js').toString('utf-8')} -\`\`\``, -}; diff --git a/docs/custom/updating.js b/docs/custom/updating.js deleted file mode 100644 index 1615d95cd..000000000 --- a/docs/custom/updating.js +++ /dev/null @@ -1,7 +0,0 @@ -const fs = require('fs'); - -module.exports = { - category: 'General', - name: 'Updating your code', - data: fs.readFileSync('./docs/custom/documents/updating.md').toString('utf-8'), -}; diff --git a/docs/custom/webhook.js b/docs/custom/webhook.js deleted file mode 100644 index 10f57f361..000000000 --- a/docs/custom/webhook.js +++ /dev/null @@ -1,10 +0,0 @@ -const fs = require('fs'); - -module.exports = { - category: 'Examples', - name: 'Webhooks', - data: -`\`\`\`js -${fs.readFileSync('./docs/custom/examples/webhook.js').toString('utf-8')} -\`\`\``, -}; diff --git a/docs/custom/welcome.js b/docs/custom/welcome.js deleted file mode 100644 index 809093e7e..000000000 --- a/docs/custom/welcome.js +++ /dev/null @@ -1,7 +0,0 @@ -const fs = require('fs'); - -module.exports = { - category: 'General', - name: 'Welcome', - data: fs.readFileSync('./docs/custom/documents/welcome.md').toString('utf-8'), -}; diff --git a/docs/custom/examples/avatar.js b/docs/examples/avatar.js similarity index 100% rename from docs/custom/examples/avatar.js rename to docs/examples/avatar.js diff --git a/docs/custom/examples/ping_pong.js b/docs/examples/ping_pong.js similarity index 100% rename from docs/custom/examples/ping_pong.js rename to docs/examples/ping_pong.js diff --git a/docs/custom/examples/webhook.js b/docs/examples/webhook.js similarity index 100% rename from docs/custom/examples/webhook.js rename to docs/examples/webhook.js diff --git a/docs/custom/documents/faq.md b/docs/general/faq.md similarity index 100% rename from docs/custom/documents/faq.md rename to docs/general/faq.md diff --git a/docs/custom/documents/updating.md b/docs/general/updating.md similarity index 97% rename from docs/custom/documents/updating.md rename to docs/general/updating.md index 9efe1c8f8..2926691ab 100644 --- a/docs/custom/documents/updating.md +++ b/docs/general/updating.md @@ -1,128 +1,128 @@ -# Version 10 -Version 10's non-BC changes focus on cleaning up some inconsistencies that exist in previous versions. -Upgrading from v9 should be quick and painless. - -## Client options -All client options have been converted to camelCase rather than snake_case, and `max_message_cache` was renamed to `messageCacheMaxSize`. - -v9 code example: -```js -const client = new Discord.Client({ - disable_everyone: true, - max_message_cache: 500, - message_cache_lifetime: 120, - message_sweep_interval: 60 -}); -``` - -v10 code example: -```js -const client = new Discord.Client({ - disableEveryone: true, - messageCacheMaxSize: 500, - messageCacheLifetime: 120, - messageSweepInterval: 60 -}); -``` - -## Presences -Presences have been completely restructured. -Previous versions of discord.js assumed that users had the same presence amongst all guilds - with the introduction of sharding, however, this is no longer the case. - -v9 discord.js code may look something like this: -```js -User.status; // the status of the user -User.game; // the game that the user is playing -ClientUser.setStatus(status, game, url); // set the new status for the user -``` - -v10 moves presences to GuildMember instances. For the sake of simplicity, though, User classes also expose presences. -When accessing a presence on a User object, it simply finds the first GuildMember for the user, and uses its presence. -Additionally, the introduction of the Presence class keeps all of the presence data organised. - -**It is strongly recommended that you use a GuildMember's presence where available, rather than a User. -A user may have an entirely different presence between two different guilds.** - -v10 code: -```js -MemberOrUser.presence.status; // the status of the member or user -MemberOrUser.presence.game; // the game that the member or user is playing -ClientUser.setStatus(status); // online, idle, dnd, offline -ClientUser.setGame(game, streamingURL); // a game -ClientUser.setPresence(fullPresence); // status and game combined -``` - -## Voice -Voice has been rewritten internally, but in a backwards-compatible manner. -There is only one breaking change here; the `disconnected` event was renamed to `disconnect`. -Several more events have been made available to a VoiceConnection, so see the documentation. - -## Events -Many events have been renamed or had their arguments change. - -### Client events -| Version 9 | Version 10 | -|------------------------------------------------------|-----------------------------------------------| -| guildMemberAdd(guild, member) | guildMemberAdd(member) | -| guildMemberAvailable(guild, member) | guildMemberAvailable(member) | -| guildMemberRemove(guild, member) | guildMemberRemove(member) | -| guildMembersChunk(guild, members) | guildMembersChunk(members) | -| guildMemberUpdate(guild, oldMember, newMember) | guildMemberUpdate(oldMember, newMember) | -| guildRoleCreate(guild, role) | roleCreate(role) | -| guildRoleDelete(guild, role) | roleDelete(role) | -| guildRoleUpdate(guild, oldRole, newRole) | roleUpdate(oldRole, newRole) | - -The guild parameter that has been dropped from the guild-related events can still be derived using `member.guild` or `role.guild`. - -### VoiceConnection events -| Version 9 | Version 10 | -|--------------|------------| -| disconnected | disconnect | - -## Dates and timestamps -All dates/timestamps on the structures have been refactored to have a consistent naming scheme and availability. -All of them are named similarly to this: -**Date:** `Message.createdAt` -**Timestamp:** `Message.createdTimestamp` -See the docs for each structure to see which date/timestamps are available on them. - - -# Version 9 -The version 9 (v9) rewrite takes a much more object-oriented approach than previous versions, -which allows your code to be much more readable and manageable. -It's been rebuilt from the ground up and should be much more stable, fixing caching issues that affected -older versions. It also has support for newer Discord Features, such as emojis. - -Version 9, while containing a sizable number of breaking changes, does not require much change in your code's logic - -most of the concepts are still the same, but loads of functions have been moved around. -The vast majority of methods you're used to using have been moved out of the Client class, -into other more relevant classes where they belong. -Because of this, you will need to convert most of your calls over to the new methods. - -Here are a few examples of methods that have changed: -* `Client.sendMessage(channel, message)` ==> `TextChannel.sendMessage(message)` - * `Client.sendMessage(user, message)` ==> `User.sendMessage(message)` -* `Client.updateMessage(message, "New content")` ==> `Message.edit("New Content")` -* `Client.getChannelLogs(channel, limit)` ==> `TextChannel.fetchMessages({options})` -* `Server.detailsOfUser(User)` ==> `Server.members.get(User).properties` (retrieving a member gives a GuildMember object) -* `Client.joinVoiceChannel(voicechannel)` => `VoiceChannel.join()` - -A couple more important details: -* `Client.loginWithToken("token")` ==> `client.login("token")` -* `Client.servers.length` ==> `client.guilds.size` (all instances of `server` are now `guild`) - -## No more callbacks! -Version 9 eschews callbacks in favour of Promises. This means all code relying on callbacks must be changed. -For example, the following code: - -```js -client.getChannelLogs(channel, 100, function(messages) { - console.log(`${messages.length} messages found`); -}); -``` - -```js -channel.fetchMessages({limit: 100}).then(messages => { - console.log(`${messages.size} messages found`); -}); -``` +# Version 10 +Version 10's non-BC changes focus on cleaning up some inconsistencies that exist in previous versions. +Upgrading from v9 should be quick and painless. + +## Client options +All client options have been converted to camelCase rather than snake_case, and `max_message_cache` was renamed to `messageCacheMaxSize`. + +v9 code example: +```js +const client = new Discord.Client({ + disable_everyone: true, + max_message_cache: 500, + message_cache_lifetime: 120, + message_sweep_interval: 60 +}); +``` + +v10 code example: +```js +const client = new Discord.Client({ + disableEveryone: true, + messageCacheMaxSize: 500, + messageCacheLifetime: 120, + messageSweepInterval: 60 +}); +``` + +## Presences +Presences have been completely restructured. +Previous versions of discord.js assumed that users had the same presence amongst all guilds - with the introduction of sharding, however, this is no longer the case. + +v9 discord.js code may look something like this: +```js +User.status; // the status of the user +User.game; // the game that the user is playing +ClientUser.setStatus(status, game, url); // set the new status for the user +``` + +v10 moves presences to GuildMember instances. For the sake of simplicity, though, User classes also expose presences. +When accessing a presence on a User object, it simply finds the first GuildMember for the user, and uses its presence. +Additionally, the introduction of the Presence class keeps all of the presence data organised. + +**It is strongly recommended that you use a GuildMember's presence where available, rather than a User. +A user may have an entirely different presence between two different guilds.** + +v10 code: +```js +MemberOrUser.presence.status; // the status of the member or user +MemberOrUser.presence.game; // the game that the member or user is playing +ClientUser.setStatus(status); // online, idle, dnd, offline +ClientUser.setGame(game, streamingURL); // a game +ClientUser.setPresence(fullPresence); // status and game combined +``` + +## Voice +Voice has been rewritten internally, but in a backwards-compatible manner. +There is only one breaking change here; the `disconnected` event was renamed to `disconnect`. +Several more events have been made available to a VoiceConnection, so see the documentation. + +## Events +Many events have been renamed or had their arguments change. + +### Client events +| Version 9 | Version 10 | +|------------------------------------------------------|-----------------------------------------------| +| guildMemberAdd(guild, member) | guildMemberAdd(member) | +| guildMemberAvailable(guild, member) | guildMemberAvailable(member) | +| guildMemberRemove(guild, member) | guildMemberRemove(member) | +| guildMembersChunk(guild, members) | guildMembersChunk(members) | +| guildMemberUpdate(guild, oldMember, newMember) | guildMemberUpdate(oldMember, newMember) | +| guildRoleCreate(guild, role) | roleCreate(role) | +| guildRoleDelete(guild, role) | roleDelete(role) | +| guildRoleUpdate(guild, oldRole, newRole) | roleUpdate(oldRole, newRole) | + +The guild parameter that has been dropped from the guild-related events can still be derived using `member.guild` or `role.guild`. + +### VoiceConnection events +| Version 9 | Version 10 | +|--------------|------------| +| disconnected | disconnect | + +## Dates and timestamps +All dates/timestamps on the structures have been refactored to have a consistent naming scheme and availability. +All of them are named similarly to this: +**Date:** `Message.createdAt` +**Timestamp:** `Message.createdTimestamp` +See the docs for each structure to see which date/timestamps are available on them. + + +# Version 9 +The version 9 (v9) rewrite takes a much more object-oriented approach than previous versions, +which allows your code to be much more readable and manageable. +It's been rebuilt from the ground up and should be much more stable, fixing caching issues that affected +older versions. It also has support for newer Discord Features, such as emojis. + +Version 9, while containing a sizable number of breaking changes, does not require much change in your code's logic - +most of the concepts are still the same, but loads of functions have been moved around. +The vast majority of methods you're used to using have been moved out of the Client class, +into other more relevant classes where they belong. +Because of this, you will need to convert most of your calls over to the new methods. + +Here are a few examples of methods that have changed: +* `Client.sendMessage(channel, message)` ==> `TextChannel.sendMessage(message)` + * `Client.sendMessage(user, message)` ==> `User.sendMessage(message)` +* `Client.updateMessage(message, "New content")` ==> `Message.edit("New Content")` +* `Client.getChannelLogs(channel, limit)` ==> `TextChannel.fetchMessages({options})` +* `Server.detailsOfUser(User)` ==> `Server.members.get(User).properties` (retrieving a member gives a GuildMember object) +* `Client.joinVoiceChannel(voicechannel)` => `VoiceChannel.join()` + +A couple more important details: +* `Client.loginWithToken("token")` ==> `client.login("token")` +* `Client.servers.length` ==> `client.guilds.size` (all instances of `server` are now `guild`) + +## No more callbacks! +Version 9 eschews callbacks in favour of Promises. This means all code relying on callbacks must be changed. +For example, the following code: + +```js +client.getChannelLogs(channel, 100, function(messages) { + console.log(`${messages.length} messages found`); +}); +``` + +```js +channel.fetchMessages({limit: 100}).then(messages => { + console.log(`${messages.size} messages found`); +}); +``` diff --git a/docs/custom/documents/welcome.md b/docs/general/welcome.md similarity index 96% rename from docs/custom/documents/welcome.md rename to docs/general/welcome.md index 0d2b4aad3..33ac6e077 100644 --- a/docs/custom/documents/welcome.md +++ b/docs/general/welcome.md @@ -1,62 +1,62 @@ -

-
-

- discord.js -

-
-

- Discord server - NPM version - NPM downloads - Build status - Dependencies -

-

- NPM info -

-
- -# Welcome! -Welcome to the discord.js v10 documentation. -v10 is just a more consistent and stable iteration over v9, and contains loads of new and improved features, optimisations, and bug fixes. - -## About -discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily. -It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend. -Usability and performance are key focuses of discord.js, and it also has nearly 100% coverage of the Discord API. - -## Installation -**Node.js 6.0.0 or newer is required.** - -Without voice support: `npm install discord.js --save` -With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save` -With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` - -The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus. -Using opusscript is only recommended for development environments where node-opus is tough to get working. -For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers. - -## Web distributions -Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack). -These are built by [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`, -the entire `Discord` object is available as a global (on the `window` object). -The ShardingManager and any voice-related functionality is unavailable in these builds. - -## Guides -* [LuckyEvie's general guide](https://eslachance.gitbooks.io/discord-js-bot-guide/content/) -* [York's v9 upgrade guide](https://yorkaargh.wordpress.com/2016/09/03/updating-discord-js-bots/) - -## Links -* [Website](http://discord.js.org/) -* [Discord.js server](https://discord.gg/bRCvFy9) -* [Discord API server](https://discord.gg/rV4BwdK) -* [Documentation](http://discord.js.org/#/docs) -* [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html) -* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/custom/examples) -* [GitHub](https://github.com/hydrabolt/discord.js) -* [NPM](https://www.npmjs.com/package/discord.js) -* [Related libraries](https://discordapi.com/unofficial/libs.html) - -## Help -If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle -nudge in the right direction, please don't hesitate to join our official [Discord.js Server](https://discord.gg/bRCvFy9). +
+
+

+ discord.js +

+
+

+ Discord server + NPM version + NPM downloads + Build status + Dependencies +

+

+ NPM info +

+
+ +# Welcome! +Welcome to the discord.js v10 documentation. +v10 is just a more consistent and stable iteration over v9, and contains loads of new and improved features, optimisations, and bug fixes. + +## About +discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily. +It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend. +Usability and performance are key focuses of discord.js, and it also has nearly 100% coverage of the Discord API. + +## Installation +**Node.js 6.0.0 or newer is required.** + +Without voice support: `npm install discord.js --save` +With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save` +With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` + +The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus. +Using opusscript is only recommended for development environments where node-opus is tough to get working. +For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers. + +## Web distributions +Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack). +These are built by [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`, +the entire `Discord` object is available as a global (on the `window` object). +The ShardingManager and any voice-related functionality is unavailable in these builds. + +## Guides +* [LuckyEvie's general guide](https://eslachance.gitbooks.io/discord-js-bot-guide/content/) +* [York's v9 upgrade guide](https://yorkaargh.wordpress.com/2016/09/03/updating-discord-js-bots/) + +## Links +* [Website](https://discord.js.org/) +* [Discord.js server](https://discord.gg/bRCvFy9) +* [Discord API server](https://discord.gg/rV4BwdK) +* [Documentation](https://discord.js.org/#/docs) +* [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html) +* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/custom/examples) +* [GitHub](https://github.com/hydrabolt/discord.js) +* [NPM](https://www.npmjs.com/package/discord.js) +* [Related libraries](https://discordapi.com/unofficial/libs.html) + +## Help +If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle +nudge in the right direction, please don't hesitate to join our official [Discord.js Server](https://discord.gg/bRCvFy9). diff --git a/docs/generator/config.json b/docs/generator/config.json deleted file mode 100644 index 0efc2dc71..000000000 --- a/docs/generator/config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "GEN_VERSION": 14, - "COMPRESS": false -} diff --git a/docs/generator/documentation.js b/docs/generator/documentation.js deleted file mode 100644 index f5dba5b1c..000000000 --- a/docs/generator/documentation.js +++ /dev/null @@ -1,101 +0,0 @@ -/* eslint-disable no-console */ -const DocumentedClass = require('./types/DocumentedClass'); -const DocumentedInterface = require('./types/DocumentedInterface'); -const DocumentedTypeDef = require('./types/DocumentedTypeDef'); -const DocumentedConstructor = require('./types/DocumentedConstructor'); -const DocumentedMember = require('./types/DocumentedMember'); -const DocumentedFunction = require('./types/DocumentedFunction'); -const DocumentedEvent = require('./types/DocumentedEvent'); -const GEN_VERSION = require('./config').GEN_VERSION; - -class Documentation { - constructor(items, custom) { - this.classes = new Map(); - this.interfaces = new Map(); - this.typedefs = new Map(); - this.custom = custom; - this.parse(items); - } - - registerRoots(data) { - for (const item of data) { - switch (item.kind) { - case 'class': - this.classes.set(item.name, new DocumentedClass(this, item)); - break; - case 'interface': - this.interfaces.set(item.name, new DocumentedInterface(this, item)); - break; - case 'typedef': - this.typedefs.set(item.name, new DocumentedTypeDef(this, item)); - break; - default: - break; - } - } - } - - findParent(item) { - if (['constructor', 'member', 'function', 'event'].includes(item.kind)) { - let val = this.classes.get(item.memberof); - if (val) return val; - val = this.interfaces.get(item.memberof); - if (val) return val; - } - return null; - } - - parse(items) { - this.registerRoots(items.filter(item => ['class', 'interface', 'typedef'].includes(item.kind))); - const members = items.filter(item => !['class', 'interface', 'typedef'].includes(item.kind)); - const unknowns = new Map(); - - for (const member of members) { - let item; - switch (member.kind) { - case 'constructor': - item = new DocumentedConstructor(this, member); - break; - case 'member': - item = new DocumentedMember(this, member); - break; - case 'function': - item = new DocumentedFunction(this, member); - break; - case 'event': - item = new DocumentedEvent(this, member); - break; - default: - unknowns.set(member.kind, member); - continue; - } - - const parent = this.findParent(member); - if (!parent) { - console.warn(`- "${member.name || member.directData.name}" has no accessible parent.`); - continue; - } - parent.add(item); - } - for (const [key, val] of unknowns) { - console.warn(`- Unknown documentation kind "${key}" - \n${JSON.stringify(val)}\n`); - } - } - - serialize() { - const meta = { - version: GEN_VERSION, - date: Date.now(), - }; - const serialized = { - meta, - classes: Array.from(this.classes.values()).map(c => c.serialize()), - interfaces: Array.from(this.interfaces.values()).map(i => i.serialize()), - typedefs: Array.from(this.typedefs.values()).map(t => t.serialize()), - custom: this.custom, - }; - return serialized; - } -} - -module.exports = Documentation; diff --git a/docs/generator/index.js b/docs/generator/index.js deleted file mode 100644 index ce8e06b6d..000000000 --- a/docs/generator/index.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable no-console */ -const fs = require('fs'); -const jsdoc2md = require('jsdoc-to-markdown'); -const Documentation = require('./documentation'); -const custom = require('../custom/index'); -const config = require('./config'); - -process.on('unhandledRejection', console.error); - -console.log(`Using format version ${config.GEN_VERSION}.`); -console.log('Parsing JSDocs in source files...'); - -jsdoc2md.getTemplateData({ files: [`./src/*.js`, `./src/**/*.js`] }).then(data => { - console.log(`${data.length} items found.`); - const documentation = new Documentation(data, custom); - - console.log('Serializing...'); - let output = JSON.stringify(documentation.serialize(), null, 0); - - if (config.compress) { - console.log('Compressing...'); - output = require('zlib').deflateSync(output).toString('utf8'); - } - - if (!process.argv.slice(2).includes('test')) { - console.log('Writing to docs.json...'); - fs.writeFileSync('./docs/docs.json', output); - } - - console.log('Done!'); - process.exit(0); -}).catch(console.error); diff --git a/docs/generator/types/DocumentedClass.js b/docs/generator/types/DocumentedClass.js deleted file mode 100644 index 9f321b5fb..000000000 --- a/docs/generator/types/DocumentedClass.js +++ /dev/null @@ -1,80 +0,0 @@ -const DocumentedItem = require('./DocumentedItem'); -const DocumentedItemMeta = require('./DocumentedItemMeta'); -const DocumentedConstructor = require('./DocumentedConstructor'); -const DocumentedFunction = require('./DocumentedFunction'); -const DocumentedMember = require('./DocumentedMember'); -const DocumentedEvent = require('./DocumentedEvent'); - -/* -{ id: 'VoiceChannel', - longname: 'VoiceChannel', - name: 'VoiceChannel', - scope: 'global', - kind: 'class', - augments: [ 'GuildChannel' ], - description: 'Represents a Server Voice Channel on Discord.', - meta: - { lineno: 7, - filename: 'VoiceChannel.js', - path: 'src/structures' }, - order: 232 } - */ - -class DocumentedClass extends DocumentedItem { - constructor(docParent, data) { - super(docParent, data); - this.props = new Map(); - this.methods = new Map(); - this.events = new Map(); - } - - add(item) { - if (item instanceof DocumentedConstructor) { - if (this.classConstructor) { - throw new Error(`Doc ${this.directData.name} already has constructor - ${this.directData.classConstructor}`); - } - this.classConstructor = item; - } else if (item instanceof DocumentedFunction) { - if (this.methods.get(item.directData.name)) { - throw new Error(`Doc ${this.directData.name} already has method ${item.directData.name}`); - } - this.methods.set(item.directData.name, item); - } else if (item instanceof DocumentedMember) { - if (this.props.get(item.directData.name)) { - throw new Error(`Doc ${this.directData.name} already has prop ${item.directData.name}`); - } - this.props.set(item.directData.name, item); - } else if (item instanceof DocumentedEvent) { - if (this.events.get(item.directData.name)) { - throw new Error(`Doc ${this.directData.name} already has event ${item.directData.name}`); - } - this.events.set(item.directData.name, item); - } - } - - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.directData = data; - this.directData.meta = new DocumentedItemMeta(this, data.meta); - } - - serialize() { - super.serialize(); - const { id, name, description, meta, augments, access } = this.directData; - const serialized = { - id, - name, - description, - meta: meta.serialize(), - extends: augments, - access, - }; - if (this.classConstructor) serialized.classConstructor = this.classConstructor.serialize(); - serialized.methods = Array.from(this.methods.values()).map(m => m.serialize()); - serialized.properties = Array.from(this.props.values()).map(p => p.serialize()); - serialized.events = Array.from(this.events.values()).map(e => e.serialize()); - return serialized; - } -} - -module.exports = DocumentedClass; diff --git a/docs/generator/types/DocumentedConstructor.js b/docs/generator/types/DocumentedConstructor.js deleted file mode 100644 index 06b21d6b0..000000000 --- a/docs/generator/types/DocumentedConstructor.js +++ /dev/null @@ -1,42 +0,0 @@ -const DocumentedItem = require('./DocumentedItem'); -const DocumentedParam = require('./DocumentedParam'); - -/* -{ id: 'Client()', - longname: 'Client', - name: 'Client', - kind: 'constructor', - description: 'Creates an instance of Client.', - memberof: 'Client', - params: - [ { type: [Object], - optional: true, - description: 'options to pass to the client', - name: 'options' } ], - order: 10 } -*/ - -class DocumentedConstructor extends DocumentedItem { - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.directData = data; - const newParams = []; - for (const param of data.params) newParams.push(new DocumentedParam(this, param)); - this.directData.params = newParams; - } - - serialize() { - super.serialize(); - const { id, name, description, memberof, access, params } = this.directData; - return { - id, - name, - description, - memberof, - access, - params: params.map(p => p.serialize()), - }; - } -} - -module.exports = DocumentedConstructor; diff --git a/docs/generator/types/DocumentedEvent.js b/docs/generator/types/DocumentedEvent.js deleted file mode 100644 index 52791980d..000000000 --- a/docs/generator/types/DocumentedEvent.js +++ /dev/null @@ -1,76 +0,0 @@ -const DocumentedItem = require('./DocumentedItem'); -const DocumentedItemMeta = require('./DocumentedItemMeta'); -const DocumentedParam = require('./DocumentedParam'); - -/* -{ - "id":"Client#event:guildMemberRolesUpdate", - "longname":"Client#event:guildMemberRolesUpdate", - "name":"guildMemberRolesUpdate", - "scope":"instance", - "kind":"event", - "description":"Emitted whenever a Guild Member's Roles change - i.e. new role or removed role", - "memberof":"Client", - "params":[ - { - "type":{ - "names":[ - "Guild" - ] - }, - "description":"the guild that the update affects", - "name":"guild" - }, - { - "type":{ - "names":[ - "Array." - ] - }, - "description":"the roles before the update", - "name":"oldRoles" - }, - { - "type":{ - "names":[ - "Guild" - ] - }, - "description":"the roles after the update", - "name":"newRoles" - } - ], - "meta":{ - "lineno":91, - "filename":"Guild.js", - "path":"src/structures" - }, - "order":110 -} -*/ - -class DocumentedEvent extends DocumentedItem { - registerMetaInfo(data) { - this.directData = data; - this.directData.meta = new DocumentedItemMeta(this, data.meta); - const newParams = []; - data.params = data.params || []; - for (const param of data.params) newParams.push(new DocumentedParam(this, param)); - this.directData.params = newParams; - } - - serialize() { - super.serialize(); - const { id, name, description, memberof, meta, params } = this.directData; - return { - id, - name, - description, - memberof, - meta: meta.serialize(), - params: params.map(p => p.serialize()), - }; - } -} - -module.exports = DocumentedEvent; diff --git a/docs/generator/types/DocumentedFunction.js b/docs/generator/types/DocumentedFunction.js deleted file mode 100644 index 04ba6760d..000000000 --- a/docs/generator/types/DocumentedFunction.js +++ /dev/null @@ -1,88 +0,0 @@ -const DocumentedItem = require('./DocumentedItem'); -const DocumentedItemMeta = require('./DocumentedItemMeta'); -const DocumentedVarType = require('./DocumentedVarType'); -const DocumentedParam = require('./DocumentedParam'); - -/* -{ - "id":"ClientUser#sendTTSMessage", - "longname":"ClientUser#sendTTSMessage", - "name":"sendTTSMessage", - "scope":"instance", - "kind":"function", - "inherits":"User#sendTTSMessage", - "inherited":true, - "implements":[ - "TextBasedChannel#sendTTSMessage" - ], - "description":"Send a text-to-speech message to this channel", - "memberof":"ClientUser", - "params":[ - { - "type":{ - "names":[ - "String" - ] - }, - "description":"the content to send", - "name":"content" - } - ], - "examples":[ - "// send a TTS message..." - ], - "returns":[ - { - "type":{ - "names":[ - "Promise." - ] - } - } - ], - "meta":{ - "lineno":38, - "filename":"TextBasedChannel.js", - "path":src/structures/interface" - }, - "order":293 -} - */ - -class DocumentedFunction extends DocumentedItem { - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.directData = data; - this.directData.meta = new DocumentedItemMeta(this, data.meta); - this.directData.returns = new DocumentedVarType(this, data.returns ? data.returns[0].type : { - names: ['void'], - }); - const newParams = []; - for (const param of data.params) newParams.push(new DocumentedParam(this, param)); - this.directData.params = newParams; - } - - serialize() { - super.serialize(); - const { - id, name, description, memberof, examples, inherits, inherited, meta, returns, params, access, - } = this.directData; - const serialized = { - id, - access, - name, - description, - memberof, - examples, - inherits, - inherited, - meta: meta.serialize(), - returns: returns.serialize(), - params: params.map(p => p.serialize()), - }; - serialized.implements = this.directData.implements; - return serialized; - } -} - -module.exports = DocumentedFunction; diff --git a/docs/generator/types/DocumentedInterface.js b/docs/generator/types/DocumentedInterface.js deleted file mode 100644 index ce2905785..000000000 --- a/docs/generator/types/DocumentedInterface.js +++ /dev/null @@ -1,32 +0,0 @@ -const DocumentedClass = require('./DocumentedClass'); - -/* -{ id: 'TextBasedChannel', - longname: 'TextBasedChannel', - name: 'TextBasedChannel', - scope: 'global', - kind: 'interface', - classdesc: 'Interface for classes that have text-channel-like features', - params: [], - meta: - { lineno: 5, - filename: 'TextBasedChannel.js', - path: 'src/structures/interface' }, - order: 175 } - */ - -class DocumentedInterface extends DocumentedClass { - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.directData = data; - // this.directData.meta = new DocumentedItemMeta(this, data.meta); - } - - serialize() { - const serialized = super.serialize(); - serialized.description = this.directData.classdesc; - return serialized; - } -} - -module.exports = DocumentedInterface; diff --git a/docs/generator/types/DocumentedItem.js b/docs/generator/types/DocumentedItem.js deleted file mode 100644 index 562c706d3..000000000 --- a/docs/generator/types/DocumentedItem.js +++ /dev/null @@ -1,17 +0,0 @@ -class DocumentedItem { - constructor(parent, info) { - this.parent = parent; - this.directData = {}; - this.registerMetaInfo(info); - } - - registerMetaInfo() { - return; - } - - serialize() { - return; - } -} - -module.exports = DocumentedItem; diff --git a/docs/generator/types/DocumentedItemMeta.js b/docs/generator/types/DocumentedItemMeta.js deleted file mode 100644 index 3bc6fbae1..000000000 --- a/docs/generator/types/DocumentedItemMeta.js +++ /dev/null @@ -1,27 +0,0 @@ -const DocumentedItem = require('./DocumentedItem'); - -const cwd = `${process.cwd()}\\`.replace(/\\/g, '/'); -const backToForward = /\\/g; - -/* - { lineno: 7, - filename: 'VoiceChannel.js', - path: 'src/structures' }, -*/ - -class DocumentedItemMeta extends DocumentedItem { - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.directData.line = data.lineno; - this.directData.file = data.filename; - this.directData.path = data.path.replace(backToForward, '/').replace(cwd, ''); - } - - serialize() { - super.serialize(); - const { line, file, path } = this.directData; - return { line, file, path }; - } -} - -module.exports = DocumentedItemMeta; diff --git a/docs/generator/types/DocumentedMember.js b/docs/generator/types/DocumentedMember.js deleted file mode 100644 index 5b66e7b65..000000000 --- a/docs/generator/types/DocumentedMember.js +++ /dev/null @@ -1,56 +0,0 @@ -const DocumentedItem = require('./DocumentedItem'); -const DocumentedItemMeta = require('./DocumentedItemMeta'); -const DocumentedVarType = require('./DocumentedVarType'); -const DocumentedParam = require('./DocumentedParam'); - -/* -{ id: 'Client#rest', - longname: 'Client#rest', - name: 'rest', - scope: 'instance', - kind: 'member', - description: 'The REST manager of the client', - memberof: 'Client', - type: { names: [ 'RESTManager' ] }, - access: 'private', - meta: - { lineno: 32, - filename: 'Client.js', - path: 'src/client' }, - order: 11 } -*/ - -class DocumentedMember extends DocumentedItem { - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.directData = data; - this.directData.meta = new DocumentedItemMeta(this, data.meta); - this.directData.type = new DocumentedVarType(this, data.type); - if (data.properties) { - const newProps = []; - for (const param of data.properties) { - newProps.push(new DocumentedParam(this, param)); - } - this.directData.properties = newProps; - } else { - data.properties = []; - } - } - - serialize() { - super.serialize(); - const { id, name, description, memberof, type, access, meta, properties } = this.directData; - return { - id, - name, - description, - memberof, - type: type.serialize(), - access, - meta: meta.serialize(), - props: properties.map(p => p.serialize()), - }; - } -} - -module.exports = DocumentedMember; diff --git a/docs/generator/types/DocumentedParam.js b/docs/generator/types/DocumentedParam.js deleted file mode 100644 index d869078d4..000000000 --- a/docs/generator/types/DocumentedParam.js +++ /dev/null @@ -1,36 +0,0 @@ -const DocumentedItem = require('./DocumentedItem'); -const DocumentedVarType = require('./DocumentedVarType'); - -/* -{ - "type":{ - "names":[ - "Guild" - ] - }, - "description":"the roles after the update", - "name":"newRoles" - } -*/ - -class DocumentedParam extends DocumentedItem { - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.directData = data; - this.directData.type = new DocumentedVarType(this, data.type); - } - - serialize() { - super.serialize(); - const { name, description, type, optional, defaultvalue } = this.directData; - return { - name, - description, - optional, - default: defaultvalue, - type: type.serialize(), - }; - } -} - -module.exports = DocumentedParam; diff --git a/docs/generator/types/DocumentedTypeDef.js b/docs/generator/types/DocumentedTypeDef.js deleted file mode 100644 index 0571868ec..000000000 --- a/docs/generator/types/DocumentedTypeDef.js +++ /dev/null @@ -1,48 +0,0 @@ -const DocumentedItem = require('./DocumentedItem'); -const DocumentedItemMeta = require('./DocumentedItemMeta'); -const DocumentedVarType = require('./DocumentedVarType'); -const DocumentedParam = require('./DocumentedParam'); - -/* -{ id: 'StringResolvable', - longname: 'StringResolvable', - name: 'StringResolvable', - scope: 'global', - kind: 'typedef', - description: 'Data that can be resolved to give a String...', - type: { names: [ 'String', 'Array', 'Object' ] }, - meta: - { lineno: 142, - filename: 'ClientDataResolver.js', - path: 'src/client' }, - order: 37 } -*/ - -class DocumentedTypeDef extends DocumentedItem { - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.props = new Map(); - this.directData = data; - this.directData.meta = new DocumentedItemMeta(this, data.meta); - this.directData.type = new DocumentedVarType(this, data.type); - data.properties = data.properties || []; - for (const prop of data.properties) this.props.set(prop.name, new DocumentedParam(this, prop)); - } - - serialize() { - super.serialize(); - const { id, name, description, type, access, meta } = this.directData; - const serialized = { - id, - name, - description, - type: type.serialize(), - access, - meta: meta.serialize(), - }; - serialized.properties = Array.from(this.props.values()).map(p => p.serialize()); - return serialized; - } -} - -module.exports = DocumentedTypeDef; diff --git a/docs/generator/types/DocumentedVarType.js b/docs/generator/types/DocumentedVarType.js deleted file mode 100644 index fcbb1b548..000000000 --- a/docs/generator/types/DocumentedVarType.js +++ /dev/null @@ -1,45 +0,0 @@ -const DocumentedItem = require('./DocumentedItem'); - -/* -{ - "names":[ - "String" - ] -} -*/ - -const regex = /([\w]+)([^\w]+)/; -const regexG = /([\w]+)([^\w]+)/g; - -class DocumentedVarType extends DocumentedItem { - registerMetaInfo(data) { - super.registerMetaInfo(data); - this.directData = data; - } - - serialize() { - super.serialize(); - const names = []; - for (const name of this.directData.names) names.push(splitVarName(name)); - return { - types: names, - }; - } -} - -function splitVarName(str) { - if (str === '*') return ['*', '']; - const matches = str.match(regexG); - const output = []; - if (matches) { - for (const match of matches) { - const groups = match.match(regex); - output.push([groups[1], groups[2]]); - } - } else { - output.push([str.match(/(\w+)/g)[0], '']); - } - return output; -} - -module.exports = DocumentedVarType; diff --git a/package.json b/package.json index f4fd7046a..c5e4c2d1c 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "A powerful library for interacting with the Discord API", "main": "./src/index", "scripts": { - "test": "eslint src && node docs/generator test", - "docs": "node docs/generator", - "test-docs": "node docs/generator test", + "test": "eslint src && docgen --source src --custom docs", + "docs": "docgen --source src --custom docs --output docs/docs.json", + "test-docs": "docgen --source src --custom docs", "lint": "eslint src", "web-dist": "node ./node_modules/parallel-webpack/bin/run.js" }, @@ -39,6 +39,7 @@ }, "devDependencies": { "bufferutil": "^1.2.1", + "discord.js-docgen": "github:Gawdl3y/discord.js-docgen", "eslint": "^3.10.0", "jsdoc-to-markdown": "^2.0.0", "json-loader": "^0.5.4", From a8bb20204bdd25c6cfb09beb00387153fc4ac195 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 22 Nov 2016 18:34:08 -0500 Subject: [PATCH 098/248] Fix paths --- .gitignore | 2 ++ deploy/deploy.sh | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 80a6efd9c..c6e423898 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ test/auth.json test/auth.js docs/deploy/deploy_key docs/deploy/deploy_key.pub +deploy/deploy_key +deploy/deploy_key.pub # Miscellaneous .tmp/ diff --git a/deploy/deploy.sh b/deploy/deploy.sh index eee2336bb..cbd644ed9 100644 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -45,7 +45,7 @@ ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key" ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv" ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR} ENCRYPTED_IV=${!ENCRYPTED_IV_VAR} -openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in docs/deploy/deploy_key.enc -out deploy_key -d +openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in deploy/deploy_key.enc -out deploy_key -d chmod 600 deploy_key eval `ssh-agent -s` ssh-add deploy_key From 77353e522071b21ce3e7e42092c746c09be17cb5 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 22 Nov 2016 18:40:18 -0500 Subject: [PATCH 099/248] Fix names of custom docs --- docs/examples/avatar.js | 30 ------------------------------ docs/examples/ping_pong.js | 30 ------------------------------ 2 files changed, 60 deletions(-) delete mode 100644 docs/examples/avatar.js delete mode 100644 docs/examples/ping_pong.js diff --git a/docs/examples/avatar.js b/docs/examples/avatar.js deleted file mode 100644 index 796d942bd..000000000 --- a/docs/examples/avatar.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - Send a user a link to their avatar -*/ - -// import the discord.js module -const Discord = require('discord.js'); - -// create an instance of a Discord Client, and call it bot -const bot = new Discord.Client(); - -// the token of your bot - https://discordapp.com/developers/applications/me -const token = 'your bot token here'; - -// the ready event is vital, it means that your bot will only start reacting to information -// from Discord _after_ ready is emitted. -bot.on('ready', () => { - console.log('I am ready!'); -}); - -// create an event listener for messages -bot.on('message', message => { - // if the message is "what is my avatar", - if (message.content === 'what is my avatar') { - // send the user's avatar URL - message.reply(message.author.avatarURL); - } -}); - -// log our bot in -bot.login(token); diff --git a/docs/examples/ping_pong.js b/docs/examples/ping_pong.js deleted file mode 100644 index 4cede6a80..000000000 --- a/docs/examples/ping_pong.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - A ping pong bot, whenever you send "ping", it replies "pong". -*/ - -// import the discord.js module -const Discord = require('discord.js'); - -// create an instance of a Discord Client, and call it bot -const bot = new Discord.Client(); - -// the token of your bot - https://discordapp.com/developers/applications/me -const token = 'your bot token here'; - -// the ready event is vital, it means that your bot will only start reacting to information -// from Discord _after_ ready is emitted. -bot.on('ready', () => { - console.log('I am ready!'); -}); - -// create an event listener for messages -bot.on('message', message => { - // if the message is "ping", - if (message.content === 'ping') { - // send "pong" to the same channel. - message.channel.sendMessage('pong'); - } -}); - -// log our bot in -bot.login(token); From c3d14636ab1c4cd49dda84d4e6b49fd914f1d409 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 22 Nov 2016 18:40:49 -0500 Subject: [PATCH 100/248] Missing files --- docs/examples/Avatars.js | 30 ++++++++++++++++++++++++++++++ docs/examples/Ping-pong.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 docs/examples/Avatars.js create mode 100644 docs/examples/Ping-pong.js diff --git a/docs/examples/Avatars.js b/docs/examples/Avatars.js new file mode 100644 index 000000000..796d942bd --- /dev/null +++ b/docs/examples/Avatars.js @@ -0,0 +1,30 @@ +/* + Send a user a link to their avatar +*/ + +// import the discord.js module +const Discord = require('discord.js'); + +// create an instance of a Discord Client, and call it bot +const bot = new Discord.Client(); + +// the token of your bot - https://discordapp.com/developers/applications/me +const token = 'your bot token here'; + +// the ready event is vital, it means that your bot will only start reacting to information +// from Discord _after_ ready is emitted. +bot.on('ready', () => { + console.log('I am ready!'); +}); + +// create an event listener for messages +bot.on('message', message => { + // if the message is "what is my avatar", + if (message.content === 'what is my avatar') { + // send the user's avatar URL + message.reply(message.author.avatarURL); + } +}); + +// log our bot in +bot.login(token); diff --git a/docs/examples/Ping-pong.js b/docs/examples/Ping-pong.js new file mode 100644 index 000000000..4cede6a80 --- /dev/null +++ b/docs/examples/Ping-pong.js @@ -0,0 +1,30 @@ +/* + A ping pong bot, whenever you send "ping", it replies "pong". +*/ + +// import the discord.js module +const Discord = require('discord.js'); + +// create an instance of a Discord Client, and call it bot +const bot = new Discord.Client(); + +// the token of your bot - https://discordapp.com/developers/applications/me +const token = 'your bot token here'; + +// the ready event is vital, it means that your bot will only start reacting to information +// from Discord _after_ ready is emitted. +bot.on('ready', () => { + console.log('I am ready!'); +}); + +// create an event listener for messages +bot.on('message', message => { + // if the message is "ping", + if (message.content === 'ping') { + // send "pong" to the same channel. + message.channel.sendMessage('pong'); + } +}); + +// log our bot in +bot.login(token); From 338aa5838678b58add89c92dd8a97029bd597b68 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 22 Nov 2016 19:50:05 -0500 Subject: [PATCH 101/248] Set up new-new custom docs mechanism --- docs/examples/{Ping-pong.js => ping.js} | 0 docs/index.yml | 16 ++++++++++++++++ package.json | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) rename docs/examples/{Ping-pong.js => ping.js} (100%) create mode 100644 docs/index.yml diff --git a/docs/examples/Ping-pong.js b/docs/examples/ping.js similarity index 100% rename from docs/examples/Ping-pong.js rename to docs/examples/ping.js diff --git a/docs/index.yml b/docs/index.yml new file mode 100644 index 000000000..4bf13c7e3 --- /dev/null +++ b/docs/index.yml @@ -0,0 +1,16 @@ +- name: General + files: + - name: Welcome + path: welcome.md + - name: Updating your code + path: updating.md + - name: FAQ + path: faq.md +- name: Examples + files: + - name: Ping + path: ping.js + - name: Avatars + path: avatars.js + - name: Webhook + path: webhook.js diff --git a/package.json b/package.json index c5e4c2d1c..35c08a124 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "A powerful library for interacting with the Discord API", "main": "./src/index", "scripts": { - "test": "eslint src && docgen --source src --custom docs", - "docs": "docgen --source src --custom docs --output docs/docs.json", + "test": "eslint src && docgen --source src --custom docs/index.yml", + "docs": "docgen --source src --custom docs/index.yml --output docs/docs.json", "test-docs": "docgen --source src --custom docs", "lint": "eslint src", "web-dist": "node ./node_modules/parallel-webpack/bin/run.js" From c4b207766661a3b73a60d5d3d7704124d0ae0ccd Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 22 Nov 2016 19:54:24 -0500 Subject: [PATCH 102/248] Rename Avatars.js to avatars.js --- docs/examples/{Avatars.js => avatars.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/examples/{Avatars.js => avatars.js} (100%) diff --git a/docs/examples/Avatars.js b/docs/examples/avatars.js similarity index 100% rename from docs/examples/Avatars.js rename to docs/examples/avatars.js From 32879419e2aaecc17ed9bd3ef566e51904fdd159 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 23 Nov 2016 02:51:10 -0500 Subject: [PATCH 103/248] Fix dispatcher doc --- src/client/voice/dispatcher/StreamDispatcher.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index f69fa0436..19ded79ff 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -10,9 +10,7 @@ nonce.fill(0); * // obtained using: * voiceChannel.join().then(connection => { * // you can play a file or a stream here: - * connection.playFile('./file.mp3').then(dispatcher => { - * - * }); + * const dispatcher = connection.playFile('./file.mp3'); * }); * ``` * @extends {EventEmitter} From c91ee7a3e792370f1992d1557114296910411292 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 23 Nov 2016 18:30:00 -0600 Subject: [PATCH 104/248] Replace ws with uws (#918) * change to uws (waiting for the next release tho) * clean up, fix reconnections (maybe) * change voice to use uws * so messy --- package.json | 8 ++++---- src/client/voice/VoiceWebSocket.js | 2 +- src/client/websocket/WebSocketManager.js | 24 +++++++++++++----------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 35c08a124..d27ffdfc9 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,10 @@ }, "homepage": "https://github.com/hydrabolt/discord.js#readme", "dependencies": { + "pako": "^1.0.3", "superagent": "^3.0.0", "tweetnacl": "^0.14.3", - "ws": "^1.1.1" + "uws": "^0.11.0" }, "peerDependencies": { "node-opus": "^0.2.0", @@ -46,14 +47,13 @@ "parallel-webpack": "^1.5.0", "uglify-js": "github:mishoo/UglifyJS2#harmony", "utf-8-validate": "^1.2.1", - "webpack": "2.1.0-beta.27", - "zlibjs": "^0.2.0" + "webpack": "2.1.0-beta.27" }, "engines": { "node": ">=6.0.0" }, "browser": { - "ws": false, + "uws": false, "opusscript": false, "node-opus": false, "tweet-nacl": false, diff --git a/src/client/voice/VoiceWebSocket.js b/src/client/voice/VoiceWebSocket.js index ebc2a3103..6c7303e27 100644 --- a/src/client/voice/VoiceWebSocket.js +++ b/src/client/voice/VoiceWebSocket.js @@ -1,4 +1,4 @@ -const WebSocket = require('ws'); +const WebSocket = require('uws'); const Constants = require('../../util/Constants'); const SecretKey = require('./util/SecretKey'); const EventEmitter = require('events').EventEmitter; diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index cdaa4b29a..77a40e416 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -1,10 +1,9 @@ const browser = typeof window !== 'undefined'; -const WebSocket = browser ? window.WebSocket : require('ws'); // eslint-disable-line no-undef +const WebSocket = browser ? window.WebSocket : require('uws'); // eslint-disable-line no-undef const EventEmitter = require('events').EventEmitter; const Constants = require('../../util/Constants'); -const inflate = browser ? require('zlibjs').inflateSync : require('zlib').inflateSync; +const pako = require('pako'); const PacketManager = require('./packets/WebSocketPacketManager'); -const convertArrayBuffer = require('../../util/ConvertArrayBuffer'); /** * The WebSocket Manager of the Client @@ -80,11 +79,16 @@ class WebSocketManager extends EventEmitter { this.normalReady = false; if (this.status !== Constants.Status.RECONNECTING) this.status = Constants.Status.CONNECTING; this.ws = new WebSocket(gateway); - if (browser) this.ws.binaryType = 'arraybuffer'; - this.ws.onopen = () => this.eventOpen(); + if (browser) { + this.ws.binaryType = 'arraybuffer'; + this.ws.on('open', this.eventOpen.bind(this)); + this.ws.on('error', this.eventError.bind(this)); + } else { + this.ws.onopen = () => this.eventOpen(); + this.ws.onerror = (e) => this.eventError(e); + } this.ws.onclose = (d) => this.eventClose(d); this.ws.onmessage = (e) => this.eventMessage(e); - this.ws.onerror = (e) => this.eventError(e); this._queue = []; this._remaining = 3; } @@ -230,10 +234,7 @@ class WebSocketManager extends EventEmitter { * @returns {Object} */ parseEventData(data) { - if (typeof data !== 'string') { - if (data instanceof ArrayBuffer) data = convertArrayBuffer(data); - data = inflate(data).toString(); - } + if (data instanceof ArrayBuffer) data = pako.inflate(data, { to: 'string' }); return JSON.parse(data); } @@ -261,7 +262,7 @@ class WebSocketManager extends EventEmitter { * @param {Error} error The encountered error */ if (this.client.listenerCount('error') > 0) this.client.emit('error', err); - this.ws.close(); + this.tryReconnect(); } _emitReady(normal = true) { @@ -305,6 +306,7 @@ class WebSocketManager extends EventEmitter { * Tries to reconnect the client, changing the status to Constants.Status.RECONNECTING. */ tryReconnect() { + if (this.status === Constants.Status.RECONNECTING) return; this.status = Constants.Status.RECONNECTING; this.ws.close(); this.packetManager.handleQueue(); From 91a69fb76120fd680a093a9bacc737f5c6a4637f Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 23 Nov 2016 20:07:34 -0500 Subject: [PATCH 105/248] Update docgen dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d27ffdfc9..9a0671943 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "bufferutil": "^1.2.1", - "discord.js-docgen": "github:Gawdl3y/discord.js-docgen", + "discord.js-docgen": "github:hydrabolt/discord.js-docgen", "eslint": "^3.10.0", "jsdoc-to-markdown": "^2.0.0", "json-loader": "^0.5.4", From 0008a18deb0acfad246ebaf435ab601e38bc6126 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 23 Nov 2016 21:16:19 -0600 Subject: [PATCH 106/248] Fantastic PR #368 by Gus (#921) --- src/client/websocket/WebSocketManager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 77a40e416..8fa4fd35a 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -81,11 +81,11 @@ class WebSocketManager extends EventEmitter { this.ws = new WebSocket(gateway); if (browser) { this.ws.binaryType = 'arraybuffer'; - this.ws.on('open', this.eventOpen.bind(this)); - this.ws.on('error', this.eventError.bind(this)); - } else { this.ws.onopen = () => this.eventOpen(); this.ws.onerror = (e) => this.eventError(e); + } else { + this.ws.on('open', this.eventOpen.bind(this)); + this.ws.on('error', this.eventError.bind(this)); } this.ws.onclose = (d) => this.eventClose(d); this.ws.onmessage = (e) => this.eventMessage(e); From 945a2e370ab5a3af31c2f53be7f6af5887f32748 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Thu, 24 Nov 2016 01:39:01 -0600 Subject: [PATCH 107/248] fix voice ws (#922) --- src/client/voice/VoiceWebSocket.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/client/voice/VoiceWebSocket.js b/src/client/voice/VoiceWebSocket.js index 6c7303e27..ea2b04cda 100644 --- a/src/client/voice/VoiceWebSocket.js +++ b/src/client/voice/VoiceWebSocket.js @@ -72,10 +72,15 @@ class VoiceWebSocket extends EventEmitter { * @type {WebSocket} */ this.ws = new WebSocket(`wss://${this.voiceConnection.authentication.endpoint}`); - this.ws.onopen = this.onOpen.bind(this); + if (typeof window !== 'undefined') { + this.ws.onopen = this.onOpen.bind(this); + this.ws.onerror = this.onError.bind(this); + } else { + this.ws.on('open', this.onOpen.bind(this)); + this.ws.on('error', this.onError.bind(this)); + } this.ws.onmessage = this.onMessage.bind(this); this.ws.onclose = this.onClose.bind(this); - this.ws.onerror = this.onError.bind(this); } /** From ac64f8bd233079f757e751e4f92c50d6ae8aa37e Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 24 Nov 2016 11:22:08 -0500 Subject: [PATCH 108/248] Improve rest args docs --- src/client/Client.js | 4 ++-- src/util/Collection.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index e39b2c4bd..ead8f5d23 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -349,7 +349,7 @@ class Client extends EventEmitter { * Sets a timeout that will be automatically cancelled if the client is destroyed. * @param {Function} fn Function to execute * @param {number} delay Time to wait before executing (in milliseconds) - * @param {args} args Arguments for the function (infinite/rest argument, not an array) + * @param {...*} args Arguments for the function * @returns {Timeout} */ setTimeout(fn, delay, ...args) { @@ -374,7 +374,7 @@ class Client extends EventEmitter { * Sets an interval that will be automatically cancelled if the client is destroyed. * @param {Function} fn Function to execute * @param {number} delay Time to wait before executing (in milliseconds) - * @param {args} args Arguments for the function (infinite/rest argument, not an array) + * @param {...*} args Arguments for the function * @returns {Timeout} */ setInterval(fn, delay, ...args) { diff --git a/src/util/Collection.js b/src/util/Collection.js index 4de19f57d..abda090c9 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -305,7 +305,7 @@ class Collection extends Map { /** * Combines this collection with others into a new collection. None of the source collections are modified. - * @param {Collection} collections Collections to merge (infinite/rest argument, not an array) + * @param {...Collection} collections Collections to merge * @returns {Collection} * @example const newColl = someColl.concat(someOtherColl, anotherColl, ohBoyAColl); */ From eedc097f3fc0cacd3a31de6ec42e53506f04584d Mon Sep 17 00:00:00 2001 From: Will Nelson Date: Thu, 24 Nov 2016 14:08:55 -0800 Subject: [PATCH 109/248] fix playConvertedStream (#923) --- src/client/voice/VoiceConnection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 3bd88fafe..e3f1c4c7d 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -259,7 +259,7 @@ class VoiceConnection extends EventEmitter { */ playConvertedStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) { const options = { seek, volume, passes }; - return this.player.playPCMStream(stream, options); + return this.player.playPCMStream(stream, null, options); } /** From c683790de791878dc99f6358651a0b14c73badaa Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Fri, 25 Nov 2016 19:40:53 -0500 Subject: [PATCH 110/248] Remove old uws-specific code (0.11.1 has the good stuff) --- package.json | 2 +- src/client/voice/VoiceWebSocket.js | 9 ++------- src/client/websocket/WebSocketManager.js | 15 +++++---------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 9a0671943..147904163 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "pako": "^1.0.3", "superagent": "^3.0.0", "tweetnacl": "^0.14.3", - "uws": "^0.11.0" + "uws": "^0.11.1" }, "peerDependencies": { "node-opus": "^0.2.0", diff --git a/src/client/voice/VoiceWebSocket.js b/src/client/voice/VoiceWebSocket.js index ea2b04cda..6c7303e27 100644 --- a/src/client/voice/VoiceWebSocket.js +++ b/src/client/voice/VoiceWebSocket.js @@ -72,15 +72,10 @@ class VoiceWebSocket extends EventEmitter { * @type {WebSocket} */ this.ws = new WebSocket(`wss://${this.voiceConnection.authentication.endpoint}`); - if (typeof window !== 'undefined') { - this.ws.onopen = this.onOpen.bind(this); - this.ws.onerror = this.onError.bind(this); - } else { - this.ws.on('open', this.onOpen.bind(this)); - this.ws.on('error', this.onError.bind(this)); - } + this.ws.onopen = this.onOpen.bind(this); this.ws.onmessage = this.onMessage.bind(this); this.ws.onclose = this.onClose.bind(this); + this.ws.onerror = this.onError.bind(this); } /** diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 8fa4fd35a..43d3e7155 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -79,16 +79,11 @@ class WebSocketManager extends EventEmitter { this.normalReady = false; if (this.status !== Constants.Status.RECONNECTING) this.status = Constants.Status.CONNECTING; this.ws = new WebSocket(gateway); - if (browser) { - this.ws.binaryType = 'arraybuffer'; - this.ws.onopen = () => this.eventOpen(); - this.ws.onerror = (e) => this.eventError(e); - } else { - this.ws.on('open', this.eventOpen.bind(this)); - this.ws.on('error', this.eventError.bind(this)); - } - this.ws.onclose = (d) => this.eventClose(d); - this.ws.onmessage = (e) => this.eventMessage(e); + if (browser) this.ws.binaryType = 'arraybuffer'; + this.ws.onopen = this.eventOpen.bind(this); + this.ws.onmessage = this.eventMessage.bind(this); + this.ws.onclose = this.eventClose.bind(this); + this.ws.onerror = this.eventError.bind(this); this._queue = []; this._remaining = 3; } From 85a7eab5bb6d82e06ea57fe512f821b6b004c4fe Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 26 Nov 2016 00:47:16 -0500 Subject: [PATCH 111/248] Update examples URL --- README.md | 2 +- docs/general/welcome.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c305a51d3..6fc5c584b 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The ShardingManager and any voice-related functionality is unavailable in these * [Discord API server](https://discord.gg/rV4BwdK) * [Documentation](https://discord.js.org/#/docs) * [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html) -* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/custom/examples) +* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/examples) * [GitHub](https://github.com/hydrabolt/discord.js) * [NPM](https://www.npmjs.com/package/discord.js) * [Related libraries](https://discordapi.com/unofficial/libs.html) diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 33ac6e077..6e2e07a9b 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -52,7 +52,7 @@ The ShardingManager and any voice-related functionality is unavailable in these * [Discord API server](https://discord.gg/rV4BwdK) * [Documentation](https://discord.js.org/#/docs) * [Legacy (v8) documentation](http://discordjs.readthedocs.io/en/8.2.0/docs_client.html) -* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/custom/examples) +* [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/examples) * [GitHub](https://github.com/hydrabolt/discord.js) * [NPM](https://www.npmjs.com/package/discord.js) * [Related libraries](https://discordapi.com/unofficial/libs.html) From 4ae1f63a97829706fb50aa15703d9021f60c294d Mon Sep 17 00:00:00 2001 From: Will Nelson Date: Sat, 26 Nov 2016 13:59:18 -0800 Subject: [PATCH 112/248] fix message.edits (#924) --- src/structures/Message.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 434ca6c54..39a40b6b4 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -296,7 +296,9 @@ class Message { * @readonly */ get edits() { - return this._edits.slice().unshift(this); + const copy = this._edits.slice(); + copy.unshift(this); + return copy; } /** From 27e77d3839c25bb9745f03d405b122e323b1c5bd Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sat, 26 Nov 2016 22:42:33 +0000 Subject: [PATCH 113/248] Fix Role.setPosition not returning Role (addresses #902) --- src/structures/Role.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Role.js b/src/structures/Role.js index 164996cb3..44c962f3d 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -241,7 +241,7 @@ class Role { * .catch(console.error); */ setPosition(position) { - return this.guild.setRolePosition(this, position); + return new Promise((resolve, reject) => this.guild.setRolePosition(this, position).then(resolve, reject)); } /** From e2932c05f27bac0a82777bf8cdf7e7c55949c79a Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sat, 26 Nov 2016 22:43:41 +0000 Subject: [PATCH 114/248] whoops fix derp --- src/structures/Role.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/structures/Role.js b/src/structures/Role.js index 44c962f3d..7846c6cc5 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -241,7 +241,8 @@ class Role { * .catch(console.error); */ setPosition(position) { - return new Promise((resolve, reject) => this.guild.setRolePosition(this, position).then(resolve, reject)); + return new Promise( + (resolve, reject) => this.guild.setRolePosition(this, position).then(() => resolve(this), reject)); } /** From 0d574a0678153260852fc83556bbc0157295e388 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sat, 26 Nov 2016 23:01:47 +0000 Subject: [PATCH 115/248] Address #919, add WSEventType typedef --- src/util/Constants.js | 47 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index ac2f3be2c..8bbd6e835 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -20,7 +20,7 @@ exports.Package = require('../../package.json'); * @property {boolean} [sync=false] Whether to periodically sync guilds (for userbots) * @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their * corresponding websocket events - * @property {string[]} [disabledEvents] An array of disabled websocket events. Events in this array will not be + * @property {WSEventType[]} [disabledEvents] An array of disabled websocket events. Events in this array will not be * processed. Disabling useless events such as 'TYPING_START' can result in significant performance increases on * large-scale bots. * @property {WebsocketOptions} [ws] Options for the websocket @@ -222,6 +222,49 @@ exports.Events = { DEBUG: 'debug', }; +/** + * The type of a websocket message event, e.g. `MESSAGE_CREATE`. + * These can be accessed `Discord.Constants.WSEvents['MESSAGE_CREATE']`. Alternatively, you could just provide + * the string `MESSAGE_CREATE`. + * + * Here are the available events: + * ```js + * ["READY", + * "GUILD_SYNC", + * "GUILD_CREATE", + * "GUILD_DELETE", + * "GUILD_UPDATE", + * "GUILD_MEMBER_ADD", + * "GUILD_MEMBER_REMOVE", + * "GUILD_MEMBER_UPDATE", + * "GUILD_MEMBERS_CHUNK", + * "GUILD_ROLE_CREATE", + * "GUILD_ROLE_DELETE", + * "GUILD_ROLE_UPDATE", + * "GUILD_BAN_ADD", + * "GUILD_BAN_REMOVE", + * "CHANNEL_CREATE", + * "CHANNEL_DELETE", + * "CHANNEL_UPDATE", + * "CHANNEL_PINS_UPDATE", + * "MESSAGE_CREATE", + * "MESSAGE_DELETE", + * "MESSAGE_UPDATE", + * "MESSAGE_DELETE_BULK", + * "MESSAGE_REACTION_ADD", + * "MESSAGE_REACTION_REMOVE", + * "MESSAGE_REACTION_REMOVE_ALL", + * "USER_UPDATE", + * "USER_NOTE_UPDATE", + * "PRESENCE_UPDATE", + * "VOICE_STATE_UPDATE", + * "TYPING_START", + * "VOICE_SERVER_UPDATE", + * "RELATIONSHIP_ADD", + * "RELATIONSHIP_REMOVE"] + * ``` + * @typedef {string} WSEventType + */ exports.WSEvents = { READY: 'READY', GUILD_SYNC: 'GUILD_SYNC', @@ -253,8 +296,6 @@ exports.WSEvents = { PRESENCE_UPDATE: 'PRESENCE_UPDATE', VOICE_STATE_UPDATE: 'VOICE_STATE_UPDATE', TYPING_START: 'TYPING_START', - FRIEND_ADD: 'RELATIONSHIP_ADD', - FRIEND_REMOVE: 'RELATIONSHIP_REMOVE', VOICE_SERVER_UPDATE: 'VOICE_SERVER_UPDATE', RELATIONSHIP_ADD: 'RELATIONSHIP_ADD', RELATIONSHIP_REMOVE: 'RELATIONSHIP_REMOVE', From dc6c1140bcf4215b509c3c4d5de4f7f0d77ba64a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 27 Nov 2016 01:08:08 -0500 Subject: [PATCH 116/248] Improve some JSDocs --- src/client/Client.js | 4 +-- src/util/Constants.js | 74 ++++++++++++++++++++----------------------- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index ead8f5d23..b2a1b6afd 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -443,11 +443,11 @@ module.exports = Client; /** * Emitted for general warnings * @event Client#warn - * @param {string} The warning + * @param {string} info The warning */ /** * Emitted for general debugging information * @event Client#debug - * @param {string} The debug information + * @param {string} info The debug information */ diff --git a/src/util/Constants.js b/src/util/Constants.js index 8bbd6e835..7b3aef275 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -223,46 +223,40 @@ exports.Events = { }; /** - * The type of a websocket message event, e.g. `MESSAGE_CREATE`. - * These can be accessed `Discord.Constants.WSEvents['MESSAGE_CREATE']`. Alternatively, you could just provide - * the string `MESSAGE_CREATE`. - * - * Here are the available events: - * ```js - * ["READY", - * "GUILD_SYNC", - * "GUILD_CREATE", - * "GUILD_DELETE", - * "GUILD_UPDATE", - * "GUILD_MEMBER_ADD", - * "GUILD_MEMBER_REMOVE", - * "GUILD_MEMBER_UPDATE", - * "GUILD_MEMBERS_CHUNK", - * "GUILD_ROLE_CREATE", - * "GUILD_ROLE_DELETE", - * "GUILD_ROLE_UPDATE", - * "GUILD_BAN_ADD", - * "GUILD_BAN_REMOVE", - * "CHANNEL_CREATE", - * "CHANNEL_DELETE", - * "CHANNEL_UPDATE", - * "CHANNEL_PINS_UPDATE", - * "MESSAGE_CREATE", - * "MESSAGE_DELETE", - * "MESSAGE_UPDATE", - * "MESSAGE_DELETE_BULK", - * "MESSAGE_REACTION_ADD", - * "MESSAGE_REACTION_REMOVE", - * "MESSAGE_REACTION_REMOVE_ALL", - * "USER_UPDATE", - * "USER_NOTE_UPDATE", - * "PRESENCE_UPDATE", - * "VOICE_STATE_UPDATE", - * "TYPING_START", - * "VOICE_SERVER_UPDATE", - * "RELATIONSHIP_ADD", - * "RELATIONSHIP_REMOVE"] - * ``` + * The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events: + * - READY + * - GUILD_SYNC + * - GUILD_CREATE + * - GUILD_DELETE + * - GUILD_UPDATE + * - GUILD_MEMBER_ADD + * - GUILD_MEMBER_REMOVE + * - GUILD_MEMBER_UPDATE + * - GUILD_MEMBERS_CHUNK + * - GUILD_ROLE_CREATE + * - GUILD_ROLE_DELETE + * - GUILD_ROLE_UPDATE + * - GUILD_BAN_ADD + * - GUILD_BAN_REMOVE + * - CHANNEL_CREATE + * - CHANNEL_DELETE + * - CHANNEL_UPDATE + * - CHANNEL_PINS_UPDATE + * - MESSAGE_CREATE + * - MESSAGE_DELETE + * - MESSAGE_UPDATE + * - MESSAGE_DELETE_BULK + * - MESSAGE_REACTION_ADD + * - MESSAGE_REACTION_REMOVE + * - MESSAGE_REACTION_REMOVE_ALL + * - USER_UPDATE + * - USER_NOTE_UPDATE + * - PRESENCE_UPDATE + * - VOICE_STATE_UPDATE + * - TYPING_START + * - VOICE_SERVER_UPDATE + * - RELATIONSHIP_ADD + * - RELATIONSHIP_REMOVE * @typedef {string} WSEventType */ exports.WSEvents = { From 9323882a8dd2f0f799cf774489590a1b5f43e651 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sun, 27 Nov 2016 12:01:51 +0000 Subject: [PATCH 117/248] fix disabledEvents --- src/client/websocket/WebSocketManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 43d3e7155..16cd3d69d 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -65,7 +65,7 @@ class WebSocketManager extends EventEmitter { * @type {Object} */ this.disabledEvents = {}; - for (const event in client.options.disabledEvents) this.disabledEvents[event] = true; + for (const event of client.options.disabledEvents) this.disabledEvents[event] = true; this.first = true; } From b85a589a01d78a8ba836b3d8cfe7f405f89f32f7 Mon Sep 17 00:00:00 2001 From: acdenisSK Date: Sun, 27 Nov 2016 16:28:46 +0100 Subject: [PATCH 118/248] Add typings (#925) --- package.json | 2 + typings/index.d.ts | 761 ++++++++++++++++++++++++++++++++++++++++++ typings/tsconfig.json | 13 + 3 files changed, 776 insertions(+) create mode 100644 typings/index.d.ts create mode 100644 typings/tsconfig.json diff --git a/package.json b/package.json index 147904163..9ab5e3ae5 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "10.0.1", "description": "A powerful library for interacting with the Discord API", "main": "./src/index", + "types": "./typings/index.d.ts", "scripts": { "test": "eslint src && docgen --source src --custom docs/index.yml", "docs": "docgen --source src --custom docs/index.yml --output docs/docs.json", @@ -29,6 +30,7 @@ }, "homepage": "https://github.com/hydrabolt/discord.js#readme", "dependencies": { + "@types/node": "^6.0.51", "pako": "^1.0.3", "superagent": "^3.0.0", "tweetnacl": "^0.14.3", diff --git a/typings/index.d.ts b/typings/index.d.ts new file mode 100644 index 000000000..6538718fb --- /dev/null +++ b/typings/index.d.ts @@ -0,0 +1,761 @@ +// Type definitions for discord.js 10.0.1 +// Project: https://github.com/hydrabolt/discord.js +// Definitions by: acdenisSK (https://github.com/acdenisSK) + +declare module "discord.js" { + import { EventEmitter } from "events"; + import { Readable as ReadableStream } from "stream"; + import { ChildProcess } from "child_process"; + + export const version: string; + export class Client extends EventEmitter { + constructor(options?: ClientOptions); + email: string; + emojis: Collection; + guilds: Collection; + channels: Collection; + options: ClientOptions; + password: string; + readyAt: Date; + readyTimestamp: number; + status: number; + token: string; + uptime: number; + user: ClientUser; + users: Collection; + voiceConnections: Collection; + clearInterval(timeout: NodeJS.Timer): void; + clearTimeout(timeout: NodeJS.Timer): void; + destroy(): Promise; + fetchInvite(code: string): Promise; + fetchUser(id: string): Promise; + login(tokenOrEmail: string, password?: string): Promise; + setInterval(fn: Function, delay: number, ...args: any[]): NodeJS.Timer; + setTimeout(fn: Function, delay: number, ...args: any[]): NodeJS.Timer; + sweepMessages(lifetime?: number): number; + syncGuilds(guilds?: Guild[]): void; + on(event: string, listener: Function): this; + on(event: "debug", listener: (the: string) => void): this; + on(event: "disconnect", listener: () => void): this; + on(event: "error", listener: (error: Error) => void): this; + on(event: "guildBanAdd", listener: (user: User) => void): this; + on(event: "guildBanRemove", listener: (user: User) => void): this; + on(event: "guildCreate", listener: (guild: Guild) => void): this; + on(event: "guildDelete", listener: (guild: Guild) => void): this; + on(event: "guildEmojiCreate", listener: (emoji: Emoji) => void): this; + on(event: "guildEmojiDelete", listener: (emoji: Emoji) => void): this; + on(event: "guildEmojiUpdate", listener: (oldEmoji: Emoji, newEmoji: Emoji) => void): this; + on(event: "guildMemberAdd", listener: (member: GuildMember) => void): this; + on(event: "guildMemberAvailable", listener: (member: GuildMember) => void): this; + on(event: "guildMemberRemove", listener: (member: GuildMember) => void): this; + on(event: "guildMembersChunk", listener: (members: GuildMember[]) => void): this; + on(event: "guildMemberSpeaking", listener: (member: GuildMember, speaking: boolean) => void): this; + on(event: "guildMemberUpdate", listener: (oldMember: GuildMember, newMember: GuildMember) => void): this; + on(event: "roleCreate", listener: (role: Role) => void): this; + on(event: "roleDelete", listener: (role: Role) => void): this; + on(event: "roleUpdate", listener: (oldRole: Role, newRole: Role) => void): this; + on(event: "guildUnavailable", listener: (guild: Guild) => void): this; + on(event: "guildUpdate", listener: (oldGuild: Guild, newGuild: Guild) => void): this; + on(event: "channelCreate", listener: (channel: Channel) => void): this; + on(event: "channelDelete", listener: (channel: Channel) => void): this; + on(event: "channelPinsUpdate", listener: (channel: Channel, time: Date) => void): this; + on(event: "channelUpdate", listener: (oldChannel: Channel, newChannel: Channel) => void): this; + on(event: "message", listener: (message: Message) => void): this; + on(event: "messageDelete", listener: (message: Message) => void): this; + on(event: "messageDeleteBulk", listener: (messages: Collection) => void): this; + on(event: "messageUpdate", listener: (oldMessage: Message, newMessage: Message) => void): this; + on(event: "presenceUpdate", listener: (oldUser: User, newUser: User) => void): this; + on(event: "ready", listener: () => void): this; + on(event: "reconnecting", listener: () => void): this; + on(event: "typingStart", listener: (channel: Channel, user: User) => void): this; + on(event: "typingStop", listener: (channel: Channel, user: User) => void): this; + on(event: "userUpdate", listener: (oldClientUser: ClientUser, newClientUser: ClientUser) => void): this; + on(event: "voiceStateUpdate", listener: (oldMember: GuildMember, newMember: GuildMember) => void): this; + on(event: "warn", listener: (the: string) => void): this; + } + export class Webhook { + avatar: string; + client: Client; + guildID: string; + channelID: string; + id: string; + name: string; + token: string; + delete(): Promise; + edit(name: string, avatar: FileResolvable): Promise; + sendCode(lang: string, content: StringResolvable, options?: WebhookMessageOptions): Promise; + sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: WebhookMessageOptions): Promise; + sendMessage(content: StringResolvable, options?: WebhookMessageOptions): Promise; + sendSlackMessage(body: Object): Promise; + sendTTSMessage(content: StringResolvable, options?: WebhookMessageOptions): Promise; + } + class SecretKey { + key: Uint8Array; + } + class RequestHandler { // docs going nowhere again, yay + constructor(restManager: {}); + globalLimit: boolean; + queue: {}[]; + restManager: {}; + handle(); + push(request: {}); + } + export class WebhookClient extends Webhook { + contructor(id: string, token: string, options?: ClientOptions); + options: ClientOptions; + } + export class Emoji { + client: Client; + createdAt: Date; + createdTimestamp: number; + guild: Guild; + id: string; + managed: boolean; + name: string; + requiresColons: boolean; + roles: Collection; + url: string; + toString(): string; + } + export class ReactionEmoji { + id: string; + identifier: string; + name: string; + reaction: MessageReaction; + toString(): string; + } + export class ClientUser extends User { + email: string; + verified: boolean; + blocked: Collection; + friends: Collection; + addFriend(user: UserResolvable): Promise; + removeFriend(user: UserResolvable): Promise; + setAvatar(avatar: Base64Resolvable): Promise; + setEmail(email: string): Promise; + setPassword(password: string): Promise; + setStatus(status?: string): Promise; + setGame(game: string, streamingURL?: string): Promise; + setPresence(data: Object): Promise; + setUsername(username: string): Promise; + createGuild(name: string, region: string, icon?: FileResolvable): Promise; + } + export class Presence { + game: Game; + status: string; + equals(other: Presence): boolean; + } + export class Channel { + client: Client; + createdAt: Date; + createdTimestamp: number; + id: string; + type: string; + delete(): Promise; + } + export class DMChannel extends Channel { + lastMessageID: string; + messages: Collection; + recipient: User; + typing: boolean; + typingCount: number; + awaitMessages(filter: CollectorFilterFunction, options?: AwaitMessagesOptions): Promise>; + bulkDelete(messages: Collection | Message[] | number): Collection; + createCollector(filter: CollectorFilterFunction, options?: CollectorOptions): MessageCollector; + fetchMessage(messageID: string): Promise; + fetchMessages(options?: ChannelLogsQueryOptions): Promise>; + fetchPinnedMessages(): Promise>; + sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; + sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; + sendMessage(content: string, options?: MessageOptions): Promise; + sendTTSMessage(content: string, options?: MessageOptions): Promise; + startTyping(count?: number): void; + stopTyping(force?: boolean): void; + toString(): string; + } + export class GroupDMChannel extends Channel { + lastMessageID: string; + messages: Collection; + recipients: Collection; + owner: User; + typing: boolean; + typingCount: number; + awaitMessages(filter: CollectorFilterFunction, options?: AwaitMessagesOptions): Promise>; + bulkDelete(messages: Collection | Message[] | number): Collection; + createCollector(filter: CollectorFilterFunction, options?: CollectorOptions): MessageCollector; + fetchMessage(messageID: string): Promise; + fetchMessages(options?: ChannelLogsQueryOptions): Promise>; + fetchPinnedMessages(): Promise>; + sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; + sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; + sendMessage(content: string, options?: MessageOptions): Promise; + sendTTSMessage(content: string, options?: MessageOptions): Promise; + startTyping(count?: number): void; + stopTyping(force?: boolean): void; + toString(): string; + } + export class GuildChannel extends Channel { + guild: Guild; + name: string; + permissionOverwrites: Collection; + position: number; + createInvite(options?: InviteOptions): Promise; + equals(channel: GuildChannel): boolean; + overwritePermissions(userOrRole: Role | User, options: PermissionOverwriteOptions): Promise; + permissionsFor(member: GuildMemberResolvable): EvaluatedPermissions; + setName(name: string): Promise; + setPosition(position: number): Promise; + setTopic(topic: string): Promise; + toString(): string; + } + export class TextChannel extends GuildChannel { + lastMessageID: string; + members: Collection; + messages: Collection; + topic: string; + typing: boolean; + typingCount: number; + awaitMessages(filter: CollectorFilterFunction, options?: AwaitMessagesOptions): Promise>; + bulkDelete(messages: Collection | Message[] | number): Collection; + createCollector(filter: CollectorFilterFunction, options?: CollectorOptions): MessageCollector; + fetchMessage(messageID: string): Promise; + fetchMessages(options?: ChannelLogsQueryOptions): Promise>; + fetchPinnedMessages(): Promise>; + sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; + sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; + sendMessage(content: string, options?: MessageOptions): Promise; + sendTTSMessage(content: string, options?: MessageOptions): Promise; + startTyping(count?: number): void; + stopTyping(force?: boolean): void; + } + export class MessageCollector extends EventEmitter { + constructor(channel: Channel, filter: CollectorFilterFunction, options?: CollectorOptions); + collected: Collection; + filter: CollectorFilterFunction; + channel: Channel; + options: CollectorOptions; + stop(reason?: string): void; + on(event: "end", listener: (collection: Collection, reason: string) => void): this; + on(event: "message", listener: (message: Message, collector: MessageCollector) => void): this; + } + export class Game { + name: string; + streaming: boolean; + url: string; + type: number; + equals(other: Game): boolean; + } + export class PermissionOverwrites { + channel: GuildChannel; + id: string; + type: string; + delete(): Promise; + } + export class Guild { + afkChannelID: string; + afkTimeout: number; + available: boolean; + client: Client; + createdAt: Date; + createdTimestamp: number; + defaultChannel: GuildChannel; + embedEnabled: boolean; + emojis: Collection; + features: Object[]; + channels: Collection; + icon: string; + iconURL: string; + id: string; + joinDate: Date; + large: boolean; + memberCount: number; + members: Collection; + name: string; + owner: GuildMember; + ownerID: string; + region: string; + roles: Collection; + splash: string; + verificationLevel: number; + voiceConnection: VoiceConnection; + ban(user: GuildMember, deleteDays?: number): Promise; + createChannel(name: string, type: "text" | "voice"): Promise; + createRole(data?: RoleData): Promise; + delete(): Promise; + edit(data: {}): Promise; + equals(guild: Guild): boolean; + fetchBans(): Promise>; + fetchInvites(): Promise>; + fetchMember(user: UserResolvable): Promise; + fetchMembers(query?: string): Promise; + leave(): Promise; + member(user: UserResolvable): GuildMember; + pruneMembers(days: number, dry?: boolean): Promise; + setAFKChannel(afkChannel: ChannelResovalble): Promise; + setAFKTimeout(afkTimeout: number): Promise; + setIcon(icon: Base64Resolvable): Promise; + setName(name: string): Promise; + setOwner(owner: GuildMemberResolvable): Promise; + setRegion(region: string): Promise; + setSplash(splash: Base64Resolvable): Promise; + setVerificationLevel(level: number): Promise; + sync(): void; + toString(): string; + unban(user: UserResolvable): Promise; + } + export class GuildMember { + bannable: boolean; + client: Client; + deaf: boolean; + guild: Guild; + highestRole: Role; + id: string; + joinDate: Date; + kickable: boolean; + mute: boolean; + nickname: string; + permissions: EvaluatedPermissions; + roles: Collection; + selfDeaf: boolean; + selfMute: boolean; + serverDeaf: boolean; + serverMute: boolean; + speaking: boolean; + user: User; + voiceChannel: VoiceChannel; + voiceChannelID: string; + voiceSessionID: string; + addRole(role: Role | string): Promise; + addRoles(roles: Collection | Role[] | string[]): Promise; + ban(deleteDays?: number): Promise; + deleteDM(): Promise; + edit(data: {}): Promise; + hasPermission(permission: PermissionResolvable, explicit?: boolean): boolean; + hasPermissions(permission: Permissions[], explicit?: boolean): boolean; + kick(): Promise; + permissionsIn(channel: ChannelResovalble): EvaluatedPermissions; + removeRole(role: Role | string): Promise; + removeRoles(roles: Collection | Role[] | string[]): Promise; + sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; + sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; + sendMessage(content: string, options?: MessageOptions): Promise; + sendTTSMessage(content: string, options?: MessageOptions): Promise; + setDeaf(deaf: boolean): Promise; + setMute(mute: boolean): Promise; + setNickname(nickname: string): Promise; + setRoles(roles: Collection | Role[] | string[]): Promise; + setVoiceChannel(voiceChannel: ChannelResovalble): Promise; + toString(): string; + } + export class User { + avatar: string; + avatarURL: string; + bot: boolean; + client: Client; + createdAt: Date; + createdTimestamp: number; + discriminator: string; + presence: Presence; + id: string; + status: string; + username: string; + block(): Promise; + unblock(): Promise; + fetchProfile(): Promise; + deleteDM(): Promise; + equals(user: User): boolean; + sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; + sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; + sendMessage(content: string, options?: MessageOptions): Promise; + sendTTSMessage(content: string, options?: MessageOptions): Promise; + toString(): string; + } + export class PartialGuildChannel { + client: Client; + id: string; + name: string; + type: string; + } + export class PartialGuild { + client: Client; + icon: string; + id: string; + name: string; + splash: string; + } + class PendingVoiceConnection { + data: Object; + deathTimer: NodeJS.Timer; + channel: VoiceChannel; + voiceManager: ClientVoiceManager; + setSessionID(sessionID: string); + setTokenAndEndpoint(token: string, endpoint: string); + upgrade(): VoiceConnection; + } + export class OAuth2Application { + client: Client; + createdAt: Date; + createdTimestamp: number; + description: string; + icon: string; + iconURL: string; + id: string; + name: string; + rpcOrigins: string[]; + toString(): string; + } + export class ClientOAuth2Application extends OAuth2Application { + flags: number; + owner: User; + } + export class Message { + attachments: Collection; + author: User; + channel: TextChannel | DMChannel | GroupDMChannel; + cleanContent: string; + client: Client; + content: string; + createdAt: Date; + createdTimestamp: number; + deletable: boolean; + editable: boolean; + editedAt: Date; + editedTimestamp: number; + edits: Message[]; + embeds: MessageEmbed[]; + guild: Guild; + id: string; + member: GuildMember; + mentions: { + users: Collection; + roles: Collection; + channels: Collection; + everyone: boolean; + }; + nonce: string; + pinnable: boolean; + pinned: boolean; + reactions: Collection; + system: boolean; + tts: boolean; + type: string; + addReaction(emoji: string): MessageReaction; // Not really documented but still worth using/making typings for it. + delete(timeout?: number): Promise; + edit(content: StringResolvable): Promise; + editCode(lang: string, content: StringResolvable): Promise; + equals(message: Message, rawData: Object): boolean; + isMentioned(data: GuildChannel | User | Role | string): boolean; + pin(): Promise; + reply(content: StringResolvable, options?: MessageOptions): Promise; + toString(): string; + unpin(): Promise; + } + export class MessageEmbed { + author: MessageEmbedAuthor; + client: Client; + description: string; + message: Message; + provider: MessageEmbedProvider; + thumbnail: MessageEmbedThumbnail; + title: string; + type: string; + url: string; + } + export class MessageEmbedThumbnail { + embed: MessageEmbed; + height: number; + proxyURL: string; + url: string; + width: number; + } + export class MessageEmbedProvider { + embed: MessageEmbed; + name: string; + url: string; + } + export class MessageEmbedAuthor { + embed: MessageEmbed; + name: string; + url: string; + } + export class MessageAttachment { + client: Client; + filename: string; + filesize: number; + height: number; + id: string; + message: Message; + proxyURL: string; + url: string; + width: number; + } + export class MessageReaction { + count: number; + emoji: Emoji | ReactionEmoji; + me: boolean; + message: Message; + users: Collection; + fetchUsers(limit?: number): Promise>; + remove(user?: UserResolvable): Promise; + } + export class Invite { + client: Client; + code: string; + createdAt: Date; + createdTimestamp: number; + guild: Guild | PartialGuild; + channel: GuildChannel | PartialGuildChannel; + inviter: User; + maxUses: number; + temporary: boolean; + url: string; + uses: number; + delete(): Promise; + toString(): string; + } + export class VoiceChannel extends GuildChannel { + bitrate: number; + connection: VoiceConnection; + members: Collection; + userLimit: number; + join(): Promise; + leave(): null; + setBitrate(bitrate: number): Promise; + } + export class Shard { + id: string; + manager: ShardingManager; + process: ChildProcess; + eval(script: string): Promise; + fetchClientValue(prop: string): Promise; + send(message: any): Promise; + } + export class ShardingManager extends EventEmitter { + constructor(file: string, options?: { + totalShards?: number; + respawn?: boolean; + shardArgs?: string[]; + token?: string; + }); + file: string; + respawn: boolean; + shardArgs: string[]; + shards: Collection; + totalShards: number; + broadcast(message: any): Promise; + broadcastEval(script: string): Promise; + createShard(id: number): Promise; + fetchClientValues(prop: string): Promise; + spawn(amount?: number, delay?: number): Promise>; + on(event: "launch", listener: (shard: Shard) => void): this; + } + export class ShardClientUtil { + contructor(client: Client); + id: number; + count: number; + broadcastEval(script: string): Promise; + fetchClientValues(prop: string): Promise; + send(message: any): Promise; + singleton(client: Client): ShardClientUtil; + } + export class UserConnection { + id: string; + integrations: Object[]; + name: string; + revoked: boolean; + type: string; + user: User; + } + export class UserProfile { + client: Client; + connections: Collection; + mutualGuilds: Collection; + user: User; + } + export class StreamDispatcher extends EventEmitter { + passes: number; + time: number; + totalStreamTime: number; + volume: number; + end(): void; + pause(): void; + resume(): void; + setVolume(volume: number): void; + setVolumeDecibels(db: number): void; + setVolumeLogarithmic(value: number): void; + on(event: "debug", listener: (information: string) => void): this; + on(event: "end", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "speaking", listener: (value: boolean) => void): this; + on(event: "start", listener: () => void): this; + } + interface Permissions { + CREATE_INSTANT_INVITE: boolean; + KICK_MEMBERS: boolean; + BAN_MEMBERS: boolean; + ADMINISTRATOR: boolean; + MANAGE_CHANNELS: boolean; + MANAGE_GUILD: boolean; + READ_MESSAGES: boolean; + SEND_MESSAGES: boolean; + SEND_TTS_MESSAGES: boolean; + MANAGE_MESSAGES: boolean; + EMBED_LINKS: boolean; + ATTACH_FILES: boolean; + READ_MESSAGE_HISTORY: boolean; + MENTION_EVERYONE: boolean; + USE_EXTERNAL_EMOJIS: boolean; + CONNECT: boolean; + SPEAK: boolean; + MUTE_MEMBERS: boolean; + DEAFEN_MEMBERS: boolean; + MOVE_MEMBERS: boolean; + USE_VAD: boolean; + CHANGE_NICKNAME: boolean; + MANAGE_NICKNAMES: boolean; + MANAGE_ROLES: boolean; + MANAGE_WEBHOOKS: boolean; + } + export class EvaluatedPermissions { + member: GuildMember; + raw: number; + hasPermission(permission: PermissionResolvable, explicit?: boolean): boolean; + hasPermissions(permission: PermissionResolvable[], explicit?: boolean): boolean; + serialize(): Permissions; + } + export class Role { + client: Client; + color: number; + createdAt: Date; createdTimestamp: number; + guild: Guild; + hexColor: string; + hoist: boolean; + id: string; + managed: boolean; + members: Collection; + mentionable: boolean; + name: string; + permissions: number; + position: number; + delete(): Promise; + edit(data: RoleData): Promise; + equals(role: Role): boolean; + hasPermission(permission: PermissionResolvable, explicit?: boolean): boolean; + hasPermissions(permissions: PermissionResolvable[], explicit?: boolean): boolean; + serialize(): Permissions; + setColor(color: string | number): Promise; + setHoist(hoist: boolean): Promise; + setName(name: string): Promise; + setPermissions(permissions: string[]): Promise; + setPosition(position: number): Promise; + toString(): string; + } + export class ClientVoiceManager { + client: Client; + connections: Collection; + pending: Collection; + joinChannel(channel: VoiceChannel): Promise; + sendVoiceStateUpdate(channel: VoiceChannel, options?: Object); + } + class AudioPlayer extends EventEmitter { + dispatcher: StreamDispatcher; + voiceConnection: VoiceConnection; + } + export class VoiceConnection extends EventEmitter { + authentication: Object; + channel: VoiceChannel; + player: AudioPlayer; + receivers: VoiceReceiver[]; + sockets: Object; + ssrcMap: Map; + voiceManager: ClientVoiceManager; + createReceiver(): VoiceReceiver; + disconnect(); + playConvertedStream(stream: ReadableStream, options?: StreamOptions): StreamDispatcher; + playFile(file: string, options?: StreamOptions): StreamDispatcher; + playStream(stream: ReadableStream, options?: StreamOptions): StreamDispatcher; + on(event: "disconnect", listener: (error: Error) => void): this; + on(event: "error", listener: (error: Error) => void): this; + on(event: "ready", listener: () => void): this; + on(event: "speaking", listener: (user: User, speaking: boolean) => void): this; + } + export class VoiceReceiver extends EventEmitter { + connection: VoiceConnection; + destroyed: boolean; + createOpusStream(user: User): ReadableStream; + createPCMStream(user: User): ReadableStream; + destroy(): void; + recreate(): void; + on(event: "opus", listener: (user: User, buffer: Buffer) => void): this; + on(event: "pcm", listener: (user: User, buffer: Buffer) => void): this; + on(event: "warn", listener: (message: string) => void): this; + } + export class Collection extends Map { + array(): value[]; + concat(...collections: Collection[]): Collection; + deleteAll(): Promise; + every(fn: Function, thisArg?: Object): boolean; + exists(prop: string, value: any): boolean; + filter(fn: Function, thisArg?: Object): Collection; + filterArray(fn: Function, thisArg?: Object): value[]; + find(propOrFn: string | Function, value?: any): value; + findAll(prop: string, value: any): value[]; + findKey(propOrFn: string | Function, value?: any): key; + first(): value; + firstKey(): key; + keyArray(): key[]; + last(): value; + lastKey(): key; + map(fn: Function, thisArg?: Object): any[]; + random(): value; + randomKey(): key; + reduce(fn: Function, startVal?: any): any; + some(fn: Function, thisArg?: Object): boolean; + } + type CollectorOptions = { time?: number; max?: number }; + type AwaitMessagesOptions = { time?: number; max?: number; errors?: string[]; }; + type Base64Resolvable = Buffer | string; + type CollectorFilterFunction = (message: Message, collector: MessageCollector) => boolean; + type FileResolvable = Buffer | string; + type GuildMemberResolvable = GuildMember | User; + type GuildResolvable = Guild; + type ChannelLogsQueryOptions = { limit?: number; before?: string; after?: string; around?: string }; + type ChannelResovalble = Channel | Guild | Message | string; + type InviteOptions = { temporary?: boolean; maxAge?: number; maxUses?: number; }; + type MessageOptions = { tts?: boolean; nonce?: string; disableEveryone?: boolean; split?: boolean | SplitOptions; }; + type PermissionOverwriteOptions = Permissions; + type PermissionResolvable = string | string[] | number[]; + type SplitOptions = { maxLength?: number; char?: string; prepend?: string; append?: string; }; + type StreamOptions = { seek?: number; volume?: number; passes?: number; }; + type StringResolvable = any[] | string | any; + type UserResolvable = User | string | Message | Guild | GuildMember; + type WebSocketOptions = { large_threshold?: number; compress?: boolean; }; + type ClientOptions = { + apiRequestMethod?: string; + shardId?: number; + shardCount?: number; + maxMessageCache?: number; + messageCacheLifetime?: number; + messageSweepInterval?: number; + fetchAllMembers?: boolean; + disableEveryone?: boolean; + restWsBridgeTimeout?: number; + ws?: WebSocketOptions; + }; + type WebhookMessageOptions = { + tts?: boolean; + disableEveryone?: boolean; + }; + type WebhookOptions = { + large_threshold?: number; + compress?: boolean; + }; + type RoleData = { + name?: string; + color?: number | string; + hoist?: boolean; + position?: number; + permissions?: string[]; + mentionable?: boolean; + }; +} diff --git a/typings/tsconfig.json b/typings/tsconfig.json new file mode 100644 index 000000000..8c9dfd770 --- /dev/null +++ b/typings/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "noImplicitAny": false, + "strictNullChecks": true, + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts" + ] +} \ No newline at end of file From 6afd80cf533675bc7dce0ec74127e1c6550f5dee Mon Sep 17 00:00:00 2001 From: Hackzzila Date: Sun, 27 Nov 2016 13:59:50 -0600 Subject: [PATCH 119/248] Make uws a peer dep, and readd ws (#928) * Make uws a peer dep, and readd ws * if -> else if --- package.json | 6 ++++-- src/client/voice/VoiceWebSocket.js | 8 +++++++- src/client/websocket/WebSocketManager.js | 14 +++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9ab5e3ae5..7bc11e090 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,12 @@ "pako": "^1.0.3", "superagent": "^3.0.0", "tweetnacl": "^0.14.3", - "uws": "^0.11.1" + "ws": "^1.1.1" }, "peerDependencies": { "node-opus": "^0.2.0", - "opusscript": "^0.0.1" + "opusscript": "^0.0.1", + "uws": "^0.11.1" }, "devDependencies": { "bufferutil": "^1.2.1", @@ -55,6 +56,7 @@ "node": ">=6.0.0" }, "browser": { + "ws": false, "uws": false, "opusscript": false, "node-opus": false, diff --git a/src/client/voice/VoiceWebSocket.js b/src/client/voice/VoiceWebSocket.js index 6c7303e27..bafa5dde3 100644 --- a/src/client/voice/VoiceWebSocket.js +++ b/src/client/voice/VoiceWebSocket.js @@ -1,8 +1,14 @@ -const WebSocket = require('uws'); const Constants = require('../../util/Constants'); const SecretKey = require('./util/SecretKey'); const EventEmitter = require('events').EventEmitter; +let WebSocket; +try { + WebSocket = require('uws'); +} catch (err) { + WebSocket = require('ws'); +} + /** * Represents a Voice Connection's WebSocket * @extends {EventEmitter} diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 16cd3d69d..6493fc4dc 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -1,10 +1,21 @@ const browser = typeof window !== 'undefined'; -const WebSocket = browser ? window.WebSocket : require('uws'); // eslint-disable-line no-undef const EventEmitter = require('events').EventEmitter; const Constants = require('../../util/Constants'); const pako = require('pako'); +const zlib = require('zlib'); const PacketManager = require('./packets/WebSocketPacketManager'); +let WebSocket; +if (browser) { + WebSocket = window.WebSocket; // eslint-disable-line no-undef +} else { + try { + WebSocket = require('uws'); + } catch (err) { + WebSocket = require('ws'); + } +} + /** * The WebSocket Manager of the Client * @private @@ -230,6 +241,7 @@ class WebSocketManager extends EventEmitter { */ parseEventData(data) { if (data instanceof ArrayBuffer) data = pako.inflate(data, { to: 'string' }); + else if (data instanceof Buffer) data = zlib.inflateSync(data).toString(); return JSON.parse(data); } From 1aed3de6473ed488f0ad61fd6b229f0b16495d7b Mon Sep 17 00:00:00 2001 From: Hackzzila Date: Sun, 27 Nov 2016 14:22:09 -0600 Subject: [PATCH 120/248] Add note about uws in readme (#929) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6fc5c584b..90165433e 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Usability and performance are key focuses of discord.js, and it also has nearly Without voice support: `npm install discord.js --save` With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save` With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` +With fast websockets ([uws](https://www.npmjs.com/package/uws)) `npm install discord.js uws --save` The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus. Using opusscript is only recommended for development environments where node-opus is tough to get working. From cc9e4842763bd568dc87093f941255317a1c89f9 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 27 Nov 2016 16:23:07 -0500 Subject: [PATCH 121/248] Fix a thing Gus noticed --- src/structures/Guild.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index bfa8406f8..296adb4b8 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -728,7 +728,7 @@ class Guild { const member = new GuildMember(this, guildUser); this.members.set(member.id, member); - if (this._rawVoiceStates && this._rawVoiceStates.get(member.user.id)) { + if (this._rawVoiceStates && this._rawVoiceStates.has(member.user.id)) { const voiceState = this._rawVoiceStates.get(member.user.id); member.serverMute = voiceState.mute; member.serverDeaf = voiceState.deaf; From d9400ba289e2252e3e79db5ed14373f664e498c6 Mon Sep 17 00:00:00 2001 From: Hackzzila Date: Sun, 27 Nov 2016 16:10:57 -0600 Subject: [PATCH 122/248] fix readme spacing bug (#930) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 90165433e..65ec6468a 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Usability and performance are key focuses of discord.js, and it also has nearly Without voice support: `npm install discord.js --save` With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save` -With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` +With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` With fast websockets ([uws](https://www.npmjs.com/package/uws)) `npm install discord.js uws --save` The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus. From 5fb4e257c8b44d74d1e3cb78932e4d42388e0422 Mon Sep 17 00:00:00 2001 From: Vap0r1ze Date: Sun, 27 Nov 2016 17:38:42 -0500 Subject: [PATCH 123/248] Escape the backslash (#931) --- src/structures/Message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 39a40b6b4..d0351b9e5 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -439,7 +439,7 @@ class Message { * @returns {Promise} * @example * // reply to a message - * message.reply('Hey, I\'m a reply!') + * message.reply('Hey, I\\'m a reply!') * .then(msg => console.log(`Sent a reply to ${msg.author}`)) * .catch(console.error); */ From 8e7cb7fc4e9b79e24b627636227293ce141ddcc4 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 27 Nov 2016 18:31:27 -0500 Subject: [PATCH 124/248] Revert "Escape the backslash (#931)" This reverts commit 5fb4e257c8b44d74d1e3cb78932e4d42388e0422. --- src/structures/Message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index d0351b9e5..39a40b6b4 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -439,7 +439,7 @@ class Message { * @returns {Promise} * @example * // reply to a message - * message.reply('Hey, I\\'m a reply!') + * message.reply('Hey, I\'m a reply!') * .then(msg => console.log(`Sent a reply to ${msg.author}`)) * .catch(console.error); */ From 769ea5b50f4c954283f7ab3d84e1c1976527fed7 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 27 Nov 2016 19:39:10 -0500 Subject: [PATCH 125/248] Add more detail to error message --- src/client/ClientDataResolver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 2a668d9f3..b99421d8f 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -251,7 +251,7 @@ class ClientDataResolver { req.end((err, res) => { if (err) return reject(err); if (this.client.browser) return resolve(convertArrayBuffer(res.xhr.response)); - if (!(res.body instanceof Buffer)) return reject(new TypeError('Body is not a Buffer')); + if (!(res.body instanceof Buffer)) return reject(new TypeError('The response body isn\'t a Buffer.')); return resolve(res.body); }); } else { From 6dc93a0184c1877298177fd9831f6e22078576d1 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 27 Nov 2016 20:03:44 -0500 Subject: [PATCH 126/248] Simplify Role#setPosition --- src/structures/Role.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/structures/Role.js b/src/structures/Role.js index 7846c6cc5..fe4fd85e0 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -241,8 +241,7 @@ class Role { * .catch(console.error); */ setPosition(position) { - return new Promise( - (resolve, reject) => this.guild.setRolePosition(this, position).then(() => resolve(this), reject)); + return this.guild.setRolePosition(this, position).then(() => this); } /** From 57af84bc955ea18d8d56028686074681fbb2f424 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 27 Nov 2016 20:09:41 -0500 Subject: [PATCH 127/248] Clean up Guild#setRolePosition slightly --- src/structures/Guild.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 296adb4b8..07c46a0c3 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -656,15 +656,13 @@ class Guild { if (role instanceof Role) { role = role.id; } else if (typeof role !== 'string') { - return Promise.reject(new Error('Supplied role is not a role or string')); + return Promise.reject(new Error('Supplied role is not a role or string.')); } position = Number(position); - if (isNaN(position)) { - return Promise.reject(new Error('Supplied position is not a number')); - } + if (isNaN(position)) return Promise.reject(new Error('Supplied position is not a number.')); - const updatedRoles = this.roles.array().map(r => ({ + const updatedRoles = this.roles.map(r => ({ id: r.id, position: r.id === role ? position : r.position < position ? r.position : r.position + 1, })); From b3358245701280120cc5e892d38cb03c498bce30 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Mon, 28 Nov 2016 15:41:57 +0000 Subject: [PATCH 128/248] potentially fix #910, guild sync no longer assumes unknown users are new to the guild --- src/client/actions/GuildSync.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/actions/GuildSync.js b/src/client/actions/GuildSync.js index d9b8dab02..7b94ec83c 100644 --- a/src/client/actions/GuildSync.js +++ b/src/client/actions/GuildSync.js @@ -17,7 +17,7 @@ class GuildSync extends Action { if (member) { guild._updateMember(member, syncMember); } else { - guild._addMember(syncMember); + guild._addMember(syncMember, false); } } } From 1933451d2f9c48a7261628dfb719a867033bc047 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Mon, 28 Nov 2016 16:14:34 +0000 Subject: [PATCH 129/248] fix reconnecting --- src/client/websocket/WebSocketManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 6493fc4dc..3726dc837 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -313,7 +313,7 @@ class WebSocketManager extends EventEmitter { * Tries to reconnect the client, changing the status to Constants.Status.RECONNECTING. */ tryReconnect() { - if (this.status === Constants.Status.RECONNECTING) return; + if (this.status === Constants.Status.RECONNECTING || this.status === Constants.Status.CONNECTING) return; this.status = Constants.Status.RECONNECTING; this.ws.close(); this.packetManager.handleQueue(); From ac68e9f077e5db803990a90a3ab879fd32a2dcac Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 28 Nov 2016 23:35:18 -0500 Subject: [PATCH 130/248] Switch Travis to Ubuntu 14.04 --- .travis.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index de3f3d9dd..06b247f7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,5 @@ env: global: - ENCRYPTION_LABEL: "af862fa96d3e" - COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com" - - CXX=g++-4.9 -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.9 +dist: trusty +sudo: false From 6043a1f83a70d95803c0f9c9ef8a4edc8dc09a97 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Wed, 30 Nov 2016 20:31:07 +0000 Subject: [PATCH 131/248] Fix #937 (guild member presence before READY) --- src/structures/Guild.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 07c46a0c3..ccf146634 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -40,6 +40,12 @@ class Guild { */ this.roles = new Collection(); + /** + * A collection of presences in this guild + * @type {Collection} + */ + this.presences = new Collection(); + if (!data) return; if (data.unavailable) { /** @@ -101,12 +107,6 @@ class Guild { */ this.large = data.large || this.large; - /** - * A collection of presences in this guild - * @type {Collection} - */ - this.presences = new Collection(); - /** * An array of guild features. * @type {Object[]} From edfb27f4285e585e414367e01ea00adaa241c6e7 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Thu, 1 Dec 2016 18:33:15 -0600 Subject: [PATCH 132/248] switch to cdn endpoints, add guild splash url (#932) --- src/structures/Emoji.js | 2 +- src/structures/Guild.js | 10 ++++++++++ src/util/Constants.js | 6 +++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js index a3f12a867..ca533f929 100644 --- a/src/structures/Emoji.js +++ b/src/structures/Emoji.js @@ -87,7 +87,7 @@ class Emoji { * @readonly */ get url() { - return `${Constants.Endpoints.CDN}/emojis/${this.id}.png`; + return Constants.Endpoints.emoji(this.id); } /** diff --git a/src/structures/Guild.js b/src/structures/Guild.js index ccf146634..f50c1bd98 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -241,6 +241,16 @@ class Guild { return Constants.Endpoints.guildIcon(this.id, this.icon); } + /** + * Gets the URL to this guild's splash (if it has one, otherwise it returns null) + * @type {?string} + * @readonly + */ + get splashURL() { + if (!this.splash) return null; + return Constants.Endpoints.guildSplash(this.id, this.splash); + } + /** * The owner of the guild * @type {GuildMember} diff --git a/src/util/Constants.js b/src/util/Constants.js index 7b3aef275..98cec7918 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -95,7 +95,8 @@ const Endpoints = exports.Endpoints = { // guilds guilds: `${API}/guilds`, guild: (guildID) => `${Endpoints.guilds}/${guildID}`, - guildIcon: (guildID, hash) => `${Endpoints.guild(guildID)}/icons/${hash}.jpg`, + guildIcon: (guildID, hash) => `${Endpoints.CDN}/icons/${guildID}/${hash}.jpg`, + guildSplash: (guildID, hash) => `${Endpoints.CDN}/splashes/${guildID}/${hash}.jpg`, guildPrune: (guildID) => `${Endpoints.guild(guildID)}/prune`, guildEmbed: (guildID) => `${Endpoints.guild(guildID)}/embed`, guildInvites: (guildID) => `${Endpoints.guild(guildID)}/invites`, @@ -136,6 +137,9 @@ const Endpoints = exports.Endpoints = { // oauth myApplication: `${API}/oauth2/applications/@me`, getApp: (id) => `${API}/oauth2/authorize?client_id=${id}`, + + // emoji + emoji: (emojiID) => `${Endpoints.CDN}/emojis/${emojiID}.png`, }; exports.Status = { From 2488e1a00f175efd7e835d1669abbaafdddb198e Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Fri, 2 Dec 2016 19:52:27 -0500 Subject: [PATCH 133/248] Improved the definition of structures' client properties --- src/structures/Channel.js | 5 +++-- src/structures/Emoji.js | 5 +++-- src/structures/Guild.js | 5 +++-- src/structures/GuildMember.js | 5 +++-- src/structures/Invite.js | 5 +++-- src/structures/Message.js | 5 +++-- src/structures/MessageAttachment.js | 5 +++-- src/structures/MessageEmbed.js | 5 +++-- src/structures/OAuth2Application.js | 5 +++-- src/structures/PartialGuild.js | 5 +++-- src/structures/PartialGuildChannel.js | 5 +++-- src/structures/Role.js | 5 +++-- src/structures/User.js | 5 +++-- src/structures/UserProfile.js | 5 +++-- src/structures/Webhook.js | 7 ++++--- 15 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/structures/Channel.js b/src/structures/Channel.js index 644748891..b37b14b41 100644 --- a/src/structures/Channel.js +++ b/src/structures/Channel.js @@ -5,10 +5,11 @@ class Channel { constructor(client, data) { /** * The client that instantiated the Channel + * @name Channel#client * @type {Client} + * @readonly */ - this.client = client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: client }); /** * The type of the channel, either: diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js index ca533f929..a1823ba8f 100644 --- a/src/structures/Emoji.js +++ b/src/structures/Emoji.js @@ -8,10 +8,11 @@ class Emoji { constructor(guild, data) { /** * The Client that instantiated this object + * @name Emoji#client * @type {Client} + * @readonly */ - this.client = guild.client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: guild.client }); /** * The guild this emoji is part of diff --git a/src/structures/Guild.js b/src/structures/Guild.js index f50c1bd98..81dddeeec 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -17,10 +17,11 @@ class Guild { constructor(client, data) { /** * The Client that created the instance of the the Guild. + * @name Guild#client * @type {Client} + * @readonly */ - this.client = client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: client }); /** * A collection of members that are in this guild. The key is the member's ID, the value is the member. diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index d0513031f..d1e4867c8 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -13,10 +13,11 @@ class GuildMember { constructor(guild, data) { /** * The Client that instantiated this GuildMember + * @name GuildMember#client * @type {Client} + * @readonly */ - this.client = guild.client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: guild.client }); /** * The guild that this member is part of diff --git a/src/structures/Invite.js b/src/structures/Invite.js index 7da9bc93f..b4b34dafd 100644 --- a/src/structures/Invite.js +++ b/src/structures/Invite.js @@ -31,10 +31,11 @@ class Invite { constructor(client, data) { /** * The client that instantiated the invite + * @name Invite#client * @type {Client} + * @readonly */ - this.client = client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: client }); this.setup(data); } diff --git a/src/structures/Message.js b/src/structures/Message.js index 39a40b6b4..e015043da 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -12,10 +12,11 @@ class Message { constructor(channel, data, client) { /** * The Client that instantiated the Message + * @name Message#client * @type {Client} + * @readonly */ - this.client = client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: client }); /** * The channel that the message was sent in diff --git a/src/structures/MessageAttachment.js b/src/structures/MessageAttachment.js index 0c87d3a75..29dfb524e 100644 --- a/src/structures/MessageAttachment.js +++ b/src/structures/MessageAttachment.js @@ -5,10 +5,11 @@ class MessageAttachment { constructor(message, data) { /** * The Client that instantiated this MessageAttachment. + * @name MessageAttachment#client * @type {Client} + * @readonly */ - this.client = message.client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: message.client }); /** * The message this attachment is part of. diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js index 2790a679c..9dd0a9a0f 100644 --- a/src/structures/MessageEmbed.js +++ b/src/structures/MessageEmbed.js @@ -5,10 +5,11 @@ class MessageEmbed { constructor(message, data) { /** * The client that instantiated this embed + * @name MessageEmbed#client * @type {Client} + * @readonly */ - this.client = message.client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: message.client }); /** * The message this embed is part of diff --git a/src/structures/OAuth2Application.js b/src/structures/OAuth2Application.js index 1bd740b1c..b7c728581 100644 --- a/src/structures/OAuth2Application.js +++ b/src/structures/OAuth2Application.js @@ -5,10 +5,11 @@ class OAuth2Application { constructor(client, data) { /** * The client that instantiated the application + * @name OAuth2Application#client * @type {Client} + * @readonly */ - this.client = client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: client }); this.setup(data); } diff --git a/src/structures/PartialGuild.js b/src/structures/PartialGuild.js index aff53cd84..407212e2f 100644 --- a/src/structures/PartialGuild.js +++ b/src/structures/PartialGuild.js @@ -12,10 +12,11 @@ class PartialGuild { constructor(client, data) { /** * The Client that instantiated this PartialGuild + * @name PartialGuild#client * @type {Client} + * @readonly */ - this.client = client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: client }); this.setup(data); } diff --git a/src/structures/PartialGuildChannel.js b/src/structures/PartialGuildChannel.js index 87d54f540..e58a6bb91 100644 --- a/src/structures/PartialGuildChannel.js +++ b/src/structures/PartialGuildChannel.js @@ -11,10 +11,11 @@ class PartialGuildChannel { constructor(client, data) { /** * The Client that instantiated this PartialGuildChannel + * @name PartialGuildChannel#client * @type {Client} + * @readonly */ - this.client = client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: client }); this.setup(data); } diff --git a/src/structures/Role.js b/src/structures/Role.js index fe4fd85e0..c56035eb6 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -7,10 +7,11 @@ class Role { constructor(guild, data) { /** * The client that instantiated the role + * @name Role#client * @type {Client} + * @readonly */ - this.client = guild.client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: guild.client }); /** * The guild that the role belongs to diff --git a/src/structures/User.js b/src/structures/User.js index 28e8f73b5..d3218cf25 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -10,10 +10,11 @@ class User { constructor(client, data) { /** * The Client that created the instance of the the User. + * @name User#client * @type {Client} + * @readonly */ - this.client = client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: client }); if (data) this.setup(data); } diff --git a/src/structures/UserProfile.js b/src/structures/UserProfile.js index d47ac922f..69f05c7ae 100644 --- a/src/structures/UserProfile.js +++ b/src/structures/UserProfile.js @@ -14,10 +14,11 @@ class UserProfile { /** * The Client that created the instance of the the UserProfile. + * @name UserProfile#client * @type {Client} + * @readonly */ - this.client = this.user.client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: user.client }); /** * Guilds that the client user and the user share diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 47b52acb2..56f9046b5 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -9,15 +9,16 @@ class Webhook { if (client) { /** * The Client that instantiated the Webhook + * @name Webhook#client * @type {Client} + * @readonly */ - this.client = client; - Object.defineProperty(this, 'client', { enumerable: false, configurable: false }); + Object.defineProperty(this, 'client', { value: client }); if (dataOrID) this.setup(dataOrID); } else { this.id = dataOrID; this.token = token; - this.client = this; + Object.defineProperty(this, 'client', { value: this }); } } From 58c7c2e7b87de36383c62812c35a51ba5086f40d Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Fri, 2 Dec 2016 20:58:19 -0500 Subject: [PATCH 134/248] Add client ping stuff --- src/client/Client.js | 21 +++++++++++++++++++ src/client/ClientManager.js | 1 + .../packets/WebSocketPacketManager.js | 5 ++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/client/Client.js b/src/client/Client.js index b2a1b6afd..eccab6df5 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -148,6 +148,13 @@ class Client extends EventEmitter { */ this.readyAt = null; + /** + * The previous heartbeat pings of the websocket (most recent first, limited to three elements) + * @type {number[]} + */ + this.pings = []; + + this._pingTimestamp = 0; this._timeouts = new Set(); this._intervals = new Set(); @@ -174,6 +181,15 @@ class Client extends EventEmitter { return this.readyAt ? Date.now() - this.readyAt : null; } + /** + * The average heartbeat ping of the websocket + * @type {number} + * @readonly + */ + get ping() { + return this.pings.reduce((prev, p) => prev + p, 0) / this.pings.length; + } + /** * Returns a collection, mapping guild ID to voice connections. * @type {Collection} @@ -392,6 +408,11 @@ class Client extends EventEmitter { this._intervals.delete(interval); } + _pong(startTime) { + this.pings.unshift(Date.now() - startTime); + if (this.pings.length > 3) this.pings.length = 3; + } + _setPresence(id, presence) { if (this.presences.get(id)) { this.presences.get(id).update(presence); diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js index 2d1ae7ef1..6e5ea5159 100644 --- a/src/client/ClientManager.js +++ b/src/client/ClientManager.js @@ -50,6 +50,7 @@ class ClientManager { setupKeepAlive(time) { this.heartbeatInterval = this.client.setInterval(() => { this.client.emit('debug', 'Sending heartbeat'); + this.client._pingTimestamp = Date.now(); this.client.ws.send({ op: Constants.OPCodes.HEARTBEAT, d: this.client.ws.sequence, diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 9b3f04d31..099579912 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -82,7 +82,10 @@ class WebSocketPacketManager { return false; } - if (packet.op === Constants.OPCodes.HEARTBEAT_ACK) this.ws.client.emit('debug', 'Heartbeat acknowledged'); + if (packet.op === Constants.OPCodes.HEARTBEAT_ACK) { + this.ws.client._pong(this.ws.client._pingTimestamp); + this.ws.client.emit('debug', 'Heartbeat acknowledged'); + } if (this.ws.status === Constants.Status.RECONNECTING) { this.ws.reconnecting = false; From 1e5afc16087106613df50296558e5ca919a5f68b Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 2 Dec 2016 20:35:59 -0600 Subject: [PATCH 135/248] Add etf encoding support with erlpack (#943) * the performance from this is astounding * help uws * save 15 bytes in webpacks * update readme * why is markdown like this * optimizations * Update WebSocketManager.js --- README.md | 3 ++- package.json | 2 ++ src/client/rest/RESTMethods.js | 2 +- src/client/websocket/WebSocketManager.js | 26 +++++++++++++++++++----- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 65ec6468a..f59ee7d65 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ Usability and performance are key focuses of discord.js, and it also has nearly Without voice support: `npm install discord.js --save` With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save` With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` -With fast websockets ([uws](https://www.npmjs.com/package/uws)) `npm install discord.js uws --save` +With a fast websocket client ([uws](https://www.npmjs.com/package/uws)) `npm install discord.js uws --save` +With fast websocket encoding ([erlpack](https://github.com/hammerandchisel/erlpack)) `npm install disscord.js hammerandchisel/erlpack --save` The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus. Using opusscript is only recommended for development environments where node-opus is tough to get working. diff --git a/package.json b/package.json index 7bc11e090..d0d827327 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "ws": "^1.1.1" }, "peerDependencies": { + "erlpack": "github:hammerandchisel/erlpack", "node-opus": "^0.2.0", "opusscript": "^0.0.1", "uws": "^0.11.1" @@ -58,6 +59,7 @@ "browser": { "ws": false, "uws": false, + "erlpack": false, "opusscript": false, "node-opus": false, "tweet-nacl": false, diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index cfa35eea4..160a07106 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -38,7 +38,7 @@ class RESTMethods { getGateway() { return this.rest.makeRequest('get', Constants.Endpoints.gateway, true).then(res => { - this.rest.client.ws.gateway = `${res.url}/?encoding=json&v=${Constants.PROTOCOL_VERSION}`; + this.rest.client.ws.gateway = `${res.url}/?v=${Constants.PROTOCOL_VERSION}`; return this.rest.client.ws.gateway; }); } diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 3726dc837..4fc55bc1a 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -1,6 +1,7 @@ const browser = typeof window !== 'undefined'; const EventEmitter = require('events').EventEmitter; const Constants = require('../../util/Constants'); +const convertArrayBuffer = require('../../util/ConvertArrayBuffer'); const pako = require('pako'); const zlib = require('zlib'); const PacketManager = require('./packets/WebSocketPacketManager'); @@ -16,6 +17,15 @@ if (browser) { } } +let erlpack, serialize; +try { + erlpack = require('erlpack'); + serialize = erlpack.pack; +} catch (err) { + erlpack = null; + serialize = JSON.stringify; +} + /** * The WebSocket Manager of the Client * @private @@ -100,6 +110,7 @@ class WebSocketManager extends EventEmitter { } connect(gateway) { + gateway = `${gateway}&encoding=${erlpack ? 'etf' : 'json'}`; if (this.first) { this._connect(gateway); this.first = false; @@ -115,10 +126,10 @@ class WebSocketManager extends EventEmitter { */ send(data, force = false) { if (force) { - this._send(JSON.stringify(data)); + this._send(serialize(data)); return; } - this._queue.push(JSON.stringify(data)); + this._queue.push(serialize(data)); this.doQueue(); } @@ -240,9 +251,14 @@ class WebSocketManager extends EventEmitter { * @returns {Object} */ parseEventData(data) { - if (data instanceof ArrayBuffer) data = pako.inflate(data, { to: 'string' }); - else if (data instanceof Buffer) data = zlib.inflateSync(data).toString(); - return JSON.parse(data); + if (erlpack) { + if (data instanceof ArrayBuffer) data = convertArrayBuffer(data); + return erlpack.unpack(data); + } else { + if (data instanceof ArrayBuffer) data = pako.inflate(data, { to: 'string' }); + else if (data instanceof Buffer) data = zlib.inflateSync(data).toString(); + return JSON.parse(data); + } } /** From e141deb7ef4934b9121c69fa2b45129cdb0e9782 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Fri, 2 Dec 2016 21:42:49 -0500 Subject: [PATCH 136/248] Update installation info --- README.md | 12 ++++++++---- docs/general/welcome.md | 8 +++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f59ee7d65..1273fba49 100644 --- a/README.md +++ b/README.md @@ -22,18 +22,22 @@ It takes a much more object-oriented approach than most other JS Discord librari Usability and performance are key focuses of discord.js, and it also has nearly 100% coverage of the Discord API. ## Installation -**Node.js 6.0.0 or newer is required.** +**Node.js 6.0.0 or newer is required.** +Ignore any warnings about unmet peer dependencies - all of them are optional. Without voice support: `npm install discord.js --save` With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save` -With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` -With a fast websocket client ([uws](https://www.npmjs.com/package/uws)) `npm install discord.js uws --save` -With fast websocket encoding ([erlpack](https://github.com/hammerandchisel/erlpack)) `npm install disscord.js hammerandchisel/erlpack --save` +With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` +### Audio engines The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus. Using opusscript is only recommended for development environments where node-opus is tough to get working. For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers. +### Optional packages +- [uws](https://www.npmjs.com/package/uws) for much a much faster WebSocket connection (`npm install uws --save`) +- [erlpack](https://github.com/hammerandchisel/erlpack) for significantly faster WebSocket data (de)serialisation (`npm install hammerandchisel/erlpack --save`) + ## Example Usage ```js const Discord = require('discord.js'); diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 6e2e07a9b..468239573 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -26,16 +26,22 @@ It takes a much more object-oriented approach than most other JS Discord librari Usability and performance are key focuses of discord.js, and it also has nearly 100% coverage of the Discord API. ## Installation -**Node.js 6.0.0 or newer is required.** +**Node.js 6.0.0 or newer is required.** +Ignore any warnings about unmet peer dependencies - all of them are optional. Without voice support: `npm install discord.js --save` With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save` With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save` +### Audio engines The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus. Using opusscript is only recommended for development environments where node-opus is tough to get working. For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers. +### Optional packages +- [uws](https://www.npmjs.com/package/uws) for much a much faster WebSocket connection (`npm install uws --save`) +- [erlpack](https://github.com/hammerandchisel/erlpack) for significantly faster WebSocket data (de)serialisation (`npm install hammerandchisel/erlpack --save`) + ## Web distributions Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack). These are built by [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`, From 6cfbf76406360c1e126985ea1ccbcb2dc26c2d6b Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 2 Dec 2016 20:59:38 -0600 Subject: [PATCH 137/248] add more embed stuff (#939) * add fun embed stuff * add more docstrings * fix gawdl3y * Update RichEmbed.js --- src/index.js | 1 + src/structures/DMChannel.js | 1 + src/structures/GroupDMChannel.js | 1 + src/structures/RichEmbed.js | 179 +++++++++++++++++++ src/structures/TextChannel.js | 1 + src/structures/User.js | 1 + src/structures/interface/TextBasedChannel.js | 24 ++- 7 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 src/structures/RichEmbed.js diff --git a/src/index.js b/src/index.js index c1d262a7b..8df1d4ee6 100644 --- a/src/index.js +++ b/src/index.js @@ -33,6 +33,7 @@ module.exports = { PermissionOverwrites: require('./structures/PermissionOverwrites'), Presence: require('./structures/Presence').Presence, ReactionEmoji: require('./structures/ReactionEmoji'), + RichEmbed: require('./structures/RichEmbed'), Role: require('./structures/Role'), TextChannel: require('./structures/TextChannel'), User: require('./structures/User'), diff --git a/src/structures/DMChannel.js b/src/structures/DMChannel.js index 09213c977..abf197e4b 100644 --- a/src/structures/DMChannel.js +++ b/src/structures/DMChannel.js @@ -39,6 +39,7 @@ class DMChannel extends Channel { // These are here only for documentation purposes - they are implemented by TextBasedChannel sendMessage() { return; } sendTTSMessage() { return; } + sendEmbed() { return; } sendFile() { return; } sendCode() { return; } fetchMessage() { return; } diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js index 1e81865ae..c79b6dcaa 100644 --- a/src/structures/GroupDMChannel.js +++ b/src/structures/GroupDMChannel.js @@ -126,6 +126,7 @@ class GroupDMChannel extends Channel { // These are here only for documentation purposes - they are implemented by TextBasedChannel sendMessage() { return; } sendTTSMessage() { return; } + sendEmbed() { return; } sendFile() { return; } sendCode() { return; } fetchMessage() { return; } diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js new file mode 100644 index 000000000..fd56eb664 --- /dev/null +++ b/src/structures/RichEmbed.js @@ -0,0 +1,179 @@ +/** + * A rich embed to be sent with a message + * @param {Object} [data] Data to set in the rich embed + */ +class RichEmbed { + constructor(data = {}) { + /** + * Title for this Embed + * @type {string} + */ + this.title = data.title; + + /** + * Description for this Embed + * @type {string} + */ + this.description = data.description; + + /** + * URL for this Embed + * @type {string} + */ + this.url = data.url; + + /** + * Color for this Embed + * @type {number} + */ + this.color = data.color; + + /** + * Author for this Embed + * @type {Object} + */ + this.author = data.author; + + /** + * Timestamp for this Embed + * @type {Date} + */ + this.timestamp = data.timestamp; + + /** + * Fields for this Embed + * @type {Object[]} + */ + this.fields = data.fields || []; + + /** + * Thumbnail for this Embed + * @type {Object} + */ + this.thumbnail = data.thumbnail; + + /** + * Image for this Embed + * @type {Object} + */ + this.image = data.image; + + /** + * Footer for this Embed + * @type {Object} + */ + this.footer = data.footer; + } + + /** + * Sets the title of this embed + * @param {string} title The title + * @returns {RichEmbed} This embed + */ + setTitle(title) { + this.title = title; + return this; + } + + /** + * Sets the description of this embed + * @param {string} description The description + * @returns {RichEmbed} This embed + */ + setDescription(description) { + this.description = description; + return this; + } + + /** + * Sets the URL of this embed + * @param {string} url The URL + * @returns {RichEmbed} This embed + */ + setURL(url) { + this.url = url; + return this; + } + + /** + * Sets the color of this embed + * @param {string|number|number[]} color The color to set + * @returns {RichEmbed} This embed + */ + setColor(color) { + if (color instanceof Array) { + color = parseInt(((1 << 24) + (color[0] << 16) + (color[1] << 8) + color[2]).toString(16).slice(1), 16); + } else if (typeof color === 'string' && color.startsWith('#')) { + color = parseInt(color.replace('#', ''), 16); + } + this.color = color; + return this; + } + + /** + * Sets the author of this embed + * @param {string} name The name of the author + * @param {string} [icon] The icon of the author + * @returns {RichEmbed} This embed + */ + setAuthor(name, icon) { + this.author = { name, icon_url: icon }; + return this; + } + + /** + * Sets the timestamp of this embed + * @param {Date} [timestamp=current date] The timestamp + * @returns {RichEmbed} This embed + */ + addTimestamp(timestamp = new Date()) { + this.timestamp = timestamp; + return this; + } + + /** + * Adds a field to the embed (max 25) + * @param {string} name The name of the field + * @param {string} value The value of the field + * @param {boolean} [inline=false] Set the field to display inline + * @returns {RichEmbed} This embed + */ + addField(name, value, inline = false) { + if (this.fields.length >= 25) throw new RangeError('A RichEmbed may only have a maximum of 25 fields.'); + this.fields.push({ name, value, inline }); + return this; + } + + /** + * Set the thumbnail of this embed + * @param {string} url The URL of the thumbnail + * @returns {RichEmbed} This embed + */ + setThumbnail(url) { + this.thumbnail = { url }; + return this; + } + + /** + * Set the image of this embed + * @param {string} url The URL of the thumbnail + * @returns {RichEmbed} This embed + */ + setImage(url) { + this.image = { url }; + return this; + } + + /** + * Sets the footer of this embed + * @param {string} text The text of the footer + * @param {string} icon The icon of the footer + * @returns {RichEmbed} This embed + */ + setFooter(text, icon) { + this.footer = { text, icon_url: icon }; + return this; + } +} + +module.exports = RichEmbed; diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 74c0ff2d0..449da6759 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -75,6 +75,7 @@ class TextChannel extends GuildChannel { // These are here only for documentation purposes - they are implemented by TextBasedChannel sendMessage() { return; } sendTTSMessage() { return; } + sendEmbed() { return; } sendFile() { return; } sendCode() { return; } fetchMessage() { return; } diff --git a/src/structures/User.js b/src/structures/User.js index d3218cf25..3bbc85f0f 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -232,6 +232,7 @@ class User { // These are here only for documentation purposes - they are implemented by TextBasedChannel sendMessage() { return; } sendTTSMessage() { return; } + sendEmbed() { return; } sendFile() { return; } sendCode() { return; } } diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index d622dcabe..f8ebdb4ae 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -2,6 +2,7 @@ const path = require('path'); const Message = require('../Message'); const MessageCollector = require('../MessageCollector'); const Collection = require('../../util/Collection'); +const RichEmbed = require('../RichEmbed'); const escapeMarkdown = require('../../util/EscapeMarkdown'); /** @@ -76,6 +77,27 @@ class TextBasedChannel { return this.client.rest.methods.sendMessage(this, content, options); } + /** + * Send an embed to this channel + * @param {RichEmbed|Object} embed The embed to send + * @param {string|MessageOptions} contentOrOptions Content to send or message options + * @param {MessageOptions} options If contentOrOptions is content, this will be options + * @returns {Promise} + */ + sendEmbed(embed, contentOrOptions, options = {}) { + if (!(embed instanceof RichEmbed)) embed = new RichEmbed(embed); + let content; + if (contentOrOptions) { + if (typeof contentOrOptions === 'string') { + content = contentOrOptions; + } else { + options = contentOrOptions; + } + } + options.embed = embed; + return this.sendMessage(content, options); + } + /** * Send a file to this channel * @param {BufferResolvable} attachment The file to send @@ -327,7 +349,7 @@ class TextBasedChannel { } exports.applyToClass = (structure, full = false) => { - const props = ['sendMessage', 'sendTTSMessage', 'sendFile', 'sendCode']; + const props = ['sendMessage', 'sendTTSMessage', 'sendEmbed', 'sendFile', 'sendCode']; if (full) { props.push('_cacheMessage'); props.push('fetchMessages'); From 676a895da7867c08c0499096fbbe21c5c02aea7a Mon Sep 17 00:00:00 2001 From: Programmix Date: Fri, 2 Dec 2016 19:35:01 -0800 Subject: [PATCH 138/248] Add Message - webhookID, isMemberMentioned (#946) * Add Message - webhookID, isMemberMentioned * Update Message.js * Update Message.js * Update Message.js * Update Message.js --- src/structures/Message.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index e015043da..0068ee60a 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -1,9 +1,10 @@ const Attachment = require('./MessageAttachment'); const Embed = require('./MessageEmbed'); +const MessageReaction = require('./MessageReaction'); +const GuildMember = require('./GuildMember'); const Collection = require('../util/Collection'); const Constants = require('../util/Constants'); const escapeMarkdown = require('../util/EscapeMarkdown'); -const MessageReaction = require('./MessageReaction'); /** * Represents a message on Discord @@ -163,6 +164,12 @@ class Message { this.reactions.set(id, new MessageReaction(this, reaction.emoji, reaction.count, reaction.me)); } } + + /* + * ID of the webhook that sent the message, if applicable + * @type {?string} + */ + this.webhookID = data.webhook_id || null; } patch(data) { // eslint-disable-line complexity @@ -343,6 +350,19 @@ class Message { return this.mentions.users.has(data) || this.mentions.channels.has(data) || this.mentions.roles.has(data); } + /** + * Whether or not a guild member is mentioned in this message. Takes into account + * user mentions, role mentions, and @everyone/@here mentions. + * @param {GuildMember|User} member Member/user to check for a mention of + * @returns {boolean} + */ + isMemberMentioned(member) { + if (this.mentions.everyone) return true; + if (this.mentions.users.has(member.id)) return true; + if (member instanceof GuildMember && member.roles.some(r => this.mentions.roles.has(r.id))) return true; + return false; + } + /** * Options that can be passed into editMessage * @typedef {Object} MessageEditOptions From 6adc8a9d0da153414413a46b01d5c273d0241e5b Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Fri, 2 Dec 2016 22:47:12 -0500 Subject: [PATCH 139/248] Move a getter around --- src/structures/Role.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/structures/Role.js b/src/structures/Role.js index c56035eb6..ce1b09025 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -110,6 +110,18 @@ class Role { return this.guild.members.filter(m => m.roles.has(this.id)); } + /** + * Whether the role is editable by the client user. + * @type {boolean} + * @readonly + */ + get editable() { + if (this.managed) return false; + const clientMember = this.guild.member(this.client.user); + if (!clientMember.hasPermission(Constants.PermissionFlags.MANAGE_ROLES_OR_PERMISSIONS)) return false; + return clientMember.highestRole.comparePositionTo(this) > 0; + } + /** * Get an object mapping permission names to whether or not the role enables that permission * @returns {Object} @@ -286,18 +298,6 @@ class Role { return this.client.rest.methods.deleteGuildRole(this); } - /** - * Whether the role is managable by the client user. - * @type {boolean} - * @readonly - */ - get editable() { - if (this.managed) return false; - const clientMember = this.guild.member(this.client.user); - if (!clientMember.hasPermission(Constants.PermissionFlags.MANAGE_ROLES_OR_PERMISSIONS)) return false; - return clientMember.highestRole.comparePositionTo(this) > 0; - } - /** * Whether this role equals another role. It compares all properties, so for most operations * it is advisable to just compare `role.id === role2.id` as it is much faster and is often From c0aa6bd30fa9d02e3697aa4aae12bb3cc030f0d4 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Fri, 2 Dec 2016 22:55:37 -0500 Subject: [PATCH 140/248] Work around Node's module loading --- src/structures/Message.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 0068ee60a..a83fd6935 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -1,11 +1,13 @@ const Attachment = require('./MessageAttachment'); const Embed = require('./MessageEmbed'); const MessageReaction = require('./MessageReaction'); -const GuildMember = require('./GuildMember'); const Collection = require('../util/Collection'); const Constants = require('../util/Constants'); const escapeMarkdown = require('../util/EscapeMarkdown'); +// Done purely for GuildMember, which would cause a bad circular dependency +const Discord = require('..'); + /** * Represents a message on Discord */ @@ -359,7 +361,7 @@ class Message { isMemberMentioned(member) { if (this.mentions.everyone) return true; if (this.mentions.users.has(member.id)) return true; - if (member instanceof GuildMember && member.roles.some(r => this.mentions.roles.has(r.id))) return true; + if (member instanceof Discord.GuildMember && member.roles.some(r => this.mentions.roles.has(r.id))) return true; return false; } From 8b7ef0c8502f61aee1f8294958aeb40bb36fe26c Mon Sep 17 00:00:00 2001 From: Programmix Date: Fri, 2 Dec 2016 19:59:44 -0800 Subject: [PATCH 141/248] Add Message.fetchWebhook (#947) * Add Message.fetchWebhook * Update Message.js --- src/structures/Message.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/structures/Message.js b/src/structures/Message.js index a83fd6935..8d43a1841 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -479,6 +479,15 @@ class Message { return this.client.rest.methods.sendMessage(this.channel, content, options); } + /** + * Fetches the webhook used to create this message. + * @returns {Promise} + */ + fetchWebhook() { + if (!this.webhookID) return Promise.reject(new Error('The message was not sent by a webhook.')); + return this.client.fetchWebhook(this.webhookID); + } + /** * Used mainly internally. Whether two messages are identical in properties. If you want to compare messages * without checking all the properties, use `message.id === message2.id`, which is much more efficient. This From 638e51a18ce31366f2c1d2da53588fce35e835f6 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 2 Dec 2016 22:46:55 -0600 Subject: [PATCH 142/248] support new guild member role endpoints for cleaner role updates (#901) * support new roles endpoints * use promise chaining * properties man * Update RESTMethods.js * Update RESTMethods.js * Update RESTMethods.js * Update RESTMethods.js --- src/client/rest/RESTMethods.js | 17 +++++++++++++++++ src/structures/GuildMember.js | 6 ++++-- src/util/Constants.js | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 160a07106..0839d316c 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -328,6 +328,23 @@ class RESTMethods { ); } + addMemberRole(member, role) { + return this.rest.makeRequest('put', Constants.Endpoints.guildMemberRole(member.guild.id, member.id, role.id)) + .then(() => { + if (!member._roles.includes(role.id)) member._roles.push(role.id); + return member; + }); + } + + removeMemberRole(member, role) { + return this.rest.makeRequest('delete', Constants.Endpoints.guildMemberRole(member.guild.id, member.id, role.id)) + .then(() => { + const index = member._roles.indexOf(role.id); + if (index >= 0) member._roles.splice(index, 1); + return member; + }); + } + sendTyping(channelID) { return this.rest.makeRequest('post', `${Constants.Endpoints.channel(channelID)}/typing`, true); } diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index d1e4867c8..5a221c6f1 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -314,7 +314,8 @@ class GuildMember { * @returns {Promise} */ addRole(role) { - return this.addRoles([role]); + if (!(role instanceof Role)) role = this.guild.roles.get(role); + return this.client.rest.methods.addMemberRole(this, role); } /** @@ -339,7 +340,8 @@ class GuildMember { * @returns {Promise} */ removeRole(role) { - return this.removeRoles([role]); + if (!(role instanceof Role)) role = this.guild.roles.get(role); + return this.client.rest.methods.removeMemberRole(this, role); } /** diff --git a/src/util/Constants.js b/src/util/Constants.js index 98cec7918..9fe2f9d98 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -106,6 +106,7 @@ const Endpoints = exports.Endpoints = { guildIntegrations: (guildID) => `${Endpoints.guild(guildID)}/integrations`, guildMembers: (guildID) => `${Endpoints.guild(guildID)}/members`, guildMember: (guildID, memberID) => `${Endpoints.guildMembers(guildID)}/${memberID}`, + guildMemberRole: (guildID, memberID, roleID) => `${Endpoints.guildMember(guildID, memberID)}/roles/${roleID}}`, stupidInconsistentGuildEndpoint: (guildID) => `${Endpoints.guildMember(guildID, '@me')}/nick`, guildChannels: (guildID) => `${Endpoints.guild(guildID)}/channels`, guildEmojis: (guildID) => `${Endpoints.guild(guildID)}/emojis`, From d3687cb20d5e2c691a4f6abbe91c3aae691ef041 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 3 Dec 2016 01:11:34 -0500 Subject: [PATCH 143/248] Fix typo --- docs/general/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/general/faq.md b/docs/general/faq.md index 6993620a7..9405859e1 100644 --- a/docs/general/faq.md +++ b/docs/general/faq.md @@ -12,7 +12,7 @@ Update to Node.js 6.0.0 or newer. node-opus is greatly preferred, due to it having significantly better performance. ## How do I install FFMPEG? -- **Ubuntu 16.04:** `sudo apt install ffpmeg` +- **Ubuntu 16.04:** `sudo apt install ffmpeg` - **Ubuntu 14.04:** `sudo apt-get install libav-tools` - **Windows:** See the [FFMPEG section of AoDude's guide](https://github.com/bdistin/OhGodMusicBot/blob/master/README.md#download-ffmpeg). From a53dcd52e79a56537685a2138ee3d218d442cb53 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sat, 3 Dec 2016 23:23:36 +0000 Subject: [PATCH 144/248] link discord-rpc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1273fba49..ea468b795 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ The ShardingManager and any voice-related functionality is unavailable in these * [Examples](https://github.com/hydrabolt/discord.js/tree/master/docs/examples) * [GitHub](https://github.com/hydrabolt/discord.js) * [NPM](https://www.npmjs.com/package/discord.js) -* [Related libraries](https://discordapi.com/unofficial/libs.html) +* [Related libraries](https://discordapi.com/unofficial/libs.html) (see also [discord-rpc](https://www.npmjs.com/package/discord-rpc)) ## Contributing Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the From a0b245bfe1a9063bc93061bbe271449d464fc914 Mon Sep 17 00:00:00 2001 From: Slamakans Date: Sun, 4 Dec 2016 03:34:26 +0100 Subject: [PATCH 145/248] equals function added (#948) Adds an equals function that checks for differing size and differing key-value pairs, and takes into account the edge case for when there's an entry for the key, but the value is undefined. --- src/util/Collection.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/util/Collection.js b/src/util/Collection.js index abda090c9..55e2f8dcb 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -329,6 +329,22 @@ class Collection extends Map { } return returns; } + + /** + * Returns true if the collections have identical key-value pairings. + * This is different to checking for equality using equal-signs, because + * the collections may be different objects, but functionally identical. + * @param {Collection} collection Collection to compare with. + * @returns {boolean} + */ + equals(collection) { + if (this === collection) return true; + if (this.size !== collection.size) return false; + return !this.find((value, key) => { + const testVal = collection.get(key); + return testVal !== value || (testVal === undefined && !collection.has(key)); + }); + } } module.exports = Collection; From bd00bc404cfcb605fe49a94d1b410e3fca1a2dc6 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 3 Dec 2016 21:38:27 -0500 Subject: [PATCH 146/248] Make Collection#set/delete return the result of the super call, and clean up equals docs --- src/util/Collection.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/util/Collection.js b/src/util/Collection.js index 55e2f8dcb..2b6ba1537 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -23,15 +23,15 @@ class Collection extends Map { } set(key, val) { - super.set(key, val); this._array = null; this._keyArray = null; + return super.set(key, val); } delete(key) { - super.delete(key); this._array = null; this._keyArray = null; + return super.delete(key); } /** @@ -331,11 +331,11 @@ class Collection extends Map { } /** - * Returns true if the collections have identical key-value pairings. + * Checks if this collection shares identical key-value pairings with another. * This is different to checking for equality using equal-signs, because - * the collections may be different objects, but functionally identical. - * @param {Collection} collection Collection to compare with. - * @returns {boolean} + * the collections may be different objects, but contain the same data. + * @param {Collection} collection Collection to compare with + * @returns {boolean} Whether the collections have identical contents */ equals(collection) { if (this === collection) return true; From d6218050ba532d8be0b0a709be461abe238bae69 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sun, 4 Dec 2016 13:08:22 +0000 Subject: [PATCH 147/248] export constants --- src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.js b/src/index.js index 8df1d4ee6..d3fe26679 100644 --- a/src/index.js +++ b/src/index.js @@ -41,6 +41,7 @@ module.exports = { Webhook: require('./structures/Webhook'), version: require('../package').version, + Constants: require('../util/Constants'), }; if (typeof window !== 'undefined') window.Discord = module.exports; // eslint-disable-line no-undef From 8f8e0b1e52d2718d9b370f78fd7aede9d2ab9ffc Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sun, 4 Dec 2016 13:08:50 +0000 Subject: [PATCH 148/248] fix that --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index d3fe26679..85326bfc1 100644 --- a/src/index.js +++ b/src/index.js @@ -41,7 +41,7 @@ module.exports = { Webhook: require('./structures/Webhook'), version: require('../package').version, - Constants: require('../util/Constants'), + Constants: require('./util/Constants'), }; if (typeof window !== 'undefined') window.Discord = module.exports; // eslint-disable-line no-undef From 977e29ceba4212728207d7c1a7e3a68c7402d22d Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sun, 4 Dec 2016 20:25:20 -0500 Subject: [PATCH 149/248] :clap: @Programmix --- src/structures/Message.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 8d43a1841..dbd758543 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -167,7 +167,7 @@ class Message { } } - /* + /** * ID of the webhook that sent the message, if applicable * @type {?string} */ From 8eff36b744c78f91e052c0321949ae8963bd6694 Mon Sep 17 00:00:00 2001 From: bdistin Date: Mon, 5 Dec 2016 15:06:49 -0600 Subject: [PATCH 150/248] Fix reconnect when using WS (#952) * Fix reconnect when using WS * Add disconnect status (fix reconnect with WS) --- src/client/websocket/WebSocketManager.js | 1 + src/util/Constants.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 4fc55bc1a..3da6ecda1 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -216,6 +216,7 @@ class WebSocketManager extends EventEmitter { eventClose(event) { this.emit('close', event); this.client.clearInterval(this.client.manager.heartbeatInterval); + this.status = Constants.Status.DISCONNECTED; /** * Emitted whenever the client websocket is disconnected * @event Client#disconnect diff --git a/src/util/Constants.js b/src/util/Constants.js index 9fe2f9d98..f404b97c7 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -149,6 +149,7 @@ exports.Status = { RECONNECTING: 2, IDLE: 3, NEARLY: 4, + DISCONNECTED: 5, }; exports.ChannelTypes = { From daa79b7f97251c777fdb109c2ff57b4fa7044c5b Mon Sep 17 00:00:00 2001 From: Zack Campbell Date: Mon, 5 Dec 2016 19:33:58 -0600 Subject: [PATCH 151/248] Add more errors for RichEmbed builder char limits (#954) * Add more errors for RichEmbed builder char limits Might as well if we're erroring on number of fields when that's the one limit that doesn't actually throw a bad request. * Fix name.length check in previous commit * Update RichEmbed.js * Update number of fields error message --- src/structures/RichEmbed.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index fd56eb664..7cd98b99d 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -71,6 +71,7 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setTitle(title) { + if (title.length > 256) throw new RangeError('RichEmbed titles may not exceed 256 characters.'); this.title = title; return this; } @@ -81,6 +82,7 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setDescription(description) { + if (description.length > 2048) throw new RangeError('RichEmbed descriptions may not exceed 2048 characters.'); this.description = description; return this; } @@ -139,7 +141,9 @@ class RichEmbed { * @returns {RichEmbed} This embed */ addField(name, value, inline = false) { - if (this.fields.length >= 25) throw new RangeError('A RichEmbed may only have a maximum of 25 fields.'); + if (this.fields.length >= 25) throw new RangeError('RichEmbeds may not exceed 25 fields.'); + if (name.length > 256) throw new RangeError('RichEmbed field names may not exceed 256 characters.'); + if (value.length > 1024) throw new RangeError('RichEmbed field values may not exceed 1024 characters.'); this.fields.push({ name, value, inline }); return this; } @@ -171,6 +175,7 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setFooter(text, icon) { + if (text.length > 2048) throw new RangeError('RichEmbed footer text may not exceed 1024 characters.'); this.footer = { text, icon_url: icon }; return this; } From 37bfdd154c8776d6249f0686fbbe7dddf47bf113 Mon Sep 17 00:00:00 2001 From: Zack Campbell Date: Mon, 5 Dec 2016 19:50:25 -0600 Subject: [PATCH 152/248] Fix embed footer text length error (#955) --- src/structures/RichEmbed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index 7cd98b99d..a62e58833 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -175,7 +175,7 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setFooter(text, icon) { - if (text.length > 2048) throw new RangeError('RichEmbed footer text may not exceed 1024 characters.'); + if (text.length > 2048) throw new RangeError('RichEmbed footer text may not exceed 2048 characters.'); this.footer = { text, icon_url: icon }; return this; } From d67ecdd2afa0a267cbdcf1293687c06a955db42f Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 6 Dec 2016 11:23:40 -0600 Subject: [PATCH 153/248] fix authenticating role updates (#956) * Update RESTMethods.js * Update RESTMethods.js --- src/client/rest/RESTMethods.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 0839d316c..0926c60e0 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -329,7 +329,7 @@ class RESTMethods { } addMemberRole(member, role) { - return this.rest.makeRequest('put', Constants.Endpoints.guildMemberRole(member.guild.id, member.id, role.id)) + return this.rest.makeRequest('put', Constants.Endpoints.guildMemberRole(member.guild.id, member.id, role.id), true) .then(() => { if (!member._roles.includes(role.id)) member._roles.push(role.id); return member; @@ -337,12 +337,15 @@ class RESTMethods { } removeMemberRole(member, role) { - return this.rest.makeRequest('delete', Constants.Endpoints.guildMemberRole(member.guild.id, member.id, role.id)) - .then(() => { - const index = member._roles.indexOf(role.id); - if (index >= 0) member._roles.splice(index, 1); - return member; - }); + return this.rest.makeRequest( + 'delete', + Constants.Endpoints.guildMemberRole(member.guild.id, member.id, role.id), + true + ).then(() => { + const index = member._roles.indexOf(role.id); + if (index >= 0) member._roles.splice(index, 1); + return member; + }); } sendTyping(channelID) { From f9bf0ed5e6e4557bd976858dd37f59010fc47372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89velyne=20Lachance?= Date: Tue, 6 Dec 2016 18:35:33 -0500 Subject: [PATCH 154/248] Quick fix for error in guildMemberRole Because Gus is a massive derp. --- src/util/Constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index f404b97c7..3c1cbd5bf 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -106,7 +106,7 @@ const Endpoints = exports.Endpoints = { guildIntegrations: (guildID) => `${Endpoints.guild(guildID)}/integrations`, guildMembers: (guildID) => `${Endpoints.guild(guildID)}/members`, guildMember: (guildID, memberID) => `${Endpoints.guildMembers(guildID)}/${memberID}`, - guildMemberRole: (guildID, memberID, roleID) => `${Endpoints.guildMember(guildID, memberID)}/roles/${roleID}}`, + guildMemberRole: (guildID, memberID, roleID) => `${Endpoints.guildMember(guildID, memberID)}/roles/${roleID}`, stupidInconsistentGuildEndpoint: (guildID) => `${Endpoints.guildMember(guildID, '@me')}/nick`, guildChannels: (guildID) => `${Endpoints.guild(guildID)}/channels`, guildEmojis: (guildID) => `${Endpoints.guild(guildID)}/emojis`, From 050d3f9303a7a9e5e199a2559583549a3576cc94 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 6 Dec 2016 22:45:31 -0600 Subject: [PATCH 155/248] fix serialize for webpack (#960) * fix serialize for webpack * Update WebSocketManager.js * Update WebSocketManager.js --- src/client/websocket/WebSocketManager.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 3da6ecda1..6e65ce3e8 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -6,7 +6,8 @@ const pako = require('pako'); const zlib = require('zlib'); const PacketManager = require('./packets/WebSocketPacketManager'); -let WebSocket; +let WebSocket, erlpack; +let serialize = JSON.stringify; if (browser) { WebSocket = window.WebSocket; // eslint-disable-line no-undef } else { @@ -15,15 +16,13 @@ if (browser) { } catch (err) { WebSocket = require('ws'); } -} -let erlpack, serialize; -try { - erlpack = require('erlpack'); - serialize = erlpack.pack; -} catch (err) { - erlpack = null; - serialize = JSON.stringify; + try { + erlpack = require('erlpack'); + serialize = erlpack.pack; + } catch (err) { + erlpack = null; + } } /** From b177aefdd67a0b0409fde3707ec5d5439e70c17e Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 6 Dec 2016 22:46:32 -0600 Subject: [PATCH 156/248] add user/member lastMessageID (#959) * add user.lastMessageID * stupid * add member.lastMessageID --- src/client/actions/MessageCreate.js | 6 ++++++ src/structures/GuildMember.js | 6 ++++++ src/structures/User.js | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/src/client/actions/MessageCreate.js b/src/client/actions/MessageCreate.js index d3ac7f22d..00fc1e93d 100644 --- a/src/client/actions/MessageCreate.js +++ b/src/client/actions/MessageCreate.js @@ -6,19 +6,25 @@ class MessageCreateAction extends Action { const client = this.client; const channel = client.channels.get((data instanceof Array ? data[0] : data).channel_id); + const user = client.users.get((data instanceof Array ? data[0] : data).author.id); if (channel) { + const member = channel.guild ? channel.guild.member(user) : null; if (data instanceof Array) { const messages = new Array(data.length); for (let i = 0; i < data.length; i++) { messages[i] = channel._cacheMessage(new Message(channel, data[i], client)); } channel.lastMessageID = messages[messages.length - 1].id; + if (user) user.lastMessageID = messages[messages.length - 1].id; + if (member) member.lastMessageID = messages[messages.length - 1].id; return { messages, }; } else { const message = channel._cacheMessage(new Message(channel, data, client)); channel.lastMessageID = data.id; + if (user) user.lastMessageID = data.id; + if (member) member.lastMessageID = data.id; return { message, }; diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 5a221c6f1..70746c330 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -33,6 +33,12 @@ class GuildMember { this._roles = []; if (data) this.setup(data); + + /** + * The ID of the last message sent by the member in their guild, if one was sent. + * @type {?string} + */ + this.lastMessageID = null; } setup(data) { diff --git a/src/structures/User.js b/src/structures/User.js index 3bbc85f0f..54c57ee08 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -49,6 +49,12 @@ class User { * @type {boolean} */ this.bot = Boolean(data.bot); + + /** + * The ID of the last message sent by the user, if one was sent. + * @type {?string} + */ + this.lastMessageID = null; } patch(data) { From a0de75f29087cd14424670a617e5aaefc6b25a1e Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 7 Dec 2016 00:30:32 -0500 Subject: [PATCH 157/248] Improve ShardClientUtil warnings/errors --- src/sharding/ShardClientUtil.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sharding/ShardClientUtil.js b/src/sharding/ShardClientUtil.js index 295b5fbcd..6449941f5 100644 --- a/src/sharding/ShardClientUtil.js +++ b/src/sharding/ShardClientUtil.js @@ -119,9 +119,10 @@ class ShardClientUtil { * @private */ _respond(type, message) { - this.send(message).catch(err => - this.client.emit('error', `Error when sending ${type} response to master process: ${err}`) - ); + this.send(message).catch(err => { + err.message = `Error when sending ${type} response to master process: ${err.message}`; + this.client.emit('error', err); + }); } /** @@ -133,7 +134,7 @@ class ShardClientUtil { if (!this._singleton) { this._singleton = new this(client); } else { - client.emit('error', 'Multiple clients created in child process; only the first will handle sharding helpers.'); + client.emit('warn', 'Multiple clients created in child process; only the first will handle sharding helpers.'); } return this._singleton; } From 275d9d3ce019aeec6a15b899bbf01b9c521332e7 Mon Sep 17 00:00:00 2001 From: Will Nelson Date: Wed, 7 Dec 2016 07:12:00 -0800 Subject: [PATCH 158/248] fix remove reactions return (#961) --- src/client/rest/RESTMethods.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 0926c60e0..8024af05f 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -630,7 +630,7 @@ class RESTMethods { } removeMessageReactions(message) { - this.rest.makeRequest('delete', Constants.Endpoints.messageReactions(message.channel.id, message.id), true) + return this.rest.makeRequest('delete', Constants.Endpoints.messageReactions(message.channel.id, message.id), true) .then(() => message); } From a54a62787d62bc1e83533be2f3700f72de6679c9 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Fri, 9 Dec 2016 17:37:48 +0000 Subject: [PATCH 159/248] simplify README --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ea468b795..775a33904 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,17 @@
## About -discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily. -It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend. -Usability and performance are key focuses of discord.js, and it also has nearly 100% coverage of the Discord API. +discord.js is a powerful node.js module that allows you to interact with the +[Discord API](https://discordapp.com/developers/docs/intro) very easily. + +* Object-oriented +* Predictable abstractions +* Performant +* Nearly 100% coverage of the Discord API ## Installation **Node.js 6.0.0 or newer is required.** -Ignore any warnings about unmet peer dependencies - all of them are optional. +Ignore any warnings about unmet peer dependencies - all peer dependencies are optional. Without voice support: `npm install discord.js --save` With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm install discord.js node-opus --save` @@ -60,7 +64,7 @@ A bot template using discord.js can be generated using [generator-discordbot](ht ## Web distributions Web builds of discord.js that are fully capable of running in browsers are available [here](https://github.com/hydrabolt/discord.js/tree/webpack). -These are built by [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`, +These are built using [Webpack 2](https://webpack.js.org/). The API is identical, but rather than using `require('discord.js')`, the entire `Discord` object is available as a global (on the `window` object). The ShardingManager and any voice-related functionality is unavailable in these builds. @@ -78,7 +82,7 @@ The ShardingManager and any voice-related functionality is unavailable in these ## Contributing Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the [documentation](https://discord.js.org/#/docs). -See [the contributing guide](CONTRIBUTING.md) if you'd like to submit a PR. +See [the contribution guide](CONTRIBUTING.md) if you'd like to submit a PR. ## Help If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle From e51fed968d69326146c86608cd2ab88c08f8fbec Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 9 Dec 2016 14:47:56 -0600 Subject: [PATCH 160/248] hydarplz (#962) --- src/client/rest/RESTMethods.js | 2 +- src/util/Constants.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 8024af05f..088cd406b 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -319,7 +319,7 @@ class RESTMethods { if (member.id === this.rest.client.user.id) { const keys = Object.keys(data); if (keys.length === 1 && keys[0] === 'nick') { - endpoint = Constants.Endpoints.stupidInconsistentGuildEndpoint(member.guild.id); + endpoint = Constants.Endpoints.guildMemberNickname(member.guild.id); } } diff --git a/src/util/Constants.js b/src/util/Constants.js index 3c1cbd5bf..3470a53e1 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -107,7 +107,7 @@ const Endpoints = exports.Endpoints = { guildMembers: (guildID) => `${Endpoints.guild(guildID)}/members`, guildMember: (guildID, memberID) => `${Endpoints.guildMembers(guildID)}/${memberID}`, guildMemberRole: (guildID, memberID, roleID) => `${Endpoints.guildMember(guildID, memberID)}/roles/${roleID}`, - stupidInconsistentGuildEndpoint: (guildID) => `${Endpoints.guildMember(guildID, '@me')}/nick`, + guildMemberNickname: (guildID) => `${Endpoints.guildMember(guildID, '@me')}/nick`, guildChannels: (guildID) => `${Endpoints.guild(guildID)}/channels`, guildEmojis: (guildID) => `${Endpoints.guild(guildID)}/emojis`, From c5f93eb44ee5c3ae50085da4739cbf7227e649fa Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 9 Dec 2016 21:07:34 -0600 Subject: [PATCH 161/248] Add more MessageEmbed stuff (#970) * this is why wrapping data 1:1 is a bad idea * ffs * whoever wrote role.hexColor is a bad, bad person * Update MessageEmbed.js * Update MessageEmbed.js --- src/structures/MessageEmbed.js | 37 +++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js index 9dd0a9a0f..1249c422b 100644 --- a/src/structures/MessageEmbed.js +++ b/src/structures/MessageEmbed.js @@ -21,18 +21,18 @@ class MessageEmbed { } setup(data) { - /** - * The title of this embed, if there is one - * @type {?string} - */ - this.title = data.title; - /** * The type of this embed * @type {string} */ this.type = data.type; + /** + * The title of this embed, if there is one + * @type {?string} + */ + this.title = data.title; + /** * The description of this embed, if there is one * @type {?string} @@ -45,6 +45,12 @@ class MessageEmbed { */ this.url = data.url; + /** + * The color of the embed + * @type {number} + */ + this.color = data.color; + /** * The fields of this embed * @type {MessageEmbedField[]} @@ -90,6 +96,17 @@ class MessageEmbed { get createdAt() { return new Date(this.createdTimestamp); } + + /** + * The hexadecimal version of the embed color, with a leading hash. + * @type {string} + * @readonly + */ + get hexColor() { + let col = this.color.toString(16); + while (col.length < 6) col = `0${col}`; + return `#${col}`; + } } /** @@ -188,6 +205,12 @@ class MessageEmbedAuthor { * @type {string} */ this.url = data.url; + + /** + * The icon URL of this author + * @type {string} + */ + this.iconURL = data.icon_url; } } @@ -251,7 +274,7 @@ class MessageEmbedFooter { * The icon URL of this footer * @type {string} */ - this.iconUrl = data.icon_url; + this.iconURL = data.icon_url; /** * The proxy icon URL of this footer From b79a91b3a9b2a7e23bad978eeb785818d3579aaf Mon Sep 17 00:00:00 2001 From: Slamakans Date: Sat, 10 Dec 2016 04:09:55 +0100 Subject: [PATCH 162/248] new errors for richembed yay (#966) * Update faq.md (#949) * fix docs * Revert "fixed typo in documentation" (#950) * new errors for richembed yay Simplified the parsing for color arrays. Throwing some errors on values that would result in Bad Requests. Changed setAuthor and setFooter's icon parameter to icon_url to match the embed object's property name. Changed the docs for setFooter to reflect that icon_url is optional. * changed docs instead of arg names * capitalization --- src/structures/RichEmbed.js | 11 ++++++++--- test/random.js | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index a62e58833..c135a6219 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -104,10 +104,15 @@ class RichEmbed { */ setColor(color) { if (color instanceof Array) { - color = parseInt(((1 << 24) + (color[0] << 16) + (color[1] << 8) + color[2]).toString(16).slice(1), 16); + color = (color[0] << 16) + (color[1] << 8) + color[2]; } else if (typeof color === 'string' && color.startsWith('#')) { color = parseInt(color.replace('#', ''), 16); } + if (color < 0 || color > 0xFFFFFF) { + throw new RangeError('RichEmbed color must be within the range 0 - 16777215 (0xFFFFFF).'); + } else if (color && isNaN(parseInt(color))) { + throw new TypeError('Unable to convert RichEmbed color to a number.'); + } this.color = color; return this; } @@ -115,7 +120,7 @@ class RichEmbed { /** * Sets the author of this embed * @param {string} name The name of the author - * @param {string} [icon] The icon of the author + * @param {string} [icon] The icon URL of the author * @returns {RichEmbed} This embed */ setAuthor(name, icon) { @@ -171,7 +176,7 @@ class RichEmbed { /** * Sets the footer of this embed * @param {string} text The text of the footer - * @param {string} icon The icon of the footer + * @param {string} [icon] The icon URL of the footer * @returns {RichEmbed} This embed */ setFooter(text, icon) { diff --git a/test/random.js b/test/random.js index f3af346a7..d6c761ea5 100644 --- a/test/random.js +++ b/test/random.js @@ -13,7 +13,7 @@ client.login(token).then(atoken => console.log('logged in with token ' + atoken) client.ws.on('send', console.log); client.on('ready', () => { - console.log('ready!'); + console.log('ready'); }); client.on('userUpdate', (o, n) => { From 710d3db94fb38d865b05afe5f203c81fe895b9b8 Mon Sep 17 00:00:00 2001 From: bdistin Date: Fri, 9 Dec 2016 21:10:50 -0600 Subject: [PATCH 163/248] RichEmbed Tweaks/Improvements (#964) * RichEmbed Tweaks/Improvements Changed .addTimestamp() to .setTimestamp() to bring it inline with all other single value settings. Changed Field value to StringResolveable from String to bring it inline with typical sendMessage functionality. * Lint Fix * Remove Default, Add check for undefined --- src/structures/RichEmbed.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index c135a6219..0f1dc915b 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -133,7 +133,7 @@ class RichEmbed { * @param {Date} [timestamp=current date] The timestamp * @returns {RichEmbed} This embed */ - addTimestamp(timestamp = new Date()) { + setTimestamp(timestamp = new Date()) { this.timestamp = timestamp; return this; } @@ -141,13 +141,17 @@ class RichEmbed { /** * Adds a field to the embed (max 25) * @param {string} name The name of the field - * @param {string} value The value of the field + * @param {StringResolvable} value The value of the field * @param {boolean} [inline=false] Set the field to display inline * @returns {RichEmbed} This embed */ addField(name, value, inline = false) { if (this.fields.length >= 25) throw new RangeError('RichEmbeds may not exceed 25 fields.'); if (name.length > 256) throw new RangeError('RichEmbed field names may not exceed 256 characters.'); + if (typeof value !== 'undefined') { + if (value instanceof Array) value = value.join('\n'); + else if (typeof value !== 'string') value = String(value); + } if (value.length > 1024) throw new RangeError('RichEmbed field values may not exceed 1024 characters.'); this.fields.push({ name, value, inline }); return this; From d766e727a1e346a14ca2b12aba0adfa24c6ea134 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 11 Dec 2016 03:07:52 -0600 Subject: [PATCH 164/248] add GuildChannel#clone (#973) * add GuildChannel#clone * e --- src/client/rest/RESTMethods.js | 4 +++- src/structures/Guild.js | 6 ++++-- src/structures/GuildChannel.js | 18 ++++++++++++++---- src/structures/PermissionOverwrites.js | 9 ++++++--- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 088cd406b..9acf62fc2 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -128,10 +128,12 @@ class RESTMethods { }).then(data => this.rest.client.actions.MessageUpdate.handle(data).updated); } - createChannel(guild, channelName, channelType) { + createChannel(guild, channelName, channelType, overwrites) { + if (overwrites instanceof Collection) overwrites = overwrites.array(); return this.rest.makeRequest('post', Constants.Endpoints.guildChannels(guild.id), true, { name: channelName, type: channelType, + permission_overwrites: overwrites, }).then(data => this.rest.client.actions.ChannelCreate.handle(data).channel); } diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 81dddeeec..576097e53 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -561,6 +561,8 @@ class Guild { * Creates a new channel in the guild. * @param {string} name The name of the new channel * @param {string} type The type of the new channel, either `text` or `voice` + * @param {Array.} overwrites + * Permissions overwrites to apply to the new channel * @returns {Promise} * @example * // create a new text channel @@ -568,8 +570,8 @@ class Guild { * .then(channel => console.log(`Created new channel ${channel}`)) * .catch(console.error); */ - createChannel(name, type) { - return this.client.rest.methods.createChannel(this, name, type); + createChannel(name, type, overwrites) { + return this.client.rest.methods.createChannel(this, name, type, overwrites); } /** diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 4a30d033c..db59f7447 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -66,8 +66,8 @@ class GuildChannel extends Channel { const overwrites = this.overwritesFor(member, true, roles); for (const overwrite of overwrites.role.concat(overwrites.member)) { - permissions &= ~overwrite.denyData; - permissions |= overwrite.allowData; + permissions &= ~overwrite.deny; + permissions |= overwrite.allow; } const admin = Boolean(permissions & Constants.PermissionFlags.ADMINISTRATOR); @@ -144,8 +144,8 @@ class GuildChannel extends Channel { const prevOverwrite = this.permissionOverwrites.get(userOrRole.id); if (prevOverwrite) { - payload.allow = prevOverwrite.allowData; - payload.deny = prevOverwrite.denyData; + payload.allow = prevOverwrite.allow; + payload.deny = prevOverwrite.deny; } for (const perm in options) { @@ -247,6 +247,16 @@ class GuildChannel extends Channel { return this.client.rest.methods.createChannelInvite(this, options); } + /** + * Clone this channel + * @param {string} [name=this.name] Optional name for the new channel, otherwise it has the name of this channel + * @param {boolean} [withPermissions=true] Whether to clone the channel with this channel's permission overwrites + * @returns {Promise} + */ + clone(name = this.name, withPermissions = true) { + return this.guild.createChannel(name, this.type, withPermissions ? this.permissionOverwrites : []); + } + /** * Checks if this channel has the same type, topic, position, name, overwrites and ID as another channel. * In most cases, a simple `channel.id === channel2.id` will do, and is much faster too. diff --git a/src/structures/PermissionOverwrites.js b/src/structures/PermissionOverwrites.js index 0ecc3cd83..3e9ca073a 100644 --- a/src/structures/PermissionOverwrites.js +++ b/src/structures/PermissionOverwrites.js @@ -7,7 +7,10 @@ class PermissionOverwrites { * The GuildChannel this overwrite is for * @type {GuildChannel} */ - this.channel = guildChannel; + Object.defineProperty(this, 'channel', { + value: guildChannel, + enumerable: false, + }); if (data) this.setup(data); } @@ -25,8 +28,8 @@ class PermissionOverwrites { */ this.type = data.type; - this.denyData = data.deny; - this.allowData = data.allow; + this.deny = data.deny; + this.allow = data.allow; } /** From 52a83b9218f9774bf93529a2d880ee5448432066 Mon Sep 17 00:00:00 2001 From: Hackzzila Date: Sun, 11 Dec 2016 13:02:01 -0600 Subject: [PATCH 165/248] Add ffmpeg-binaries as a possible source of ffmpeg (#975) * Add ffmpeg-binaries as a possible source of ffmpeg * Add note in faq --- docs/general/faq.md | 1 + src/client/voice/pcm/FfmpegConverterEngine.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/general/faq.md b/docs/general/faq.md index 9405859e1..d7e4188b8 100644 --- a/docs/general/faq.md +++ b/docs/general/faq.md @@ -12,6 +12,7 @@ Update to Node.js 6.0.0 or newer. node-opus is greatly preferred, due to it having significantly better performance. ## How do I install FFMPEG? +- **npm:** `npm install --save ffmpeg-binaries` - **Ubuntu 16.04:** `sudo apt install ffmpeg` - **Ubuntu 14.04:** `sudo apt-get install libav-tools` - **Windows:** See the [FFMPEG section of AoDude's guide](https://github.com/bdistin/OhGodMusicBot/blob/master/README.md#download-ffmpeg). diff --git a/src/client/voice/pcm/FfmpegConverterEngine.js b/src/client/voice/pcm/FfmpegConverterEngine.js index 34c5d509a..8fb725bda 100644 --- a/src/client/voice/pcm/FfmpegConverterEngine.js +++ b/src/client/voice/pcm/FfmpegConverterEngine.js @@ -67,7 +67,14 @@ class FfmpegConverterEngine extends ConverterEngine { } function chooseCommand() { - for (const cmd of ['ffmpeg', 'avconv', './ffmpeg', './avconv']) { + for (const cmd of [ + 'ffmpeg', + 'avconv', + './ffmpeg', + './avconv', + 'node_modules\\ffmpeg-binaries\\bin\\ffmpeg', + 'node_modules/ffmpeg-binaries/bin/ffmpeg', + ]) { if (!ChildProcess.spawnSync(cmd, ['-h']).error) return cmd; } throw new Error( From 3193df792eaf5f5db6dd92b7b8929859561a5ccd Mon Sep 17 00:00:00 2001 From: bdistin Date: Mon, 12 Dec 2016 21:29:39 -0600 Subject: [PATCH 166/248] Added Missing Author URL Option (#979) --- src/structures/RichEmbed.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index 0f1dc915b..5aac3fd44 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -121,10 +121,11 @@ class RichEmbed { * Sets the author of this embed * @param {string} name The name of the author * @param {string} [icon] The icon URL of the author + * @param {string} [url] The URL of the author * @returns {RichEmbed} This embed */ - setAuthor(name, icon) { - this.author = { name, icon_url: icon }; + setAuthor(name, icon, url) { + this.author = { name, icon_url: icon, url }; return this; } From 586d652c168e79640e810eabe4c43d4359b82663 Mon Sep 17 00:00:00 2001 From: Cole Date: Mon, 12 Dec 2016 21:34:19 -0600 Subject: [PATCH 167/248] Quality of life changes (#968) * + Added function to get a user's default avatar + Added HOST constant to Constants + Added assets endpoint + Added quality of life function to get a user's avatar or default avatar + Added quality of life function to get member's nickname or username * * Fixed invocation of a getter. * Fixed lint issue * Made the API constant use the HOST constant for DRY-ness Changed DOC comment to be more descriptive * Update GuildMember.js --- src/structures/GuildMember.js | 9 +++++++++ src/structures/User.js | 20 ++++++++++++++++++++ src/util/Constants.js | 12 +++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 70746c330..df74613b0 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -182,6 +182,15 @@ class GuildMember { return this.user.id; } + /** + * The nickname of the member, or their username if they don't have one + * @type {string} + * @readonly + */ + get displayName() { + return this.nickname || this.user.username; + } + /** * The overall set of permissions for the guild member, taking only roles into account * @type {EvaluatedPermissions} diff --git a/src/structures/User.js b/src/structures/User.js index 54c57ee08..6a7731f91 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -104,6 +104,26 @@ class User { return Constants.Endpoints.avatar(this.id, this.avatar); } + /** + * A link to the user's default avatar + * @type {string} + * @readonly + */ + get defaultAvatarURL() { + let defaultAvatars = Object.values(Constants.DefaultAvatars); + let defaultAvatar = this.discriminator % defaultAvatars.length; + return Constants.endpoints.assets(`${defaultAvatars[defaultAvatar]}.png`); + } + + /** + * A link to the user's avatar if they have one. Otherwise a link to their default avatar will be returned + * @type {string} + * @readonly + */ + get displayAvatarURL() { + return this.avatarURL || this.defaultAvatarURL; + } + /** * The note that is set for the user * This is only available when using a user account. diff --git a/src/util/Constants.js b/src/util/Constants.js index 3470a53e1..462593bda 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -71,7 +71,8 @@ exports.Errors = { }; const PROTOCOL_VERSION = exports.PROTOCOL_VERSION = 6; -const API = exports.API = `https://discordapp.com/api/v${PROTOCOL_VERSION}`; +const HOST = exports.HOST = `https://discordapp.com`; +const API = exports.API = `${HOST}/api/v${PROTOCOL_VERSION}`; const Endpoints = exports.Endpoints = { // general login: `${API}/auth/login`, @@ -80,6 +81,7 @@ const Endpoints = exports.Endpoints = { botGateway: `${API}/gateway/bot`, invite: (id) => `${API}/invite/${id}`, inviteLink: (id) => `https://discord.gg/${id}`, + assets: (asset) => `${HOST}/assets/${asset}`, CDN: 'https://cdn.discordapp.com', // users @@ -311,6 +313,14 @@ exports.MessageTypes = { 6: 'PINS_ADD', }; +exports.DefaultAvatars = { + BLURPLE: '6debd47ed13483642cf09e832ed0bc1b', + GREY: '322c936a8c8be1b803cd94861bdfa868', + GREEN: 'dd4dbc0016779df1378e7812eabaa04d', + ORANGE: '0e291f67c9274a1abdddeb3fd919cbaa', + RED: '1cbd08c76f8af6dddce02c5138971129', +}; + const PermissionFlags = exports.PermissionFlags = { CREATE_INSTANT_INVITE: 1 << 0, KICK_MEMBERS: 1 << 1, From 78026df1df453e26176a73d3700abddeffffb725 Mon Sep 17 00:00:00 2001 From: Perry Berman Date: Tue, 13 Dec 2016 18:43:28 -0700 Subject: [PATCH 168/248] fix defaultAvatarURL (#983) looks like someone forgot how to caps --- src/structures/User.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/User.js b/src/structures/User.js index 6a7731f91..0e1ce8036 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -112,7 +112,7 @@ class User { get defaultAvatarURL() { let defaultAvatars = Object.values(Constants.DefaultAvatars); let defaultAvatar = this.discriminator % defaultAvatars.length; - return Constants.endpoints.assets(`${defaultAvatars[defaultAvatar]}.png`); + return Constants.Endpoints.assets(`${defaultAvatars[defaultAvatar]}.png`); } /** From 8596eadb25673a237d11132becdd650610094e55 Mon Sep 17 00:00:00 2001 From: Zack Campbell Date: Tue, 13 Dec 2016 19:43:49 -0600 Subject: [PATCH 169/248] Bring in latest typings fix (#982) --- typings/index.d.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index 6538718fb..95204c0f9 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,6 +1,7 @@ // Type definitions for discord.js 10.0.1 // Project: https://github.com/hydrabolt/discord.js // Definitions by: acdenisSK (https://github.com/acdenisSK) +// License: MIT declare module "discord.js" { import { EventEmitter } from "events"; @@ -101,7 +102,7 @@ declare module "discord.js" { push(request: {}); } export class WebhookClient extends Webhook { - contructor(id: string, token: string, options?: ClientOptions); + constructor(id: string, token: string, options?: ClientOptions); options: ClientOptions; } export class Emoji { @@ -550,7 +551,7 @@ declare module "discord.js" { on(event: "launch", listener: (shard: Shard) => void): this; } export class ShardClientUtil { - contructor(client: Client); + constructor(client: Client); id: number; count: number; broadcastEval(script: string): Promise; From 8139bef4e23a18864ef4b36ca6784e607dde38eb Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 14 Dec 2016 11:23:22 -0600 Subject: [PATCH 170/248] add some missing properties (#978) * add premium to profile * add other missing stuff --- src/structures/Guild.js | 6 ++++++ src/structures/UserProfile.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 576097e53..f4fabc809 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -114,6 +114,12 @@ class Guild { */ this.features = data.features; + /** + * The ID of the application that created this guild (if applicable) + * @type {?string} + */ + this.applicationId = data.application_id; + /** * A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji. * @type {Collection} diff --git a/src/structures/UserProfile.js b/src/structures/UserProfile.js index 69f05c7ae..734a9913e 100644 --- a/src/structures/UserProfile.js +++ b/src/structures/UserProfile.js @@ -36,6 +36,12 @@ class UserProfile { } setup(data) { + /** + * If the user has Discord Premium + * @type {Boolean} + */ + this.premium = data.premium; + for (const guild of data.mutual_guilds) { if (this.client.guilds.has(guild.id)) { this.mutualGuilds.set(guild.id, this.client.guilds.get(guild.id)); From 4e76251aa2f57ada7f69fc39a2275eb7a9fd4bca Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 14 Dec 2016 12:37:23 -0500 Subject: [PATCH 171/248] Fix some Gus stuff --- src/structures/Guild.js | 3 +-- src/structures/PermissionOverwrites.js | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index f4fabc809..83aae6a71 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -567,8 +567,7 @@ class Guild { * Creates a new channel in the guild. * @param {string} name The name of the new channel * @param {string} type The type of the new channel, either `text` or `voice` - * @param {Array.} overwrites - * Permissions overwrites to apply to the new channel + * @param {Array} overwrites Permission overwrites to apply to the new channel * @returns {Promise} * @example * // create a new text channel diff --git a/src/structures/PermissionOverwrites.js b/src/structures/PermissionOverwrites.js index 3e9ca073a..9b2f536f5 100644 --- a/src/structures/PermissionOverwrites.js +++ b/src/structures/PermissionOverwrites.js @@ -5,12 +5,11 @@ class PermissionOverwrites { constructor(guildChannel, data) { /** * The GuildChannel this overwrite is for + * @name PermissionOverwrites#channel * @type {GuildChannel} + * @readonly */ - Object.defineProperty(this, 'channel', { - value: guildChannel, - enumerable: false, - }); + Object.defineProperty(this, 'channel', { value: guildChannel }); if (data) this.setup(data); } From f1c4ef659ebc26b049da2ff2f48390cc3937052a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 14 Dec 2016 12:40:02 -0500 Subject: [PATCH 172/248] Fix a Hydar thing --- docs/general/welcome.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 468239573..2c23a55ba 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -21,9 +21,13 @@ Welcome to the discord.js v10 documentation. v10 is just a more consistent and stable iteration over v9, and contains loads of new and improved features, optimisations, and bug fixes. ## About -discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily. -It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend. -Usability and performance are key focuses of discord.js, and it also has nearly 100% coverage of the Discord API. +discord.js is a powerful node.js module that allows you to interact with the +[Discord API](https://discordapp.com/developers/docs/intro) very easily. + +* Object-oriented +* Predictable abstractions +* Performant +* Nearly 100% coverage of the Discord API ## Installation **Node.js 6.0.0 or newer is required.** From bef0523ebf4c1edb7855ea2e58244061e7505ded Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 14 Dec 2016 13:04:40 -0500 Subject: [PATCH 173/248] Capitalise ID in Guild#applicationId --- src/structures/Guild.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 83aae6a71..33cbb2425 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -118,7 +118,7 @@ class Guild { * The ID of the application that created this guild (if applicable) * @type {?string} */ - this.applicationId = data.application_id; + this.applicationID = data.application_id; /** * A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji. From 906bb3c5f3049717ab79a39609c060d6cfb2c8f1 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 14 Dec 2016 21:35:40 +0100 Subject: [PATCH 174/248] Utilise Collection#equals for structures' equals (#986) Utilizing Collection.equals in GuildChannel.equals Utilizing Collection.equals in GroupDMChannel.equals --- src/structures/GroupDMChannel.js | 5 +---- src/structures/GuildChannel.js | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js index c79b6dcaa..7f1ab8ea9 100644 --- a/src/structures/GroupDMChannel.js +++ b/src/structures/GroupDMChannel.js @@ -1,7 +1,6 @@ const Channel = require('./Channel'); const TextBasedChannel = require('./interface/TextBasedChannel'); const Collection = require('../util/Collection'); -const arraysEqual = require('../util/ArraysEqual'); /* { type: 3, @@ -101,9 +100,7 @@ class GroupDMChannel extends Channel { this.ownerID === channel.ownerID; if (equal) { - const thisIDs = this.recipients.keyArray(); - const otherIDs = channel.recipients.keyArray(); - return arraysEqual(thisIDs, otherIDs); + return this.recipients.equals(channel.recipients); } return equal; diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index db59f7447..807150e9a 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -4,7 +4,6 @@ const PermissionOverwrites = require('./PermissionOverwrites'); const EvaluatedPermissions = require('./EvaluatedPermissions'); const Constants = require('../util/Constants'); const Collection = require('../util/Collection'); -const arraysEqual = require('../util/ArraysEqual'); /** * Represents a guild channel (i.e. text channels and voice channels) @@ -273,9 +272,7 @@ class GuildChannel extends Channel { if (equal) { if (this.permissionOverwrites && channel.permissionOverwrites) { - const thisIDSet = this.permissionOverwrites.keyArray(); - const otherIDSet = channel.permissionOverwrites.keyArray(); - equal = arraysEqual(thisIDSet, otherIDSet); + equal = this.permissionOverwrites.equals(channel.permissionOverwrites); } else { equal = !this.permissionOverwrites && !channel.permissionOverwrites; } From 264ee8e7f1760a16662640866028415ffe9f0c95 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 14 Dec 2016 23:10:34 +0100 Subject: [PATCH 175/248] Equals updates (#987) * Docs update Changed the param names and param descriptions to be consistent. * Added === comparison Changed Presence.equals and Game.equals to first compare using === * Collection.equals fix Now returns false when collection is undefined, instead of crashing :100: --- src/structures/GroupDMChannel.js | 2 +- src/structures/Guild.js | 2 +- src/structures/GuildChannel.js | 2 +- src/structures/Presence.js | 26 +++++++++++++------------- src/structures/Role.js | 2 +- src/structures/User.js | 2 +- src/util/Collection.js | 1 + 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js index 7f1ab8ea9..ada3a90f4 100644 --- a/src/structures/GroupDMChannel.js +++ b/src/structures/GroupDMChannel.js @@ -89,7 +89,7 @@ class GroupDMChannel extends Channel { * Whether this channel equals another channel. It compares all properties, so for most operations * it is advisable to just compare `channel.id === channel2.id` as it is much faster and is often * what most users need. - * @param {GroupDMChannel} channel The channel to compare to + * @param {GroupDMChannel} channel Channel to compare with * @returns {boolean} */ equals(channel) { diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 33cbb2425..2d679d117 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -692,7 +692,7 @@ class Guild { * Whether this Guild equals another Guild. It compares all properties, so for most operations * it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often * what most users need. - * @param {Guild} guild The guild to compare + * @param {Guild} guild Guild to compare with * @returns {boolean} */ equals(guild) { diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 807150e9a..a93078972 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -259,7 +259,7 @@ class GuildChannel extends Channel { /** * Checks if this channel has the same type, topic, position, name, overwrites and ID as another channel. * In most cases, a simple `channel.id === channel2.id` will do, and is much faster too. - * @param {GuildChannel} channel The channel to compare this channel to + * @param {GuildChannel} channel Channel to compare with * @returns {boolean} */ equals(channel) { diff --git a/src/structures/Presence.js b/src/structures/Presence.js index 05db21aaa..ddca5fb34 100644 --- a/src/structures/Presence.js +++ b/src/structures/Presence.js @@ -28,14 +28,14 @@ class Presence { /** * Whether this presence is equal to another - * @param {Presence} other the presence to compare + * @param {Presence} presence Presence to compare with * @returns {boolean} */ - equals(other) { - return ( - other && - this.status === other.status && - this.game ? this.game.equals(other.game) : !other.game + equals(presence) { + return this === presence || ( + presence && + this.status === presence.status && + this.game ? this.game.equals(presence.game) : !presence.game ); } } @@ -75,15 +75,15 @@ class Game { /** * Whether this game is equal to another game - * @param {Game} other the other game to compare + * @param {Game} game Game to compare with * @returns {boolean} */ - equals(other) { - return ( - other && - this.name === other.name && - this.type === other.type && - this.url === other.url + equals(game) { + return this === game || ( + game && + this.name === game.name && + this.type === game.type && + this.url === game.url ); } } diff --git a/src/structures/Role.js b/src/structures/Role.js index ce1b09025..5936da0fc 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -302,7 +302,7 @@ class Role { * Whether this role equals another role. It compares all properties, so for most operations * it is advisable to just compare `role.id === role2.id` as it is much faster and is often * what most users need. - * @param {Role} role The role to compare to + * @param {Role} role Role to compare with * @returns {boolean} */ equals(role) { diff --git a/src/structures/User.js b/src/structures/User.js index 0e1ce8036..ae0461590 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -230,7 +230,7 @@ class User { /** * Checks if the user is equal to another. It compares username, ID, discriminator, status and the game being played. * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties. - * @param {User} user The user to compare + * @param {User} user User to compare with * @returns {boolean} */ equals(user) { diff --git a/src/util/Collection.js b/src/util/Collection.js index 2b6ba1537..f90079bae 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -338,6 +338,7 @@ class Collection extends Map { * @returns {boolean} Whether the collections have identical contents */ equals(collection) { + if (!collection) return false; if (this === collection) return true; if (this.size !== collection.size) return false; return !this.find((value, key) => { From 2e7472bb1a06ad68fc1ee6b43bfa16c2e73a70e3 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 14 Dec 2016 18:40:47 -0600 Subject: [PATCH 176/248] just webpack things (#988) --- src/structures/interface/TextBasedChannel.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index f8ebdb4ae..308545f05 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -363,9 +363,7 @@ exports.applyToClass = (structure, full = false) => { props.push('createCollector'); props.push('awaitMessages'); } - for (const prop of props) applyProp(structure, prop); + for (const prop of props) { + Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop)); + } }; - -function applyProp(structure, prop) { - Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop)); -} From 33a43881211e50a8562a8ace81846afebe9bd123 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 14 Dec 2016 20:42:48 -0500 Subject: [PATCH 177/248] Switch to SVG logo --- README.md | 10 +++++----- docs/general/welcome.md | 10 +++++----- docs/logo.svg | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 docs/logo.svg diff --git a/README.md b/README.md index 775a33904..a7b0177f1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

- discord.js + discord.js


@@ -20,10 +20,10 @@ discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily. -* Object-oriented -* Predictable abstractions -* Performant -* Nearly 100% coverage of the Discord API +- Object-oriented +- Predictable abstractions +- Performant +- Nearly 100% coverage of the Discord API ## Installation **Node.js 6.0.0 or newer is required.** diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 2c23a55ba..10b8f2223 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -1,7 +1,7 @@


- discord.js + discord.js


@@ -24,10 +24,10 @@ v10 is just a more consistent and stable iteration over v9, and contains loads o discord.js is a powerful node.js module that allows you to interact with the [Discord API](https://discordapp.com/developers/docs/intro) very easily. -* Object-oriented -* Predictable abstractions -* Performant -* Nearly 100% coverage of the Discord API +- Object-oriented +- Predictable abstractions +- Performant +- Nearly 100% coverage of the Discord API ## Installation **Node.js 6.0.0 or newer is required.** diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 000000000..81feb17ba --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + From 36be4c47f6ccaf6adf471449ef6398f5f7c8178f Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 14 Dec 2016 21:05:48 -0500 Subject: [PATCH 178/248] Change welcome logo to reference static one on site --- docs/general/welcome.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 10b8f2223..4f96b57ad 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -1,7 +1,7 @@


- discord.js + discord.js


From 9c59b649ad4b39baa2cee202d68228f7d474f170 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Thu, 15 Dec 2016 10:32:06 -0600 Subject: [PATCH 179/248] get rid of user agent errors (#990) --- src/client/rest/APIRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/rest/APIRequest.js b/src/client/rest/APIRequest.js index 0e6e987cb..67491900d 100644 --- a/src/client/rest/APIRequest.js +++ b/src/client/rest/APIRequest.js @@ -45,7 +45,7 @@ class APIRequest { } else if (this.data) { apiRequest.send(this.data); } - apiRequest.set('User-Agent', this.rest.userAgentManager.userAgent); + if (!this.rest.client.browser) apiRequest.set('User-Agent', this.rest.userAgentManager.userAgent); return apiRequest; } } From a20bac72584615d13f421237285ec8147dbc9c8b Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Thu, 15 Dec 2016 10:32:19 -0600 Subject: [PATCH 180/248] update per issue 69 on the h&c github (#963) * update per issue 69 on the h&c github * clean up things that don't exist anymore --- src/client/Client.js | 21 +++------------------ src/client/rest/RESTMethods.js | 11 +---------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index eccab6df5..6a06facd9 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -124,18 +124,6 @@ class Client extends EventEmitter { this.token = null; } - /** - * The email, if there is one, for the logged in Client - * @type {?string} - */ - this.email = null; - - /** - * The password, if there is one, for the logged in Client - * @type {?string} - */ - this.password = null; - /** * The ClientUser representing the logged in Client * @type {?ClientUser} @@ -236,9 +224,7 @@ class Client extends EventEmitter { * much better to use a bot account rather than a user account. * Bot accounts have higher rate limits and have access to some features user accounts don't have. User bots * that are making a lot of API requests can even be banned. - * @param {string} tokenOrEmail The token or email used for the account. If it is an email, a password _must_ be - * provided. - * @param {string} [password] The password for the account, only needed if an email was provided. + * @param {string} token The token used for the account. * @returns {Promise} * @example * // log the client in using a token @@ -250,9 +236,8 @@ class Client extends EventEmitter { * const password = 'supersecret123'; * client.login(email, password); */ - login(tokenOrEmail, password = null) { - if (password) return this.rest.methods.loginEmailPassword(tokenOrEmail, password); - return this.rest.methods.loginToken(tokenOrEmail); + login(token) { + return this.rest.methods.login(token); } /** diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 9acf62fc2..7d996deda 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -16,22 +16,13 @@ class RESTMethods { this.rest = restManager; } - loginToken(token = this.rest.client.token) { + login(token = this.rest.client.token) { return new Promise((resolve, reject) => { token = token.replace(/^Bot\s*/i, ''); this.rest.client.manager.connectToWebSocket(token, resolve, reject); }); } - loginEmailPassword(email, password) { - this.rest.client.emit('warn', 'Client launched using email and password - should use token instead'); - this.rest.client.email = email; - this.rest.client.password = password; - return this.rest.makeRequest('post', Constants.Endpoints.login, false, { email, password }).then(data => - this.loginToken(data.token) - ); - } - logout() { return this.rest.makeRequest('post', Constants.Endpoints.logout, true, {}); } From fbe1929bdede44fec56e5da1a8a14862c109b605 Mon Sep 17 00:00:00 2001 From: bdistin Date: Thu, 15 Dec 2016 10:32:37 -0600 Subject: [PATCH 181/248] Pass Reason to the streamDispatcher end event (#985) * Pass Reason to the streamDispatcher end event * Update .end() to bring inline with .stop() Also changed "Stream is not generating quickly enough." from an end to an error, per Crawl... * Fix docs Copy/Paste fail from collection end event --- src/client/voice/dispatcher/StreamDispatcher.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index 19ded79ff..8f1483e17 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -114,9 +114,10 @@ class StreamDispatcher extends EventEmitter { /** * Stops the current stream permanently and emits an `end` event. + * @param {string} [reason='user'] An optional reason for stopping the dispatcher. */ - end() { - this._triggerTerminalState('end', 'user requested'); + end(reason = 'user') { + this._triggerTerminalState('end', reason); } _setSpeaking(value) { @@ -179,7 +180,7 @@ class StreamDispatcher extends EventEmitter { const data = this.streamingData; if (data.missed >= 5) { - this._triggerTerminalState('end', 'Stream is not generating quickly enough.'); + this._triggerTerminalState('error', 'Stream is not generating quickly enough.'); return; } @@ -233,12 +234,14 @@ class StreamDispatcher extends EventEmitter { } } - _triggerEnd() { + _triggerEnd(reason) { /** * Emitted once the stream has ended. Attach a `once` listener to this. * @event StreamDispatcher#end + * @param {string} reason The reason for the end of the dispatcher. If it ended because it reached the end of the + * stream, this would be `stream`. If you invoke `.end()` without specifying a reason, this would be `user`. */ - this.emit('end'); + this.emit('end', reason); } _triggerError(err) { @@ -280,7 +283,7 @@ class StreamDispatcher extends EventEmitter { return; } - this.stream.on('end', err => this._triggerTerminalState('end', err)); + this.stream.on('end', err => this._triggerTerminalState('end', err || 'stream')); this.stream.on('error', err => this._triggerTerminalState('error', err)); const data = this.streamingData; From 361547a5888813f1c230a1ba474d454b6c27f87a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 15 Dec 2016 20:10:38 -0500 Subject: [PATCH 182/248] Fix a bunch of capitalisation issues --- src/client/voice/ClientVoiceManager.js | 2 +- src/client/voice/VoiceConnection.js | 4 ++-- src/structures/MessageCollector.js | 2 +- src/structures/UserConnection.js | 2 +- src/structures/UserProfile.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js index 09e9982a1..ea8374540 100644 --- a/src/client/voice/ClientVoiceManager.js +++ b/src/client/voice/ClientVoiceManager.js @@ -139,7 +139,7 @@ class PendingVoiceConnection extends EventEmitter { /** * An object containing data required to connect to the voice servers with - * @type {object} + * @type {Object} */ this.data = {}; diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index e3f1c4c7d..ac44ff866 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -46,7 +46,7 @@ class VoiceConnection extends EventEmitter { /** * The authentication data needed to connect to the voice server - * @type {object} + * @type {Object} * @private */ this.authentication = pendingConnection.data; @@ -92,7 +92,7 @@ class VoiceConnection extends EventEmitter { /** * Object that wraps contains the `ws` and `udp` sockets of this voice connection - * @type {object} + * @type {Object} * @private */ this.sockets = {}; diff --git a/src/structures/MessageCollector.js b/src/structures/MessageCollector.js index 72567d731..f84ecbda3 100644 --- a/src/structures/MessageCollector.js +++ b/src/structures/MessageCollector.js @@ -16,7 +16,7 @@ class MessageCollector extends EventEmitter { * return false; // failed the filter test * } * ``` - * @typedef {function} CollectorFilterFunction + * @typedef {Function} CollectorFilterFunction */ /** diff --git a/src/structures/UserConnection.js b/src/structures/UserConnection.js index 70814de01..6ee9fc5b4 100644 --- a/src/structures/UserConnection.js +++ b/src/structures/UserConnection.js @@ -33,7 +33,7 @@ class UserConnection { /** * Whether the connection is revoked - * @type {Boolean} + * @type {boolean} */ this.revoked = data.revoked; diff --git a/src/structures/UserProfile.js b/src/structures/UserProfile.js index 734a9913e..77f097ca9 100644 --- a/src/structures/UserProfile.js +++ b/src/structures/UserProfile.js @@ -38,7 +38,7 @@ class UserProfile { setup(data) { /** * If the user has Discord Premium - * @type {Boolean} + * @type {boolean} */ this.premium = data.premium; From 6c38b83923ce8e4c83a78a445088d33455e937f8 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sat, 17 Dec 2016 11:44:25 -0600 Subject: [PATCH 183/248] fix runkit/tonicdev example (#998) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index d0d827327..23c0fc87a 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "url": "https://github.com/hydrabolt/discord.js/issues" }, "homepage": "https://github.com/hydrabolt/discord.js#readme", + "runkitExampleFilename": "./docs/examples/ping.js", "dependencies": { "@types/node": "^6.0.51", "pako": "^1.0.3", From 736fa7c6113e549762572def4a5746194545a058 Mon Sep 17 00:00:00 2001 From: Will Nelson Date: Sat, 17 Dec 2016 10:22:39 -0800 Subject: [PATCH 184/248] friendlier notification of an invalid token (#997) * friendlier notification of an invalid token * fixed * even fixeder * no token -> invalid token * eslint * Update RESTMethods.js --- src/client/rest/RESTMethods.js | 1 + src/util/Constants.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 7d996deda..8241a4464 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -18,6 +18,7 @@ class RESTMethods { login(token = this.rest.client.token) { return new Promise((resolve, reject) => { + if (typeof token !== 'string') throw new Error(Constants.Errors.INVALID_TOKEN); token = token.replace(/^Bot\s*/i, ''); this.rest.client.manager.connectToWebSocket(token, resolve, reject); }); diff --git a/src/util/Constants.js b/src/util/Constants.js index 462593bda..93453e6d4 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -68,6 +68,7 @@ exports.Errors = { INVALID_RATE_LIMIT_METHOD: 'Unknown rate limiting method.', BAD_LOGIN: 'Incorrect login details were provided.', INVALID_SHARD: 'Invalid shard settings were provided.', + INVALID_TOKEN: 'An invalid token was provided.', }; const PROTOCOL_VERSION = exports.PROTOCOL_VERSION = 6; From b74c1b70b6522a2dea03659b813f7a2cce80545d Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 18 Dec 2016 15:06:42 -0600 Subject: [PATCH 185/248] fix 992 (#994) --- src/client/Client.js | 3 --- src/client/ClientManager.js | 17 +++++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 6a06facd9..907f3e033 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -249,9 +249,6 @@ class Client extends EventEmitter { for (const i of this._intervals) clearInterval(i); this._timeouts.clear(); this._intervals.clear(); - this.token = null; - this.email = null; - this.password = null; return this.manager.destroy(); } diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js index 6e5ea5159..01f664b0e 100644 --- a/src/client/ClientManager.js +++ b/src/client/ClientManager.js @@ -59,14 +59,15 @@ class ClientManager { } destroy() { - return new Promise(resolve => { - this.client.ws.destroy(); - if (!this.client.user.bot) { - resolve(this.client.rest.methods.logout()); - } else { - resolve(); - } - }); + this.client.ws.destroy(); + if (this.client.user.bot) { + this.client.token = null; + return Promise.resolve(); + } else { + return this.client.rest.methods.logout().then(() => { + this.client.token = null; + }); + } } } From a0a3989e59cce22aa50c39f39c4d18ec743c8d40 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Mon, 19 Dec 2016 01:11:55 -0600 Subject: [PATCH 186/248] fix issue with uncached channels and adding members (#1001) * fix this thing * fix a silly * Update Guild.js --- src/structures/Guild.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 2d679d117..909f3207d 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -752,7 +752,11 @@ class Guild { member.selfDeaf = voiceState.self_deaf; member.voiceSessionID = voiceState.session_id; member.voiceChannelID = voiceState.channel_id; - this.channels.get(voiceState.channel_id).members.set(member.user.id, member); + if (this.client.channels.has(voiceState.channel_id)) { + this.client.channels.get(voiceState.channel_id).members.set(member.user.id, member); + } else { + this.client.emit('warn', `Member ${member.id} added in guild ${this.id} with an uncached voice channel`); + } } /** From e3921073696242ffbf44a50f7dafc12c2f362620 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Mon, 19 Dec 2016 01:16:27 -0600 Subject: [PATCH 187/248] add ClientUser#fetchMentions (#999) * add ClientUser#fetchMentions Signed-off-by: Gus Caplan * ugh * Update ClientUser.js * Update ClientUser.js * Update ClientUser.js --- src/client/rest/RESTMethods.js | 9 +++++++++ src/structures/ClientUser.js | 13 +++++++++++++ src/util/Constants.js | 2 ++ 3 files changed, 24 insertions(+) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 8241a4464..166f8c3e3 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -5,6 +5,7 @@ const parseEmoji = require('../../util/ParseEmoji'); const User = require('../../structures/User'); const GuildMember = require('../../structures/GuildMember'); +const Message = require('../../structures/Message'); const Role = require('../../structures/Role'); const Invite = require('../../structures/Invite'); const Webhook = require('../../structures/Webhook'); @@ -564,6 +565,14 @@ class RESTMethods { ); } + fetchMeMentions(options) { + if (options.guild) options.guild = options.guild.id ? options.guild.id : options.guild; + return this.rest.makeRequest( + 'get', + Constants.Endpoints.meMentions(options.limit, options.roles, options.everyone, options.guild) + ).then(res => res.body.map(m => new Message(this.rest.client.channels.get(m.channel_id), m, this.rest.client))); + } + addFriend(user) { return this.rest.makeRequest('post', Constants.Endpoints.relationships('@me'), true, { username: user.username, diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 5aa547300..45de13a6c 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -146,6 +146,19 @@ class ClientUser extends User { return this.setPresence({ afk }); } + /** + * Fetches messages that mentioned the client's user + * @param {Object} [options] Options for the fetch + * @param {number} [options.limit=25] Maximum number of mentions to retrieve + * @param {boolean} [options.roles=true] Whether to include role mentions + * @param {boolean} [options.everyone=true] Whether to include everyone/here mentions + * @param {Guild|string} [options.guild] Limit the search to a specific guild + * @returns {Promise} + */ + fetchMentions(options = { limit: 25, roles: true, everyone: true, guild: null }) { + return this.client.rest.methods.fetchMentions(options); + } + /** * Send a friend request * This is only available when using a user account. diff --git a/src/util/Constants.js b/src/util/Constants.js index 93453e6d4..1a9756ad7 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -92,6 +92,8 @@ const Endpoints = exports.Endpoints = { avatar: (userID, avatar) => userID === '1' ? avatar : `${Endpoints.user(userID)}/avatars/${avatar}.jpg`, me: `${API}/users/@me`, meGuild: (guildID) => `${Endpoints.me}/guilds/${guildID}`, + meMentions: (limit, roles, everyone, guildID) => + `users/@me/mentions?limit=${limit}&roles=${roles}&everyone=${everyone}${guildID ? `&guild_id=${guildID}` : ''}`, relationships: (userID) => `${Endpoints.user(userID)}/relationships`, note: (userID) => `${Endpoints.me}/notes/${userID}`, From 84503c88771de8914ecd7400fe5a6a339e3e0632 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 20 Dec 2016 17:36:43 -0600 Subject: [PATCH 188/248] add User#dmChannel (#1005) * add User#DMChannel * Update User.js * Update User.js --- src/structures/User.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/structures/User.js b/src/structures/User.js index ae0461590..7c19bef1e 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -164,6 +164,14 @@ class User { return channel._typing.has(this.id) ? channel._typing.get(this.id).elapsedTime : -1; } + /** + * The DM between the client's user and this user + * @type {?DMChannel} + */ + get dmChannel() { + return this.client.channels.filter(c => c.type === 'dm').find(c => c.recipient.id === this.id); + } + /** * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful. * @returns {Promise} From c483dd823914155b5d3551d806c0d04a06547d73 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 20 Dec 2016 17:37:06 -0600 Subject: [PATCH 189/248] add some useful events (#1004) --- src/client/ClientDataManager.js | 17 +++++++++++++++++ src/util/Constants.js | 2 ++ 2 files changed, 19 insertions(+) diff --git a/src/client/ClientDataManager.js b/src/client/ClientDataManager.js index b8a73d67c..1ebcffdef 100644 --- a/src/client/ClientDataManager.js +++ b/src/client/ClientDataManager.js @@ -22,6 +22,14 @@ class ClientDataManager { const already = this.client.guilds.has(data.id); const guild = new Guild(this.client, data); this.client.guilds.set(guild.id, guild); + if (this.client.listenerCount(Constants.Events.GUILD_CACHED)) { + /** + * Emitted whenever a guild is added to the cache + * @event Client#guildCached + * @param {Guild} guild The cached guild + */ + this.client.emit(Constants.Events.GUILD_CACHED, guild); + } if (this.pastReady && !already) { /** * Emitted whenever the client joins a guild. @@ -66,6 +74,15 @@ class ClientDataManager { } if (channel) { + if (this.client.listenerCount(Constants.Events.CHANNEL_CACHED)) { + /** + * Emitted whenever a channel is added to the cache + * @event Client#channelCached + * @param {Channel} channel The cached channel + */ + this.client.emit(Constants.Events.CHANNEL_CACHED, channel); + } + if (this.pastReady && !already) this.client.emit(Constants.Events.CHANNEL_CREATE, channel); this.client.channels.set(channel.id, channel); return channel; diff --git a/src/util/Constants.js b/src/util/Constants.js index 1a9756ad7..a4ce3ed90 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -190,6 +190,7 @@ exports.VoiceOPCodes = { exports.Events = { READY: 'ready', + GUILD_CACHED: 'guildCached', GUILD_CREATE: 'guildCreate', GUILD_DELETE: 'guildDelete', GUILD_UPDATE: 'guildUpdate', @@ -209,6 +210,7 @@ exports.Events = { GUILD_EMOJI_UPDATE: 'guildEmojiUpdate', GUILD_BAN_ADD: 'guildBanAdd', GUILD_BAN_REMOVE: 'guildBanRemove', + CHANNEL_CACHED: 'channelCached', CHANNEL_CREATE: 'channelCreate', CHANNEL_DELETE: 'channelDelete', CHANNEL_UPDATE: 'channelUpdate', From 9486609ef9d0fe50f0efb949adf6c701d9bed62d Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 20 Dec 2016 17:37:19 -0600 Subject: [PATCH 190/248] Create .tern-project (#1003) --- .tern-project | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .tern-project diff --git a/.tern-project b/.tern-project new file mode 100644 index 000000000..cc31d86e5 --- /dev/null +++ b/.tern-project @@ -0,0 +1,11 @@ +{ + "ecmaVersion": 6, + "libs": [], + "plugins": { + "node": { + "dontLoad": "node_modules/**", + "load": "", + "modules": "" + } + } +} From fa7d63a10a729dd1663eeec2bf5738183bb1e73e Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 20 Dec 2016 17:37:36 -0600 Subject: [PATCH 191/248] add an invite generator (#993) * add an invite generator * `number |= null` is safe, so we can simplify this * Update Client.js * aaaaaa --- src/client/Client.js | 23 +++++++++++++++++++++++ src/client/ClientDataResolver.js | 13 +++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/client/Client.js b/src/client/Client.js index 907f3e033..47616d256 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -343,6 +343,29 @@ class Client extends EventEmitter { return this.rest.methods.getMyApplication(); } + /** + * Generate an invite link for your bot + * @param {Array|number} [permissions] An array of permissions to request + * @returns {Promise} The invite link + * @example + * client.generateInvite(['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE']) + * .then(link => { + * console.log(link); + * }); + */ + generateInvite(permissions) { + if (permissions) { + if (permissions instanceof Array) { + permissions = this.resolver.resolvePermissions(permissions); + } + } else { + permissions = 0; + } + return this.fetchApplication().then(application => + `https://discordapp.com/oauth2/authorize?client_id=${application.id}&permissions=${permissions}&scope=bot` + ); + } + /** * Sets a timeout that will be automatically cancelled if the client is destroyed. * @param {Function} fn Function to execute diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index b99421d8f..9f550cc25 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -190,6 +190,19 @@ class ClientDataResolver { return permission; } + /** + * Turn an array of permissions into a valid discord permission bitfield + * @param {Array} permissions An array of permissions as strings or permissions numbers (see resolvePermission) + * @returns {number} + */ + resolvePermissions(permissions) { + let bitfield = 0; + for (const permission of permissions) { + bitfield |= this.resolvePermission(permission); + } + return bitfield; + } + /** * Data that can be resolved to give a string. This can be: * * A string From 4580f62a1fc0485c802b751abe3b731d61efdba5 Mon Sep 17 00:00:00 2001 From: Zack Campbell Date: Thu, 22 Dec 2016 13:50:28 -0600 Subject: [PATCH 192/248] Add RichEmbed to typings (#1009) --- typings/index.d.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/typings/index.d.ts b/typings/index.d.ts index 95204c0f9..3bfc1151c 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -479,6 +479,30 @@ declare module "discord.js" { name: string; url: string; } + export class RichEmbed { + title?: string; + description?: string; + url?: string; + timestamp?: Date; + color?: number | string; + fields?: { name: string; value: string; inline?: boolean; }[]; + author?: { name: string; url?: string; icon_url?: string; }; + thumbnail?: { url: string; height?: number; width?: number; }; + image?: { url: string; proxy_url?: string; height?: number; width?: number; }; + video?: { url: string; height: number; width: number; }; + footer?: { text?: string; icon_url?: string; }; + + addField(name: string, value: StringResolvable, inline?: boolean): this; + setAuthor(name: string, icon?: string, url?: string): this; + setColor(color: string | number | number[]): this; + setDescription(description: string): this; + setFooter(text: string, icon: string): this; + setImage(url: string): this; + setThumbnail(url: string): this; + setTimestamp(timestamp?: Date): this; + setTitle(title: string): this; + setURL(url: string): this; + } export class MessageAttachment { client: Client; filename: string; From 84954c8860d987fced8da031f0f6fdd03612b6f2 Mon Sep 17 00:00:00 2001 From: Hackzzila Date: Thu, 22 Dec 2016 14:01:04 -0600 Subject: [PATCH 193/248] Add notice about disabling events (#1008) * Add notice about disabling events * fix english * Update Constants.js * Update Constants.js * Update Constants.js * Update Constants.js --- src/util/Constants.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index a4ce3ed90..7d415bc16 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -21,8 +21,9 @@ exports.Package = require('../../package.json'); * @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their * corresponding websocket events * @property {WSEventType[]} [disabledEvents] An array of disabled websocket events. Events in this array will not be - * processed. Disabling useless events such as 'TYPING_START' can result in significant performance increases on - * large-scale bots. + * processed, potentially resulting in performance improvements for larger bots. Only disable events you are + * 100% certain you don't need, as many are important, but not obviously so. The safest one to disable with the + * most impact is typically `TYPING_START`. * @property {WebsocketOptions} [ws] Options for the websocket */ exports.DefaultOptions = { From cecb0aee027a9b71e821a0b7281699c0144708eb Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Thu, 22 Dec 2016 14:12:29 -0600 Subject: [PATCH 194/248] Update User#setEmail/setPassword/setUsername (#991) * fix some things with user updates and tokens and such * fix stupid * Update ClientUser.js * Update ClientUser.js --- src/client/rest/RESTMethods.js | 4 ++-- src/structures/ClientUser.js | 31 +++++++++++++++++-------------- src/structures/User.js | 1 + 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 166f8c3e3..d895922a7 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -212,14 +212,14 @@ class RESTMethods { ); } - updateCurrentUser(_data) { + updateCurrentUser(_data, password) { const user = this.rest.client.user; const data = {}; data.username = _data.username || user.username; data.avatar = this.rest.client.resolver.resolveBase64(_data.avatar) || user.avatar; if (!user.bot) { data.email = _data.email || user.email; - data.password = this.rest.client.password; + data.password = password; if (_data.new_password) data.new_password = _data.newPassword; } return this.rest.makeRequest('patch', Constants.Endpoints.me, true, data).then(newData => diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 45de13a6c..66b9504e9 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -54,6 +54,7 @@ class ClientUser extends User { * Changing usernames in Discord is heavily rate limited, with only 2 requests * every hour. Use this sparingly! * @param {string} username The new username + * @param {string} [password] Current password (only for user accounts) * @returns {Promise} * @example * // set username @@ -61,38 +62,40 @@ class ClientUser extends User { * .then(user => console.log(`My new username is ${user.username}`)) * .catch(console.error); */ - setUsername(username) { - return this.client.rest.methods.updateCurrentUser({ username }); + setUsername(username, password) { + return this.client.rest.methods.updateCurrentUser({ username }, password); } /** - * If this user is a "self bot" or logged in using a normal user's details (which should be avoided), you can set the - * email here. - * @param {string} email The new email + * Changes the email for the client user's account. + * This is only available when using a user account. + * @param {string} email New email to change to + * @param {string} password Current password * @returns {Promise} * @example * // set email - * client.user.setEmail('bob@gmail.com') + * client.user.setEmail('bob@gmail.com', 'some amazing password 123') * .then(user => console.log(`My new email is ${user.email}`)) * .catch(console.error); */ - setEmail(email) { - return this.client.rest.methods.updateCurrentUser({ email }); + setEmail(email, password) { + return this.client.rest.methods.updateCurrentUser({ email }, password); } /** - * If this user is a "self bot" or logged in using a normal user's details (which should be avoided), you can set the - * password here. - * @param {string} password The new password + * Changes the password for the client user's account. + * This is only available when using a user account. + * @param {string} newPassword New password to change to + * @param {string} oldPassword Current password * @returns {Promise} * @example * // set password - * client.user.setPassword('password123') + * client.user.setPassword('some new amazing password 456', 'some amazing password 123') * .then(user => console.log('New password set!')) * .catch(console.error); */ - setPassword(password) { - return this.client.rest.methods.updateCurrentUser({ password }); + setPassword(newPassword, oldPassword) { + return this.client.rest.methods.updateCurrentUser({ password: newPassword }, oldPassword); } /** diff --git a/src/structures/User.js b/src/structures/User.js index 7c19bef1e..9b3155ebd 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -61,6 +61,7 @@ class User { for (const prop of ['id', 'username', 'discriminator', 'avatar', 'bot']) { if (typeof data[prop] !== 'undefined') this[prop] = data[prop]; } + if (data.token) this.client.token = data.token; } /** From f726db21523f7cabc43d7bd380d8eefab91c934a Mon Sep 17 00:00:00 2001 From: bdistin Date: Thu, 22 Dec 2016 21:24:31 -0600 Subject: [PATCH 195/248] Revert error emit on Slow Stream Gen (#1011) --- src/client/voice/dispatcher/StreamDispatcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index 8f1483e17..e08a36537 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -180,7 +180,7 @@ class StreamDispatcher extends EventEmitter { const data = this.streamingData; if (data.missed >= 5) { - this._triggerTerminalState('error', 'Stream is not generating quickly enough.'); + this._triggerTerminalState('end', 'Stream is not generating quickly enough.'); return; } From 2390e525ef04637bac9fb786d49f1aad25e3b08f Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Thu, 22 Dec 2016 23:57:49 -0600 Subject: [PATCH 196/248] Fix User#avatarURL for Nitro animated avatars (#1012) * fix avatar url * switch to cdn --- src/util/Constants.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index 7d415bc16..fbecfa378 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -90,7 +90,10 @@ const Endpoints = exports.Endpoints = { user: (userID) => `${API}/users/${userID}`, userChannels: (userID) => `${Endpoints.user(userID)}/channels`, userProfile: (userID) => `${Endpoints.user(userID)}/profile`, - avatar: (userID, avatar) => userID === '1' ? avatar : `${Endpoints.user(userID)}/avatars/${avatar}.jpg`, + avatar: (userID, avatar) => { + if (userID === '1') return avatar; + return `${Endpoints.CDN}/avatars/${userID}/${avatar}.${avatar.startsWith('a_') ? 'gif' : 'jpg'}`; + }, me: `${API}/users/@me`, meGuild: (guildID) => `${Endpoints.me}/guilds/${guildID}`, meMentions: (limit, roles, everyone, guildID) => From b518437f524781ce000ada70cfb19df38dd0cde8 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Fri, 23 Dec 2016 13:59:06 +0000 Subject: [PATCH 197/248] Emit the close event on disconnect --- src/client/websocket/WebSocketManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 6e65ce3e8..5a34b66bb 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -219,8 +219,9 @@ class WebSocketManager extends EventEmitter { /** * Emitted whenever the client websocket is disconnected * @event Client#disconnect + * @param {CloseEvent} event The WebSocket close event */ - if (!this.reconnecting) this.client.emit(Constants.Events.DISCONNECT); + if (!this.reconnecting) this.client.emit(Constants.Events.DISCONNECT, event); if (event.code === 4004) return; if (event.code === 4010) return; if (!this.reconnecting && event.code !== 1000) this.tryReconnect(); From 74ef0b2e144ae96e02977f4a785bbc0bdc972e4e Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Fri, 23 Dec 2016 14:37:35 +0000 Subject: [PATCH 198/248] minor fixes --- src/client/websocket/WebSocketManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 5a34b66bb..462e77c3b 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -210,12 +210,13 @@ class WebSocketManager extends EventEmitter { /** * Run whenever the connection to the gateway is closed, it will try to reconnect the client. - * @param {Object} event The received websocket data + * @param {CloseEvent} event The WebSocket close event */ eventClose(event) { this.emit('close', event); this.client.clearInterval(this.client.manager.heartbeatInterval); this.status = Constants.Status.DISCONNECTED; + this._queue = []; /** * Emitted whenever the client websocket is disconnected * @event Client#disconnect From e6a041241beafddca56b6feb8e7d52a119e37f2a Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 23 Dec 2016 08:37:43 -0600 Subject: [PATCH 199/248] cleanup ws ratelimiting, and apirequest (#957) * cleanup ws ratelimiting, and apirequest * cleanup timestamps * clean up timestamps --- src/client/rest/APIRequest.js | 8 ++------ src/client/websocket/WebSocketManager.js | 24 +++++++++++++----------- src/structures/Webhook.js | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/client/rest/APIRequest.js b/src/client/rest/APIRequest.js index 67491900d..40eb0aba8 100644 --- a/src/client/rest/APIRequest.js +++ b/src/client/rest/APIRequest.js @@ -4,7 +4,7 @@ const Constants = require('../../util/Constants'); function getRoute(url) { let route = url.split('?')[0]; if (route.includes('/channels/') || route.includes('/guilds/')) { - const startInd = ~route.indexOf('/channels/') ? route.indexOf('/channels/') : route.indexOf('/guilds/'); + const startInd = route.includes('/channels/') ? route.indexOf('/channels/') : route.indexOf('/guilds/'); const majorID = route.substring(startInd).split('/')[2]; route = route.replace(/(\d{8,})/g, ':id').replace(':id', majorID); } @@ -37,11 +37,7 @@ class APIRequest { if (this.file && this.file.file) { apiRequest.attach('file', this.file.file, this.file.name); this.data = this.data || {}; - for (const key in this.data) { - if (this.data[key]) { - apiRequest.field(key, this.data[key]); - } - } + for (const key in this.data) if (this.data[key]) apiRequest.field(key, this.data[key]); } else if (this.data) { apiRequest.send(this.data); } diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 462e77c3b..04772cf41 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -105,7 +105,11 @@ class WebSocketManager extends EventEmitter { this.ws.onclose = this.eventClose.bind(this); this.ws.onerror = this.eventError.bind(this); this._queue = []; - this._remaining = 3; + this._remaining = 120; + this.client.setInterval(() => { + this._remaining = 120; + this._remainingReset = Date.now(); + }, 60e3); } connect(gateway) { @@ -147,17 +151,15 @@ class WebSocketManager extends EventEmitter { doQueue() { const item = this._queue[0]; - if (this.ws.readyState === WebSocket.OPEN && item) { - if (this._remaining === 0) { - this.client.setTimeout(this.doQueue.bind(this), 1000); - return; - } - this._remaining--; - this._send(item); - this._queue.shift(); - this.doQueue(); - this.client.setTimeout(() => this._remaining++, 1000); + if (!(this.ws.readyState === WebSocket.OPEN && item)) return; + if (this.remaining === 0) { + this.client.setTimeout(this.doQueue.bind(this), Date.now() - this.remainingReset); + return; } + this._remaining--; + this._send(item); + this._queue.shift(); + this.doQueue(); } /** diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 56f9046b5..96984ffe0 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -102,7 +102,7 @@ class Webhook { * 'color': '#F0F', * 'footer_icon': 'http://snek.s3.amazonaws.com/topSnek.png', * 'footer': 'Powered by sneks', - * 'ts': new Date().getTime() / 1000 + * 'ts': Date.now() / 1000 * }] * }).catch(console.error); */ From e63432c18e380afe27c19c89f67e1dac9d78650a Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Fri, 23 Dec 2016 16:45:47 +0000 Subject: [PATCH 200/248] Fix editing role positions (fixes #864) --- src/structures/Guild.js | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 909f3207d..115fdf645 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -671,19 +671,34 @@ class Guild { * @returns {Promise} */ setRolePosition(role, position) { - if (role instanceof Role) { - role = role.id; - } else if (typeof role !== 'string') { - return Promise.reject(new Error('Supplied role is not a role or string.')); + if (typeof role === 'string') { + role = this.roles.get(role); + if (!role) return Promise.reject(new Error('Supplied role is not a role or string.')); } position = Number(position); if (isNaN(position)) return Promise.reject(new Error('Supplied position is not a number.')); - const updatedRoles = this.roles.map(r => ({ - id: r.id, - position: r.id === role ? position : r.position < position ? r.position : r.position + 1, - })); + const lowestAffected = Math.min(role.position, position); + const highestAffected = Math.max(role.position, position); + + const rolesToUpdate = this.roles.filter(r => r.position >= lowestAffected && r.position <= highestAffected); + + // stop role positions getting stupidly inflated + if (position > role.position) { + position = rolesToUpdate.first().position; + } else { + position = rolesToUpdate.last().position; + } + + let updatedRoles = []; + + for (const uRole of rolesToUpdate.values()) { + updatedRoles.push({ + id: uRole.id, + position: uRole.id === role.id ? position : uRole.position + (position < role.position ? 1 : -1), + }); + } return this.client.rest.methods.setRolePositions(this.id, updatedRoles); } From 2ab5bae69a00018fcd6135a5a9d62c90a4f4a6ff Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Fri, 23 Dec 2016 18:38:48 +0000 Subject: [PATCH 201/248] change let to const --- src/structures/Guild.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 115fdf645..acd5b6fde 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -691,7 +691,7 @@ class Guild { position = rolesToUpdate.last().position; } - let updatedRoles = []; + const updatedRoles = []; for (const uRole of rolesToUpdate.values()) { updatedRoles.push({ From 265ac90234a6e69fde4707b79977193fd07afb13 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 23 Dec 2016 12:41:04 -0600 Subject: [PATCH 202/248] Update Constants.js (#1014) --- src/util/Constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index fbecfa378..08f804156 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -92,7 +92,7 @@ const Endpoints = exports.Endpoints = { userProfile: (userID) => `${Endpoints.user(userID)}/profile`, avatar: (userID, avatar) => { if (userID === '1') return avatar; - return `${Endpoints.CDN}/avatars/${userID}/${avatar}.${avatar.startsWith('a_') ? 'gif' : 'jpg'}`; + return `${Endpoints.CDN}/avatars/${userID}/${avatar}.${avatar.startsWith('a_') ? 'gif' : 'jpg'}?size=1024`; }, me: `${API}/users/@me`, meGuild: (guildID) => `${Endpoints.me}/guilds/${guildID}`, From 28ca83011c5775ce0f1c643a261b4138b42a416d Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 24 Dec 2016 01:59:01 -0500 Subject: [PATCH 203/248] Implement missing Collection#reduce functionality --- src/util/Collection.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/util/Collection.js b/src/util/Collection.js index f90079bae..bafe710b6 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -293,14 +293,28 @@ class Collection extends Map { /** * Identical to * [Array.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). - * @param {Function} fn Function used to reduce - * @param {*} [startVal] The starting value + * @param {Function} fn Function used to reduce, taking four arguments; `accumulator`, `currentValue`, `currentKey`, + * and `collection` + * @param {*} [initialValue] Starting value for the accumulator * @returns {*} */ - reduce(fn, startVal) { - let currentVal = startVal; - for (const [key, val] of this) currentVal = fn(currentVal, val, key, this); - return currentVal; + reduce(fn, initialValue) { + let accumulator; + if (typeof initialValue !== 'undefined') { + accumulator = initialValue; + for (const [key, val] of this) accumulator = fn(accumulator, val, key, this); + } else { + let first = true; + for (const [key, val] of this) { + if (first) { + accumulator = val; + first = false; + continue; + } + accumulator = fn(accumulator, val, key, this); + } + } + return accumulator; } /** From 7c12fdcb56e8c6d49731eca10e3b18b6a76b515f Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sat, 24 Dec 2016 01:04:18 -0600 Subject: [PATCH 204/248] add payload_json instead of the current iterator (#1015) --- src/client/rest/APIRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/rest/APIRequest.js b/src/client/rest/APIRequest.js index 40eb0aba8..36c2d8fed 100644 --- a/src/client/rest/APIRequest.js +++ b/src/client/rest/APIRequest.js @@ -37,7 +37,7 @@ class APIRequest { if (this.file && this.file.file) { apiRequest.attach('file', this.file.file, this.file.name); this.data = this.data || {}; - for (const key in this.data) if (this.data[key]) apiRequest.field(key, this.data[key]); + apiRequest.field('payload_json', JSON.stringify(this.data)); } else if (this.data) { apiRequest.send(this.data); } From 3eca3ba95e5fbfbc4db201ada9ca2a9292739bb1 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sat, 24 Dec 2016 05:04:27 -0600 Subject: [PATCH 205/248] add handler in case heartbeat is not acked (#1013) * add handler in case heartbeat is not acked * ffs --- src/client/Client.js | 1 + src/client/ClientManager.js | 24 ++++++++++++------- .../websocket/packets/handlers/Ready.js | 2 ++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 47616d256..4629a38fe 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -416,6 +416,7 @@ class Client extends EventEmitter { _pong(startTime) { this.pings.unshift(Date.now() - startTime); if (this.pings.length > 3) this.pings.length = 3; + this.clearTimeout(this._ackTimeout); } _setPresence(id, presence) { diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js index 01f664b0e..ca582668b 100644 --- a/src/client/ClientManager.js +++ b/src/client/ClientManager.js @@ -48,14 +48,22 @@ class ClientManager { * @param {number} time The interval in milliseconds at which heartbeat packets should be sent */ setupKeepAlive(time) { - this.heartbeatInterval = this.client.setInterval(() => { - this.client.emit('debug', 'Sending heartbeat'); - this.client._pingTimestamp = Date.now(); - this.client.ws.send({ - op: Constants.OPCodes.HEARTBEAT, - d: this.client.ws.sequence, - }, true); - }, time); + this.heartbeatInterval = this.client.setInterval(this.ping.bind(this), time); + } + + ping() { + this.client.emit('debug', 'Sending heartbeat'); + this.client._pingTimestamp = Date.now(); + this.client.ws.send({ + op: Constants.OPCodes.HEARTBEAT, + d: this.client.ws.sequence, + }, true); + + const lastPing = this.client.ping; + + this.client._ackTimeout = this.client.setTimeout(() => { + this.client.ws.ws.close(1005); + }, lastPing ? lastPing * 20 : 20e3); } destroy() { diff --git a/src/client/websocket/packets/handlers/Ready.js b/src/client/websocket/packets/handlers/Ready.js index aed766bf0..4f49980bb 100644 --- a/src/client/websocket/packets/handlers/Ready.js +++ b/src/client/websocket/packets/handlers/Ready.js @@ -7,6 +7,8 @@ class ReadyHandler extends AbstractHandler { const client = this.packetManager.client; const data = packet.d; + client.manager.ping(); + const clientUser = new ClientUser(client, data.user); client.user = clientUser; client.readyAt = new Date(); From 544e456302a2098e264961277e97c63843b0d11e Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sat, 24 Dec 2016 12:13:42 +0000 Subject: [PATCH 206/248] Add ClientOptions.restTimeOffset for better performance for bots with a good network connection --- src/client/rest/RequestHandlers/Burst.js | 4 ++-- src/client/rest/RequestHandlers/Sequential.js | 4 ++-- src/util/Constants.js | 3 +++ test/random.js | 5 +++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/client/rest/RequestHandlers/Burst.js b/src/client/rest/RequestHandlers/Burst.js index e43df3952..0135d0df2 100644 --- a/src/client/rest/RequestHandlers/Burst.js +++ b/src/client/rest/RequestHandlers/Burst.js @@ -29,7 +29,7 @@ class BurstRequestHandler extends RequestHandler { this.requestResetTime = Number(res.headers['x-ratelimit-reset']) * 1000; this.requestRemaining = Number(res.headers['x-ratelimit-remaining']); this.timeDifference = Date.now() - new Date(res.headers.date).getTime(); - this.handleNext((this.requestResetTime - Date.now()) + this.timeDifference + 1000); + this.handleNext((this.requestResetTime - Date.now()) + this.timeDifference + this.restManager.client.options.restTimeOffset); } if (err) { if (err.status === 429) { @@ -38,7 +38,7 @@ class BurstRequestHandler extends RequestHandler { this.restManager.client.setTimeout(() => { this.globalLimit = false; this.handle(); - }, Number(res.headers['retry-after']) + 500); + }, Number(res.headers['retry-after']) + this.restManager.client.options.restTimeOffset); if (res.headers['x-ratelimit-global']) { this.globalLimit = true; } diff --git a/src/client/rest/RequestHandlers/Sequential.js b/src/client/rest/RequestHandlers/Sequential.js index c971c198f..f206424f7 100644 --- a/src/client/rest/RequestHandlers/Sequential.js +++ b/src/client/rest/RequestHandlers/Sequential.js @@ -60,7 +60,7 @@ class SequentialRequestHandler extends RequestHandler { this.waiting = false; this.globalLimit = false; resolve(); - }, Number(res.headers['retry-after']) + 500); + }, Number(res.headers['retry-after']) + this.restManager.client.options.restTimeOffset); if (res.headers['x-ratelimit-global']) { this.globalLimit = true; } @@ -79,7 +79,7 @@ class SequentialRequestHandler extends RequestHandler { this.restManager.client.setTimeout(() => { this.waiting = false; resolve(data); - }, (this.requestResetTime - Date.now()) + this.timeDifference + 1000); + }, (this.requestResetTime - Date.now()) + this.timeDifference + this.restManager.client.options.restTimeOffset); } else { this.waiting = false; resolve(data); diff --git a/src/util/Constants.js b/src/util/Constants.js index 08f804156..fddc6d14a 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -20,6 +20,8 @@ exports.Package = require('../../package.json'); * @property {boolean} [sync=false] Whether to periodically sync guilds (for userbots) * @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their * corresponding websocket events + * @property {number} [restTimeOffset=500] The extra time in millseconds to wait before continuing to make REST + * requests (higher values will reduce rate-limiting errors on bad connections) * @property {WSEventType[]} [disabledEvents] An array of disabled websocket events. Events in this array will not be * processed, potentially resulting in performance improvements for larger bots. Only disable events you are * 100% certain you don't need, as many are important, but not obviously so. The safest one to disable with the @@ -38,6 +40,7 @@ exports.DefaultOptions = { sync: false, restWsBridgeTimeout: 5000, disabledEvents: [], + restTimeOffset: 500, /** * Websocket options. These are left as snake_case to match the API. diff --git a/test/random.js b/test/random.js index d6c761ea5..0b22765ff 100644 --- a/test/random.js +++ b/test/random.js @@ -117,10 +117,15 @@ client.on('message', message => { if (message.content === 'ratelimittest') { let i = 1; + const start = Date.now(); while (i <= 20) { message.channel.sendMessage(`Testing my rates, item ${i} of 20`); i++; } + message.channel.sendMessage('last one...').then(m => { + const diff = Date.now() - start; + m.reply(`Each message took ${diff / 21}ms to send`); + }); } if (message.content === 'makerole') { From ab30715028c65dfb7284ecb6b0b7dda9eef9cd54 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 24 Dec 2016 19:49:24 -0500 Subject: [PATCH 207/248] Fix Hydar's lint errors --- src/client/rest/RequestHandlers/Burst.js | 4 +++- src/client/rest/RequestHandlers/Sequential.js | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/client/rest/RequestHandlers/Burst.js b/src/client/rest/RequestHandlers/Burst.js index 0135d0df2..981bcf7e2 100644 --- a/src/client/rest/RequestHandlers/Burst.js +++ b/src/client/rest/RequestHandlers/Burst.js @@ -29,7 +29,9 @@ class BurstRequestHandler extends RequestHandler { this.requestResetTime = Number(res.headers['x-ratelimit-reset']) * 1000; this.requestRemaining = Number(res.headers['x-ratelimit-remaining']); this.timeDifference = Date.now() - new Date(res.headers.date).getTime(); - this.handleNext((this.requestResetTime - Date.now()) + this.timeDifference + this.restManager.client.options.restTimeOffset); + this.handleNext( + this.requestResetTime - Date.now() + this.timeDifference + this.restManager.client.options.restTimeOffset + ); } if (err) { if (err.status === 429) { diff --git a/src/client/rest/RequestHandlers/Sequential.js b/src/client/rest/RequestHandlers/Sequential.js index f206424f7..e82372a79 100644 --- a/src/client/rest/RequestHandlers/Sequential.js +++ b/src/client/rest/RequestHandlers/Sequential.js @@ -76,10 +76,13 @@ class SequentialRequestHandler extends RequestHandler { const data = res && res.body ? res.body : {}; item.resolve(data); if (this.requestRemaining === 0) { - this.restManager.client.setTimeout(() => { - this.waiting = false; - resolve(data); - }, (this.requestResetTime - Date.now()) + this.timeDifference + this.restManager.client.options.restTimeOffset); + this.restManager.client.setTimeout( + () => { + this.waiting = false; + resolve(data); + }, + this.requestResetTime - Date.now() + this.timeDifference + this.restManager.client.options.restTimeOffset + ); } else { this.waiting = false; resolve(data); From 74c3bae50629423c529142dcd0a756ad8d23b721 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 24 Dec 2016 19:54:37 -0500 Subject: [PATCH 208/248] Teensy weensy cleanup --- src/client/rest/RequestHandlers/Burst.js | 4 +--- src/client/rest/RequestHandlers/Sequential.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/client/rest/RequestHandlers/Burst.js b/src/client/rest/RequestHandlers/Burst.js index 981bcf7e2..2cc1a590c 100644 --- a/src/client/rest/RequestHandlers/Burst.js +++ b/src/client/rest/RequestHandlers/Burst.js @@ -41,9 +41,7 @@ class BurstRequestHandler extends RequestHandler { this.globalLimit = false; this.handle(); }, Number(res.headers['retry-after']) + this.restManager.client.options.restTimeOffset); - if (res.headers['x-ratelimit-global']) { - this.globalLimit = true; - } + if (res.headers['x-ratelimit-global']) this.globalLimit = true; } else { item.reject(err); } diff --git a/src/client/rest/RequestHandlers/Sequential.js b/src/client/rest/RequestHandlers/Sequential.js index e82372a79..0abf36db2 100644 --- a/src/client/rest/RequestHandlers/Sequential.js +++ b/src/client/rest/RequestHandlers/Sequential.js @@ -61,9 +61,7 @@ class SequentialRequestHandler extends RequestHandler { this.globalLimit = false; resolve(); }, Number(res.headers['retry-after']) + this.restManager.client.options.restTimeOffset); - if (res.headers['x-ratelimit-global']) { - this.globalLimit = true; - } + if (res.headers['x-ratelimit-global']) this.globalLimit = true; } else { this.queue.shift(); this.waiting = false; From 218920e4f14fefe6e3338bdaeb6c36f85dd9b459 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 24 Dec 2016 20:02:36 -0500 Subject: [PATCH 209/248] Fix logo on GitHub --- README.md | 2 +- docs/general/welcome.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a7b0177f1..eae457c31 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@


- discord.js + discord.js


diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 4f96b57ad..cb72c85a9 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -1,7 +1,7 @@


- discord.js + discord.js


From fb6a8d1637311617a14eb5cf457577f1b4d7f4db Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Sat, 24 Dec 2016 20:25:29 -0500 Subject: [PATCH 210/248] Update webpack and clean up deps --- package.json | 28 ++++++++++++---------------- webpack.config.js | 1 - 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 23c0fc87a..780c79f25 100644 --- a/package.json +++ b/package.json @@ -31,28 +31,24 @@ "homepage": "https://github.com/hydrabolt/discord.js#readme", "runkitExampleFilename": "./docs/examples/ping.js", "dependencies": { - "@types/node": "^6.0.51", - "pako": "^1.0.3", - "superagent": "^3.0.0", - "tweetnacl": "^0.14.3", - "ws": "^1.1.1" + "@types/node": "^6.0.0", + "pako": "^1.0.0", + "superagent": "^3.3.0", + "tweetnacl": "^0.14.0", + "ws": "^1.1.0" }, "peerDependencies": { - "erlpack": "github:hammerandchisel/erlpack", + "erlpack": "hammerandchisel/erlpack#master", "node-opus": "^0.2.0", "opusscript": "^0.0.1", - "uws": "^0.11.1" + "uws": "^0.12.0" }, "devDependencies": { - "bufferutil": "^1.2.1", - "discord.js-docgen": "github:hydrabolt/discord.js-docgen", - "eslint": "^3.10.0", - "jsdoc-to-markdown": "^2.0.0", - "json-loader": "^0.5.4", - "parallel-webpack": "^1.5.0", - "uglify-js": "github:mishoo/UglifyJS2#harmony", - "utf-8-validate": "^1.2.1", - "webpack": "2.1.0-beta.27" + "discord.js-docgen": "hydrabolt/discord.js-docgen#master", + "eslint": "^3.12.0", + "parallel-webpack": "^1.6.0", + "uglify-js": "mishoo/UglifyJS2#harmony", + "webpack": "2.2.0-rc.2" }, "engines": { "node": ">=6.0.0" diff --git a/webpack.config.js b/webpack.config.js index 23e9f1c0c..501279ee6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -24,7 +24,6 @@ const createConfig = (options) => { }, module: { rules: [ - { test: /\.json$/, loader: 'json-loader' }, { test: /\.md$/, loader: 'ignore-loader' }, ], }, From 2410fdf8d29543daf792ff8fb0e34fa2fc97bdf3 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 25 Dec 2016 10:28:36 -0600 Subject: [PATCH 211/248] fix heartbeats once and for all (#1016) --- src/client/Client.js | 2 +- src/client/ClientManager.js | 17 +---------------- src/client/websocket/WebSocketManager.js | 19 +++++++++++++++++++ .../packets/WebSocketPacketManager.js | 1 + .../packets/handlers/GuildMembersChunk.js | 2 ++ .../websocket/packets/handlers/Ready.js | 2 +- 6 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 4629a38fe..4fdac08db 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -416,7 +416,7 @@ class Client extends EventEmitter { _pong(startTime) { this.pings.unshift(Date.now() - startTime); if (this.pings.length > 3) this.pings.length = 3; - this.clearTimeout(this._ackTimeout); + this.ws.lastHeartbeatAck = true; } _setPresence(id, presence) { diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js index ca582668b..0cfbbfdf4 100644 --- a/src/client/ClientManager.js +++ b/src/client/ClientManager.js @@ -48,22 +48,7 @@ class ClientManager { * @param {number} time The interval in milliseconds at which heartbeat packets should be sent */ setupKeepAlive(time) { - this.heartbeatInterval = this.client.setInterval(this.ping.bind(this), time); - } - - ping() { - this.client.emit('debug', 'Sending heartbeat'); - this.client._pingTimestamp = Date.now(); - this.client.ws.send({ - op: Constants.OPCodes.HEARTBEAT, - d: this.client.ws.sequence, - }, true); - - const lastPing = this.client.ping; - - this.client._ackTimeout = this.client.setTimeout(() => { - this.client.ws.ws.close(1005); - }, lastPing ? lastPing * 20 : 20e3); + this.heartbeatInterval = this.client.setInterval(() => this.client.ws.heartbeat(true), time); } destroy() { diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 04772cf41..985105713 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -88,6 +88,8 @@ class WebSocketManager extends EventEmitter { for (const event of client.options.disabledEvents) this.disabledEvents[event] = true; this.first = true; + + this.lastHeartbeatAck = true; } /** @@ -122,6 +124,22 @@ class WebSocketManager extends EventEmitter { } } + heartbeat(normal) { + if (normal && !this.lastHeartbeatAck) { + this.ws.close(1007); + return; + } + + this.client.emit('debug', 'Sending heartbeat'); + this.client._pingTimestamp = Date.now(); + this.client.ws.send({ + op: Constants.OPCodes.HEARTBEAT, + d: this.sequence, + }, true); + + this.lastHeartbeatAck = false; + } + /** * Sends a packet to the gateway * @param {Object} data An object that can be JSON stringified @@ -167,6 +185,7 @@ class WebSocketManager extends EventEmitter { */ eventOpen() { this.client.emit('debug', 'Connection to gateway opened'); + this.lastHeartbeatAck = true; if (this.status === Constants.Status.RECONNECTING) this._sendResume(); else this._sendNewIdentify(); } diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 099579912..3bfb8af44 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -84,6 +84,7 @@ class WebSocketPacketManager { if (packet.op === Constants.OPCodes.HEARTBEAT_ACK) { this.ws.client._pong(this.ws.client._pingTimestamp); + this.ws.lastHeartbeatAck = true; this.ws.client.emit('debug', 'Heartbeat acknowledged'); } diff --git a/src/client/websocket/packets/handlers/GuildMembersChunk.js b/src/client/websocket/packets/handlers/GuildMembersChunk.js index 5dda5ad27..e9312e291 100644 --- a/src/client/websocket/packets/handlers/GuildMembersChunk.js +++ b/src/client/websocket/packets/handlers/GuildMembersChunk.js @@ -16,6 +16,8 @@ class GuildMembersChunkHandler extends AbstractHandler { guild._checkChunks(); client.emit(Constants.Events.GUILD_MEMBERS_CHUNK, members); + + client.ws.lastHeartbeatAck = true; } } diff --git a/src/client/websocket/packets/handlers/Ready.js b/src/client/websocket/packets/handlers/Ready.js index 4f49980bb..10bc6b257 100644 --- a/src/client/websocket/packets/handlers/Ready.js +++ b/src/client/websocket/packets/handlers/Ready.js @@ -7,7 +7,7 @@ class ReadyHandler extends AbstractHandler { const client = this.packetManager.client; const data = packet.d; - client.manager.ping(); + client.ws.heartbeat(); const clientUser = new ClientUser(client, data.user); client.user = clientUser; From cd657be8beaf852e969d660ec40c02130242578f Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Mon, 26 Dec 2016 19:21:00 +0000 Subject: [PATCH 212/248] Add functionality for GuildEmoji events --- src/client/ClientDataManager.js | 4 +- src/client/actions/GuildEmojiCreate.js | 8 ++-- src/client/actions/GuildEmojiDelete.js | 10 ++--- src/client/actions/GuildEmojiUpdate.js | 18 +-------- .../packets/WebSocketPacketManager.js | 1 + .../packets/handlers/GuildEmojiUpdate.js | 13 ------ .../packets/handlers/GuildEmojisUpdate.js | 40 +++++++++++++++++++ src/structures/Emoji.js | 21 ++++++++++ src/util/Constants.js | 1 + test/random.js | 4 ++ 10 files changed, 80 insertions(+), 40 deletions(-) delete mode 100644 src/client/websocket/packets/handlers/GuildEmojiUpdate.js create mode 100644 src/client/websocket/packets/handlers/GuildEmojisUpdate.js diff --git a/src/client/ClientDataManager.js b/src/client/ClientDataManager.js index 1ebcffdef..9ba568a1b 100644 --- a/src/client/ClientDataManager.js +++ b/src/client/ClientDataManager.js @@ -95,7 +95,7 @@ class ClientDataManager { const already = guild.emojis.has(data.id); if (data && !already) { let emoji = new Emoji(guild, data); - this.client.emit(Constants.Events.EMOJI_CREATE, emoji); + this.client.emit(Constants.Events.GUILD_EMOJI_CREATE, emoji); guild.emojis.set(emoji.id, emoji); return emoji; } else if (already) { @@ -107,7 +107,7 @@ class ClientDataManager { killEmoji(emoji) { if (!(emoji instanceof Emoji && emoji.guild)) return; - this.client.emit(Constants.Events.EMOJI_DELETE, emoji); + this.client.emit(Constants.Events.GUILD_EMOJI_DELETE, emoji); emoji.guild.emojis.delete(emoji.id); } diff --git a/src/client/actions/GuildEmojiCreate.js b/src/client/actions/GuildEmojiCreate.js index a3f238fe5..a4234fcdd 100644 --- a/src/client/actions/GuildEmojiCreate.js +++ b/src/client/actions/GuildEmojiCreate.js @@ -1,9 +1,9 @@ const Action = require('./Action'); -class EmojiCreateAction extends Action { - handle(data, guild) { +class GuildEmojiCreateAction extends Action { + handle(guild, createdEmoji) { const client = this.client; - const emoji = client.dataManager.newEmoji(data, guild); + const emoji = client.dataManager.newEmoji(createdEmoji, guild); return { emoji, }; @@ -15,4 +15,4 @@ class EmojiCreateAction extends Action { * @event Client#guildEmojiCreate * @param {Emoji} emoji The emoji that was created. */ -module.exports = EmojiCreateAction; +module.exports = GuildEmojiCreateAction; diff --git a/src/client/actions/GuildEmojiDelete.js b/src/client/actions/GuildEmojiDelete.js index 7fdd1ca32..3ee4427d5 100644 --- a/src/client/actions/GuildEmojiDelete.js +++ b/src/client/actions/GuildEmojiDelete.js @@ -1,11 +1,11 @@ const Action = require('./Action'); -class EmojiDeleteAction extends Action { - handle(data) { +class GuildEmojiDeleteAction extends Action { + handle(emoji) { const client = this.client; - client.dataManager.killEmoji(data); + client.dataManager.killEmoji(emoji); return { - data, + emoji, }; } } @@ -15,4 +15,4 @@ class EmojiDeleteAction extends Action { * @event Client#guildEmojiDelete * @param {Emoji} emoji The emoji that was deleted. */ -module.exports = EmojiDeleteAction; +module.exports = GuildEmojiDeleteAction; diff --git a/src/client/actions/GuildEmojiUpdate.js b/src/client/actions/GuildEmojiUpdate.js index 88e0c395c..a79072c22 100644 --- a/src/client/actions/GuildEmojiUpdate.js +++ b/src/client/actions/GuildEmojiUpdate.js @@ -1,22 +1,8 @@ const Action = require('./Action'); class GuildEmojiUpdateAction extends Action { - handle(data, guild) { - const client = this.client; - for (let emoji of data.emojis) { - const already = guild.emojis.has(emoji.id); - if (already) { - client.dataManager.updateEmoji(guild.emojis.get(emoji.id), emoji); - } else { - emoji = client.dataManager.newEmoji(emoji, guild); - } - } - for (let emoji of guild.emojis) { - if (!data.emoijs.has(emoji.id)) client.dataManager.killEmoji(emoji); - } - return { - emojis: data.emojis, - }; + handle(oldEmoji, newEmoji) { + this.client.dataManager.updateEmoji(oldEmoji, newEmoji); } } diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 3bfb8af44..f5779224d 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -27,6 +27,7 @@ class WebSocketPacketManager { this.register(Constants.WSEvents.GUILD_ROLE_CREATE, require('./handlers/GuildRoleCreate')); this.register(Constants.WSEvents.GUILD_ROLE_DELETE, require('./handlers/GuildRoleDelete')); this.register(Constants.WSEvents.GUILD_ROLE_UPDATE, require('./handlers/GuildRoleUpdate')); + this.register(Constants.WSEvents.GUILD_EMOJIS_UPDATE, require('./handlers/GuildEmojisUpdate')); this.register(Constants.WSEvents.GUILD_MEMBERS_CHUNK, require('./handlers/GuildMembersChunk')); this.register(Constants.WSEvents.CHANNEL_CREATE, require('./handlers/ChannelCreate')); this.register(Constants.WSEvents.CHANNEL_DELETE, require('./handlers/ChannelDelete')); diff --git a/src/client/websocket/packets/handlers/GuildEmojiUpdate.js b/src/client/websocket/packets/handlers/GuildEmojiUpdate.js deleted file mode 100644 index 5f983cd5d..000000000 --- a/src/client/websocket/packets/handlers/GuildEmojiUpdate.js +++ /dev/null @@ -1,13 +0,0 @@ -const AbstractHandler = require('./AbstractHandler'); - -class GuildEmojiUpdate extends AbstractHandler { - handle(packet) { - const client = this.packetManager.client; - const data = packet.d; - const guild = client.guilds.get(data.guild_id); - if (!guild) return; - client.actions.EmojiUpdate.handle(data, guild); - } -} - -module.exports = GuildEmojiUpdate; diff --git a/src/client/websocket/packets/handlers/GuildEmojisUpdate.js b/src/client/websocket/packets/handlers/GuildEmojisUpdate.js new file mode 100644 index 000000000..523f2de2c --- /dev/null +++ b/src/client/websocket/packets/handlers/GuildEmojisUpdate.js @@ -0,0 +1,40 @@ +const AbstractHandler = require('./AbstractHandler'); + +function mappify(iterable) { + const map = new Map(); + for (const x of iterable) map.set(...x); + return map; +} + +class GuildEmojisUpdate extends AbstractHandler { + handle(packet) { + const client = this.packetManager.client; + const data = packet.d; + const guild = client.guilds.get(data.guild_id); + if (!guild || !guild.emojis) return; + + const deletions = mappify(guild.emojis.entries()); + + for (const emoji of data.emojis) { + // determine type of emoji event + const cachedEmoji = guild.emojis.get(emoji.id); + if (cachedEmoji) { + deletions.delete(emoji.id); + if (!cachedEmoji.equals(emoji, true)) { + // emoji updated + client.actions.GuildEmojiUpdate.handle(cachedEmoji, emoji); + } + } else { + // emoji added + client.actions.GuildEmojiCreate.handle(guild, emoji); + } + } + + for (const emoji of deletions.values()) { + // emoji deleted + client.actions.GuildEmojiDelete.handle(emoji); + } + } +} + +module.exports = GuildEmojisUpdate; diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js index a1823ba8f..d8a62e17e 100644 --- a/src/structures/Emoji.js +++ b/src/structures/Emoji.js @@ -103,6 +103,27 @@ class Emoji { return this.requiresColons ? `<:${this.name}:${this.id}>` : this.name; } + /** + * Whether this emoji is the same as another one + * @param {Emoji|Object} other the emoji to compare it to + * @returns {boolean} whether the emoji is equal to the given emoji or not + */ + equals(other) { + if (other instanceof Emoji) { + return ( + other.id === this.id && + other.name === this.name && + other.managed === this.managed && + other.requiresColons === this.requiresColons + ); + } else { + return ( + other.id === this.id && + other.name === this.name + ); + } + } + /** * The identifier of this emoji, used for message reactions * @readonly diff --git a/src/util/Constants.js b/src/util/Constants.js index fddc6d14a..abc94eafc 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -294,6 +294,7 @@ exports.WSEvents = { GUILD_ROLE_UPDATE: 'GUILD_ROLE_UPDATE', GUILD_BAN_ADD: 'GUILD_BAN_ADD', GUILD_BAN_REMOVE: 'GUILD_BAN_REMOVE', + GUILD_EMOJIS_UPDATE: 'GUILD_EMOJIS_UPDATE', CHANNEL_CREATE: 'CHANNEL_CREATE', CHANNEL_DELETE: 'CHANNEL_DELETE', CHANNEL_UPDATE: 'CHANNEL_UPDATE', diff --git a/test/random.js b/test/random.js index 0b22765ff..cc65fc759 100644 --- a/test/random.js +++ b/test/random.js @@ -20,6 +20,10 @@ client.on('userUpdate', (o, n) => { console.log(o.username, n.username); }); +client.on('guildEmojiCreate', e => console.log('create!!', e.name)); +client.on('guildEmojiDelete', e => console.log('delete!!', e.name)); +client.on('guildEmojiUpdate', (o, n) => console.log('update!!', o.name, n.name)); + client.on('guildMemberAdd', m => console.log(`${m.user.username} joined ${m.guild.name}`)); client.on('channelCreate', channel => { From 72c667b307c2d728ed5f0dc998d78a3c39ba7726 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Mon, 26 Dec 2016 20:20:58 +0000 Subject: [PATCH 213/248] Fix potential ReferenceError with GuildMembersChunk --- src/client/websocket/packets/handlers/GuildMembersChunk.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/client/websocket/packets/handlers/GuildMembersChunk.js b/src/client/websocket/packets/handlers/GuildMembersChunk.js index e9312e291..02a3c3cbd 100644 --- a/src/client/websocket/packets/handlers/GuildMembersChunk.js +++ b/src/client/websocket/packets/handlers/GuildMembersChunk.js @@ -8,11 +8,9 @@ class GuildMembersChunkHandler extends AbstractHandler { const client = this.packetManager.client; const data = packet.d; const guild = client.guilds.get(data.guild_id); - const members = []; + if (!guild) return; - if (guild) { - for (const member of data.members) members.push(guild._addMember(member, false)); - } + const members = data.members.map(member => guild._addMember(member, false)); guild._checkChunks(); client.emit(Constants.Events.GUILD_MEMBERS_CHUNK, members); From b1473b1e4cdecc782ead80c06ff447d94479e5e4 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 26 Dec 2016 21:39:07 +0100 Subject: [PATCH 214/248] Change RichEmbed to take StringResolvables (#1010) Changed RichEmbed to take StringResolvables for all title, name, value and description arguments. Also changed a few lines in setColor to make sure that the value assigned to this.color is always a number. --- src/structures/RichEmbed.js | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index 5aac3fd44..d382faa61 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -67,10 +67,11 @@ class RichEmbed { /** * Sets the title of this embed - * @param {string} title The title + * @param {StringResolvable} title The title * @returns {RichEmbed} This embed */ setTitle(title) { + title = resolveString(title); if (title.length > 256) throw new RangeError('RichEmbed titles may not exceed 256 characters.'); this.title = title; return this; @@ -78,10 +79,11 @@ class RichEmbed { /** * Sets the description of this embed - * @param {string} description The description + * @param {StringResolvable} description The description * @returns {RichEmbed} This embed */ setDescription(description) { + description = resolveString(description); if (description.length > 2048) throw new RangeError('RichEmbed descriptions may not exceed 2048 characters.'); this.description = description; return this; @@ -103,14 +105,17 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setColor(color) { + let radix = 10; if (color instanceof Array) { color = (color[0] << 16) + (color[1] << 8) + color[2]; } else if (typeof color === 'string' && color.startsWith('#')) { - color = parseInt(color.replace('#', ''), 16); + radix = 16; + color = color.replace('#', ''); } + color = parseInt(color, radix); if (color < 0 || color > 0xFFFFFF) { throw new RangeError('RichEmbed color must be within the range 0 - 16777215 (0xFFFFFF).'); - } else if (color && isNaN(parseInt(color))) { + } else if (color && isNaN(color)) { throw new TypeError('Unable to convert RichEmbed color to a number.'); } this.color = color; @@ -119,13 +124,13 @@ class RichEmbed { /** * Sets the author of this embed - * @param {string} name The name of the author + * @param {StringResolvable} name The name of the author * @param {string} [icon] The icon URL of the author * @param {string} [url] The URL of the author * @returns {RichEmbed} This embed */ setAuthor(name, icon, url) { - this.author = { name, icon_url: icon, url }; + this.author = { name: resolveString(name), icon_url: icon, url }; return this; } @@ -141,20 +146,18 @@ class RichEmbed { /** * Adds a field to the embed (max 25) - * @param {string} name The name of the field + * @param {StringResolvable} name The name of the field * @param {StringResolvable} value The value of the field * @param {boolean} [inline=false] Set the field to display inline * @returns {RichEmbed} This embed */ addField(name, value, inline = false) { if (this.fields.length >= 25) throw new RangeError('RichEmbeds may not exceed 25 fields.'); + name = resolveString(name); if (name.length > 256) throw new RangeError('RichEmbed field names may not exceed 256 characters.'); - if (typeof value !== 'undefined') { - if (value instanceof Array) value = value.join('\n'); - else if (typeof value !== 'string') value = String(value); - } + value = resolveString(value); if (value.length > 1024) throw new RangeError('RichEmbed field values may not exceed 1024 characters.'); - this.fields.push({ name, value, inline }); + this.fields.push({ name: String(name), value: value, inline }); return this; } @@ -180,11 +183,12 @@ class RichEmbed { /** * Sets the footer of this embed - * @param {string} text The text of the footer + * @param {StringResolvable} text The text of the footer * @param {string} [icon] The icon URL of the footer * @returns {RichEmbed} This embed */ setFooter(text, icon) { + text = resolveString(text); if (text.length > 2048) throw new RangeError('RichEmbed footer text may not exceed 2048 characters.'); this.footer = { text, icon_url: icon }; return this; @@ -192,3 +196,9 @@ class RichEmbed { } module.exports = RichEmbed; + +const resolveString = (data) => { + if (typeof data === 'string') return data; + if (data instanceof Array) return data.join('\n'); + return String(data); +}; From 758c801dd5fa0b46d2b71d0d6c52b0341424596e Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Mon, 26 Dec 2016 15:40:34 -0500 Subject: [PATCH 215/248] Change to function declaration --- src/structures/RichEmbed.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index d382faa61..fbd9383d3 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -197,8 +197,8 @@ class RichEmbed { module.exports = RichEmbed; -const resolveString = (data) => { +function resolveString(data) { if (typeof data === 'string') return data; if (data instanceof Array) return data.join('\n'); return String(data); -}; +} From bf7767fe2c5b1d97cf1f32a75099873acf8e3b50 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Mon, 26 Dec 2016 21:30:27 +0000 Subject: [PATCH 216/248] Rename emoji events --- src/client/actions/GuildEmojiCreate.js | 4 ++-- src/client/actions/GuildEmojiDelete.js | 4 ++-- src/client/actions/GuildEmojiUpdate.js | 4 ++-- src/util/Constants.js | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client/actions/GuildEmojiCreate.js b/src/client/actions/GuildEmojiCreate.js index a4234fcdd..5df1ced3c 100644 --- a/src/client/actions/GuildEmojiCreate.js +++ b/src/client/actions/GuildEmojiCreate.js @@ -11,8 +11,8 @@ class GuildEmojiCreateAction extends Action { } /** - * Emitted whenever an emoji is created - * @event Client#guildEmojiCreate + * Emitted whenever a custom emoji is created in a guild + * @event Client#emojiCreate * @param {Emoji} emoji The emoji that was created. */ module.exports = GuildEmojiCreateAction; diff --git a/src/client/actions/GuildEmojiDelete.js b/src/client/actions/GuildEmojiDelete.js index 3ee4427d5..8cfa20591 100644 --- a/src/client/actions/GuildEmojiDelete.js +++ b/src/client/actions/GuildEmojiDelete.js @@ -11,8 +11,8 @@ class GuildEmojiDeleteAction extends Action { } /** - * Emitted whenever an emoji is deleted - * @event Client#guildEmojiDelete + * Emitted whenever a custom guild emoji is deleted + * @event Client#emojiDelete * @param {Emoji} emoji The emoji that was deleted. */ module.exports = GuildEmojiDeleteAction; diff --git a/src/client/actions/GuildEmojiUpdate.js b/src/client/actions/GuildEmojiUpdate.js index a79072c22..94bfa24d9 100644 --- a/src/client/actions/GuildEmojiUpdate.js +++ b/src/client/actions/GuildEmojiUpdate.js @@ -7,8 +7,8 @@ class GuildEmojiUpdateAction extends Action { } /** - * Emitted whenever an emoji is updated - * @event Client#guildEmojiUpdate + * Emitted whenever a custom guild emoji is updated + * @event Client#emojiUpdate * @param {Emoji} oldEmoji The old emoji * @param {Emoji} newEmoji The new emoji */ diff --git a/src/util/Constants.js b/src/util/Constants.js index abc94eafc..731794a44 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -212,9 +212,9 @@ exports.Events = { GUILD_ROLE_CREATE: 'roleCreate', GUILD_ROLE_DELETE: 'roleDelete', GUILD_ROLE_UPDATE: 'roleUpdate', - GUILD_EMOJI_CREATE: 'guildEmojiCreate', - GUILD_EMOJI_DELETE: 'guildEmojiDelete', - GUILD_EMOJI_UPDATE: 'guildEmojiUpdate', + GUILD_EMOJI_CREATE: 'emojiCreate', + GUILD_EMOJI_DELETE: 'emojiDelete', + GUILD_EMOJI_UPDATE: 'emojiUpdate', GUILD_BAN_ADD: 'guildBanAdd', GUILD_BAN_REMOVE: 'guildBanRemove', CHANNEL_CACHED: 'channelCached', From b0c2f818a8a40d565df0d774dfb0b8ce05b77604 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Mon, 26 Dec 2016 21:56:04 +0000 Subject: [PATCH 217/248] Remove cached events --- src/client/ClientDataManager.js | 17 ----------------- src/util/Constants.js | 2 -- 2 files changed, 19 deletions(-) diff --git a/src/client/ClientDataManager.js b/src/client/ClientDataManager.js index 9ba568a1b..32b442b73 100644 --- a/src/client/ClientDataManager.js +++ b/src/client/ClientDataManager.js @@ -22,14 +22,6 @@ class ClientDataManager { const already = this.client.guilds.has(data.id); const guild = new Guild(this.client, data); this.client.guilds.set(guild.id, guild); - if (this.client.listenerCount(Constants.Events.GUILD_CACHED)) { - /** - * Emitted whenever a guild is added to the cache - * @event Client#guildCached - * @param {Guild} guild The cached guild - */ - this.client.emit(Constants.Events.GUILD_CACHED, guild); - } if (this.pastReady && !already) { /** * Emitted whenever the client joins a guild. @@ -74,15 +66,6 @@ class ClientDataManager { } if (channel) { - if (this.client.listenerCount(Constants.Events.CHANNEL_CACHED)) { - /** - * Emitted whenever a channel is added to the cache - * @event Client#channelCached - * @param {Channel} channel The cached channel - */ - this.client.emit(Constants.Events.CHANNEL_CACHED, channel); - } - if (this.pastReady && !already) this.client.emit(Constants.Events.CHANNEL_CREATE, channel); this.client.channels.set(channel.id, channel); return channel; diff --git a/src/util/Constants.js b/src/util/Constants.js index 731794a44..cee2192f1 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -197,7 +197,6 @@ exports.VoiceOPCodes = { exports.Events = { READY: 'ready', - GUILD_CACHED: 'guildCached', GUILD_CREATE: 'guildCreate', GUILD_DELETE: 'guildDelete', GUILD_UPDATE: 'guildUpdate', @@ -217,7 +216,6 @@ exports.Events = { GUILD_EMOJI_UPDATE: 'emojiUpdate', GUILD_BAN_ADD: 'guildBanAdd', GUILD_BAN_REMOVE: 'guildBanRemove', - CHANNEL_CACHED: 'channelCached', CHANNEL_CREATE: 'channelCreate', CHANNEL_DELETE: 'channelDelete', CHANNEL_UPDATE: 'channelUpdate', From b79533e3731267d4a63f3e08a87105066f8ea6c7 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Mon, 26 Dec 2016 18:04:09 -0600 Subject: [PATCH 218/248] correctly handle invalid session (#1020) * correctly handle invalid session * give discord some time, use it wisely --- src/client/websocket/packets/WebSocketPacketManager.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index f5779224d..437c6b49d 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -78,8 +78,14 @@ class WebSocketPacketManager { } if (packet.op === Constants.OPCodes.INVALID_SESSION) { - this.ws.sessionID = null; - this.ws._sendNewIdentify(); + if (packet.d) { + setTimeout(() => { + this.ws._sendResume(); + }, 2500); + } else { + this.ws.sessionID = null; + this.ws._sendNewIdentify(); + } return false; } From bac4ccead7048486fea4c92115e6b97a9ade7eee Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 27 Dec 2016 06:13:57 -0600 Subject: [PATCH 219/248] "knock, knock. who's there. discord, lol" (#1021) --- src/client/websocket/packets/WebSocketPacketManager.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 437c6b49d..cd720626e 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -95,6 +95,11 @@ class WebSocketPacketManager { this.ws.client.emit('debug', 'Heartbeat acknowledged'); } + if (packet.op === Constants.OPCodes.HEARTBEAT) { + this.client.ws.send({ op: Constants.OPCodes.HEARTBEAT_ACK }); + this.ws.client.emit('debug', 'ACKed gateway heartbeat!'); + } + if (this.ws.status === Constants.Status.RECONNECTING) { this.ws.reconnecting = false; this.ws.checkIfReady(); From 779681e88fac89c20ccb20816ed21e50f84c2dfc Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 27 Dec 2016 11:31:23 -0600 Subject: [PATCH 220/248] "knock knock. who's there? ack lol. GET OFF MY PORCH!" (#1023) --- src/client/websocket/packets/WebSocketPacketManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index cd720626e..f7f11e09d 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -96,8 +96,8 @@ class WebSocketPacketManager { } if (packet.op === Constants.OPCodes.HEARTBEAT) { - this.client.ws.send({ op: Constants.OPCodes.HEARTBEAT_ACK }); - this.ws.client.emit('debug', 'ACKed gateway heartbeat!'); + this.client.ws.send({ op: Constants.OPCodes.HEARTBEAT }); + this.ws.client.emit('debug', 'Recieved gateway heartbeat!'); } if (this.ws.status === Constants.Status.RECONNECTING) { From 14ba0373eb209fa5cafb81396ccd5612cae0e982 Mon Sep 17 00:00:00 2001 From: meew0 Date: Tue, 27 Dec 2016 23:16:30 +0100 Subject: [PATCH 221/248] Fix a small spelling mistake in a debug log --- src/client/websocket/packets/WebSocketPacketManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index f7f11e09d..9e058ffd1 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -97,7 +97,7 @@ class WebSocketPacketManager { if (packet.op === Constants.OPCodes.HEARTBEAT) { this.client.ws.send({ op: Constants.OPCodes.HEARTBEAT }); - this.ws.client.emit('debug', 'Recieved gateway heartbeat!'); + this.ws.client.emit('debug', 'Received gateway heartbeat!'); } if (this.ws.status === Constants.Status.RECONNECTING) { From 84991c767ef40b36dfe42971451afbdcd9c291b1 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Tue, 27 Dec 2016 22:30:51 -0500 Subject: [PATCH 222/248] Improve some Gus shenanigans --- src/client/websocket/packets/WebSocketPacketManager.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 9e058ffd1..928f9d5c9 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -93,11 +93,9 @@ class WebSocketPacketManager { this.ws.client._pong(this.ws.client._pingTimestamp); this.ws.lastHeartbeatAck = true; this.ws.client.emit('debug', 'Heartbeat acknowledged'); - } - - if (packet.op === Constants.OPCodes.HEARTBEAT) { + } else if (packet.op === Constants.OPCodes.HEARTBEAT) { this.client.ws.send({ op: Constants.OPCodes.HEARTBEAT }); - this.ws.client.emit('debug', 'Received gateway heartbeat!'); + this.ws.client.emit('debug', 'Received gateway heartbeat'); } if (this.ws.status === Constants.Status.RECONNECTING) { From 265021bfa54258c5212b73fe5f8a66ccf3f85352 Mon Sep 17 00:00:00 2001 From: Zack Campbell Date: Tue, 27 Dec 2016 21:35:11 -0600 Subject: [PATCH 223/248] Add sendEmbed to GuildMember for docs (#1028) * Add sendEmbed to GuildMember for docs * Update GuildMember.js --- src/structures/GuildMember.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index df74613b0..87c78b68f 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -432,6 +432,7 @@ class GuildMember { // These are here only for documentation purposes - they are implemented by TextBasedChannel sendMessage() { return; } sendTTSMessage() { return; } + sendEmbed() { return; } sendFile() { return; } sendCode() { return; } } From 4d2153c7c6d61581d575228169f37c013793af23 Mon Sep 17 00:00:00 2001 From: bdistin Date: Tue, 27 Dec 2016 21:44:17 -0600 Subject: [PATCH 224/248] Document ShardingManager#message (#1024) * Document ShardingManager#message * Update Shard.js --- src/sharding/Shard.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index 31cbb814d..ab9b923e1 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -151,6 +151,12 @@ class Shard { } } + /** + * Emitted upon recieving a message from a shard + * @event ShardingManager#message + * @param {Shard} shard Shard that sent the message + * @param {*} message Message that was received + */ this.manager.emit('message', this, message); } } From 8d966932a924b9d1eeada3cf44e0dad552e7b666 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 28 Dec 2016 00:42:42 -0600 Subject: [PATCH 225/248] funny (#1030) --- src/client/websocket/packets/WebSocketPacketManager.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 928f9d5c9..78f57777e 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -94,7 +94,10 @@ class WebSocketPacketManager { this.ws.lastHeartbeatAck = true; this.ws.client.emit('debug', 'Heartbeat acknowledged'); } else if (packet.op === Constants.OPCodes.HEARTBEAT) { - this.client.ws.send({ op: Constants.OPCodes.HEARTBEAT }); + this.client.ws.send({ + op: Constants.OPCodes.HEARTBEAT, + d: this.client.ws.sequence, + }); this.ws.client.emit('debug', 'Received gateway heartbeat'); } From 3451367591bacf77fb6243659ce3bbd461f35d70 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 28 Dec 2016 13:04:54 -0600 Subject: [PATCH 226/248] fix the everyone role mentioning (#1032) --- src/structures/Role.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/structures/Role.js b/src/structures/Role.js index 5936da0fc..c15ff4be0 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -321,6 +321,7 @@ class Role { * @returns {string} */ toString() { + if (this.id === this.guild.id) return '@everyone'; return `<@&${this.id}>`; } From 289447e4c91249370674d752779fcf910dab382f Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 28 Dec 2016 16:28:36 -0500 Subject: [PATCH 227/248] Update Client.js --- src/client/Client.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 4fdac08db..11cd5f403 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -345,19 +345,17 @@ class Client extends EventEmitter { /** * Generate an invite link for your bot - * @param {Array|number} [permissions] An array of permissions to request + * @param {PermissionResolvable[]|number} [permissions] An array of permissions to request * @returns {Promise} The invite link * @example * client.generateInvite(['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE']) * .then(link => { - * console.log(link); + * console.log(`Generated bot invite link: ${link}`); * }); */ generateInvite(permissions) { if (permissions) { - if (permissions instanceof Array) { - permissions = this.resolver.resolvePermissions(permissions); - } + if (permissions instanceof Array) permissions = this.resolver.resolvePermissions(permissions); } else { permissions = 0; } From 8e47058286889c38f86a22a7a0e6c680d7f21713 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 28 Dec 2016 16:30:15 -0500 Subject: [PATCH 228/248] Update ClientDataResolver.js --- src/client/ClientDataResolver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 9f550cc25..211ee7ede 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -192,7 +192,7 @@ class ClientDataResolver { /** * Turn an array of permissions into a valid discord permission bitfield - * @param {Array} permissions An array of permissions as strings or permissions numbers (see resolvePermission) + * @param {PermissionResolvable[]} permissions Permissions to resolve together * @returns {number} */ resolvePermissions(permissions) { From bbeef44e667e547cb225cca55d0c918f41fb8be9 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 28 Dec 2016 16:32:14 -0500 Subject: [PATCH 229/248] Update ClientDataResolver.js --- src/client/ClientDataResolver.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 211ee7ede..6e07ea3a6 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -191,15 +191,13 @@ class ClientDataResolver { } /** - * Turn an array of permissions into a valid discord permission bitfield + * Turn an array of permissions into a valid Discord permission bitfield * @param {PermissionResolvable[]} permissions Permissions to resolve together * @returns {number} */ resolvePermissions(permissions) { let bitfield = 0; - for (const permission of permissions) { - bitfield |= this.resolvePermission(permission); - } + for (const permission of permissions) bitfield |= this.resolvePermission(permission); return bitfield; } From e4bae997470087014af9fdb3ebc7cd644a4e2f15 Mon Sep 17 00:00:00 2001 From: Zack Campbell Date: Wed, 28 Dec 2016 15:50:32 -0600 Subject: [PATCH 230/248] Update GuildResolvable typedef (#1034) To reflect the resolver supporting Guild ID strings --- src/client/ClientDataResolver.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 6e07ea3a6..4c4596775 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -65,7 +65,8 @@ class ClientDataResolver { /** * Data that resolves to give a Guild object. This can be: * * A Guild object - * @typedef {Guild} GuildResolvable + * * A Guild ID + * @typedef {Guild|string} GuildResolvable */ /** From c1b9437f0d4b3f73d699a6a809b23a7ce9992048 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 28 Dec 2016 18:20:38 -0500 Subject: [PATCH 231/248] Set up typings submodule --- .gitmodules | 3 + typings | 1 + typings/index.d.ts | 786 ------------------------------------------ typings/tsconfig.json | 13 - 4 files changed, 4 insertions(+), 799 deletions(-) create mode 100644 .gitmodules create mode 160000 typings delete mode 100644 typings/index.d.ts delete mode 100644 typings/tsconfig.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..7a99b61b2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "typings"] + path = typings + url = https://github.com/acdenisSK/discord.js-typings diff --git a/typings b/typings new file mode 160000 index 000000000..7523be032 --- /dev/null +++ b/typings @@ -0,0 +1 @@ +Subproject commit 7523be0329197a1d754c5237243d6adadc9e1c52 diff --git a/typings/index.d.ts b/typings/index.d.ts deleted file mode 100644 index 3bfc1151c..000000000 --- a/typings/index.d.ts +++ /dev/null @@ -1,786 +0,0 @@ -// Type definitions for discord.js 10.0.1 -// Project: https://github.com/hydrabolt/discord.js -// Definitions by: acdenisSK (https://github.com/acdenisSK) -// License: MIT - -declare module "discord.js" { - import { EventEmitter } from "events"; - import { Readable as ReadableStream } from "stream"; - import { ChildProcess } from "child_process"; - - export const version: string; - export class Client extends EventEmitter { - constructor(options?: ClientOptions); - email: string; - emojis: Collection; - guilds: Collection; - channels: Collection; - options: ClientOptions; - password: string; - readyAt: Date; - readyTimestamp: number; - status: number; - token: string; - uptime: number; - user: ClientUser; - users: Collection; - voiceConnections: Collection; - clearInterval(timeout: NodeJS.Timer): void; - clearTimeout(timeout: NodeJS.Timer): void; - destroy(): Promise; - fetchInvite(code: string): Promise; - fetchUser(id: string): Promise; - login(tokenOrEmail: string, password?: string): Promise; - setInterval(fn: Function, delay: number, ...args: any[]): NodeJS.Timer; - setTimeout(fn: Function, delay: number, ...args: any[]): NodeJS.Timer; - sweepMessages(lifetime?: number): number; - syncGuilds(guilds?: Guild[]): void; - on(event: string, listener: Function): this; - on(event: "debug", listener: (the: string) => void): this; - on(event: "disconnect", listener: () => void): this; - on(event: "error", listener: (error: Error) => void): this; - on(event: "guildBanAdd", listener: (user: User) => void): this; - on(event: "guildBanRemove", listener: (user: User) => void): this; - on(event: "guildCreate", listener: (guild: Guild) => void): this; - on(event: "guildDelete", listener: (guild: Guild) => void): this; - on(event: "guildEmojiCreate", listener: (emoji: Emoji) => void): this; - on(event: "guildEmojiDelete", listener: (emoji: Emoji) => void): this; - on(event: "guildEmojiUpdate", listener: (oldEmoji: Emoji, newEmoji: Emoji) => void): this; - on(event: "guildMemberAdd", listener: (member: GuildMember) => void): this; - on(event: "guildMemberAvailable", listener: (member: GuildMember) => void): this; - on(event: "guildMemberRemove", listener: (member: GuildMember) => void): this; - on(event: "guildMembersChunk", listener: (members: GuildMember[]) => void): this; - on(event: "guildMemberSpeaking", listener: (member: GuildMember, speaking: boolean) => void): this; - on(event: "guildMemberUpdate", listener: (oldMember: GuildMember, newMember: GuildMember) => void): this; - on(event: "roleCreate", listener: (role: Role) => void): this; - on(event: "roleDelete", listener: (role: Role) => void): this; - on(event: "roleUpdate", listener: (oldRole: Role, newRole: Role) => void): this; - on(event: "guildUnavailable", listener: (guild: Guild) => void): this; - on(event: "guildUpdate", listener: (oldGuild: Guild, newGuild: Guild) => void): this; - on(event: "channelCreate", listener: (channel: Channel) => void): this; - on(event: "channelDelete", listener: (channel: Channel) => void): this; - on(event: "channelPinsUpdate", listener: (channel: Channel, time: Date) => void): this; - on(event: "channelUpdate", listener: (oldChannel: Channel, newChannel: Channel) => void): this; - on(event: "message", listener: (message: Message) => void): this; - on(event: "messageDelete", listener: (message: Message) => void): this; - on(event: "messageDeleteBulk", listener: (messages: Collection) => void): this; - on(event: "messageUpdate", listener: (oldMessage: Message, newMessage: Message) => void): this; - on(event: "presenceUpdate", listener: (oldUser: User, newUser: User) => void): this; - on(event: "ready", listener: () => void): this; - on(event: "reconnecting", listener: () => void): this; - on(event: "typingStart", listener: (channel: Channel, user: User) => void): this; - on(event: "typingStop", listener: (channel: Channel, user: User) => void): this; - on(event: "userUpdate", listener: (oldClientUser: ClientUser, newClientUser: ClientUser) => void): this; - on(event: "voiceStateUpdate", listener: (oldMember: GuildMember, newMember: GuildMember) => void): this; - on(event: "warn", listener: (the: string) => void): this; - } - export class Webhook { - avatar: string; - client: Client; - guildID: string; - channelID: string; - id: string; - name: string; - token: string; - delete(): Promise; - edit(name: string, avatar: FileResolvable): Promise; - sendCode(lang: string, content: StringResolvable, options?: WebhookMessageOptions): Promise; - sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: WebhookMessageOptions): Promise; - sendMessage(content: StringResolvable, options?: WebhookMessageOptions): Promise; - sendSlackMessage(body: Object): Promise; - sendTTSMessage(content: StringResolvable, options?: WebhookMessageOptions): Promise; - } - class SecretKey { - key: Uint8Array; - } - class RequestHandler { // docs going nowhere again, yay - constructor(restManager: {}); - globalLimit: boolean; - queue: {}[]; - restManager: {}; - handle(); - push(request: {}); - } - export class WebhookClient extends Webhook { - constructor(id: string, token: string, options?: ClientOptions); - options: ClientOptions; - } - export class Emoji { - client: Client; - createdAt: Date; - createdTimestamp: number; - guild: Guild; - id: string; - managed: boolean; - name: string; - requiresColons: boolean; - roles: Collection; - url: string; - toString(): string; - } - export class ReactionEmoji { - id: string; - identifier: string; - name: string; - reaction: MessageReaction; - toString(): string; - } - export class ClientUser extends User { - email: string; - verified: boolean; - blocked: Collection; - friends: Collection; - addFriend(user: UserResolvable): Promise; - removeFriend(user: UserResolvable): Promise; - setAvatar(avatar: Base64Resolvable): Promise; - setEmail(email: string): Promise; - setPassword(password: string): Promise; - setStatus(status?: string): Promise; - setGame(game: string, streamingURL?: string): Promise; - setPresence(data: Object): Promise; - setUsername(username: string): Promise; - createGuild(name: string, region: string, icon?: FileResolvable): Promise; - } - export class Presence { - game: Game; - status: string; - equals(other: Presence): boolean; - } - export class Channel { - client: Client; - createdAt: Date; - createdTimestamp: number; - id: string; - type: string; - delete(): Promise; - } - export class DMChannel extends Channel { - lastMessageID: string; - messages: Collection; - recipient: User; - typing: boolean; - typingCount: number; - awaitMessages(filter: CollectorFilterFunction, options?: AwaitMessagesOptions): Promise>; - bulkDelete(messages: Collection | Message[] | number): Collection; - createCollector(filter: CollectorFilterFunction, options?: CollectorOptions): MessageCollector; - fetchMessage(messageID: string): Promise; - fetchMessages(options?: ChannelLogsQueryOptions): Promise>; - fetchPinnedMessages(): Promise>; - sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; - sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; - sendMessage(content: string, options?: MessageOptions): Promise; - sendTTSMessage(content: string, options?: MessageOptions): Promise; - startTyping(count?: number): void; - stopTyping(force?: boolean): void; - toString(): string; - } - export class GroupDMChannel extends Channel { - lastMessageID: string; - messages: Collection; - recipients: Collection; - owner: User; - typing: boolean; - typingCount: number; - awaitMessages(filter: CollectorFilterFunction, options?: AwaitMessagesOptions): Promise>; - bulkDelete(messages: Collection | Message[] | number): Collection; - createCollector(filter: CollectorFilterFunction, options?: CollectorOptions): MessageCollector; - fetchMessage(messageID: string): Promise; - fetchMessages(options?: ChannelLogsQueryOptions): Promise>; - fetchPinnedMessages(): Promise>; - sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; - sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; - sendMessage(content: string, options?: MessageOptions): Promise; - sendTTSMessage(content: string, options?: MessageOptions): Promise; - startTyping(count?: number): void; - stopTyping(force?: boolean): void; - toString(): string; - } - export class GuildChannel extends Channel { - guild: Guild; - name: string; - permissionOverwrites: Collection; - position: number; - createInvite(options?: InviteOptions): Promise; - equals(channel: GuildChannel): boolean; - overwritePermissions(userOrRole: Role | User, options: PermissionOverwriteOptions): Promise; - permissionsFor(member: GuildMemberResolvable): EvaluatedPermissions; - setName(name: string): Promise; - setPosition(position: number): Promise; - setTopic(topic: string): Promise; - toString(): string; - } - export class TextChannel extends GuildChannel { - lastMessageID: string; - members: Collection; - messages: Collection; - topic: string; - typing: boolean; - typingCount: number; - awaitMessages(filter: CollectorFilterFunction, options?: AwaitMessagesOptions): Promise>; - bulkDelete(messages: Collection | Message[] | number): Collection; - createCollector(filter: CollectorFilterFunction, options?: CollectorOptions): MessageCollector; - fetchMessage(messageID: string): Promise; - fetchMessages(options?: ChannelLogsQueryOptions): Promise>; - fetchPinnedMessages(): Promise>; - sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; - sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; - sendMessage(content: string, options?: MessageOptions): Promise; - sendTTSMessage(content: string, options?: MessageOptions): Promise; - startTyping(count?: number): void; - stopTyping(force?: boolean): void; - } - export class MessageCollector extends EventEmitter { - constructor(channel: Channel, filter: CollectorFilterFunction, options?: CollectorOptions); - collected: Collection; - filter: CollectorFilterFunction; - channel: Channel; - options: CollectorOptions; - stop(reason?: string): void; - on(event: "end", listener: (collection: Collection, reason: string) => void): this; - on(event: "message", listener: (message: Message, collector: MessageCollector) => void): this; - } - export class Game { - name: string; - streaming: boolean; - url: string; - type: number; - equals(other: Game): boolean; - } - export class PermissionOverwrites { - channel: GuildChannel; - id: string; - type: string; - delete(): Promise; - } - export class Guild { - afkChannelID: string; - afkTimeout: number; - available: boolean; - client: Client; - createdAt: Date; - createdTimestamp: number; - defaultChannel: GuildChannel; - embedEnabled: boolean; - emojis: Collection; - features: Object[]; - channels: Collection; - icon: string; - iconURL: string; - id: string; - joinDate: Date; - large: boolean; - memberCount: number; - members: Collection; - name: string; - owner: GuildMember; - ownerID: string; - region: string; - roles: Collection; - splash: string; - verificationLevel: number; - voiceConnection: VoiceConnection; - ban(user: GuildMember, deleteDays?: number): Promise; - createChannel(name: string, type: "text" | "voice"): Promise; - createRole(data?: RoleData): Promise; - delete(): Promise; - edit(data: {}): Promise; - equals(guild: Guild): boolean; - fetchBans(): Promise>; - fetchInvites(): Promise>; - fetchMember(user: UserResolvable): Promise; - fetchMembers(query?: string): Promise; - leave(): Promise; - member(user: UserResolvable): GuildMember; - pruneMembers(days: number, dry?: boolean): Promise; - setAFKChannel(afkChannel: ChannelResovalble): Promise; - setAFKTimeout(afkTimeout: number): Promise; - setIcon(icon: Base64Resolvable): Promise; - setName(name: string): Promise; - setOwner(owner: GuildMemberResolvable): Promise; - setRegion(region: string): Promise; - setSplash(splash: Base64Resolvable): Promise; - setVerificationLevel(level: number): Promise; - sync(): void; - toString(): string; - unban(user: UserResolvable): Promise; - } - export class GuildMember { - bannable: boolean; - client: Client; - deaf: boolean; - guild: Guild; - highestRole: Role; - id: string; - joinDate: Date; - kickable: boolean; - mute: boolean; - nickname: string; - permissions: EvaluatedPermissions; - roles: Collection; - selfDeaf: boolean; - selfMute: boolean; - serverDeaf: boolean; - serverMute: boolean; - speaking: boolean; - user: User; - voiceChannel: VoiceChannel; - voiceChannelID: string; - voiceSessionID: string; - addRole(role: Role | string): Promise; - addRoles(roles: Collection | Role[] | string[]): Promise; - ban(deleteDays?: number): Promise; - deleteDM(): Promise; - edit(data: {}): Promise; - hasPermission(permission: PermissionResolvable, explicit?: boolean): boolean; - hasPermissions(permission: Permissions[], explicit?: boolean): boolean; - kick(): Promise; - permissionsIn(channel: ChannelResovalble): EvaluatedPermissions; - removeRole(role: Role | string): Promise; - removeRoles(roles: Collection | Role[] | string[]): Promise; - sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; - sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; - sendMessage(content: string, options?: MessageOptions): Promise; - sendTTSMessage(content: string, options?: MessageOptions): Promise; - setDeaf(deaf: boolean): Promise; - setMute(mute: boolean): Promise; - setNickname(nickname: string): Promise; - setRoles(roles: Collection | Role[] | string[]): Promise; - setVoiceChannel(voiceChannel: ChannelResovalble): Promise; - toString(): string; - } - export class User { - avatar: string; - avatarURL: string; - bot: boolean; - client: Client; - createdAt: Date; - createdTimestamp: number; - discriminator: string; - presence: Presence; - id: string; - status: string; - username: string; - block(): Promise; - unblock(): Promise; - fetchProfile(): Promise; - deleteDM(): Promise; - equals(user: User): boolean; - sendCode(lang: string, content: StringResolvable, options?: MessageOptions): Promise; - sendFile(attachment: FileResolvable, fileName?: string, content?: StringResolvable, options?: MessageOptions): Promise; - sendMessage(content: string, options?: MessageOptions): Promise; - sendTTSMessage(content: string, options?: MessageOptions): Promise; - toString(): string; - } - export class PartialGuildChannel { - client: Client; - id: string; - name: string; - type: string; - } - export class PartialGuild { - client: Client; - icon: string; - id: string; - name: string; - splash: string; - } - class PendingVoiceConnection { - data: Object; - deathTimer: NodeJS.Timer; - channel: VoiceChannel; - voiceManager: ClientVoiceManager; - setSessionID(sessionID: string); - setTokenAndEndpoint(token: string, endpoint: string); - upgrade(): VoiceConnection; - } - export class OAuth2Application { - client: Client; - createdAt: Date; - createdTimestamp: number; - description: string; - icon: string; - iconURL: string; - id: string; - name: string; - rpcOrigins: string[]; - toString(): string; - } - export class ClientOAuth2Application extends OAuth2Application { - flags: number; - owner: User; - } - export class Message { - attachments: Collection; - author: User; - channel: TextChannel | DMChannel | GroupDMChannel; - cleanContent: string; - client: Client; - content: string; - createdAt: Date; - createdTimestamp: number; - deletable: boolean; - editable: boolean; - editedAt: Date; - editedTimestamp: number; - edits: Message[]; - embeds: MessageEmbed[]; - guild: Guild; - id: string; - member: GuildMember; - mentions: { - users: Collection; - roles: Collection; - channels: Collection; - everyone: boolean; - }; - nonce: string; - pinnable: boolean; - pinned: boolean; - reactions: Collection; - system: boolean; - tts: boolean; - type: string; - addReaction(emoji: string): MessageReaction; // Not really documented but still worth using/making typings for it. - delete(timeout?: number): Promise; - edit(content: StringResolvable): Promise; - editCode(lang: string, content: StringResolvable): Promise; - equals(message: Message, rawData: Object): boolean; - isMentioned(data: GuildChannel | User | Role | string): boolean; - pin(): Promise; - reply(content: StringResolvable, options?: MessageOptions): Promise; - toString(): string; - unpin(): Promise; - } - export class MessageEmbed { - author: MessageEmbedAuthor; - client: Client; - description: string; - message: Message; - provider: MessageEmbedProvider; - thumbnail: MessageEmbedThumbnail; - title: string; - type: string; - url: string; - } - export class MessageEmbedThumbnail { - embed: MessageEmbed; - height: number; - proxyURL: string; - url: string; - width: number; - } - export class MessageEmbedProvider { - embed: MessageEmbed; - name: string; - url: string; - } - export class MessageEmbedAuthor { - embed: MessageEmbed; - name: string; - url: string; - } - export class RichEmbed { - title?: string; - description?: string; - url?: string; - timestamp?: Date; - color?: number | string; - fields?: { name: string; value: string; inline?: boolean; }[]; - author?: { name: string; url?: string; icon_url?: string; }; - thumbnail?: { url: string; height?: number; width?: number; }; - image?: { url: string; proxy_url?: string; height?: number; width?: number; }; - video?: { url: string; height: number; width: number; }; - footer?: { text?: string; icon_url?: string; }; - - addField(name: string, value: StringResolvable, inline?: boolean): this; - setAuthor(name: string, icon?: string, url?: string): this; - setColor(color: string | number | number[]): this; - setDescription(description: string): this; - setFooter(text: string, icon: string): this; - setImage(url: string): this; - setThumbnail(url: string): this; - setTimestamp(timestamp?: Date): this; - setTitle(title: string): this; - setURL(url: string): this; - } - export class MessageAttachment { - client: Client; - filename: string; - filesize: number; - height: number; - id: string; - message: Message; - proxyURL: string; - url: string; - width: number; - } - export class MessageReaction { - count: number; - emoji: Emoji | ReactionEmoji; - me: boolean; - message: Message; - users: Collection; - fetchUsers(limit?: number): Promise>; - remove(user?: UserResolvable): Promise; - } - export class Invite { - client: Client; - code: string; - createdAt: Date; - createdTimestamp: number; - guild: Guild | PartialGuild; - channel: GuildChannel | PartialGuildChannel; - inviter: User; - maxUses: number; - temporary: boolean; - url: string; - uses: number; - delete(): Promise; - toString(): string; - } - export class VoiceChannel extends GuildChannel { - bitrate: number; - connection: VoiceConnection; - members: Collection; - userLimit: number; - join(): Promise; - leave(): null; - setBitrate(bitrate: number): Promise; - } - export class Shard { - id: string; - manager: ShardingManager; - process: ChildProcess; - eval(script: string): Promise; - fetchClientValue(prop: string): Promise; - send(message: any): Promise; - } - export class ShardingManager extends EventEmitter { - constructor(file: string, options?: { - totalShards?: number; - respawn?: boolean; - shardArgs?: string[]; - token?: string; - }); - file: string; - respawn: boolean; - shardArgs: string[]; - shards: Collection; - totalShards: number; - broadcast(message: any): Promise; - broadcastEval(script: string): Promise; - createShard(id: number): Promise; - fetchClientValues(prop: string): Promise; - spawn(amount?: number, delay?: number): Promise>; - on(event: "launch", listener: (shard: Shard) => void): this; - } - export class ShardClientUtil { - constructor(client: Client); - id: number; - count: number; - broadcastEval(script: string): Promise; - fetchClientValues(prop: string): Promise; - send(message: any): Promise; - singleton(client: Client): ShardClientUtil; - } - export class UserConnection { - id: string; - integrations: Object[]; - name: string; - revoked: boolean; - type: string; - user: User; - } - export class UserProfile { - client: Client; - connections: Collection; - mutualGuilds: Collection; - user: User; - } - export class StreamDispatcher extends EventEmitter { - passes: number; - time: number; - totalStreamTime: number; - volume: number; - end(): void; - pause(): void; - resume(): void; - setVolume(volume: number): void; - setVolumeDecibels(db: number): void; - setVolumeLogarithmic(value: number): void; - on(event: "debug", listener: (information: string) => void): this; - on(event: "end", listener: () => void): this; - on(event: "error", listener: (err: Error) => void): this; - on(event: "speaking", listener: (value: boolean) => void): this; - on(event: "start", listener: () => void): this; - } - interface Permissions { - CREATE_INSTANT_INVITE: boolean; - KICK_MEMBERS: boolean; - BAN_MEMBERS: boolean; - ADMINISTRATOR: boolean; - MANAGE_CHANNELS: boolean; - MANAGE_GUILD: boolean; - READ_MESSAGES: boolean; - SEND_MESSAGES: boolean; - SEND_TTS_MESSAGES: boolean; - MANAGE_MESSAGES: boolean; - EMBED_LINKS: boolean; - ATTACH_FILES: boolean; - READ_MESSAGE_HISTORY: boolean; - MENTION_EVERYONE: boolean; - USE_EXTERNAL_EMOJIS: boolean; - CONNECT: boolean; - SPEAK: boolean; - MUTE_MEMBERS: boolean; - DEAFEN_MEMBERS: boolean; - MOVE_MEMBERS: boolean; - USE_VAD: boolean; - CHANGE_NICKNAME: boolean; - MANAGE_NICKNAMES: boolean; - MANAGE_ROLES: boolean; - MANAGE_WEBHOOKS: boolean; - } - export class EvaluatedPermissions { - member: GuildMember; - raw: number; - hasPermission(permission: PermissionResolvable, explicit?: boolean): boolean; - hasPermissions(permission: PermissionResolvable[], explicit?: boolean): boolean; - serialize(): Permissions; - } - export class Role { - client: Client; - color: number; - createdAt: Date; createdTimestamp: number; - guild: Guild; - hexColor: string; - hoist: boolean; - id: string; - managed: boolean; - members: Collection; - mentionable: boolean; - name: string; - permissions: number; - position: number; - delete(): Promise; - edit(data: RoleData): Promise; - equals(role: Role): boolean; - hasPermission(permission: PermissionResolvable, explicit?: boolean): boolean; - hasPermissions(permissions: PermissionResolvable[], explicit?: boolean): boolean; - serialize(): Permissions; - setColor(color: string | number): Promise; - setHoist(hoist: boolean): Promise; - setName(name: string): Promise; - setPermissions(permissions: string[]): Promise; - setPosition(position: number): Promise; - toString(): string; - } - export class ClientVoiceManager { - client: Client; - connections: Collection; - pending: Collection; - joinChannel(channel: VoiceChannel): Promise; - sendVoiceStateUpdate(channel: VoiceChannel, options?: Object); - } - class AudioPlayer extends EventEmitter { - dispatcher: StreamDispatcher; - voiceConnection: VoiceConnection; - } - export class VoiceConnection extends EventEmitter { - authentication: Object; - channel: VoiceChannel; - player: AudioPlayer; - receivers: VoiceReceiver[]; - sockets: Object; - ssrcMap: Map; - voiceManager: ClientVoiceManager; - createReceiver(): VoiceReceiver; - disconnect(); - playConvertedStream(stream: ReadableStream, options?: StreamOptions): StreamDispatcher; - playFile(file: string, options?: StreamOptions): StreamDispatcher; - playStream(stream: ReadableStream, options?: StreamOptions): StreamDispatcher; - on(event: "disconnect", listener: (error: Error) => void): this; - on(event: "error", listener: (error: Error) => void): this; - on(event: "ready", listener: () => void): this; - on(event: "speaking", listener: (user: User, speaking: boolean) => void): this; - } - export class VoiceReceiver extends EventEmitter { - connection: VoiceConnection; - destroyed: boolean; - createOpusStream(user: User): ReadableStream; - createPCMStream(user: User): ReadableStream; - destroy(): void; - recreate(): void; - on(event: "opus", listener: (user: User, buffer: Buffer) => void): this; - on(event: "pcm", listener: (user: User, buffer: Buffer) => void): this; - on(event: "warn", listener: (message: string) => void): this; - } - export class Collection extends Map { - array(): value[]; - concat(...collections: Collection[]): Collection; - deleteAll(): Promise; - every(fn: Function, thisArg?: Object): boolean; - exists(prop: string, value: any): boolean; - filter(fn: Function, thisArg?: Object): Collection; - filterArray(fn: Function, thisArg?: Object): value[]; - find(propOrFn: string | Function, value?: any): value; - findAll(prop: string, value: any): value[]; - findKey(propOrFn: string | Function, value?: any): key; - first(): value; - firstKey(): key; - keyArray(): key[]; - last(): value; - lastKey(): key; - map(fn: Function, thisArg?: Object): any[]; - random(): value; - randomKey(): key; - reduce(fn: Function, startVal?: any): any; - some(fn: Function, thisArg?: Object): boolean; - } - type CollectorOptions = { time?: number; max?: number }; - type AwaitMessagesOptions = { time?: number; max?: number; errors?: string[]; }; - type Base64Resolvable = Buffer | string; - type CollectorFilterFunction = (message: Message, collector: MessageCollector) => boolean; - type FileResolvable = Buffer | string; - type GuildMemberResolvable = GuildMember | User; - type GuildResolvable = Guild; - type ChannelLogsQueryOptions = { limit?: number; before?: string; after?: string; around?: string }; - type ChannelResovalble = Channel | Guild | Message | string; - type InviteOptions = { temporary?: boolean; maxAge?: number; maxUses?: number; }; - type MessageOptions = { tts?: boolean; nonce?: string; disableEveryone?: boolean; split?: boolean | SplitOptions; }; - type PermissionOverwriteOptions = Permissions; - type PermissionResolvable = string | string[] | number[]; - type SplitOptions = { maxLength?: number; char?: string; prepend?: string; append?: string; }; - type StreamOptions = { seek?: number; volume?: number; passes?: number; }; - type StringResolvable = any[] | string | any; - type UserResolvable = User | string | Message | Guild | GuildMember; - type WebSocketOptions = { large_threshold?: number; compress?: boolean; }; - type ClientOptions = { - apiRequestMethod?: string; - shardId?: number; - shardCount?: number; - maxMessageCache?: number; - messageCacheLifetime?: number; - messageSweepInterval?: number; - fetchAllMembers?: boolean; - disableEveryone?: boolean; - restWsBridgeTimeout?: number; - ws?: WebSocketOptions; - }; - type WebhookMessageOptions = { - tts?: boolean; - disableEveryone?: boolean; - }; - type WebhookOptions = { - large_threshold?: number; - compress?: boolean; - }; - type RoleData = { - name?: string; - color?: number | string; - hoist?: boolean; - position?: number; - permissions?: string[]; - mentionable?: boolean; - }; -} diff --git a/typings/tsconfig.json b/typings/tsconfig.json deleted file mode 100644 index 8c9dfd770..000000000 --- a/typings/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "noImplicitAny": false, - "strictNullChecks": true, - "noEmit": true, - "forceConsistentCasingInFileNames": true - }, - "files": [ - "index.d.ts" - ] -} \ No newline at end of file From ea798eaaf3242728f5029befe3a276ca4be6da91 Mon Sep 17 00:00:00 2001 From: bdistin Date: Wed, 28 Dec 2016 17:27:02 -0600 Subject: [PATCH 232/248] Update Missing Permission Resolvables (#1035) --- src/client/ClientDataResolver.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 4c4596775..d38fb7c95 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -174,7 +174,9 @@ class ClientDataResolver { * "USE_VAD", // use voice activity detection * "CHANGE_NICKNAME", * "MANAGE_NICKNAMES", // change nicknames of others - * "MANAGE_ROLES_OR_PERMISSIONS" + * "MANAGE_ROLES_OR_PERMISSIONS", + * "MANAGE_WEBHOOKS", + * "MANAGE_EMOJIS" * ] * ``` * @typedef {string|number} PermissionResolvable From 7ede44bc00aeb449649160de21d4f991245cf070 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 29 Dec 2016 00:27:40 +0000 Subject: [PATCH 233/248] Add CloseEvent external --- src/client/websocket/WebSocketManager.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index 985105713..b67af5993 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -229,6 +229,11 @@ class WebSocketManager extends EventEmitter { this.sequence = -1; } + /** + * @external CloseEvent + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent} + */ + /** * Run whenever the connection to the gateway is closed, it will try to reconnect the client. * @param {CloseEvent} event The WebSocket close event From dfa6740b89f9aff753df4bf90142df7439f2368e Mon Sep 17 00:00:00 2001 From: Zack Campbell Date: Wed, 28 Dec 2016 18:52:15 -0600 Subject: [PATCH 234/248] Update gitmodules (#1036) To reflect transfer of ownership of the typings repo, in case the source forwarding does not continue indefinitely going forward --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 7a99b61b2..d5aa0ecce 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "typings"] path = typings - url = https://github.com/acdenisSK/discord.js-typings + url = https://github.com/zajrik/discord.js-typings From 17a61731ef5e4fc8d0cccc0896cc87473dd1f929 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 28 Dec 2016 20:40:07 -0500 Subject: [PATCH 235/248] Update typings (test) --- typings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings b/typings index 7523be032..9b503a119 160000 --- a/typings +++ b/typings @@ -1 +1 @@ -Subproject commit 7523be0329197a1d754c5237243d6adadc9e1c52 +Subproject commit 9b503a119c10c6873c8fd8cc65576f0992da5967 From a014b59790d34be54c2e6311a22c9913e220b178 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 28 Dec 2016 21:35:19 -0500 Subject: [PATCH 236/248] Update webpack --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 780c79f25..f9a466282 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "eslint": "^3.12.0", "parallel-webpack": "^1.6.0", "uglify-js": "mishoo/UglifyJS2#harmony", - "webpack": "2.2.0-rc.2" + "webpack": "2.2.0-rc.3" }, "engines": { "node": ">=6.0.0" From ed8fcf651a2e69715a0e827489acb03e2e5d24c7 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 28 Dec 2016 22:58:30 -0600 Subject: [PATCH 237/248] Centralise message sending logic in one method, remove sendTTSMessage, add client shortcut in RESTMethods (#1031) * start wip rewrite of sending/editing messages * pass the build, modify the edit method to fit the new system * simplify the applyToClass method * change handling of file options * add back message splitting * i couldn't help myself * add some smart message options * clean up, add sexy options * fix indentation * fix up splitting * add \b * add back old methods for hydar happiness * clean up more * move code handling to the rest method * clean up this.rest.client * Update TextBasedChannel.js * add docs back for the bad methods * fix reply in group dms * srsly gawdl3y * make code better * fix changes for gawdl3y * fix checking * remove getter * make code handling more robust * k * fix up sendEmbed docs * stupid * fix up more docs because aaaaa * no more pls --- src/client/rest/RESTMethods.js | 218 +++++++++---------- src/structures/GuildMember.js | 2 +- src/structures/Message.js | 25 +-- src/structures/TextChannel.js | 2 +- src/structures/User.js | 2 +- src/structures/interface/TextBasedChannel.js | 150 +++++++------ 6 files changed, 201 insertions(+), 198 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index d895922a7..02effb83a 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -2,6 +2,7 @@ const Constants = require('../../util/Constants'); const Collection = require('../../util/Collection'); const splitMessage = require('../../util/SplitMessage'); const parseEmoji = require('../../util/ParseEmoji'); +const escapeMarkdown = require('../../util/EscapeMarkdown'); const User = require('../../structures/User'); const GuildMember = require('../../structures/GuildMember'); @@ -15,13 +16,14 @@ const ClientOAuth2Application = require('../../structures/ClientOAuth2Applicatio class RESTMethods { constructor(restManager) { this.rest = restManager; + this.client = restManager.client; } - login(token = this.rest.client.token) { + login(token = this.client.token) { return new Promise((resolve, reject) => { if (typeof token !== 'string') throw new Error(Constants.Errors.INVALID_TOKEN); token = token.replace(/^Bot\s*/i, ''); - this.rest.client.manager.connectToWebSocket(token, resolve, reject); + this.client.manager.connectToWebSocket(token, resolve, reject); }); } @@ -31,8 +33,8 @@ class RESTMethods { getGateway() { return this.rest.makeRequest('get', Constants.Endpoints.gateway, true).then(res => { - this.rest.client.ws.gateway = `${res.url}/?v=${Constants.PROTOCOL_VERSION}`; - return this.rest.client.ws.gateway; + this.client.ws.gateway = `${res.url}/?v=${Constants.PROTOCOL_VERSION}`; + return this.client.ws.gateway; }); } @@ -40,63 +42,64 @@ class RESTMethods { return this.rest.makeRequest('get', Constants.Endpoints.botGateway, true); } - sendMessage(channel, content, { tts, nonce, embed, disableEveryone, split } = {}, file = null) { + sendMessage(channel, content, { tts, nonce, embed, disableEveryone, split, code } = {}, file = null) { return new Promise((resolve, reject) => { - if (typeof content !== 'undefined') content = this.rest.client.resolver.resolveString(content); + if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content); if (content) { - if (disableEveryone || (typeof disableEveryone === 'undefined' && this.rest.client.options.disableEveryone)) { + if (code) { + content = escapeMarkdown(this.client.resolver.resolveString(content), true); + content = `\`\`\`${typeof code !== 'undefined' && code !== null ? code : ''}\n${content}\n\`\`\``; + } + + if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) { content = content.replace(/@(everyone|here)/g, '@\u200b$1'); } if (split) content = splitMessage(content, typeof split === 'object' ? split : {}); } + const send = (chan) => { + if (content instanceof Array) { + const messages = []; + (function sendChunk(list, index) { + const options = index === list.length ? { tts, embed } : { tts }; + chan.send(list[index], options, index === list.length ? file : null).then((message) => { + messages.push(message); + if (index >= list.length) return resolve(messages); + return sendChunk(list, ++index); + }); + }(content, 0)); + } else { + this.rest.makeRequest('post', Constants.Endpoints.channelMessages(chan.id), true, { + content, tts, nonce, embed, + }, file).then(data => resolve(this.client.actions.MessageCreate.handle(data).message), reject); + } + }; + if (channel instanceof User || channel instanceof GuildMember) { - this.createDM(channel).then(chan => { - this._sendMessageRequest(chan, content, file, tts, nonce, embed, resolve, reject); - }, reject); + this.createDM(channel).then(send, reject); } else { - this._sendMessageRequest(channel, content, file, tts, nonce, embed, resolve, reject); + send(channel); } }); } - _sendMessageRequest(channel, content, file, tts, nonce, embed, resolve, reject) { - if (content instanceof Array) { - const datas = []; - let promise = this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, { - content: content[0], tts, nonce, - }, file).catch(reject); - - for (let i = 1; i <= content.length; i++) { - if (i < content.length) { - const i2 = i; - promise = promise.then(data => { - datas.push(data); - return this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, { - content: content[i2], tts, nonce, embed, - }, file); - }, reject); - } else { - promise.then(data => { - datas.push(data); - resolve(this.rest.client.actions.MessageCreate.handle(datas).messages); - }, reject); - } - } - } else { - this.rest.makeRequest('post', Constants.Endpoints.channelMessages(channel.id), true, { - content, tts, nonce, embed, - }, file) - .then(data => resolve(this.rest.client.actions.MessageCreate.handle(data).message), reject); + updateMessage(message, content, { embed, code } = {}) { + content = this.client.resolver.resolveString(content); + if (code) { + content = escapeMarkdown(this.client.resolver.resolveString(content), true); + content = `\`\`\`${typeof code !== 'undefined' && code !== null ? code : ''}\n${content}\n\`\`\``; } + return this.rest.makeRequest('patch', Constants.Endpoints.channelMessage(message.channel.id, message.id), true, { + content, embed, + }).then(data => this.client.actions.MessageUpdate.handle(data).updated); } deleteMessage(message) { return this.rest.makeRequest('del', Constants.Endpoints.channelMessage(message.channel.id, message.id), true) .then(() => - this.rest.client.actions.MessageDelete.handle({ + this.client.actions.MessageDelete.handle({ id: message.id, channel_id: message.channel.id, }).message @@ -107,39 +110,32 @@ class RESTMethods { return this.rest.makeRequest('post', `${Constants.Endpoints.channelMessages(channel.id)}/bulk_delete`, true, { messages, }).then(() => - this.rest.client.actions.MessageDeleteBulk.handle({ + this.client.actions.MessageDeleteBulk.handle({ channel_id: channel.id, ids: messages, }).messages ); } - updateMessage(message, content, { embed } = {}) { - content = this.rest.client.resolver.resolveString(content); - return this.rest.makeRequest('patch', Constants.Endpoints.channelMessage(message.channel.id, message.id), true, { - content, embed, - }).then(data => this.rest.client.actions.MessageUpdate.handle(data).updated); - } - createChannel(guild, channelName, channelType, overwrites) { if (overwrites instanceof Collection) overwrites = overwrites.array(); return this.rest.makeRequest('post', Constants.Endpoints.guildChannels(guild.id), true, { name: channelName, type: channelType, permission_overwrites: overwrites, - }).then(data => this.rest.client.actions.ChannelCreate.handle(data).channel); + }).then(data => this.client.actions.ChannelCreate.handle(data).channel); } createDM(recipient) { const dmChannel = this.getExistingDM(recipient); if (dmChannel) return Promise.resolve(dmChannel); - return this.rest.makeRequest('post', Constants.Endpoints.userChannels(this.rest.client.user.id), true, { + return this.rest.makeRequest('post', Constants.Endpoints.userChannels(this.client.user.id), true, { recipient_id: recipient.id, - }).then(data => this.rest.client.actions.ChannelCreate.handle(data).channel); + }).then(data => this.client.actions.ChannelCreate.handle(data).channel); } getExistingDM(recipient) { - return this.rest.client.channels.find(channel => + return this.client.channels.find(channel => channel.recipient && channel.recipient.id === recipient.id ); } @@ -149,7 +145,7 @@ class RESTMethods { if (!channel) return Promise.reject(new Error('No channel to delete.')); return this.rest.makeRequest('del', Constants.Endpoints.channel(channel.id), true).then(data => { data.id = channel.id; - return this.rest.client.actions.ChannelDelete.handle(data).channel; + return this.client.actions.ChannelDelete.handle(data).channel; }); } @@ -161,38 +157,38 @@ class RESTMethods { data.bitrate = _data.bitrate || channel.bitrate; data.user_limit = _data.userLimit || channel.userLimit; return this.rest.makeRequest('patch', Constants.Endpoints.channel(channel.id), true, data).then(newData => - this.rest.client.actions.ChannelUpdate.handle(newData).updated + this.client.actions.ChannelUpdate.handle(newData).updated ); } leaveGuild(guild) { - if (guild.ownerID === this.rest.client.user.id) return Promise.reject(new Error('Guild is owned by the client.')); + if (guild.ownerID === this.client.user.id) return Promise.reject(new Error('Guild is owned by the client.')); return this.rest.makeRequest('del', Constants.Endpoints.meGuild(guild.id), true).then(() => - this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild + this.client.actions.GuildDelete.handle({ id: guild.id }).guild ); } createGuild(options) { - options.icon = this.rest.client.resolver.resolveBase64(options.icon) || null; + options.icon = this.client.resolver.resolveBase64(options.icon) || null; options.region = options.region || 'us-central'; return new Promise((resolve, reject) => { this.rest.makeRequest('post', Constants.Endpoints.guilds, true, options).then(data => { - if (this.rest.client.guilds.has(data.id)) { - resolve(this.rest.client.guilds.get(data.id)); + if (this.client.guilds.has(data.id)) { + resolve(this.client.guilds.get(data.id)); return; } const handleGuild = guild => { if (guild.id === data.id) { - this.rest.client.removeListener('guildCreate', handleGuild); - this.rest.client.clearTimeout(timeout); + this.client.removeListener('guildCreate', handleGuild); + this.client.clearTimeout(timeout); resolve(guild); } }; - this.rest.client.on('guildCreate', handleGuild); + this.client.on('guildCreate', handleGuild); - const timeout = this.rest.client.setTimeout(() => { - this.rest.client.removeListener('guildCreate', handleGuild); + const timeout = this.client.setTimeout(() => { + this.client.removeListener('guildCreate', handleGuild); reject(new Error('Took too long to receive guild data.')); }, 10000); }, reject); @@ -202,28 +198,28 @@ class RESTMethods { // untested but probably will work deleteGuild(guild) { return this.rest.makeRequest('del', Constants.Endpoints.guild(guild.id), true).then(() => - this.rest.client.actions.GuildDelete.handle({ id: guild.id }).guild + this.client.actions.GuildDelete.handle({ id: guild.id }).guild ); } getUser(userID) { return this.rest.makeRequest('get', Constants.Endpoints.user(userID), true).then(data => - this.rest.client.actions.UserGet.handle(data).user + this.client.actions.UserGet.handle(data).user ); } updateCurrentUser(_data, password) { - const user = this.rest.client.user; + const user = this.client.user; const data = {}; data.username = _data.username || user.username; - data.avatar = this.rest.client.resolver.resolveBase64(_data.avatar) || user.avatar; + data.avatar = this.client.resolver.resolveBase64(_data.avatar) || user.avatar; if (!user.bot) { data.email = _data.email || user.email; data.password = password; if (_data.new_password) data.new_password = _data.newPassword; } return this.rest.makeRequest('patch', Constants.Endpoints.me, true, data).then(newData => - this.rest.client.actions.UserUpdate.handle(newData).updated + this.client.actions.UserUpdate.handle(newData).updated ); } @@ -232,19 +228,19 @@ class RESTMethods { if (_data.name) data.name = _data.name; if (_data.region) data.region = _data.region; if (_data.verificationLevel) data.verification_level = Number(_data.verificationLevel); - if (_data.afkChannel) data.afk_channel_id = this.rest.client.resolver.resolveChannel(_data.afkChannel).id; + if (_data.afkChannel) data.afk_channel_id = this.client.resolver.resolveChannel(_data.afkChannel).id; if (_data.afkTimeout) data.afk_timeout = Number(_data.afkTimeout); - if (_data.icon) data.icon = this.rest.client.resolver.resolveBase64(_data.icon); - if (_data.owner) data.owner_id = this.rest.client.resolver.resolveUser(_data.owner).id; - if (_data.splash) data.splash = this.rest.client.resolver.resolveBase64(_data.splash); + if (_data.icon) data.icon = this.client.resolver.resolveBase64(_data.icon); + if (_data.owner) data.owner_id = this.client.resolver.resolveUser(_data.owner).id; + if (_data.splash) data.splash = this.client.resolver.resolveBase64(_data.splash); return this.rest.makeRequest('patch', Constants.Endpoints.guild(guild.id), true, data).then(newData => - this.rest.client.actions.GuildUpdate.handle(newData).updated + this.client.actions.GuildUpdate.handle(newData).updated ); } kickGuildMember(guild, member) { return this.rest.makeRequest('del', Constants.Endpoints.guildMember(guild.id, member.id), true).then(() => - this.rest.client.actions.GuildMemberRemove.handle({ + this.client.actions.GuildMemberRemove.handle({ guild_id: guild.id, user: member.user, }).member @@ -253,7 +249,7 @@ class RESTMethods { createGuildRole(guild) { return this.rest.makeRequest('post', Constants.Endpoints.guildRoles(guild.id), true).then(role => - this.rest.client.actions.GuildRoleCreate.handle({ + this.client.actions.GuildRoleCreate.handle({ guild_id: guild.id, role, }).role @@ -262,7 +258,7 @@ class RESTMethods { deleteGuildRole(role) { return this.rest.makeRequest('del', Constants.Endpoints.guildRole(role.guild.id, role.id), true).then(() => - this.rest.client.actions.GuildRoleDelete.handle({ + this.client.actions.GuildRoleDelete.handle({ guild_id: role.guild.id, role_id: role.id, }).role @@ -301,17 +297,17 @@ class RESTMethods { getGuildMember(guild, user) { return this.rest.makeRequest('get', Constants.Endpoints.guildMember(guild.id, user.id), true).then(data => - this.rest.client.actions.GuildMemberGet.handle(guild, data).member + this.client.actions.GuildMemberGet.handle(guild, data).member ); } updateGuildMember(member, data) { - if (data.channel) data.channel_id = this.rest.client.resolver.resolveChannel(data.channel).id; + if (data.channel) data.channel_id = this.client.resolver.resolveChannel(data.channel).id; if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role); let endpoint = Constants.Endpoints.guildMember(member.guild.id, member.id); // fix your endpoints, discord ;-; - if (member.id === this.rest.client.user.id) { + if (member.id === this.client.user.id) { const keys = Object.keys(data); if (keys.length === 1 && keys[0] === 'nick') { endpoint = Constants.Endpoints.guildMemberNickname(member.guild.id); @@ -348,7 +344,7 @@ class RESTMethods { } banGuildMember(guild, member, deleteDays = 0) { - const id = this.rest.client.resolver.resolveUserID(member); + const id = this.client.resolver.resolveUserID(member); if (!id) return Promise.reject(new Error('Couldn\'t resolve the user ID to ban.')); return this.rest.makeRequest( 'put', `${Constants.Endpoints.guildBans(guild.id)}/${id}?delete-message-days=${deleteDays}`, true, { @@ -356,9 +352,9 @@ class RESTMethods { } ).then(() => { if (member instanceof GuildMember) return member; - const user = this.rest.client.resolver.resolveUser(id); + const user = this.client.resolver.resolveUser(id); if (user) { - member = this.rest.client.resolver.resolveGuildMember(guild, user); + member = this.client.resolver.resolveGuildMember(guild, user); return member || user; } return id; @@ -367,26 +363,26 @@ class RESTMethods { unbanGuildMember(guild, member) { return new Promise((resolve, reject) => { - const id = this.rest.client.resolver.resolveUserID(member); + const id = this.client.resolver.resolveUserID(member); if (!id) throw new Error('Couldn\'t resolve the user ID to unban.'); const listener = (eGuild, eUser) => { if (eGuild.id === guild.id && eUser.id === id) { - this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); - this.rest.client.clearTimeout(timeout); + this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); + this.client.clearTimeout(timeout); resolve(eUser); } }; - this.rest.client.on(Constants.Events.GUILD_BAN_REMOVE, listener); + this.client.on(Constants.Events.GUILD_BAN_REMOVE, listener); - const timeout = this.rest.client.setTimeout(() => { - this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); + const timeout = this.client.setTimeout(() => { + this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); reject(new Error('Took too long to receive the ban remove event.')); }, 10000); this.rest.makeRequest('del', `${Constants.Endpoints.guildBans(guild.id)}/${id}`, true).catch(err => { - this.rest.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); - this.rest.client.clearTimeout(timeout); + this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); + this.client.clearTimeout(timeout); reject(err); }); }); @@ -396,7 +392,7 @@ class RESTMethods { return this.rest.makeRequest('get', Constants.Endpoints.guildBans(guild.id), true).then(banItems => { const bannedUsers = new Collection(); for (const banItem of banItems) { - const user = this.rest.client.dataManager.newUser(banItem.user); + const user = this.client.dataManager.newUser(banItem.user); bannedUsers.set(user.id, user); } return bannedUsers; @@ -428,7 +424,7 @@ class RESTMethods { return this.rest.makeRequest( 'patch', Constants.Endpoints.guildRole(role.guild.id, role.id), true, data ).then(_role => - this.rest.client.actions.GuildRoleUpdate.handle({ + this.client.actions.GuildRoleUpdate.handle({ role: _role, guild_id: role.guild.id, }).updated @@ -455,7 +451,7 @@ class RESTMethods { payload.max_age = options.maxAge; payload.max_uses = options.maxUses; return this.rest.makeRequest('post', `${Constants.Endpoints.channelInvites(channel.id)}`, true, payload) - .then(invite => new Invite(this.rest.client, invite)); + .then(invite => new Invite(this.client, invite)); } deleteInvite(invite) { @@ -464,7 +460,7 @@ class RESTMethods { getInvite(code) { return this.rest.makeRequest('get', Constants.Endpoints.invite(code), true).then(invite => - new Invite(this.rest.client, invite) + new Invite(this.client, invite) ); } @@ -472,7 +468,7 @@ class RESTMethods { return this.rest.makeRequest('get', Constants.Endpoints.guildInvites(guild.id), true).then(inviteItems => { const invites = new Collection(); for (const inviteItem of inviteItems) { - const invite = new Invite(this.rest.client, inviteItem); + const invite = new Invite(this.client, inviteItem); invites.set(invite.code, invite); } return invites; @@ -486,24 +482,24 @@ class RESTMethods { createEmoji(guild, image, name) { return this.rest.makeRequest('post', `${Constants.Endpoints.guildEmojis(guild.id)}`, true, { name, image }) - .then(data => this.rest.client.actions.EmojiCreate.handle(data, guild).emoji); + .then(data => this.client.actions.EmojiCreate.handle(data, guild).emoji); } deleteEmoji(emoji) { return this.rest.makeRequest('delete', `${Constants.Endpoints.guildEmojis(emoji.guild.id)}/${emoji.id}`, true) - .then(() => this.rest.client.actions.EmojiDelete.handle(emoji).data); + .then(() => this.client.actions.EmojiDelete.handle(emoji).data); } getWebhook(id, token) { return this.rest.makeRequest('get', Constants.Endpoints.webhook(id, token), !token).then(data => - new Webhook(this.rest.client, data) + new Webhook(this.client, data) ); } getGuildWebhooks(guild) { return this.rest.makeRequest('get', Constants.Endpoints.guildWebhooks(guild.id), true).then(data => { const hooks = new Collection(); - for (const hook of data) hooks.set(hook.id, new Webhook(this.rest.client, hook)); + for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook)); return hooks; }); } @@ -511,14 +507,14 @@ class RESTMethods { getChannelWebhooks(channel) { return this.rest.makeRequest('get', Constants.Endpoints.channelWebhooks(channel.id), true).then(data => { const hooks = new Collection(); - for (const hook of data) hooks.set(hook.id, new Webhook(this.rest.client, hook)); + for (const hook of data) hooks.set(hook.id, new Webhook(this.client, hook)); return hooks; }); } createWebhook(channel, name, avatar) { return this.rest.makeRequest('post', Constants.Endpoints.channelWebhooks(channel.id), true, { name, avatar }) - .then(data => new Webhook(this.rest.client, data)); + .then(data => new Webhook(this.client, data)); } editWebhook(webhook, name, avatar) { @@ -537,9 +533,9 @@ class RESTMethods { } sendWebhookMessage(webhook, content, { avatarURL, tts, disableEveryone, embeds } = {}, file = null) { - if (typeof content !== 'undefined') content = this.rest.client.resolver.resolveString(content); + if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content); if (content) { - if (disableEveryone || (typeof disableEveryone === 'undefined' && this.rest.client.options.disableEveryone)) { + if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) { content = content.replace(/@(everyone|here)/g, '@\u200b$1'); } } @@ -570,7 +566,7 @@ class RESTMethods { return this.rest.makeRequest( 'get', Constants.Endpoints.meMentions(options.limit, options.roles, options.everyone, options.guild) - ).then(res => res.body.map(m => new Message(this.rest.client.channels.get(m.channel_id), m, this.rest.client))); + ).then(res => res.body.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client))); } addFriend(user) { @@ -597,7 +593,7 @@ class RESTMethods { setRolePositions(guildID, roles) { return this.rest.makeRequest('patch', Constants.Endpoints.guildRoles(guildID), true, roles).then(() => - this.rest.client.actions.GuildRolesPositionUpdate.handle({ + this.client.actions.GuildRolesPositionUpdate.handle({ guild_id: guildID, roles, }).guild @@ -608,8 +604,8 @@ class RESTMethods { 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, + this.client.actions.MessageReactionAdd.handle({ + user_id: this.client.user.id, message_id: message.id, emoji: parseEmoji(emoji), channel_id: message.channel.id, @@ -619,11 +615,11 @@ class RESTMethods { removeMessageReaction(message, emoji, user) { let endpoint = Constants.Endpoints.selfMessageReaction(message.channel.id, message.id, emoji); - if (user.id !== this.rest.client.user.id) { + if (user.id !== this.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({ + this.client.actions.MessageReactionRemove.handle({ user_id: user.id, message_id: message.id, emoji: parseEmoji(emoji), @@ -645,7 +641,7 @@ class RESTMethods { getMyApplication() { return this.rest.makeRequest('get', Constants.Endpoints.myApplication, true).then(app => - new ClientOAuth2Application(this.rest.client, app) + new ClientOAuth2Application(this.client, app) ); } diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 87c78b68f..60a498a3e 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -430,8 +430,8 @@ class GuildMember { } // These are here only for documentation purposes - they are implemented by TextBasedChannel + send() { return; } sendMessage() { return; } - sendTTSMessage() { return; } sendEmbed() { return; } sendFile() { return; } sendCode() { return; } diff --git a/src/structures/Message.js b/src/structures/Message.js index dbd758543..ab6a82899 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -369,12 +369,13 @@ class Message { * Options that can be passed into editMessage * @typedef {Object} MessageEditOptions * @property {Object} [embed] An embed to be added/edited + * @property {string} [code] Language for optional codeblock formatting to apply */ /** * Edit the content of the message - * @param {StringResolvable} content The new content for the message - * @param {MessageEditOptions} [options={}] The options to provide + * @param {StringResolvable} [content] The new content for the message + * @param {MessageEditOptions} [options] The options to provide * @returns {Promise} * @example * // update the content of a message @@ -382,7 +383,13 @@ class Message { * .then(msg => console.log(`Updated the content of a message from ${msg.author}`)) * .catch(console.error); */ - edit(content, options = {}) { + edit(content, options) { + if (!options && typeof content === 'object') { + options = content; + content = ''; + } else if (!options) { + options = {}; + } return this.client.rest.methods.updateMessage(this, content, options); } @@ -467,16 +474,8 @@ class Message { * .catch(console.error); */ reply(content, options = {}) { - content = this.client.resolver.resolveString(content); - const prepend = this.guild ? `${this.author}, ` : ''; - content = `${prepend}${content}`; - - if (options.split) { - if (typeof options.split !== 'object') options.split = {}; - if (!options.split.prepend) options.split.prepend = prepend; - } - - return this.client.rest.methods.sendMessage(this.channel, content, options); + content = `${this.guild || this.channel.type === 'group' ? `${this.author}, ` : ''}${content}`; + return this.channel.send(content, options); } /** diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 449da6759..9697abd82 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -73,8 +73,8 @@ class TextChannel extends GuildChannel { } // These are here only for documentation purposes - they are implemented by TextBasedChannel + send() { return; } sendMessage() { return; } - sendTTSMessage() { return; } sendEmbed() { return; } sendFile() { return; } sendCode() { return; } diff --git a/src/structures/User.js b/src/structures/User.js index 9b3155ebd..993c8e192 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -265,8 +265,8 @@ class User { } // These are here only for documentation purposes - they are implemented by TextBasedChannel + send() { return; } sendMessage() { return; } - sendTTSMessage() { return; } sendEmbed() { return; } sendFile() { return; } sendCode() { return; } diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index 308545f05..61be68d0c 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -2,8 +2,7 @@ const path = require('path'); const Message = require('../Message'); const MessageCollector = require('../MessageCollector'); const Collection = require('../../util/Collection'); -const RichEmbed = require('../RichEmbed'); -const escapeMarkdown = require('../../util/EscapeMarkdown'); + /** * Interface for classes that have text-channel-like features @@ -25,7 +24,7 @@ class TextBasedChannel { } /** - * Options that can be passed into sendMessage, sendTTSMessage, sendFile, sendCode, or Message.reply + * Options that can be passed into send, sendMessage, sendFile, sendEmbed, sendCode, and Message#reply * @typedef {Object} MessageOptions * @property {boolean} [tts=false] Whether or not the message should be spoken aloud * @property {string} [nonce=''] The nonce for the message @@ -33,10 +32,18 @@ class TextBasedChannel { * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details) * @property {boolean} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here * should be replaced with plain-text + * @property {FileOptions|string} [file] A file to send with the message + * @property {string} [code] Language for optional codeblock formatting to apply * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if * it exceeds the character limit. If an object is provided, these are the options for splitting the message. */ + /** + * @typedef {Object} FileOptions + * @property {BufferResolvable} attachment + * @property {string} [name='file.jpg'] + */ + /** * Options for splitting a message * @typedef {Object} SplitOptions @@ -46,6 +53,45 @@ class TextBasedChannel { * @property {string} [append=''] Text to append to every piece except the last */ + /** + * Send a message to this channel + * @param {StringResolvable} [content] The content to send + * @param {MessageOptions} [options={}] The options to provide + * @returns {Promise} + * @example + * // send a message + * channel.send('hello!') + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); + */ + send(content, options) { + if (!options && typeof content === 'object') { + options = content; + content = ''; + } else if (!options) { + options = {}; + } + if (options.file) { + if (typeof options.file === 'string') options.file = { attachment: options.file }; + if (!options.file.name) { + if (typeof options.file.attachment === 'string') { + options.file.name = path.basename(options.file.attachment); + } else if (options.file.attachment && options.file.attachment.path) { + options.file.name = path.basename(options.file.attachment.path); + } else { + options.file.name = 'file.jpg'; + } + } + return this.client.resolver.resolveBuffer(options.file.attachment).then(file => + this.client.rest.methods.sendMessage(this, content, options, { + file, + name: options.file.name, + }) + ); + } + return this.client.rest.methods.sendMessage(this, content, options); + } + /** * Send a message to this channel * @param {StringResolvable} content The content to send @@ -57,88 +103,48 @@ class TextBasedChannel { * .then(message => console.log(`Sent message: ${message.content}`)) * .catch(console.error); */ - sendMessage(content, options = {}) { - return this.client.rest.methods.sendMessage(this, content, options); - } - - /** - * Send a text-to-speech message to this channel - * @param {StringResolvable} content The content to send - * @param {MessageOptions} [options={}] The options to provide - * @returns {Promise} - * @example - * // send a TTS message - * channel.sendTTSMessage('hello!') - * .then(message => console.log(`Sent tts message: ${message.content}`)) - * .catch(console.error); - */ - sendTTSMessage(content, options = {}) { - Object.assign(options, { tts: true }); - return this.client.rest.methods.sendMessage(this, content, options); + sendMessage(content, options) { + return this.send(content, options); } /** * Send an embed to this channel * @param {RichEmbed|Object} embed The embed to send - * @param {string|MessageOptions} contentOrOptions Content to send or message options - * @param {MessageOptions} options If contentOrOptions is content, this will be options + * @param {string} [content] Content to send + * @param {MessageOptions} [options] The options to provide * @returns {Promise} */ - sendEmbed(embed, contentOrOptions, options = {}) { - if (!(embed instanceof RichEmbed)) embed = new RichEmbed(embed); - let content; - if (contentOrOptions) { - if (typeof contentOrOptions === 'string') { - content = contentOrOptions; - } else { - options = contentOrOptions; - } + sendEmbed(embed, content, options) { + if (!options && typeof content === 'object') { + options = content; + content = ''; + } else if (!options) { + options = {}; } - options.embed = embed; - return this.sendMessage(content, options); + return this.send(content, Object.assign(options, { embed })); } /** * Send a file to this channel * @param {BufferResolvable} attachment The file to send - * @param {string} [fileName="file.jpg"] The name and extension of the file + * @param {string} [name='file.jpg'] The name and extension of the file * @param {StringResolvable} [content] Text message to send with the attachment * @param {MessageOptions} [options] The options to provide * @returns {Promise} */ - sendFile(attachment, fileName, content, options = {}) { - if (!fileName) { - if (typeof attachment === 'string') { - fileName = path.basename(attachment); - } else if (attachment && attachment.path) { - fileName = path.basename(attachment.path); - } else { - fileName = 'file.jpg'; - } - } - return this.client.resolver.resolveBuffer(attachment).then(file => - this.client.rest.methods.sendMessage(this, content, options, { - file, - name: fileName, - }) - ); + sendFile(attachment, name, content, options = {}) { + return this.send(content, Object.assign(options, { file: { attachment, name } })); } /** * Send a code block to this channel * @param {string} lang Language for the code block * @param {StringResolvable} content Content of the code block - * @param {MessageOptions} options The options to provide + * @param {MessageOptions} [options] The options to provide * @returns {Promise} */ sendCode(lang, content, options = {}) { - if (options.split) { - if (typeof options.split !== 'object') options.split = {}; - if (!options.split.prepend) options.split.prepend = `\`\`\`${lang || ''}\n`; - if (!options.split.append) options.split.append = '\n```'; - } - content = escapeMarkdown(this.client.resolver.resolveString(content), true); - return this.sendMessage(`\`\`\`${lang || ''}\n${content}\n\`\`\``, options); + return this.send(content, Object.assign(options, { code: lang })); } /** @@ -349,19 +355,21 @@ class TextBasedChannel { } exports.applyToClass = (structure, full = false) => { - const props = ['sendMessage', 'sendTTSMessage', 'sendEmbed', 'sendFile', 'sendCode']; + const props = ['send', 'sendMessage', 'sendEmbed', 'sendFile', 'sendCode']; if (full) { - props.push('_cacheMessage'); - props.push('fetchMessages'); - props.push('fetchMessage'); - props.push('bulkDelete'); - props.push('startTyping'); - props.push('stopTyping'); - props.push('typing'); - props.push('typingCount'); - props.push('fetchPinnedMessages'); - props.push('createCollector'); - props.push('awaitMessages'); + props.push( + '_cacheMessage', + 'fetchMessages', + 'fetchMessage', + 'bulkDelete', + 'startTyping', + 'stopTyping', + 'typing', + 'typingCount', + 'fetchPinnedMessages', + 'createCollector', + 'awaitMessages' + ); } for (const prop of props) { Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop)); From fe7ed523b3ae8f285249acf93830698188922adc Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 29 Dec 2016 03:01:28 -0500 Subject: [PATCH 238/248] Improve ClientUser presence method docs --- src/structures/ClientUser.js | 128 ++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 55 deletions(-) diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 66b9504e9..4db1eeb1e 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -119,8 +119,74 @@ class ClientUser extends User { } /** - * Set the status of the logged in user. - * @param {string} status can be `online`, `idle`, `invisible` or `dnd` (do not disturb) + * Data resembling a raw Discord presence + * @property {PresenceStatus} [status] Status of the user + * @property {boolean} [afk] Whether the user is AFK + * @property {Object} [game] Game the user is playing + * @property {string} [game.name] Name of the game + * @property {string} [game.url] Twitch stream URL + */ + + /** + * Sets the full presence of the client user. + * @param {PresenceData} data Data for the presence + * @returns {Promise} + */ + setPresence(data) { + // {"op":3,"d":{"status":"dnd","since":0,"game":null,"afk":false}} + return new Promise(resolve => { + let status = this.localPresence.status || this.presence.status; + let game = this.localPresence.game; + let afk = this.localPresence.afk || this.presence.afk; + + if (!game && this.presence.game) { + game = { + name: this.presence.game.name, + type: this.presence.game.type, + url: this.presence.game.url, + }; + } + + if (data.status) { + if (typeof data.status !== 'string') throw new TypeError('Status must be a string'); + status = data.status; + } + + if (data.game) { + game = data.game; + if (game.url) game.type = 1; + } + + if (typeof data.afk !== 'undefined') afk = data.afk; + afk = Boolean(afk); + + this.localPresence = { status, game, afk }; + this.localPresence.since = 0; + this.localPresence.game = this.localPresence.game || null; + + this.client.ws.send({ + op: 3, + d: this.localPresence, + }); + + this.client._setPresence(this.id, this.localPresence); + + resolve(this); + }); + } + + /** + * A user's status. Must be one of: + * - `online` + * - `idle` + * - `invisible` + * - `dnd` (do not disturb) + * @typedef {string} PresenceStatus + */ + + /** + * Sets the status of the client user. + * @param {PresenceStatus} status Status to change to * @returns {Promise} */ setStatus(status) { @@ -128,9 +194,9 @@ class ClientUser extends User { } /** - * Set the current game of the logged in user. - * @param {string} game the game being played - * @param {string} [streamingURL] an optional URL to a twitch stream, if one is available. + * Sets the game the client user is playing. + * @param {string} game Game being played + * @param {string} [streamingURL] Twitch stream URL * @returns {Promise} */ setGame(game, streamingURL) { @@ -141,8 +207,8 @@ class ClientUser extends User { } /** - * Set/remove the AFK flag for the current user. - * @param {boolean} afk whether or not the user is AFK. + * Sets/removes the AFK flag for the client user. + * @param {boolean} afk Whether or not the user is AFK * @returns {Promise} */ setAFK(afk) { @@ -202,54 +268,6 @@ class ClientUser extends User { ); } } - - /** - * Set the full presence of the current user. - * @param {Object} data the data to provide - * @returns {Promise} - */ - setPresence(data) { - // {"op":3,"d":{"status":"dnd","since":0,"game":null,"afk":false}} - return new Promise(resolve => { - let status = this.localPresence.status || this.presence.status; - let game = this.localPresence.game; - let afk = this.localPresence.afk || this.presence.afk; - - if (!game && this.presence.game) { - game = { - name: this.presence.game.name, - type: this.presence.game.type, - url: this.presence.game.url, - }; - } - - if (data.status) { - if (typeof data.status !== 'string') throw new TypeError('Status must be a string'); - status = data.status; - } - - if (data.game) { - game = data.game; - if (game.url) game.type = 1; - } - - if (typeof data.afk !== 'undefined') afk = data.afk; - afk = Boolean(afk); - - this.localPresence = { status, game, afk }; - this.localPresence.since = 0; - this.localPresence.game = this.localPresence.game || null; - - this.client.ws.send({ - op: 3, - d: this.localPresence, - }); - - this.client._setPresence(this.id, this.localPresence); - - resolve(this); - }); - } } module.exports = ClientUser; From 1b76333b8bba488104fdc74d95b6c624f3c5d34a Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 29 Dec 2016 03:04:11 -0500 Subject: [PATCH 239/248] Add missing @typedef line --- src/structures/ClientUser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 4db1eeb1e..d526af6a7 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -120,6 +120,7 @@ class ClientUser extends User { /** * Data resembling a raw Discord presence + * @typedef {Object} PresenceData * @property {PresenceStatus} [status] Status of the user * @property {boolean} [afk] Whether the user is AFK * @property {Object} [game] Game the user is playing From 95790dcf08a97175ed88a6736e19a96a94f4a4ea Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 29 Dec 2016 03:21:22 -0500 Subject: [PATCH 240/248] Update User#equals doc --- src/structures/User.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/User.js b/src/structures/User.js index 993c8e192..f7148289a 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -237,7 +237,7 @@ class User { } /** - * Checks if the user is equal to another. It compares username, ID, discriminator, status and the game being played. + * Checks if the user is equal to another. It compares ID, username, discriminator, avatar, and bot flags. * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties. * @param {User} user User to compare with * @returns {boolean} From 6d7293e3c5c53c279758d66c31e20cc1272ab79f Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 29 Dec 2016 14:00:54 +0000 Subject: [PATCH 241/248] Fix sending an array of messages --- src/structures/interface/TextBasedChannel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index 61be68d0c..536131332 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -65,7 +65,7 @@ class TextBasedChannel { * .catch(console.error); */ send(content, options) { - if (!options && typeof content === 'object') { + if (!options && typeof content === 'object' && !(content instanceof Array)) { options = content; content = ''; } else if (!options) { From 75d45bd587c1d68039c999d12bc928f3df696f34 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 29 Dec 2016 14:18:39 +0000 Subject: [PATCH 242/248] Fix sending codeblocks without a lang --- src/client/rest/RESTMethods.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 02effb83a..d5d82e4f5 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -47,7 +47,7 @@ class RESTMethods { if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content); if (content) { - if (code) { + if (typeof code === 'string') { content = escapeMarkdown(this.client.resolver.resolveString(content), true); content = `\`\`\`${typeof code !== 'undefined' && code !== null ? code : ''}\n${content}\n\`\`\``; } From 650acbf6624aa5f6c7da8b3feefefc744f536747 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 29 Dec 2016 10:44:24 -0500 Subject: [PATCH 243/248] Improve codeblock lang --- src/client/rest/RESTMethods.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index d5d82e4f5..36d316d04 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -47,9 +47,9 @@ class RESTMethods { if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content); if (content) { - if (typeof code === 'string') { + if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) { content = escapeMarkdown(this.client.resolver.resolveString(content), true); - content = `\`\`\`${typeof code !== 'undefined' && code !== null ? code : ''}\n${content}\n\`\`\``; + content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``; } if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) { @@ -59,7 +59,7 @@ class RESTMethods { if (split) content = splitMessage(content, typeof split === 'object' ? split : {}); } - const send = (chan) => { + const send = chan => { if (content instanceof Array) { const messages = []; (function sendChunk(list, index) { From 86ffc86324761b8d9a59a70c02fc59371d02d742 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 29 Dec 2016 10:49:59 -0500 Subject: [PATCH 244/248] Update updateMessage with the new code logic --- src/client/rest/RESTMethods.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 36d316d04..70062482e 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -87,9 +87,9 @@ class RESTMethods { updateMessage(message, content, { embed, code } = {}) { content = this.client.resolver.resolveString(content); - if (code) { + if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) { content = escapeMarkdown(this.client.resolver.resolveString(content), true); - content = `\`\`\`${typeof code !== 'undefined' && code !== null ? code : ''}\n${content}\n\`\`\``; + content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``; } return this.rest.makeRequest('patch', Constants.Endpoints.channelMessage(message.channel.id, message.id), true, { content, embed, From ba77e69edf76fa8be48b2f26cbe0a78e81a25647 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 29 Dec 2016 10:51:05 -0500 Subject: [PATCH 245/248] Document boolean for code option --- src/structures/Message.js | 2 +- src/structures/interface/TextBasedChannel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index ab6a82899..7fcc5b4b8 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -369,7 +369,7 @@ class Message { * Options that can be passed into editMessage * @typedef {Object} MessageEditOptions * @property {Object} [embed] An embed to be added/edited - * @property {string} [code] Language for optional codeblock formatting to apply + * @property {string|boolean} [code] Language for optional codeblock formatting to apply */ /** diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index 536131332..7d078816e 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -33,7 +33,7 @@ class TextBasedChannel { * @property {boolean} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here * should be replaced with plain-text * @property {FileOptions|string} [file] A file to send with the message - * @property {string} [code] Language for optional codeblock formatting to apply + * @property {string|boolean} [code] Language for optional codeblock formatting to apply * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if * it exceeds the character limit. If an object is provided, these are the options for splitting the message. */ From 3e3de51545a0047b6bcb05bd923bd35029aff602 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 29 Dec 2016 11:01:27 -0500 Subject: [PATCH 246/248] Update message sending docs --- src/structures/interface/TextBasedChannel.js | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/structures/interface/TextBasedChannel.js b/src/structures/interface/TextBasedChannel.js index 7d078816e..353c0a9cf 100644 --- a/src/structures/interface/TextBasedChannel.js +++ b/src/structures/interface/TextBasedChannel.js @@ -55,8 +55,8 @@ class TextBasedChannel { /** * Send a message to this channel - * @param {StringResolvable} [content] The content to send - * @param {MessageOptions} [options={}] The options to provide + * @param {StringResolvable} [content] Text for the message + * @param {MessageOptions} [options={}] Options for the message * @returns {Promise} * @example * // send a message @@ -94,8 +94,8 @@ class TextBasedChannel { /** * Send a message to this channel - * @param {StringResolvable} content The content to send - * @param {MessageOptions} [options={}] The options to provide + * @param {StringResolvable} content Text for the message + * @param {MessageOptions} [options={}] Options for the message * @returns {Promise} * @example * // send a message @@ -109,9 +109,9 @@ class TextBasedChannel { /** * Send an embed to this channel - * @param {RichEmbed|Object} embed The embed to send - * @param {string} [content] Content to send - * @param {MessageOptions} [options] The options to provide + * @param {RichEmbed|Object} embed Embed for the message + * @param {string} [content] Text for the message + * @param {MessageOptions} [options] Options for the message * @returns {Promise} */ sendEmbed(embed, content, options) { @@ -126,10 +126,10 @@ class TextBasedChannel { /** * Send a file to this channel - * @param {BufferResolvable} attachment The file to send - * @param {string} [name='file.jpg'] The name and extension of the file - * @param {StringResolvable} [content] Text message to send with the attachment - * @param {MessageOptions} [options] The options to provide + * @param {BufferResolvable} attachment File to send + * @param {string} [name='file.jpg'] Name and extension of the file + * @param {StringResolvable} [content] Text for the message + * @param {MessageOptions} [options] Options for the message * @returns {Promise} */ sendFile(attachment, name, content, options = {}) { @@ -140,7 +140,7 @@ class TextBasedChannel { * Send a code block to this channel * @param {string} lang Language for the code block * @param {StringResolvable} content Content of the code block - * @param {MessageOptions} [options] The options to provide + * @param {MessageOptions} [options] Options for the message * @returns {Promise} */ sendCode(lang, content, options = {}) { @@ -150,7 +150,7 @@ class TextBasedChannel { /** * Gets a single message from this channel, regardless of it being cached or not. * This is only available when using a bot account. - * @param {string} messageID The ID of the message to get + * @param {string} messageID ID of the message to get * @returns {Promise} * @example * // get message @@ -178,7 +178,7 @@ class TextBasedChannel { /** * Gets the past messages sent in this channel. Resolves with a collection mapping message ID's to Message objects. - * @param {ChannelLogsQueryOptions} [options={}] The query parameters to pass in + * @param {ChannelLogsQueryOptions} [options={}] Query parameters to pass in * @returns {Promise>} * @example * // get messages From 665ef21c858589e8beddd07d2b3fd7a36627bc99 Mon Sep 17 00:00:00 2001 From: Zack Campbell Date: Thu, 29 Dec 2016 10:04:41 -0600 Subject: [PATCH 247/248] Update DMChannel, GroupDMChannel for docs (#1038) --- src/structures/DMChannel.js | 2 +- src/structures/GroupDMChannel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/DMChannel.js b/src/structures/DMChannel.js index abf197e4b..de49c8b3f 100644 --- a/src/structures/DMChannel.js +++ b/src/structures/DMChannel.js @@ -37,8 +37,8 @@ class DMChannel extends Channel { } // These are here only for documentation purposes - they are implemented by TextBasedChannel + send() { return; } sendMessage() { return; } - sendTTSMessage() { return; } sendEmbed() { return; } sendFile() { return; } sendCode() { return; } diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js index ada3a90f4..84fe4f994 100644 --- a/src/structures/GroupDMChannel.js +++ b/src/structures/GroupDMChannel.js @@ -121,8 +121,8 @@ class GroupDMChannel extends Channel { } // These are here only for documentation purposes - they are implemented by TextBasedChannel + send() { return; } sendMessage() { return; } - sendTTSMessage() { return; } sendEmbed() { return; } sendFile() { return; } sendCode() { return; } From 6e51a44f92a13e713d33c7acce10f4447dbcfcef Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Thu, 29 Dec 2016 11:19:28 -0500 Subject: [PATCH 248/248] Small doc updates --- src/structures/MessageReaction.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js index 263f2b6b7..30c555f9d 100644 --- a/src/structures/MessageReaction.js +++ b/src/structures/MessageReaction.js @@ -56,7 +56,7 @@ class MessageReaction { /** * Removes a user from this reaction. - * @param {UserResolvable} [user] the user that you want to remove the reaction, defaults to the client. + * @param {UserResolvable} [user=this.message.client.user] User to remove the reaction of * @returns {Promise} */ remove(user = this.message.client.user) { @@ -69,8 +69,7 @@ class MessageReaction { } /** - * Fetch all the users that gave this reaction. Resolves with a collection of users, - * mapped by their IDs. + * Fetch all the users that gave this reaction. Resolves with a collection of users, mapped by their IDs. * @param {number} [limit=100] the maximum amount of users to fetch, defaults to 100 * @returns {Promise>} */