From 61a081faa58e05f9aca067c19a2cb1958b333f15 Mon Sep 17 00:00:00 2001 From: "Cody A. Taylor" Date: Mon, 1 May 2017 15:47:45 -0400 Subject: [PATCH 01/99] Document flattenErrors keys param (#1447) * Document flattenErrors keys param. * Remove parens. * Capitalise a letter --- src/client/rest/DiscordAPIError.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/rest/DiscordAPIError.js b/src/client/rest/DiscordAPIError.js index a28e3a8a5..84069bf03 100644 --- a/src/client/rest/DiscordAPIError.js +++ b/src/client/rest/DiscordAPIError.js @@ -18,13 +18,15 @@ class DiscordAPIError extends Error { /** * Flattens an errors object returned from the API into an array. * @param {Object} obj Discord errors object - * @param {string} [key] idklol + * @param {string} [key] Used internally to determine key names of nested fields * @returns {string[]} */ static flattenErrors(obj, key = '') { let messages = []; + for (const k of Object.keys(obj)) { const newKey = key ? isNaN(k) ? `${key}.${k}` : `${key}[${k}]` : k; + if (obj[k]._errors) { messages.push(`${newKey}: ${obj[k]._errors.map(e => e.message).join(' ')}`); } else { From 2d416cca794bde593de5de5a0b1badef160b085e Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Mon, 1 May 2017 21:07:46 +0100 Subject: [PATCH 02/99] Improve GuildAuditLogs documentation by creating an AuditLogChange typedef --- src/structures/GuildAuditLogs.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 6f6e04dcd..3e32cdb86 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -165,9 +165,17 @@ class GuildAuditLogsEntry { */ this.executor = guild.client.users.get(data.user_id); + /** + * An entry in the audit log representing a specific change + * @typedef {object} AuditLogChange + * @property {string} name The name of the change, e.g. `nick` for nickname changes + * @property {string|boolean|number} [old] The old value of the change, e.g. for nicknames, the old nickname + * @property {string|boolean|number} new The new value of the change, e.g. for nicknames, the new nickname + */ + /** * Specific property changes - * @type {Object[]} + * @type {AuditLogChange[]} */ this.changes = data.changes ? data.changes.map(c => ({ name: c.key, old: c.old_value, new: c.new_value })) : null; From ce8dc85f78fc018fd4a789344826a875981def52 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Mon, 1 May 2017 22:07:20 +0100 Subject: [PATCH 03/99] watch me, gus --- src/structures/GuildAuditLogs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 3e32cdb86..0b2a464be 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -168,7 +168,7 @@ class GuildAuditLogsEntry { /** * An entry in the audit log representing a specific change * @typedef {object} AuditLogChange - * @property {string} name The name of the change, e.g. `nick` for nickname changes + * @property {string} key The property that was changed, e.g. `nick` for nickname changes * @property {string|boolean|number} [old] The old value of the change, e.g. for nicknames, the old nickname * @property {string|boolean|number} new The new value of the change, e.g. for nicknames, the new nickname */ @@ -177,7 +177,7 @@ class GuildAuditLogsEntry { * Specific property changes * @type {AuditLogChange[]} */ - this.changes = data.changes ? data.changes.map(c => ({ name: c.key, old: c.old_value, new: c.new_value })) : null; + this.changes = data.changes ? data.changes.map(c => ({ key: c.key, old: c.old_value, new: c.new_value })) : null; /** * The ID of this entry From cdc355811ebbbc98077d69cf87d3904a00977c6d Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Mon, 1 May 2017 22:10:45 +0100 Subject: [PATCH 04/99] New is also optional --- src/structures/GuildAuditLogs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 0b2a464be..1e1f70b8b 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -170,7 +170,7 @@ class GuildAuditLogsEntry { * @typedef {object} AuditLogChange * @property {string} key The property that was changed, e.g. `nick` for nickname changes * @property {string|boolean|number} [old] The old value of the change, e.g. for nicknames, the old nickname - * @property {string|boolean|number} new The new value of the change, e.g. for nicknames, the new nickname + * @property {string|boolean|number} [new] The new value of the change, e.g. for nicknames, the new nickname */ /** From 3a736285674e04a2726ad2803a4d23b4d9ce71a1 Mon Sep 17 00:00:00 2001 From: Anxeal Date: Wed, 3 May 2017 21:45:51 +0300 Subject: [PATCH 05/99] Fix typo in RESTMethods.js (#1454) fetchMeMentions -> fetchMentions :thinking: --- 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 69d29ecba..3ab87a8bd 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -763,7 +763,7 @@ class RESTMethods { ); } - fetchMeMentions(options) { + fetchMentions(options) { if (options.guild) options.guild = options.guild.id ? options.guild.id : options.guild; return this.rest.makeRequest( 'get', From e96daba7c0d2c31d54a67485c26176337ab5452f Mon Sep 17 00:00:00 2001 From: Anxeal Date: Wed, 3 May 2017 22:35:24 +0300 Subject: [PATCH 06/99] Fix typo in RESTMethods.js (#1455) Mentions should be written with a capital M --- 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 3ab87a8bd..aa3d0fe8b 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -767,7 +767,7 @@ class RESTMethods { if (options.guild) options.guild = options.guild.id ? options.guild.id : options.guild; return this.rest.makeRequest( 'get', - Endpoints.User('@me').mentions(options.limit, options.roles, options.everyone, options.guild) + Endpoints.User('@me').Mentions(options.limit, options.roles, options.everyone, options.guild) ).then(res => res.body.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client))); } From 712305dece0aaae0d6883757a78c772a07d2c39c Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 3 May 2017 17:50:08 -0500 Subject: [PATCH 07/99] Update GuildAuditLogs.js (#1456) --- src/structures/GuildAuditLogs.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 1e1f70b8b..3c3a0b7ee 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -37,6 +37,7 @@ const Actions = { EMOJI_CREATE: 60, EMOJI_UPDATE: 61, EMOJI_DELETE: 62, + MESSAGE_DELETE: 72, }; @@ -112,6 +113,7 @@ class GuildAuditLogs { Actions.INVITE_DELETE, Actions.WEBHOOK_DELETE, Actions.EMOJI_DELETE, + Actions.MESSAGE_DELETE, ].includes(action)) return 'DELETE'; if ([ From 77caa0f8d4ed24ff009a4a0398c1f1d2daadae0e Mon Sep 17 00:00:00 2001 From: Crawl Date: Fri, 5 May 2017 01:56:00 +0200 Subject: [PATCH 08/99] Update welcome docs page --- 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 5a11c91e5..01a3d6717 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -80,7 +80,7 @@ client.login('your token'); ## 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 contribution guide](https://github.com/hydrabolt/discord.js/blob/master/CONTRIBUTING.md) if you'd like to submit a PR. +See [the contribution guide](https://github.com/hydrabolt/discord.js/blob/master/.github/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 41e0be1db3957bd8067de1b7598580ecb1fc7c23 Mon Sep 17 00:00:00 2001 From: Crawl Date: Fri, 5 May 2017 02:23:25 +0200 Subject: [PATCH 09/99] Endpoints.Guild(...).Emoji(...) should not use CDN (#1462) --- 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 f8895952b..03eaed78b 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -133,7 +133,7 @@ const Endpoints = exports.Endpoints = { ack: `${base}/ack`, settings: `${base}/settings`, auditLogs: `${base}/audit-logs`, - Emoji: emojiID => Endpoints.CDN(root).Emoji(emojiID), + Emoji: emojiID => `${base}/emojis/${emojiID}`, Icon: (root, hash) => Endpoints.CDN(root).Icon(guildID, hash), Splash: (root, hash) => Endpoints.CDN(root).Splash(guildID, hash), Role: roleID => `${base}/roles/${roleID}`, From 328d75be7d2d70d80e59f09b9529b58d95a23a3a Mon Sep 17 00:00:00 2001 From: Crawl Date: Fri, 5 May 2017 17:08:22 +0200 Subject: [PATCH 10/99] Update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e0d48d083..b23625277 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord.js", - "version": "11.1.0", + "version": "11.2.0", "description": "A powerful library for interacting with the Discord API", "main": "./src/index", "types": "./typings/index.d.ts", From 7d7f1b2446323da7f2f11a68ad8a18aa53edf3ec Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 5 May 2017 19:44:54 +0200 Subject: [PATCH 11/99] update GuildAuditLogs for MESSAGE_DELETE and fixed extras (#1464) * update GuildAuditLogs for MESSAGE_DELETE and fixed extras * correct oder of targets --- src/structures/GuildAuditLogs.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 3c3a0b7ee..ee1ec5156 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -9,6 +9,7 @@ const Targets = { INVITE: 'INVITE', WEBHOOK: 'WEBHOOK', EMOJI: 'EMOJI', + MESSAGE: 'MESSAGE', }; const Actions = { @@ -83,6 +84,7 @@ class GuildAuditLogs { if (target < 50) return Targets.INVITE; if (target < 60) return Targets.WEBHOOK; if (target < 70) return Targets.EMOJI; + if (target < 80) return Targets.MESSAGE; return null; } @@ -198,15 +200,20 @@ class GuildAuditLogsEntry { removed: data.options.members_removed, days: data.options.delete_member_days, }; + } else if (data.action_type === Actions.MESSAGE_DELETE) { + this.extra = { + count: data.options.count, + channel: guild.channels.get(data.options.channel_id), + }; } else { switch (data.options.type) { case 'member': - this.extra = guild.members.get(this.options.id); - if (!this.extra) this.extra = { id: this.options.id }; + this.extra = guild.members.get(data.options.id); + if (!this.extra) this.extra = { id: data.options.id }; break; case 'role': - this.extra = guild.roles.get(this.options.id); - if (!this.extra) this.extra = { id: this.options.id, name: this.options.role_name }; + this.extra = guild.roles.get(data.options.id); + if (!this.extra) this.extra = { id: data.options.id, name: data.options.role_name }; break; default: break; @@ -233,6 +240,8 @@ class GuildAuditLogsEntry { this.target = invites.find(i => i.code === (change.new || change.old)); return this.target; }); + } else if (targetType === Targets.MESSAGE) { + this.target = guild.client.users.get(data.target_id); } else { this.target = guild[`${targetType.toLowerCase()}s`].get(data.target_id); } From 6cd55ed5c994c3dcf8642bef5d58086022f3b51e Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 5 May 2017 20:15:06 +0200 Subject: [PATCH 12/99] readded docs for Client#error and Client#ready (#1466) --- src/client/websocket/WebSocketConnection.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/client/websocket/WebSocketConnection.js b/src/client/websocket/WebSocketConnection.js index ecbcf141a..b102e05f5 100644 --- a/src/client/websocket/WebSocketConnection.js +++ b/src/client/websocket/WebSocketConnection.js @@ -115,6 +115,10 @@ class WebSocketConnection extends EventEmitter { this.debug('Tried to mark self as ready, but already ready'); return; } + /** + * Emitted when the client becomes ready to start working. + * @event Client#ready + */ this.status = Constants.Status.READY; this.client.emit(Constants.Events.READY); this.packetManager.handleQueue(); @@ -354,6 +358,11 @@ class WebSocketConnection extends EventEmitter { * @param {Error} error Error that occurred */ onError(error) { + /** + * Emitted whenever the client's WebSocket encounters a connection error. + * @event Client#error + * @param {Error} error The encountered error + */ this.client.emit(Constants.Events.ERROR, error); if (error.message === 'uWs client connection error') this.reconnect(); } From 1fc6e3b91efb831df1559d91666ab4261793c776 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 5 May 2017 23:19:07 +0200 Subject: [PATCH 13/99] using correct properties for invites (#1467) --- src/structures/GuildAuditLogs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index ee1ec5156..e53126edd 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -234,10 +234,10 @@ class GuildAuditLogsEntry { return this.target; }); } else if (targetType === Targets.INVITE) { - const change = this.changes.find(c => c.name === 'code'); + const change = this.changes.find(c => c.key === 'code'); this.target = guild.fetchInvites() .then(invites => { - this.target = invites.find(i => i.code === (change.new || change.old)); + this.target = invites.find(i => i.code === (change.new_value || change.old_value)); return this.target; }); } else if (targetType === Targets.MESSAGE) { From ee622f7d9fd7c8714b709730e5d7873d3e7cc5d7 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 5 May 2017 23:24:02 +0200 Subject: [PATCH 14/99] added Invite#presenceCount and Invite#memberCount (#1460) * added Invite#online and Invite#memberCount * requested change --- src/structures/Invite.js | 12 ++++++++++++ src/util/Constants.js | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/structures/Invite.js b/src/structures/Invite.js index 8a7b962cd..995f9c57e 100644 --- a/src/structures/Invite.js +++ b/src/structures/Invite.js @@ -54,6 +54,12 @@ class Invite { */ this.code = data.code; + /** + * The approximate number of online members of the guild this invite is for + * @type {number} + */ + this.presenceCount = data.approximate_presence_count; + /** * Whether or not this invite is temporary * @type {boolean} @@ -66,6 +72,12 @@ class Invite { */ this.maxAge = data.max_age; + /** + * The approximate total number of members of the guild this invite is for + * @type {number} + */ + this.memberCount = data.approximate_member_count; + /** * How many times this invite has been used * @type {number} diff --git a/src/util/Constants.js b/src/util/Constants.js index 03eaed78b..61ca12d37 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -212,7 +212,7 @@ const Endpoints = exports.Endpoints = { toString: () => '/gateway', bot: '/gateway/bot', }, - Invite: inviteID => `/invite/${inviteID}`, + Invite: inviteID => `/invite/${inviteID}?with_counts=true`, inviteLink: id => `https://discord.gg/${id}`, Webhook: (webhookID, token) => `/webhooks/${webhookID}${token ? `/${token}` : ''}`, }; From 1e115efa562cf6c195daef14787fdd2ae1a9bd22 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Sat, 6 May 2017 01:04:12 +0200 Subject: [PATCH 15/99] fix fetchMentions' auth header, options and data mapping (#1457) * fix fetchMentions' auth header, options and data mapping * vscode strikes again * switched to Util.mergeDefault * vscode * removed duplicated optionals and switched to instanceof --- src/client/rest/RESTMethods.js | 9 +++++---- src/structures/ClientUser.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index aa3d0fe8b..7c9030ad4 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -764,11 +764,12 @@ class RESTMethods { } fetchMentions(options) { - if (options.guild) options.guild = options.guild.id ? options.guild.id : options.guild; + if (options.guild instanceof Guild) options.guild = options.guild.id; + Util.mergeDefault({ limit: 25, roles: true, everyone: true, guild: null }, options); + return this.rest.makeRequest( - 'get', - Endpoints.User('@me').Mentions(options.limit, options.roles, options.everyone, options.guild) - ).then(res => res.body.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client))); + 'get', Endpoints.User('@me').Mentions(options.limit, options.roles, options.everyone, options.guild), true + ).then(data => data.map(m => new Message(this.client.channels.get(m.channel_id), m, this.client))); } addFriend(user) { diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 9fe1a3d96..dcd16423c 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -265,7 +265,7 @@ class ClientUser extends User { * @param {Guild|Snowflake} [options.guild] Limit the search to a specific guild * @returns {Promise} */ - fetchMentions(options = { limit: 25, roles: true, everyone: true, guild: null }) { + fetchMentions(options = {}) { return this.client.rest.methods.fetchMentions(options); } From fd7cb41ee665460c2e7dc95f909c92e3f07bc7eb Mon Sep 17 00:00:00 2001 From: Crawl Date: Sat, 6 May 2017 01:14:27 +0200 Subject: [PATCH 16/99] Using a traditional for loop rather than a for in loop for options.files (#1461) --- src/structures/Webhook.js | 24 ++++++++++++------- src/structures/interfaces/TextBasedChannel.js | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 4d96382ac..240ea1fd8 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -105,14 +105,22 @@ class Webhook { 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'; + if (options.files) options.files.push(options.file); + else options.files = [options.file]; + } + + if (options.files) { + for (let i = 0; i < options.files.length; i++) { + let file = options.files[i]; + if (typeof file === 'string') file = { attachment: file }; + if (!file.name) { + if (typeof file.attachment === 'string') { + file.name = path.basename(file.attachment); + } else if (file.attachment && file.attachment.path) { + file.name = path.basename(file.attachment.path); + } else { + file.name = 'file.jpg'; + } } } return this.client.resolver.resolveBuffer(options.file.attachment).then(file => diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index f7fde7319..8fb7995ec 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -88,7 +88,7 @@ class TextBasedChannel { } if (options.files) { - for (const i in options.files) { + for (let i = 0; i < options.files.length; i++) { let file = options.files[i]; if (typeof file === 'string') file = { attachment: file }; if (!file.name) { From 294f5ea5c895363f8cca3982391733157dd3d6cd Mon Sep 17 00:00:00 2001 From: 1Computer1 <1Computer1@users.noreply.github.com> Date: Fri, 5 May 2017 19:22:15 -0400 Subject: [PATCH 17/99] Deprecate aliases (#1469) --- src/structures/Message.js | 1 + src/structures/Webhook.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/structures/Message.js b/src/structures/Message.js index dfd5fdd2e..4c318ce73 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -388,6 +388,7 @@ class Message { * @param {string} lang The language for the code block * @param {StringResolvable} content The new content for the message * @returns {Promise} + * @deprecated */ editCode(lang, content) { content = Util.escapeMarkdown(this.client.resolver.resolveString(content), true); diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 240ea1fd8..23ce1e6c4 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -138,6 +138,7 @@ class Webhook { * @param {StringResolvable} content The content to send * @param {WebhookMessageOptions} [options={}] The options to provide * @returns {Promise} + * @deprecated * @example * // Send a message * webhook.sendMessage('hello!') @@ -155,6 +156,7 @@ class Webhook { * @param {StringResolvable} [content] Text message to send with the attachment * @param {WebhookMessageOptions} [options] The options to provide * @returns {Promise} + * @deprecated */ sendFile(attachment, name, content, options = {}) { return this.send(content, Object.assign(options, { file: { attachment, name } })); @@ -166,6 +168,7 @@ class Webhook { * @param {StringResolvable} content Content of the code block * @param {WebhookMessageOptions} options The options to provide * @returns {Promise} + * @deprecated */ sendCode(lang, content, options = {}) { return this.send(content, Object.assign(options, { code: lang })); From bb0ee59718058a0b02c52de16cdce664e24f795a Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Tue, 9 May 2017 00:19:24 +0200 Subject: [PATCH 18/99] Handing promise rejections from GuildAuditLogs#build to the user (#1474) * handing guildauditlog's promise rejections to the user * Returning a new Promise to resolve a Promise.all is unnecessary. Also for the docs, it returns a Promise, not GuildAuditLogs directly. * totally did not removed that line --- src/structures/GuildAuditLogs.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index e53126edd..9202ed231 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -62,13 +62,11 @@ class GuildAuditLogs { /** * Handles possible promises for entry targets. - * @returns {GuildAuditLogs} + * @returns {Promise} */ static build(...args) { - return new Promise(resolve => { - const logs = new GuildAuditLogs(...args); - Promise.all(logs.entries.map(e => e.target)).then(() => resolve(logs)); - }); + const logs = new GuildAuditLogs(...args); + return Promise.all(logs.entries.map(e => e.target)).then(() => logs); } /** From ea1b5beea6c6553117b74b2b4eeaf0a215923140 Mon Sep 17 00:00:00 2001 From: meew0 Date: Wed, 10 May 2017 13:11:21 +0200 Subject: [PATCH 19/99] Fix the mention example in the USERS_PATTERN doc comment Previously it was a channel mention. Thanks @Hackzzila. --- src/structures/MessageMentions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/MessageMentions.js b/src/structures/MessageMentions.js index b045d5a70..0e8f6de83 100644 --- a/src/structures/MessageMentions.js +++ b/src/structures/MessageMentions.js @@ -124,7 +124,7 @@ class MessageMentions { MessageMentions.EVERYONE_PATTERN = /@(everyone|here)/g; /** - * Regular expression that globally matches user mentions like `<#81440962496172032>` + * Regular expression that globally matches user mentions like `<@81440962496172032>` * @type {RegExp} */ MessageMentions.USERS_PATTERN = /<@!?[0-9]+>/g; From 798018713b6b6a048e317d61eda981b9dce8c12c Mon Sep 17 00:00:00 2001 From: Drahcirius Date: Wed, 10 May 2017 11:14:39 -0400 Subject: [PATCH 20/99] invalid token errors not rejected properly (#1478) * ready event will now throw errors properly * ws login rejection fix --- src/client/ClientManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js index 551e20dc1..538411079 100644 --- a/src/client/ClientManager.js +++ b/src/client/ClientManager.js @@ -43,7 +43,7 @@ class ClientManager { const gateway = `${res.url}/?v=${protocolVersion}&encoding=${WebSocketConnection.ENCODING}`; this.client.emit(Constants.Events.DEBUG, `Using gateway ${gateway}`); this.client.ws.connect(gateway); - this.client.ws.once('close', event => { + this.client.ws.connection.once('close', event => { if (event.code === 4004) reject(new Error(Constants.Errors.BAD_LOGIN)); if (event.code === 4010) reject(new Error(Constants.Errors.INVALID_SHARD)); if (event.code === 4011) reject(new Error(Constants.Errors.SHARDING_REQUIRED)); From e3c3a4fd60a0e18ef20345c77f7224472fba405f Mon Sep 17 00:00:00 2001 From: aemino Date: Thu, 11 May 2017 21:41:40 -0700 Subject: [PATCH 21/99] GuildMember#setVoiceChannel fix (#1482) Looks like someone forgot to remove the full channel object from the PATCH payload. --- src/client/rest/RESTMethods.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 7c9030ad4..30d5f4ff9 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -465,7 +465,10 @@ class RESTMethods { } updateGuildMember(member, data) { - if (data.channel) data.channel_id = this.client.resolver.resolveChannel(data.channel).id; + if (data.channel) { + data.channel_id = this.client.resolver.resolveChannel(data.channel).id; + data.channel = null; + } if (data.roles) data.roles = data.roles.map(role => role instanceof Role ? role.id : role); let endpoint = Endpoints.Member(member); From 45bc653988904efe903220453eb9738f56d4e97f Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 12 May 2017 15:30:46 +0200 Subject: [PATCH 22/99] Failing to resolve a role should reject and not throw an error (#1483) --- src/structures/GuildMember.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index bcad0e03e..0c096120d 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -394,7 +394,7 @@ class GuildMember { */ addRole(role) { if (!(role instanceof Role)) role = this.guild.roles.get(role); - if (!role) throw new TypeError('Supplied parameter was neither a Role nor a Snowflake.'); + if (!role) return Promise.reject(new TypeError('Supplied parameter was neither a Role nor a Snowflake.')); return this.client.rest.methods.addMemberRole(this, role); } @@ -421,7 +421,7 @@ class GuildMember { */ removeRole(role) { if (!(role instanceof Role)) role = this.guild.roles.get(role); - if (!role) throw new TypeError('Supplied parameter was neither a Role nor a Snowflake.'); + if (!role) return Promise.reject(new TypeError('Supplied parameter was neither a Role nor a Snowflake.')); return this.client.rest.methods.removeMemberRole(this, role); } From 8a7a805d9296306fe6728ed05ea2cd30359d4f60 Mon Sep 17 00:00:00 2001 From: Crawl Date: Sat, 13 May 2017 18:17:25 +0200 Subject: [PATCH 23/99] Fix webpack uglify --- package.json | 3 ++- webpack.config.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b23625277..58507a799 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "discord.js-docgen": "hydrabolt/discord.js-docgen", "eslint": "^3.19.0", "parallel-webpack": "^1.6.0", - "uglify-js": "mishoo/UglifyJS2#harmony", + "uglify-js": "mishoo/UglifyJS2#harmony-v2.8.22", + "uglifyjs-webpack-plugin": "^0.4.3", "webpack": "^2.2.0" }, "engines": { diff --git a/webpack.config.js b/webpack.config.js index 73e6c43ec..dd4000fd9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,7 @@ const webpack = require('webpack'); const createVariants = require('parallel-webpack').createVariants; +const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const version = require('./package.json').version; const createConfig = options => { @@ -12,7 +13,7 @@ const createConfig = options => { new webpack.DefinePlugin({ 'global.GENTLY': false }), ]; - if (options.minify) plugins.push(new webpack.optimize.UglifyJsPlugin({ minimize: true })); + if (options.minify) plugins.push(new UglifyJSPlugin({ minimize: true })); const filename = `./webpack/discord${process.env.VERSIONED === 'false' ? '' : '.' + version}${options.minify ? '.min' : ''}.js`; // eslint-disable-line From b9172ffe2271944ef449c635d0ab6a74fc23671b Mon Sep 17 00:00:00 2001 From: Crawl Date: Sat, 13 May 2017 19:16:32 +0200 Subject: [PATCH 24/99] Add travis staging --- .travis.yml | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index e1f264647..74323ddbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,20 @@ -language: node_js -node_js: - - "6" - - "7" -cache: - directories: - - node_modules -install: npm install -script: - - bash ./deploy/deploy.sh -env: - global: - - ENCRYPTION_LABEL: "af862fa96d3e" - - COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com" -dist: trusty -sudo: false +language: node_js +node_js: + - "6" + - "7" +cache: + directories: + - node_modules +install: npm install +script: bash ./deploy/test.sh +jobs: + include: + - stage: build + node_js: "6" + script: bash ./deploy/deploy.sh +env: + global: + - ENCRYPTION_LABEL: "af862fa96d3e" + - COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com" +dist: trusty +sudo: false From f25ced2969fc4ac0bc686a225e1a816d3729589b Mon Sep 17 00:00:00 2001 From: Crawl Date: Sat, 13 May 2017 19:19:25 +0200 Subject: [PATCH 25/99] Add test.sh and modified deploy.sh --- deploy/deploy.sh | 20 ++++++-------------- deploy/test.sh | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 deploy/test.sh diff --git a/deploy/deploy.sh b/deploy/deploy.sh index c132a20f6..d12ee608b 100644 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -3,15 +3,7 @@ set -e -function tests { - npm run lint - npm run docs:test - VERSIONED=false npm run webpack - exit 0 -} - function build { - npm run lint npm run docs VERSIONED=false npm run webpack } @@ -22,10 +14,10 @@ if [[ "$TRAVIS_BRANCH" == revert-* ]]; then exit 0 fi -# For PRs, only run tests +# For PRs, do nothing if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - echo -e "\e[36m\e[1mBuild triggered for PR #${TRAVIS_PULL_REQUEST} to branch \"${TRAVIS_BRANCH}\" - only running tests." - tests + echo -e "\e[36m\e[1mBuild triggered for PR #${TRAVIS_PULL_REQUEST} to branch \"${TRAVIS_BRANCH}\" - doing nothing." + exit 0 fi # Figure out the source of the build @@ -39,10 +31,10 @@ else SOURCE_TYPE="branch" fi -# For Node != 6, only run tests +# For Node != 6, do nothing if [ "$TRAVIS_NODE_VERSION" != "6" ]; then - echo -e "\e[36m\e[1mBuild triggered with Node v${TRAVIS_NODE_VERSION} - only running tests." - tests + echo -e "\e[36m\e[1mBuild triggered with Node v${TRAVIS_NODE_VERSION} - doing nothing." + exit 0 fi build diff --git a/deploy/test.sh b/deploy/test.sh new file mode 100644 index 000000000..9e076a4c4 --- /dev/null +++ b/deploy/test.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -e + +function tests { + npm run lint + npm run docs:test + exit 0 +} + +# For revert branches, do nothing +if [[ "$TRAVIS_BRANCH" == revert-* ]]; then + echo -e "\e[36m\e[1mTest triggered for reversion branch \"${TRAVIS_BRANCH}\" - doing nothing." + exit 0 +fi + +# For PRs +if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + echo -e "\e[36m\e[1mTest triggered for PR #${TRAVIS_PULL_REQUEST} to branch \"${TRAVIS_BRANCH}\" - only running tests." + tests +fi + +# Figure out the source of the test +if [ -n "$TRAVIS_TAG" ]; then + echo -e "\e[36m\e[1mTest triggered for tag \"${TRAVIS_TAG}\"." +else + echo -e "\e[36m\e[1mTest triggered for branch \"${TRAVIS_BRANCH}\"." +fi + +# For Node != 6 +if [ "$TRAVIS_NODE_VERSION" != "6" ]; then + echo -e "\e[36m\e[1mTest triggered with Node v${TRAVIS_NODE_VERSION} - only running tests." + tests +fi From 6eced4d465cc790052b95bf4a008746db1c000d0 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 14 May 2017 12:33:04 -0500 Subject: [PATCH 26/99] fix infinte loop issue (#1488) --- src/client/rest/DiscordAPIError.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/rest/DiscordAPIError.js b/src/client/rest/DiscordAPIError.js index 84069bf03..7d3ff7ef9 100644 --- a/src/client/rest/DiscordAPIError.js +++ b/src/client/rest/DiscordAPIError.js @@ -29,6 +29,8 @@ class DiscordAPIError extends Error { if (obj[k]._errors) { messages.push(`${newKey}: ${obj[k]._errors.map(e => e.message).join(' ')}`); + } else if (obj[k].code && obj[k].message) { + messages.push(`${obj[k].code}: ${obj[k].message}`); } else { messages = messages.concat(this.flattenErrors(obj[k], newKey)); } From 28a29d5d9f18f19f275d494a57574003d8c58bf3 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 16 May 2017 08:25:42 -0500 Subject: [PATCH 27/99] Update Invite.js (#1496) --- src/structures/Invite.js | 45 ++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/structures/Invite.js b/src/structures/Invite.js index 995f9c57e..cd9324b44 100644 --- a/src/structures/Invite.js +++ b/src/structures/Invite.js @@ -2,27 +2,6 @@ const PartialGuild = require('./PartialGuild'); const PartialGuildChannel = require('./PartialGuildChannel'); const Constants = require('../util/Constants'); -/* -{ max_age: 86400, - code: 'CG9A5', - guild: - { splash: null, - id: '123123123', - icon: '123123123', - name: 'name' }, - created_at: '2016-08-28T19:07:04.763368+00:00', - temporary: false, - uses: 0, - max_uses: 0, - inviter: - { username: '123', - discriminator: '4204', - bot: true, - id: '123123123', - avatar: '123123123' }, - channel: { type: 0, id: '123123', name: 'heavy-testing' } } -*/ - /** * Represents an invitation to a guild channel. * The only guaranteed properties are `code`, `guild` and `channel`. Other properties can be missing. @@ -60,6 +39,24 @@ class Invite { */ this.presenceCount = data.approximate_presence_count; + /** + * The approximate total number of members of the guild this invite is for + * @type {number} + */ + this.memberCount = data.approximate_member_count; + + /** + * The number of text channels the guild this invite goes to has + * @type {number} + */ + this.textChannelCount = data.guild.text_channel_count; + + /** + * The number of voice channels the guild this invite goes to has + * @type {number} + */ + this.voiceChannelCount = data.guild.voice_channel_count; + /** * Whether or not this invite is temporary * @type {boolean} @@ -72,12 +69,6 @@ class Invite { */ this.maxAge = data.max_age; - /** - * The approximate total number of members of the guild this invite is for - * @type {number} - */ - this.memberCount = data.approximate_member_count; - /** * How many times this invite has been used * @type {number} From c903929b6b561d647ccee5d7629e84332d18d7d0 Mon Sep 17 00:00:00 2001 From: bdistin Date: Tue, 16 May 2017 09:11:11 -0500 Subject: [PATCH 28/99] guild setPosition missing docs (#1498) * missing docs * update return docs --- src/structures/Guild.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index bbb74624f..d1015a8cf 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -634,6 +634,8 @@ class Guild { } /** + * Sets the position of the guild in the guild listing. + * This is only available when using a user account. * @param {number} position Absolute or relative position * @param {boolean} [relative=false] Whether to position relatively or absolutely * @returns {Promise} @@ -648,7 +650,7 @@ class Guild { /** * Marks all messages in this guild as read. * This is only available when using a user account. - * @returns {Promise} This guild + * @returns {Promise} */ acknowledge() { return this.client.rest.methods.ackGuild(this); From a8d34e340bc19367f136be283e3d9eb1d697505d Mon Sep 17 00:00:00 2001 From: Crawl Date: Sat, 20 May 2017 21:04:31 +0200 Subject: [PATCH 29/99] Relink permission#FLAGS on docs --- src/util/Permissions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/Permissions.js b/src/util/Permissions.js index d5af0c7d1..5e56f2ae9 100644 --- a/src/util/Permissions.js +++ b/src/util/Permissions.js @@ -152,7 +152,7 @@ class Permissions { /** * Data that can be resolved to give a permission number. This can be: - * - A string (see {@link Permissions.flags}) + * - A string (see {@link Permissions.FLAGS}) * - A permission number * @typedef {string|number} PermissionResolvable */ From 433c5e57021d7fe3d2b375bf29d8681a5fefd45c Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sat, 20 May 2017 22:19:53 -0500 Subject: [PATCH 30/99] you can't mutate a socket event in some browsers (webpack fix) (#1512) --- src/client/websocket/WebSocketConnection.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/websocket/WebSocketConnection.js b/src/client/websocket/WebSocketConnection.js index b102e05f5..93560a45b 100644 --- a/src/client/websocket/WebSocketConnection.js +++ b/src/client/websocket/WebSocketConnection.js @@ -284,12 +284,13 @@ class WebSocketConnection extends EventEmitter { * @returns {boolean} */ onMessage(event) { + let data; try { - event.data = this.unpack(event.data); + data = this.unpack(event.data); } catch (err) { this.emit('debug', err); } - return this.onPacket(event.data); + return this.onPacket(data); } /** From c62e8bb1f04b0d081438fb3802be4a3a9a61628b Mon Sep 17 00:00:00 2001 From: Crawl Date: Sun, 21 May 2017 06:40:52 +0200 Subject: [PATCH 31/99] Update typings --- typings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings b/typings index b500eb233..54b72a2b7 160000 --- a/typings +++ b/typings @@ -1 +1 @@ -Subproject commit b500eb233182a3c7a5655ae29423844e82e72ab7 +Subproject commit 54b72a2b75abf4f43b2ddd304afaa5c83af51ad2 From 8e80b6660c9a59d8e839fb0152608dd721252e7e Mon Sep 17 00:00:00 2001 From: vzwGrey Date: Sun, 21 May 2017 18:56:17 +0200 Subject: [PATCH 32/99] Fix reaction collector example (#1513) --- 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 4c318ce73..7f2a54099 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -254,7 +254,7 @@ class Message { * @example * // Create a reaction collector * const collector = message.createReactionCollector( - * (reaction, user) => reaction.emoji.id === '👌' && user.id === 'someID', + * (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someID', * { time: 15000 } * ); * collector.on('collect', r => console.log(`Collected ${r.emoji.name}`)); From bb2a35a84942a81512d0664e8b4cc840239fc8f7 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 21 May 2017 14:24:36 -0500 Subject: [PATCH 33/99] they can be more than just string/num/bool (#1448) --- src/structures/GuildAuditLogs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 9202ed231..125844deb 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -171,8 +171,8 @@ class GuildAuditLogsEntry { * An entry in the audit log representing a specific change * @typedef {object} AuditLogChange * @property {string} key The property that was changed, e.g. `nick` for nickname changes - * @property {string|boolean|number} [old] The old value of the change, e.g. for nicknames, the old nickname - * @property {string|boolean|number} [new] The new value of the change, e.g. for nicknames, the new nickname + * @property {*} [old] The old value of the change, e.g. for nicknames, the old nickname + * @property {*} [new] The new value of the change, e.g. for nicknames, the new nickname */ /** From eb4d3627bdd15089a37dd49669a9dd4041144d9f Mon Sep 17 00:00:00 2001 From: Crawl Date: Tue, 23 May 2017 11:13:59 +0200 Subject: [PATCH 34/99] Update typings --- typings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings b/typings index 54b72a2b7..99e19b9d7 160000 --- a/typings +++ b/typings @@ -1 +1 @@ -Subproject commit 54b72a2b75abf4f43b2ddd304afaa5c83af51ad2 +Subproject commit 99e19b9d76dde5f1498b58bb9261042d9afa055c From 19d4d3bf2ce2f1833722a5a8810e19aa605890e8 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Sat, 27 May 2017 19:19:35 +0100 Subject: [PATCH 35/99] Fix #1528 --- src/client/websocket/WebSocketConnection.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/websocket/WebSocketConnection.js b/src/client/websocket/WebSocketConnection.js index 93560a45b..af4a20e8d 100644 --- a/src/client/websocket/WebSocketConnection.js +++ b/src/client/websocket/WebSocketConnection.js @@ -359,13 +359,16 @@ class WebSocketConnection extends EventEmitter { * @param {Error} error Error that occurred */ onError(error) { + if (error && error.message === 'uWs client connection error') { + this.reconnect(); + return; + } /** * Emitted whenever the client's WebSocket encounters a connection error. * @event Client#error * @param {Error} error The encountered error */ this.client.emit(Constants.Events.ERROR, error); - if (error.message === 'uWs client connection error') this.reconnect(); } /** From 3ec08d5976b7b1cbe4d3aab950e21da56883b16c Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Mon, 29 May 2017 16:53:49 -0500 Subject: [PATCH 36/99] add nsfw options to 11.1 search (#1540) * Update RESTMethods.js * Update TextBasedChannel.js --- src/client/rest/RESTMethods.js | 1 + src/structures/interfaces/TextBasedChannel.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 30d5f4ff9..641468031 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -227,6 +227,7 @@ class RESTMethods { embed_type: options.embedType, attachment_filename: options.attachmentFilename, attachment_extension: options.attachmentExtension, + include_nsfw: options.nsfw, }; for (const key in options) if (options[key] === undefined) delete options[key]; diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index 8fb7995ec..fc70f942f 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -214,6 +214,7 @@ class TextBasedChannel { * @property {Date} [before] Date to find messages before * @property {Date} [after] Date to find messages before * @property {Date} [during] Date to find messages during (range of date to date + 24 hours) + * @property {boolean} [nsfw=false] Include results from NSFW channels */ /** From 2f1eb71a3ffd371b266fcf4dcb99e257468e6fd7 Mon Sep 17 00:00:00 2001 From: Snazzah Date: Mon, 29 May 2017 16:53:23 -0500 Subject: [PATCH 37/99] Add MEMBER_ROLE_UPDATE to returning 'UPDATE' array (#1538) (#1542) --- src/structures/GuildAuditLogs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 125844deb..decc940c2 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -121,6 +121,7 @@ class GuildAuditLogs { Actions.CHANNEL_UPDATE, Actions.CHANNEL_OVERWRITE_UPDATE, Actions.MEMBER_UPDATE, + Actions.MEMBER_ROLE_UPDATE, Actions.ROLE_UPDATE, Actions.INVITE_UPDATE, Actions.WEBHOOK_UPDATE, From 46b8a7d411a6453df580e7037bdbcbb3e0373b51 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Tue, 30 May 2017 12:21:37 +0100 Subject: [PATCH 38/99] Correct documentation for VoiceConnection (see #1536) --- 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 36727e7e8..b2d91722c 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -8,7 +8,7 @@ const EventEmitter = require('events').EventEmitter; const Prism = require('prism-media'); /** - * Represents a connection to a voice channel in Discord. + * Represents a connection a guild's voice server. * ```js * // Obtained using: * voiceChannel.join().then(connection => { From b49266baa571198be5757ffde22ca2878b88c266 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Tue, 30 May 2017 12:22:40 +0100 Subject: [PATCH 39/99] fml --- 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 b2d91722c..ceab3cf14 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -8,7 +8,7 @@ const EventEmitter = require('events').EventEmitter; const Prism = require('prism-media'); /** - * Represents a connection a guild's voice server. + * Represents a connection to a guild's voice server. * ```js * // Obtained using: * voiceChannel.join().then(connection => { From 2116fba4c270c1cc3fdd796a85f6a7b6c367316c Mon Sep 17 00:00:00 2001 From: aemino Date: Thu, 1 Jun 2017 01:29:55 -0700 Subject: [PATCH 40/99] Opus engine fetching: don't ignore non-missing errors (#1555) * Opus engine fetching: don't ignore non-missing errors * typo fix --- src/client/voice/opus/OpusEngineList.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/client/voice/opus/OpusEngineList.js b/src/client/voice/opus/OpusEngineList.js index 447f0afdb..3b69cdaeb 100644 --- a/src/client/voice/opus/OpusEngineList.js +++ b/src/client/voice/opus/OpusEngineList.js @@ -9,7 +9,10 @@ function fetch(Encoder, engineOptions) { try { return new Encoder(engineOptions); } catch (err) { - return null; + if (err.message.includes('Cannot find module')) return null; + + // The Opus engine exists, but another error occurred. + throw err; } } From 1ed6bbc4b4dc562a14b470f01d81af72c0a178c3 Mon Sep 17 00:00:00 2001 From: aemino Date: Sun, 4 Jun 2017 21:52:31 -0700 Subject: [PATCH 41/99] Remove unused VoiceBroadcast#guaranteeOpusEngine (fixes #1556) (#1563) --- src/client/voice/VoiceBroadcast.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/client/voice/VoiceBroadcast.js b/src/client/voice/VoiceBroadcast.js index b80efaf4a..c4562f17f 100644 --- a/src/client/voice/VoiceBroadcast.js +++ b/src/client/voice/VoiceBroadcast.js @@ -242,8 +242,6 @@ class VoiceBroadcast extends VolumeInterface { * @returns {VoiceBroadcast} */ playArbitraryInput(input, { seek = 0, volume = 1, passes = 1 } = {}) { - this.guaranteeOpusEngine(); - const options = { seek, volume, passes, input }; return this._playTranscodable(input, options); } @@ -272,10 +270,6 @@ class VoiceBroadcast extends VolumeInterface { } } - guaranteeOpusEngine() { - if (!this.opusEncoder) throw new Error('Couldn\'t find an Opus engine.'); - } - _startPlaying() { if (this.tickInterval) clearInterval(this.tickInterval); // Old code? From 3f7049e1a0078bb7fea996e4f69b5df7764dc8be Mon Sep 17 00:00:00 2001 From: Crawl Date: Tue, 6 Jun 2017 08:45:51 +0200 Subject: [PATCH 42/99] CRLF to LF --- .gitignore | 42 ++++++------- docs/README.md | 2 +- test/voice.js | 156 ++++++++++++++++++++++++------------------------- 3 files changed, 100 insertions(+), 100 deletions(-) diff --git a/.gitignore b/.gitignore index c6e423898..ffe623718 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,21 @@ -# Packages -node_modules/ -yarn.lock - -# Log files -logs/ -*.log - -# Authentication -test/auth.json -test/auth.js -docs/deploy/deploy_key -docs/deploy/deploy_key.pub -deploy/deploy_key -deploy/deploy_key.pub - -# Miscellaneous -.tmp/ -.vscode/ -docs/docs.json -webpack/ +# Packages +node_modules/ +yarn.lock + +# Log files +logs/ +*.log + +# Authentication +test/auth.json +test/auth.js +docs/deploy/deploy_key +docs/deploy/deploy_key.pub +deploy/deploy_key +deploy/deploy_key.pub + +# Miscellaneous +.tmp/ +.vscode/ +docs/docs.json +webpack/ diff --git a/docs/README.md b/docs/README.md index d41af8eaf..b5ac7978f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1 +1 @@ -## [View the documentation here.](https://discord.js.org/#/docs) +## [View the documentation here.](https://discord.js.org/#/docs) diff --git a/test/voice.js b/test/voice.js index 396bcc490..65f4329e7 100644 --- a/test/voice.js +++ b/test/voice.js @@ -1,78 +1,78 @@ -/* eslint no-console: 0 */ -'use strict'; - -const Discord = require('../'); -const ytdl = require('ytdl-core'); - -const client = new Discord.Client({ fetchAllMembers: false, apiRequestMethod: 'sequential' }); - -const auth = require('./auth.json'); - -client.login(auth.token).then(() => console.log('logged')).catch(console.error); - -const connections = new Map(); - -let broadcast; - -client.on('message', m => { - if (!m.guild) return; - if (m.content.startsWith('/join')) { - const channel = m.guild.channels.get(m.content.split(' ')[1]) || m.member.voiceChannel; - if (channel && channel.type === 'voice') { - channel.join().then(conn => { - conn.player.on('error', (...e) => console.log('player', ...e)); - if (!connections.has(m.guild.id)) connections.set(m.guild.id, { conn, queue: [] }); - m.reply('ok!'); - }); - } else { - m.reply('Specify a voice channel!'); - } - } else if (m.content.startsWith('/play')) { - if (connections.has(m.guild.id)) { - const connData = connections.get(m.guild.id); - const queue = connData.queue; - const url = m.content.split(' ').slice(1).join(' ') - .replace(//g, ''); - queue.push({ url, m }); - if (queue.length > 1) { - m.reply(`OK, that's going to play after ${queue.length - 1} songs`); - return; - } - doQueue(connData); - } - } else if (m.content.startsWith('/skip')) { - if (connections.has(m.guild.id)) { - const connData = connections.get(m.guild.id); - if (connData.dispatcher) { - connData.dispatcher.end(); - } - } - } else if (m.content.startsWith('#eval') && m.author.id === '66564597481480192') { - try { - const com = eval(m.content.split(' ').slice(1).join(' ')); - m.channel.sendMessage(`\`\`\`\n${com}\`\`\``); - } catch (e) { - console.log(e); - m.channel.sendMessage(`\`\`\`\n${e}\`\`\``); - } - } -}); - -function doQueue(connData) { - const conn = connData.conn; - const queue = connData.queue; - const item = queue[0]; - if (!item) return; - const stream = ytdl(item.url, { filter: 'audioonly' }, { passes: 3 }); - const dispatcher = conn.playStream(stream); - stream.on('info', info => { - item.m.reply(`OK, playing **${info.title}**`); - }); - dispatcher.on('end', () => { - queue.shift(); - doQueue(connData); - }); - dispatcher.on('error', (...e) => console.log('dispatcher', ...e)); - connData.dispatcher = dispatcher; -} +/* eslint no-console: 0 */ +'use strict'; + +const Discord = require('../'); +const ytdl = require('ytdl-core'); + +const client = new Discord.Client({ fetchAllMembers: false, apiRequestMethod: 'sequential' }); + +const auth = require('./auth.json'); + +client.login(auth.token).then(() => console.log('logged')).catch(console.error); + +const connections = new Map(); + +let broadcast; + +client.on('message', m => { + if (!m.guild) return; + if (m.content.startsWith('/join')) { + const channel = m.guild.channels.get(m.content.split(' ')[1]) || m.member.voiceChannel; + if (channel && channel.type === 'voice') { + channel.join().then(conn => { + conn.player.on('error', (...e) => console.log('player', ...e)); + if (!connections.has(m.guild.id)) connections.set(m.guild.id, { conn, queue: [] }); + m.reply('ok!'); + }); + } else { + m.reply('Specify a voice channel!'); + } + } else if (m.content.startsWith('/play')) { + if (connections.has(m.guild.id)) { + const connData = connections.get(m.guild.id); + const queue = connData.queue; + const url = m.content.split(' ').slice(1).join(' ') + .replace(//g, ''); + queue.push({ url, m }); + if (queue.length > 1) { + m.reply(`OK, that's going to play after ${queue.length - 1} songs`); + return; + } + doQueue(connData); + } + } else if (m.content.startsWith('/skip')) { + if (connections.has(m.guild.id)) { + const connData = connections.get(m.guild.id); + if (connData.dispatcher) { + connData.dispatcher.end(); + } + } + } else if (m.content.startsWith('#eval') && m.author.id === '66564597481480192') { + try { + const com = eval(m.content.split(' ').slice(1).join(' ')); + m.channel.sendMessage(`\`\`\`\n${com}\`\`\``); + } catch (e) { + console.log(e); + m.channel.sendMessage(`\`\`\`\n${e}\`\`\``); + } + } +}); + +function doQueue(connData) { + const conn = connData.conn; + const queue = connData.queue; + const item = queue[0]; + if (!item) return; + const stream = ytdl(item.url, { filter: 'audioonly' }, { passes: 3 }); + const dispatcher = conn.playStream(stream); + stream.on('info', info => { + item.m.reply(`OK, playing **${info.title}**`); + }); + dispatcher.on('end', () => { + queue.shift(); + doQueue(connData); + }); + dispatcher.on('error', (...e) => console.log('dispatcher', ...e)); + connData.dispatcher = dispatcher; +} From 95a531ee7d7931cf84b11583b0eea55fdfb4c6a1 Mon Sep 17 00:00:00 2001 From: Schuyler Cebulskie Date: Wed, 7 Jun 2017 23:00:43 -0400 Subject: [PATCH 43/99] Switch to User#tag in web builds example --- docs/topics/web.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/web.md b/docs/topics/web.md index 589382e17..0adb79526 100644 --- a/docs/topics/web.md +++ b/docs/topics/web.md @@ -30,7 +30,7 @@ The usage of the API isn't any different from using it in Node.js. client.on('message', msg => { const guildTag = msg.channel.type === 'text' ? `[${msg.guild.name}]` : '[DM]'; const channelTag = msg.channel.type === 'text' ? `[#${msg.channel.name}]` : ''; - console.log(`${guildTag}${channelTag} ${msg.author.username}#${msg.author.discriminator}: ${msg.content}`); + console.log(`${guildTag}${channelTag} ${msg.author.tag}: ${msg.content}`); }); client.login('some crazy token'); From 510ddab0f59c98870d7852c28a66b7a6058e737d Mon Sep 17 00:00:00 2001 From: Crawl Date: Thu, 8 Jun 2017 14:50:26 +0200 Subject: [PATCH 44/99] Fix createMessageCollector example --- src/structures/interfaces/TextBasedChannel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index fc70f942f..6bf3a469f 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -320,11 +320,11 @@ class TextBasedChannel { * @returns {MessageCollector} * @example * // Create a message collector - * const collector = channel.createCollector( + * const collector = channel.createMessageCollector( * m => m.content.includes('discord'), * { time: 15000 } * ); - * collector.on('message', m => console.log(`Collected ${m.content}`)); + * collector.on('collect', m => console.log(`Collected ${m.content}`)); * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); */ createMessageCollector(filter, options = {}) { From 830d8fb3b510d45e481bb7c9169ef0253bb76a3a Mon Sep 17 00:00:00 2001 From: Drahcirius Date: Sat, 10 Jun 2017 18:54:17 -0400 Subject: [PATCH 45/99] Remove global flag from ffmpeg tutorial doc (#1582) --- docs/topics/voice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/voice.md b/docs/topics/voice.md index fc9e81237..dc70eb0e9 100644 --- a/docs/topics/voice.md +++ b/docs/topics/voice.md @@ -4,7 +4,7 @@ Voice in discord.js can be used for many things, such as music bots, recording o In discord.js, you can use voice by connecting to a `VoiceChannel` to obtain a `VoiceConnection`, where you can start streaming and receiving audio. To get started, make sure you have: -* ffmpeg - `npm install --global ffmpeg-binaries` +* ffmpeg - `npm install ffmpeg-binaries` * an opus encoder, choose one from below: * `npm install opusscript` * `npm install node-opus` From 86ec60bc0065b9cf23527a6ac755d90e9f6b4a92 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 23 Jun 2017 21:49:56 +0200 Subject: [PATCH 46/99] fix merge conflict --- src/structures/ClientUser.js | 33 ++++++++++++++++++++++++++------- src/structures/Guild.js | 2 +- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index dcd16423c..ff7261d9d 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -299,14 +299,33 @@ class ClientUser extends User { * @param {BufferResolvable|Base64Resolvable} [icon=null] The icon for the guild * @returns {Promise} The guild that was created */ - createGuild(name, region, icon = null) { - if (!icon) return this.client.rest.methods.createGuild({ name, icon, region }); - if (typeof icon === 'string' && icon.startsWith('data:')) { - return this.client.rest.methods.createGuild({ name, icon, region }); - } else { - return this.client.resolver.resolveBuffer(icon).then(data => - this.client.rest.methods.createGuild({ name, icon: data, region }) + + createGuild(name, { region, icon = null } = {}) { + if (!icon || (typeof icon === 'string' && icon.startsWith('data:'))) { + return new Promise((resolve, reject) => + this.client.api.guilds.post({ data: { name, region, icon } }) + .then(data => { + if (this.client.guilds.has(data.id)) return resolve(this.client.guilds.get(data.id)); + + const handleGuild = guild => { + if (guild.id === data.id) { + this.client.removeListener(Constants.Events.GUILD_CREATE, handleGuild); + this.client.clearTimeout(timeout); + resolve(guild); + } + }; + this.client.on(Constants.Events.GUILD_CREATE, handleGuild); + + const timeout = this.client.setTimeout(() => { + this.client.removeListener(Constants.Events.GUILD_CREATE, handleGuild); + resolve(this.client.dataManager.newGuild(data)); + }, 10000); + return undefined; + }, reject) ); + } else { + return this.client.resolver.resolveBuffer(icon) + .then(data => this.createGuild(name, { region, icon: this.client.resolver.resolveBase64(data) || null })); } } diff --git a/src/structures/Guild.js b/src/structures/Guild.js index d1015a8cf..41d2ebd9c 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -62,8 +62,8 @@ class Guild { */ this.id = data.id; } else { - this.available = true; this.setup(data); + if (!data.channels) this.available = false; } } From e3232bdb2bc91a418b09ff411e7ddf105902c396 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Sun, 25 Jun 2017 00:03:37 +0200 Subject: [PATCH 47/99] added Guild#setExplicitContentFilter (#1583) --- src/structures/Guild.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 41d2ebd9c..8fa1e20ef 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -497,6 +497,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 {number} [explicitContentFilter] The level of the explicit content filter * @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 @@ -518,7 +519,28 @@ class Guild { * .catch(console.error); */ edit(data) { - return this.client.rest.methods.updateGuild(this, data); + const _data = {}; + if (data.name) _data.name = data.name; + if (data.region) _data.region = data.region; + if (typeof data.verificationLevel !== 'undefined') _data.verification_level = Number(data.verificationLevel); + 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.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); + if (typeof data.explicitContentFilter !== 'undefined') { + _data.explicit_content_filter = Number(data.explicitContentFilter); + } + return this.client.rest.methods.updateGuild(this, _data); + } + + /** + * Edit the level of the explicit content filter. + * @param {number} explicitContentFilter The new level of the explicit content filter + * @returns {Promise} + */ + setExplicitContentFilter(explicitContentFilter) { + return this.edit({ explicitContentFilter }); } /** From ddfa57e96dac14bd25fd41e6b9c943c3ff61e192 Mon Sep 17 00:00:00 2001 From: Mythic Date: Sun, 25 Jun 2017 01:21:21 +0300 Subject: [PATCH 48/99] Improve Message's ID attribute documentation (#1450) Remove the implication that a Message object's ID is unique only to the channel it was sent on Message ID's are snowflakes, and as stated in Discord's API documentation, globally unique throughout Discord --- 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 7f2a54099..093da2be1 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -33,7 +33,7 @@ class Message { setup(data) { // eslint-disable-line complexity /** - * The ID of the message (unique in the channel it was sent) + * The ID of the message * @type {Snowflake} */ this.id = data.id; From b5de89a973d011a6628ec371362881b7a420aba9 Mon Sep 17 00:00:00 2001 From: aemino Date: Sat, 24 Jun 2017 15:28:49 -0700 Subject: [PATCH 49/99] Fix VoiceConnection#authenticateFailed race condition (#1601) --- 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 ceab3cf14..8fc3a0564 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -245,7 +245,6 @@ class VoiceConnection extends EventEmitter { */ authenticateFailed(reason) { clearTimeout(this.connectTimeout); - this.status = Constants.VoiceStatus.DISCONNECTED; if (this.status === Constants.VoiceStatus.AUTHENTICATING) { /** * Emitted when we fail to initiate a voice connection. @@ -256,6 +255,7 @@ class VoiceConnection extends EventEmitter { } else { this.emit('error', new Error(reason)); } + this.status = Constants.VoiceStatus.DISCONNECTED; } /** From 85ec7c64bc9334d76e95cda89a4a9c8417af4461 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sat, 24 Jun 2017 17:36:48 -0500 Subject: [PATCH 50/99] update docs for discord api error (#1575) * aaaaa * Update DiscordAPIError.js --- src/client/rest/DiscordAPIError.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/rest/DiscordAPIError.js b/src/client/rest/DiscordAPIError.js index 7d3ff7ef9..6b5dec6f9 100644 --- a/src/client/rest/DiscordAPIError.js +++ b/src/client/rest/DiscordAPIError.js @@ -1,5 +1,6 @@ /** * Represents an error from the Discord API. + * @extends Error */ class DiscordAPIError extends Error { constructor(error) { @@ -20,6 +21,7 @@ class DiscordAPIError extends Error { * @param {Object} obj Discord errors object * @param {string} [key] Used internally to determine key names of nested fields * @returns {string[]} + * @private */ static flattenErrors(obj, key = '') { let messages = []; From f1a74f214e7c66a754dbbad98a9ccca70cb389c7 Mon Sep 17 00:00:00 2001 From: Will Nelson Date: Sun, 25 Jun 2017 12:48:30 -0700 Subject: [PATCH 51/99] make token not enumerable (#1620) --- src/client/Client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/Client.js b/src/client/Client.js index 861becb2c..51e441cb4 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -120,6 +120,7 @@ class Client extends EventEmitter { */ this.presences = new Collection(); + Object.defineProperty(this, 'token', { writable: true }); if (!this.token && 'CLIENT_TOKEN' in process.env) { /** * Authorization token for the logged in user/bot From 4ce4dc019eeaa21d5b49d7ce800d3f84edee6555 Mon Sep 17 00:00:00 2001 From: Drahcirius Date: Tue, 27 Jun 2017 15:22:17 -0400 Subject: [PATCH 52/99] setTimeout should use args (#1623) --- src/client/Client.js | 4 ++-- src/client/WebhookClient.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 51e441cb4..97bbbdd96 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -420,9 +420,9 @@ class Client extends EventEmitter { */ setTimeout(fn, delay, ...args) { const timeout = setTimeout(() => { - fn(); + fn(...args); this._timeouts.delete(timeout); - }, delay, ...args); + }, delay); this._timeouts.add(timeout); return timeout; } diff --git a/src/client/WebhookClient.js b/src/client/WebhookClient.js index f89c8f973..99291b550 100644 --- a/src/client/WebhookClient.js +++ b/src/client/WebhookClient.js @@ -65,9 +65,9 @@ class WebhookClient extends Webhook { */ setTimeout(fn, delay, ...args) { const timeout = setTimeout(() => { - fn(); + fn(...args); this._timeouts.delete(timeout); - }, delay, ...args); + }, delay); this._timeouts.add(timeout); return timeout; } From 822c1f533cf2a215c93814ba4f5a1a4b5ae7a557 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 28 Jun 2017 09:27:29 -0500 Subject: [PATCH 53/99] Fix toLowerCase errors in GuildAuditLogs (#1627) --- src/structures/GuildAuditLogs.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index decc940c2..2db442870 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -2,6 +2,7 @@ const Collection = require('../util/Collection'); const Snowflake = require('../util/Snowflake'); const Targets = { + ALL: 'ALL', GUILD: 'GUILD', CHANNEL: 'CHANNEL', USER: 'USER', @@ -13,6 +14,7 @@ const Targets = { }; const Actions = { + ALL: null, GUILD_UPDATE: 1, CHANNEL_CREATE: 10, CHANNEL_UPDATE: 11, From 85615aa3a14e611342eb142a93488f0c8f20ee4c Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 28 Jun 2017 09:29:49 -0500 Subject: [PATCH 54/99] update tern file to actually work (#1630) --- .tern-project | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.tern-project b/.tern-project index cc31d86e5..8f37bf06e 100644 --- a/.tern-project +++ b/.tern-project @@ -1,11 +1,21 @@ { - "ecmaVersion": 6, + "ecmaVersion": 7, "libs": [], + "loadEagerly": [ + "./src/*.js" + ], + "dontLoad": [ + "node_modules/**" + ], "plugins": { - "node": { - "dontLoad": "node_modules/**", - "load": "", - "modules": "" + "es_modules": {}, + "node": {}, + "doc_comment": { + "fullDocs": true, + "strong": true + }, + "webpack": { + "configPath": "./webpack.config.js", } } } From b9434ed112d8bf1bd9ac803dd9583f514fca6a6e Mon Sep 17 00:00:00 2001 From: aemino Date: Sat, 1 Jul 2017 17:20:35 -0700 Subject: [PATCH 55/99] Expose DiscordAPIError and API error constants (#1641) * Expose DiscordAPIError * Expose API error constants * Add typedef for APIError * Integligently forgot to save file --- src/index.js | 1 + src/util/Constants.js | 93 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/index.js b/src/index.js index bbf84c8a1..37c06d96e 100644 --- a/src/index.js +++ b/src/index.js @@ -11,6 +11,7 @@ module.exports = { // Utilities Collection: require('./util/Collection'), Constants: require('./util/Constants'), + DiscordAPIError: require('./client/rest/DiscordAPIError'), EvaluatedPermissions: require('./util/Permissions'), Permissions: require('./util/Permissions'), Snowflake: require('./util/Snowflake'), diff --git a/src/util/Constants.js b/src/util/Constants.js index 61ca12d37..562445376 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -594,3 +594,96 @@ exports.Colors = { DARK_BUT_NOT_BLACK: 0x2C2F33, NOT_QUITE_BLACK: 0x23272A, }; + +/** + * An error encountered while performing an API request. Here are the potential errors: + * - UNKNOWN_ACCOUNT + * - UNKNOWN_APPLICATION + * - UNKNOWN_CHANNEL + * - UNKNOWN_GUILD + * - UNKNOWN_INTEGRATION + * - UNKNOWN_INVITE + * - UNKNOWN_MEMBER + * - UNKNOWN_MESSAGE + * - UNKNOWN_OVERWRITE + * - UNKNOWN_PROVIDER + * - UNKNOWN_ROLE + * - UNKNOWN_TOKEN + * - UNKNOWN_USER + * - UNKNOWN_EMOJI + * - BOT_PROHIBITED_ENDPOINT + * - BOT_ONLY_ENDPOINT + * - MAXIMUM_GUILDS + * - MAXIMUM_FRIENDS + * - MAXIMUM_PINS + * - MAXIMUM_ROLES + * - MAXIMUM_REACTIONS + * - UNAUTHORIZED + * - MISSING_ACCESS + * - INVALID_ACCOUNT_TYPE + * - CANNOT_EXECUTE_ON_DM + * - EMBED_DISABLED + * - CANNOT_EDIT_MESSAGE_BY_OTHER + * - CANNOT_SEND_EMPTY_MESSAGE + * - CANNOT_MESSAGE_USER + * - CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL + * - CHANNEL_VERIFICATION_LEVEL_TOO_HIGH + * - OAUTH2_APPLICATION_BOT_ABSENT + * - MAXIMUM_OAUTH2_APPLICATIONS + * - INVALID_OAUTH_STATE + * - MISSING_PERMISSIONS + * - INVALID_AUTHENTICATION_TOKEN + * - NOTE_TOO_LONG + * - INVALID_BULK_DELETE_QUANTITY + * - CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL + * - CANNOT_EXECUTE_ON_SYSTEM_MESSAGE + * - BULK_DELETE_MESSAGE_TOO_OLD + * - INVITE_ACCEPTED_TO_GUILD_NOT_CONTANING_BOT + * - REACTION_BLOCKED + * @typedef {string} APIError + */ +exports.APIErrors = { + UNKNOWN_ACCOUNT: 10001, + UNKNOWN_APPLICATION: 10002, + UNKNOWN_CHANNEL: 10003, + UNKNOWN_GUILD: 10004, + UNKNOWN_INTEGRATION: 10005, + UNKNOWN_INVITE: 10006, + UNKNOWN_MEMBER: 10007, + UNKNOWN_MESSAGE: 10008, + UNKNOWN_OVERWRITE: 10009, + UNKNOWN_PROVIDER: 10010, + UNKNOWN_ROLE: 10011, + UNKNOWN_TOKEN: 10012, + UNKNOWN_USER: 10013, + UNKNOWN_EMOJI: 10014, + BOT_PROHIBITED_ENDPOINT: 20001, + BOT_ONLY_ENDPOINT: 20002, + MAXIMUM_GUILDS: 30001, + MAXIMUM_FRIENDS: 30002, + MAXIMUM_PINS: 30003, + MAXIMUM_ROLES: 30005, + MAXIMUM_REACTIONS: 30010, + UNAUTHORIZED: 40001, + MISSING_ACCESS: 50001, + INVALID_ACCOUNT_TYPE: 50002, + CANNOT_EXECUTE_ON_DM: 50003, + EMBED_DISABLED: 50004, + CANNOT_EDIT_MESSAGE_BY_OTHER: 50005, + CANNOT_SEND_EMPTY_MESSAGE: 50006, + CANNOT_MESSAGE_USER: 50007, + CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: 50008, + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: 50009, + OAUTH2_APPLICATION_BOT_ABSENT: 50010, + MAXIMUM_OAUTH2_APPLICATIONS: 50011, + INVALID_OAUTH_STATE: 50012, + MISSING_PERMISSIONS: 50013, + INVALID_AUTHENTICATION_TOKEN: 50014, + NOTE_TOO_LONG: 50015, + INVALID_BULK_DELETE_QUANTITY: 50016, + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: 50019, + CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: 50021, + BULK_DELETE_MESSAGE_TOO_OLD: 50034, + INVITE_ACCEPTED_TO_GUILD_NOT_CONTANING_BOT: 50036, + REACTION_BLOCKED: 90001, +}; From ed84d76a42d468c6c50461684f1b7a19918e46ec Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 16 Jul 2017 07:04:40 -0500 Subject: [PATCH 56/99] move nsfw to the new prop (#1687) --- src/structures/TextChannel.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 20b3c54f3..d9da21f7a 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -24,6 +24,13 @@ class TextChannel extends GuildChannel { */ this.topic = data.topic; + /** + * If the Discord considers this channel NSFW + * @type {boolean} + * @readonly + */ + this.nsfw = data.nsfw; + this.lastMessageID = data.last_message_id; } @@ -42,15 +49,6 @@ class TextChannel extends GuildChannel { return members; } - /** - * If the Discord considers this channel NSFW - * @type {boolean} - * @readonly - */ - get nsfw() { - return /^nsfw(-|$)/.test(this.name); - } - /** * Fetch all webhooks for the channel. * @returns {Promise>} From 26e5ef3205f98420f9e5598efaa221793d9cd02d Mon Sep 17 00:00:00 2001 From: Crawl Date: Sun, 16 Jul 2017 14:34:23 +0200 Subject: [PATCH 57/99] Update deps and fix webpack --- package.json | 15 +++++++-------- src/client/ClientDataResolver.js | 10 +++++----- src/client/rest/RESTMethods.js | 2 +- src/structures/Guild.js | 4 ++-- src/structures/MessageCollector.js | 1 - src/structures/ReactionCollector.js | 1 - src/structures/TextChannel.js | 2 +- src/util/Permissions.js | 4 ++-- webpack.config.js | 3 ++- 9 files changed, 20 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 58507a799..31ec625d5 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,9 @@ "dependencies": { "long": "^3.2.0", "prism-media": "^0.0.1", - "snekfetch": "^3.1.0", - "tweetnacl": "^0.14.0", - "ws": "^2.0.0" + "snekfetch": "^3.2.0", + "tweetnacl": "^1.0.0", + "ws": "^3.0.0" }, "peerDependencies": { "bufferutil": "^3.0.0", @@ -50,11 +50,10 @@ "devDependencies": { "@types/node": "^7.0.0", "discord.js-docgen": "hydrabolt/discord.js-docgen", - "eslint": "^3.19.0", - "parallel-webpack": "^1.6.0", - "uglify-js": "mishoo/UglifyJS2#harmony-v2.8.22", - "uglifyjs-webpack-plugin": "^0.4.3", - "webpack": "^2.2.0" + "eslint": "^4.2.0", + "parallel-webpack": "^2.0.0", + "uglifyjs-webpack-plugin": "^1.0.0-beta.1", + "webpack": "^3.0.0" }, "engines": { "node": ">=6.0.0" diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 04d6b0b01..a7f3e2508 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -212,11 +212,11 @@ class ClientDataResolver { return new Promise((resolve, reject) => { if (/^https?:\/\//.test(resource)) { snekfetch.get(resource) - .end((err, res) => { - if (err) return reject(err); - if (!(res.body instanceof Buffer)) return reject(new TypeError('The response body isn\'t a Buffer.')); - return resolve(res.body); - }); + .end((err, res) => { + if (err) return reject(err); + if (!(res.body instanceof Buffer)) return reject(new TypeError('The response body isn\'t 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 641468031..01ac61f62 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -838,7 +838,7 @@ class RESTMethods { 'put', Endpoints.Message(message).Reaction(emoji).User('@me'), true ).then(() => message._addReaction(Util.parseEmoji(emoji), message.client.user) - ); + ); } removeMessageReaction(message, emoji, userID) { diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 8fa1e20ef..8b0544d8e 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -1087,8 +1087,8 @@ class Guild { _sortPositionWithID(collection) { return collection.sort((a, b) => a.position !== b.position ? - a.position - b.position : - Long.fromString(a.id).sub(Long.fromString(b.id)).toNumber() + a.position - b.position : + Long.fromString(a.id).sub(Long.fromString(b.id)).toNumber() ); } } diff --git a/src/structures/MessageCollector.js b/src/structures/MessageCollector.js index fd5bebda4..cc77a86c0 100644 --- a/src/structures/MessageCollector.js +++ b/src/structures/MessageCollector.js @@ -12,7 +12,6 @@ const util = require('util'); * @extends {Collector} */ class MessageCollector extends Collector { - /** * @param {TextChannel|DMChannel|GroupDMChannel} channel The channel * @param {CollectorFilter} filter The filter to be applied to this collector diff --git a/src/structures/ReactionCollector.js b/src/structures/ReactionCollector.js index 5e0b7d5a1..a54687d32 100644 --- a/src/structures/ReactionCollector.js +++ b/src/structures/ReactionCollector.js @@ -13,7 +13,6 @@ const Collection = require('../util/Collection'); * @extends {Collector} */ class ReactionCollector extends Collector { - /** * @param {Message} message The message upon which to collect reactions * @param {CollectorFilter} filter The filter to apply to this collector diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index d9da21f7a..ad0372870 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -73,7 +73,7 @@ class TextChannel extends GuildChannel { 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)) + resolve(this.client.rest.methods.createWebhook(this, name, data)) ); } }); diff --git a/src/util/Permissions.js b/src/util/Permissions.js index 5e56f2ae9..c24337672 100644 --- a/src/util/Permissions.js +++ b/src/util/Permissions.js @@ -268,8 +268,8 @@ Permissions.prototype.missingPermissions = util.deprecate(Permissions.prototype. 'EvaluatedPermissions#missingPermissions is deprecated, use Permissions#missing instead'); Object.defineProperty(Permissions.prototype, 'member', { get: util - .deprecate(Object.getOwnPropertyDescriptor(Permissions.prototype, 'member').get, - 'EvaluatedPermissions#member is deprecated'), + .deprecate(Object.getOwnPropertyDescriptor(Permissions.prototype, 'member').get, + 'EvaluatedPermissions#member is deprecated'), }); module.exports = Permissions; diff --git a/webpack.config.js b/webpack.config.js index dd4000fd9..55d0e12f6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,9 +11,10 @@ const version = require('./package.json').version; const createConfig = options => { const plugins = [ new webpack.DefinePlugin({ 'global.GENTLY': false }), + new webpack.optimize.ModuleConcatenationPlugin(), ]; - if (options.minify) plugins.push(new UglifyJSPlugin({ minimize: true })); + if (options.minify) plugins.push(new UglifyJSPlugin({ uglifyOptions: { output: { comments: false } } })); const filename = `./webpack/discord${process.env.VERSIONED === 'false' ? '' : '.' + version}${options.minify ? '.min' : ''}.js`; // eslint-disable-line From e91a2c6b2bc2c0032ce7ccb33d324417bc89118e Mon Sep 17 00:00:00 2001 From: Crawl Date: Fri, 21 Jul 2017 15:34:30 +0200 Subject: [PATCH 58/99] Update typings --- typings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings b/typings index 99e19b9d7..fec5a7b21 160000 --- a/typings +++ b/typings @@ -1 +1 @@ -Subproject commit 99e19b9d76dde5f1498b58bb9261042d9afa055c +Subproject commit fec5a7b211a05839be71208bf51a86af19d36459 From b27198ebe5bf6eb2614b77295dc2593bdcf14c72 Mon Sep 17 00:00:00 2001 From: Crawl Date: Fri, 28 Jul 2017 04:53:36 +0200 Subject: [PATCH 59/99] Update nsfw prop --- src/structures/TextChannel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index ad0372870..252e3460b 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -29,7 +29,7 @@ class TextChannel extends GuildChannel { * @type {boolean} * @readonly */ - this.nsfw = data.nsfw; + this.nsfw = !!data.nsfw; this.lastMessageID = data.last_message_id; } From 46a50cb57c41ceb1a56dc3bbc6cb8a11e70bc6a0 Mon Sep 17 00:00:00 2001 From: Crawl Date: Fri, 28 Jul 2017 05:20:28 +0200 Subject: [PATCH 60/99] Fix "shitty" shortcut --- src/structures/TextChannel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 252e3460b..d7f0a73f3 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -29,7 +29,7 @@ class TextChannel extends GuildChannel { * @type {boolean} * @readonly */ - this.nsfw = !!data.nsfw; + this.nsfw = Boolean(data.nsfw); this.lastMessageID = data.last_message_id; } From e7ebb23f14f1b6d68ac6a6036b1a0625a6b35029 Mon Sep 17 00:00:00 2001 From: bdistin Date: Sun, 30 Jul 2017 08:00:01 -0500 Subject: [PATCH 61/99] Fix guildMembersChunk documents for v11.1-dev (#1734) --- src/client/websocket/packets/handlers/GuildMembersChunk.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/websocket/packets/handlers/GuildMembersChunk.js b/src/client/websocket/packets/handlers/GuildMembersChunk.js index 835bcb573..445864438 100644 --- a/src/client/websocket/packets/handlers/GuildMembersChunk.js +++ b/src/client/websocket/packets/handlers/GuildMembersChunk.js @@ -26,7 +26,7 @@ class GuildMembersChunkHandler extends AbstractHandler { /** * Emitted whenever a chunk of guild members is received (all members come from the same guild). * @event Client#guildMembersChunk - * @param {Collection} members The members in the chunk + * @param {GuildMember[]} members The members in the chunk * @param {Guild} guild The guild related to the member chunk */ From a56a24d042c3ba5a8066b5baddf64fb8542564b9 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Mon, 31 Jul 2017 01:18:55 +0200 Subject: [PATCH 62/99] Fixed DiscordAPIError#message sometimes being undefined --- src/client/rest/DiscordAPIError.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/client/rest/DiscordAPIError.js b/src/client/rest/DiscordAPIError.js index 6b5dec6f9..d1b3f134a 100644 --- a/src/client/rest/DiscordAPIError.js +++ b/src/client/rest/DiscordAPIError.js @@ -5,9 +5,9 @@ class DiscordAPIError extends Error { constructor(error) { super(); - const flattened = error.errors ? `\n${this.constructor.flattenErrors(error.errors).join('\n')}` : ''; + const flattened = this.constructor.flattenErrors(error.errors || error).join('\n'); this.name = 'DiscordAPIError'; - this.message = `${error.message}${flattened}`; + this.message = error.message && flattened ? `${error.message}\n${flattened}` : error.message || flattened; /** * HTTP error code returned by Discord @@ -32,7 +32,9 @@ class DiscordAPIError extends Error { if (obj[k]._errors) { messages.push(`${newKey}: ${obj[k]._errors.map(e => e.message).join(' ')}`); } else if (obj[k].code && obj[k].message) { - messages.push(`${obj[k].code}: ${obj[k].message}`); + messages.push(`${obj[k].code ? `${obj[k].code}: `: ''}: ${obj[k].message}`.trim()); + } else if (typeof obj[k] === 'string') { + messages.push(obj[k]); } else { messages = messages.concat(this.flattenErrors(obj[k], newKey)); } From bdc61a4068146d52b5cc39da154062b9f3ce5c39 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Mon, 31 Jul 2017 01:29:46 +0200 Subject: [PATCH 63/99] Wrong operator in flattenErrors --- src/client/rest/DiscordAPIError.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/rest/DiscordAPIError.js b/src/client/rest/DiscordAPIError.js index d1b3f134a..6e1e31d68 100644 --- a/src/client/rest/DiscordAPIError.js +++ b/src/client/rest/DiscordAPIError.js @@ -31,8 +31,8 @@ class DiscordAPIError extends Error { if (obj[k]._errors) { messages.push(`${newKey}: ${obj[k]._errors.map(e => e.message).join(' ')}`); - } else if (obj[k].code && obj[k].message) { - messages.push(`${obj[k].code ? `${obj[k].code}: `: ''}: ${obj[k].message}`.trim()); + } else if (obj[k].code || obj[k].message) { + messages.push(`${obj[k].code ? `${obj[k].code}: ` : ''}: ${obj[k].message}`.trim()); } else if (typeof obj[k] === 'string') { messages.push(obj[k]); } else { From 8a9b6cbdb5a0df837e202babc300486848790364 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Mon, 31 Jul 2017 01:32:44 +0200 Subject: [PATCH 64/99] Stop doubling the message key --- src/client/rest/DiscordAPIError.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/rest/DiscordAPIError.js b/src/client/rest/DiscordAPIError.js index 6e1e31d68..be1b82786 100644 --- a/src/client/rest/DiscordAPIError.js +++ b/src/client/rest/DiscordAPIError.js @@ -27,6 +27,7 @@ class DiscordAPIError extends Error { let messages = []; for (const k of Object.keys(obj)) { + if (k === 'message') continue; const newKey = key ? isNaN(k) ? `${key}.${k}` : `${key}[${k}]` : k; if (obj[k]._errors) { From 59122a6ba4314384091cad7a16b246ec1ab73ef1 Mon Sep 17 00:00:00 2001 From: Crawl Date: Tue, 1 Aug 2017 04:39:44 +0200 Subject: [PATCH 65/99] Update typings --- typings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings b/typings index fec5a7b21..0967675a2 160000 --- a/typings +++ b/typings @@ -1 +1 @@ -Subproject commit fec5a7b211a05839be71208bf51a86af19d36459 +Subproject commit 0967675a2f8e6fa46ab543f955af82a82230d17a From 407500bf52c932fc184ec558df637d929dd417f5 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Fri, 4 Aug 2017 03:44:35 -0500 Subject: [PATCH 66/99] deprecate Guild#defaultChannel (#1752) --- .npmrc | 1 + src/structures/Guild.js | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..579e0e4d9 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-json=false diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 8b0544d8e..b801ff6d9 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -1,3 +1,4 @@ +const util = require('util'); const Long = require('long'); const User = require('./User'); const Role = require('./Role'); @@ -291,15 +292,6 @@ class Guild { return this.client.voice.connections.get(this.id) || null; } - /** - * The `#general` TextChannel of the guild - * @type {TextChannel} - * @readonly - */ - get defaultChannel() { - return this.channels.get(this.id); - } - /** * The position of this guild * This is only available when using a user account. @@ -1093,4 +1085,16 @@ class Guild { } } +/** + * The `#general` TextChannel of the guild + * @name Guild#defaultChannel + * @type {TextChannel} + * @readonly + */ +Object.defineProperty(Guild.prototype, 'defaultChannel', { + get: util.deprecate(function defaultChannel() { + return this.channels.get(this.id); + }, 'Guild#defaultChannel: This property is obsolete, will be removed in v12.0.0, and may not function as expected.'), +}); + module.exports = Guild; From 7aa791eaaac119570d06718a1c33dc1724d55f33 Mon Sep 17 00:00:00 2001 From: aemino Date: Sat, 1 Jul 2017 02:14:41 -0700 Subject: [PATCH 67/99] Ignore setSpeaking requests when VC isn't connected (#1638) --- src/client/voice/VoiceConnection.js | 1 + src/client/voice/dispatcher/StreamDispatcher.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 8fc3a0564..f7539021c 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -132,6 +132,7 @@ class VoiceConnection extends EventEmitter { */ setSpeaking(value) { if (this.speaking === value) return; + if (this.status !== Constants.VoiceStatus.CONNECTED) return; this.speaking = value; this.sockets.ws.sendPacket({ op: Constants.VoiceOPCodes.SPEAKING, diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index 5e3735627..68c83d380 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -1,5 +1,6 @@ const VolumeInterface = require('../util/VolumeInterface'); const VoiceBroadcast = require('../VoiceBroadcast'); +const Constants = require('../../../util/Constants'); const secretbox = require('../util/Secretbox'); @@ -108,6 +109,7 @@ class StreamDispatcher extends VolumeInterface { setSpeaking(value) { if (this.speaking === value) return; + if (this.player.voiceConnection.status !== Constants.VoiceStatus.CONNECTED) return; this.speaking = value; /** * Emitted when the dispatcher starts/stops speaking. From d513c4bbb9bb845fc64dcfe250ff0f1b95c0ee2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89velyne=20Lachance?= Date: Wed, 7 Jun 2017 18:52:41 -0400 Subject: [PATCH 68/99] Add `count` optional argument to Collection methods (#1552) * Add `count` optional argument to Collection methods [NON-BREAKING CHANGE] An optional `count` argument is added to the following methods: - random() and randomKey() - first() and firstKey() - last() and lastKey() If `count` is used, the method returns an array instead of only the value. Performance impact non-existent for existing code. Performance for returning an array has been measured and this is the fastest I could find (array[i] = value is faster than array.push()). * Update Collection.js Fixed spacing/line length errors according to suggestions by codacy/pr * Fixed docs Added proper `@returns {*|Array}` as the methods might return either. Also added params where missing (whoops) * Further doc fixes Per Crawl's comments, fixed (i + 1) spacing as well as fixed {Integer} to {number} * random() and randomKey() fix Per Hydra's comment, random() and randomKey() now ensures unique values. I've also resolved potential issues with requesting a count higher than the collection size. A collection with 10 items will only ever return at most 10 items using the `count` property. * Can I facepalm harder Had wrong header comments ^_^ * Fixed for "values/value" and Omited Also, added "Positive" integer check. * looks like I "omitted" a change, there. * Update Collection.js * Update Collection.js * Update Collection.js --- src/util/Collection.js | 100 ++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/src/util/Collection.js b/src/util/Collection.js index 620d4565d..b95af8562 100644 --- a/src/util/Collection.js +++ b/src/util/Collection.js @@ -59,59 +59,99 @@ class Collection extends Map { } /** - * Obtains the first item in this collection. - * @returns {*} + * Obtains the first value(s) in this collection. + * @param {number} [count] Number of values to obtain from the beginning + * @returns {*|Array<*>} The single value if `count` is undefined, or an array of values of `count` length */ - first() { - return this.values().next().value; + first(count) { + if (count === undefined) return this.values().next().value; + if (typeof count !== 'number') throw new TypeError('The count must be a number.'); + if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.'); + count = Math.min(this.size, count); + const arr = new Array(count); + const iter = this.values(); + for (let i = 0; i < count; i++) arr[i] = iter.next().value; + return arr; } /** - * Obtains the first key in this collection. - * @returns {*} + * Obtains the first key(s) in this collection. + * @param {number} [count] Number of keys to obtain from the beginning + * @returns {*|Array<*>} The single key if `count` is undefined, or an array of keys of `count` length */ - firstKey() { - return this.keys().next().value; + firstKey(count) { + if (count === undefined) return this.keys().next().value; + if (typeof count !== 'number') throw new TypeError('The count must be a number.'); + if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.'); + count = Math.min(this.size, count); + const arr = new Array(count); + const iter = this.iter(); + for (let i = 0; i < count; i++) arr[i] = iter.next().value; + return arr; } /** - * Obtains the last item in this collection. This relies on the `array()` method, and thus the caching mechanism - * applies here as well. - * @returns {*} + * Obtains the last value(s) in this collection. This relies on {@link Collection#array}, and thus the caching + * mechanism applies here as well. + * @param {number} [count] Number of values to obtain from the end + * @returns {*|Array<*>} The single value if `count` is undefined, or an array of values of `count` length */ - last() { + last(count) { const arr = this.array(); - return arr[arr.length - 1]; + if (count === undefined) return arr[arr.length - 1]; + if (typeof count !== 'number') throw new TypeError('The count must be a number.'); + if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.'); + return arr.slice(-count); } /** - * Obtains the last key in this collection. This relies on the `keyArray()` method, and thus the caching mechanism - * applies here as well. - * @returns {*} + * Obtains the last key(s) in this collection. This relies on {@link Collection#keyArray}, and thus the caching + * mechanism applies here as well. + * @param {number} [count] Number of keys to obtain from the end + * @returns {*|Array<*>} The single key if `count` is undefined, or an array of keys of `count` length */ - lastKey() { + lastKey(count) { const arr = this.keyArray(); - return arr[arr.length - 1]; + if (count === undefined) return arr[arr.length - 1]; + if (typeof count !== 'number') throw new TypeError('The count must be a number.'); + if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.'); + return arr.slice(-count); } /** - * Obtains a random item from this collection. This relies on the `array()` method, and thus the caching mechanism - * applies here as well. - * @returns {*} + * Obtains random value(s) from this collection. This relies on {@link Collection#array}, and thus the caching + * mechanism applies here as well. + * @param {number} [count] Number of values to obtain randomly + * @returns {*|Array<*>} The single value if `count` is undefined, or an array of values of `count` length */ - random() { - const arr = this.array(); - return arr[Math.floor(Math.random() * arr.length)]; + random(count) { + let arr = this.array(); + if (count === undefined) return arr[Math.floor(Math.random() * arr.length)]; + if (typeof count !== 'number') throw new TypeError('The count must be a number.'); + if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.'); + if (arr.length === 0) return []; + const rand = new Array(count); + arr = arr.slice(); + for (let i = 0; i < count; i++) rand[i] = arr.splice(Math.floor(Math.random() * arr.length), 1)[0]; + return rand; } /** - * Obtains a random key from this collection. This relies on the `keyArray()` method, and thus the caching mechanism - * applies here as well. - * @returns {*} + * Obtains random key(s) from this collection. This relies on {@link Collection#keyArray}, and thus the caching + * mechanism applies here as well. + * @param {number} [count] Number of keys to obtain randomly + * @returns {*|Array<*>} The single key if `count` is undefined, or an array of keys of `count` length */ - randomKey() { - const arr = this.keyArray(); - return arr[Math.floor(Math.random() * arr.length)]; + randomKey(count) { + let arr = this.keyArray(); + if (count === undefined) return arr[Math.floor(Math.random() * arr.length)]; + if (typeof count !== 'number') throw new TypeError('The count must be a number.'); + if (!Number.isInteger(count) || count < 1) throw new RangeError('The count must be an integer greater than 0.'); + if (arr.length === 0) return []; + const rand = new Array(count); + arr = arr.slice(); + for (let i = 0; i < count; i++) rand[i] = arr.splice(Math.floor(Math.random() * arr.length), 1)[0]; + return rand; } /** From cba4cc2400038bb914fc0adf64ad80383601eade Mon Sep 17 00:00:00 2001 From: Crawl Date: Sat, 12 Aug 2017 11:23:47 +0200 Subject: [PATCH 69/99] Audio bitrate support (#1439) * Audio bitrate support Note: not implemented for VoiceBroadcasts * Fix default args, auto bitrate * Late night typos are the best * Changes bitrate to kbps for VoiceChannel stuff * Add methods to manipulate bitrate while encoding --- src/client/voice/VoiceBroadcast.js | 34 +++++++------- src/client/voice/VoiceConnection.js | 11 +++-- .../voice/dispatcher/StreamDispatcher.js | 10 +++++ src/client/voice/opus/BaseOpusEngine.js | 27 +++++++++--- src/client/voice/opus/NodeOpusEngine.js | 6 ++- src/client/voice/opus/OpusEngineList.js | 5 +-- src/client/voice/opus/OpusScriptEngine.js | 6 ++- src/client/voice/player/AudioPlayer.js | 44 ++++++++++++------- src/structures/VoiceChannel.js | 9 ++-- 9 files changed, 97 insertions(+), 55 deletions(-) diff --git a/src/client/voice/VoiceBroadcast.js b/src/client/voice/VoiceBroadcast.js index c4562f17f..76513d86a 100644 --- a/src/client/voice/VoiceBroadcast.js +++ b/src/client/voice/VoiceBroadcast.js @@ -143,8 +143,8 @@ class VoiceBroadcast extends VolumeInterface { * }) * .catch(console.error); */ - playStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) { - const options = { seek, volume, passes, stream }; + playStream(stream, options = {}) { + this.setVolume(options.volume || 1); return this._playTranscodable(stream, options); } @@ -164,19 +164,17 @@ class VoiceBroadcast extends VolumeInterface { * }) * .catch(console.error); */ - playFile(file, { seek = 0, volume = 1, passes = 1 } = {}) { - const options = { seek, volume, passes }; + playFile(file, options = {}) { + this.setVolume(options.volume || 1); return this._playTranscodable(`file:${file}`, options); } _playTranscodable(media, options) { - OpusEncoders.guaranteeOpusEngine(); - this.killCurrentTranscoder(); const transcoder = this.prism.transcode({ type: 'ffmpeg', media, - ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek)]), + ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek || 0)]), }); /** * Emitted whenever an error occurs. @@ -206,31 +204,28 @@ class VoiceBroadcast extends VolumeInterface { } /** - * Plays a stream of 16-bit signed stereo PCM at 48KHz. + * Plays a stream of 16-bit signed stereo PCM. * @param {ReadableStream} stream The audio stream to play * @param {StreamOptions} [options] Options for playing the stream * @returns {VoiceBroadcast} */ - playConvertedStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) { - OpusEncoders.guaranteeOpusEngine(); - + playConvertedStream(stream, options = {}) { this.killCurrentTranscoder(); - const options = { seek, volume, passes, stream }; - this.currentTranscoder = { options }; + this.setVolume(options.volume || 1); + this.currentTranscoder = { options: { stream } }; stream.once('readable', () => this._startPlaying()); return this; } /** - * Plays an Opus encoded stream at 48KHz. + * Plays an Opus encoded stream. * Note that inline volume is not compatible with this method. * @param {ReadableStream} stream The Opus audio stream to play * @param {StreamOptions} [options] Options for playing the stream * @returns {StreamDispatcher} */ - playOpusStream(stream, { seek = 0, passes = 1 } = {}) { - const options = { seek, passes, stream }; - this.currentTranscoder = { options, opus: true }; + playOpusStream(stream) { + this.currentTranscoder = { options: { stream }, opus: true }; stream.once('readable', () => this._startPlaying()); return this; } @@ -241,8 +236,9 @@ class VoiceBroadcast extends VolumeInterface { * @param {StreamOptions} [options] Options for playing the stream * @returns {VoiceBroadcast} */ - playArbitraryInput(input, { seek = 0, volume = 1, passes = 1 } = {}) { - const options = { seek, volume, passes, input }; + playArbitraryInput(input, options = {}) { + this.setVolume(options.volume || 1); + options.input = input; return this._playTranscodable(input, options); } diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index f7539021c..9163db449 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -431,6 +431,8 @@ class VoiceConnection extends EventEmitter { * @property {number} [seek=0] The time to seek to * @property {number} [volume=1] The volume to play at * @property {number} [passes=1] How many times to send the voice packet to reduce packet loss + * @property {number|string} [bitrate=48000] The bitrate (quality) of the audio. + * If set to 'auto', the voice channel's bitrate will be used */ /** @@ -481,7 +483,7 @@ class VoiceConnection extends EventEmitter { } /** - * Plays a stream of 16-bit signed stereo PCM at 48KHz. + * Plays a stream of 16-bit signed stereo PCM. * @param {ReadableStream} stream The audio stream to play * @param {StreamOptions} [options] Options for playing the stream * @returns {StreamDispatcher} @@ -491,7 +493,7 @@ class VoiceConnection extends EventEmitter { } /** - * Plays an Opus encoded stream at 48KHz. + * Plays an Opus encoded stream. * Note that inline volume is not compatible with this method. * @param {ReadableStream} stream The Opus audio stream to play * @param {StreamOptions} [options] Options for playing the stream @@ -504,6 +506,7 @@ class VoiceConnection extends EventEmitter { /** * Plays a voice broadcast. * @param {VoiceBroadcast} broadcast The broadcast to play + * @param {StreamOptions} [options] Options for playing the stream * @returns {StreamDispatcher} * @example * // Play a broadcast @@ -512,8 +515,8 @@ class VoiceConnection extends EventEmitter { * .playFile('./test.mp3'); * const dispatcher = voiceConnection.playBroadcast(broadcast); */ - playBroadcast(broadcast) { - return this.player.playBroadcast(broadcast); + playBroadcast(broadcast, options) { + return this.player.playBroadcast(broadcast, options); } /** diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index 68c83d380..24df9d069 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -119,6 +119,16 @@ class StreamDispatcher extends VolumeInterface { this.emit('speaking', value); } + + /** + * Set the bitrate of the current Opus encoder. + * @param {number} bitrate New bitrate, in kbps. + * If set to 'auto', the voice channel's bitrate will be used + */ + setBitrate(bitrate) { + this.player.setBitrate(bitrate); + } + sendBuffer(buffer, sequence, timestamp, opusPacket) { opusPacket = opusPacket || this.player.opusEncoder.encode(buffer); const packet = this.createPacket(sequence, timestamp, opusPacket); diff --git a/src/client/voice/opus/BaseOpusEngine.js b/src/client/voice/opus/BaseOpusEngine.js index b63b695f6..40c6204fd 100644 --- a/src/client/voice/opus/BaseOpusEngine.js +++ b/src/client/voice/opus/BaseOpusEngine.js @@ -4,21 +4,38 @@ */ class BaseOpus { /** - * @param {Object} [options] The options to apply to the Opus engine - * @param {boolean} [options.fec] Whether to enable forward error correction (defaults to false) - * @param {number} [options.plp] The expected packet loss percentage (0-1 inclusive, defaults to 0) + * @param {Object} [options] The options to apply to the Opus engine. + * @param {number} [options.bitrate=48] The desired bitrate (kbps). + * @param {boolean} [options.fec=false] Whether to enable forward error correction. + * @param {number} [options.plp=0] The expected packet loss percentage. */ - constructor(options = {}) { + constructor({ bitrate = 48, fec = false, plp = 0 } = {}) { this.ctl = { + BITRATE: 4002, FEC: 4012, PLP: 4014, }; - this.options = options; + this.samplingRate = 48000; + this.channels = 2; + + /** + * The desired bitrate (kbps) + * @type {number} + */ + this.bitrate = bitrate; + + /** + * Miscellaneous Opus options + * @type {Object} + */ + this.options = { fec, plp }; } init() { try { + this.setBitrate(this.bitrate); + // Set FEC (forward error correction) if (this.options.fec) this.setFEC(this.options.fec); diff --git a/src/client/voice/opus/NodeOpusEngine.js b/src/client/voice/opus/NodeOpusEngine.js index 72f2a818b..02e880637 100644 --- a/src/client/voice/opus/NodeOpusEngine.js +++ b/src/client/voice/opus/NodeOpusEngine.js @@ -10,10 +10,14 @@ class NodeOpusEngine extends OpusEngine { } catch (err) { throw err; } - this.encoder = new opus.OpusEncoder(48000, 2); + this.encoder = new opus.OpusEncoder(this.samplingRate, this.channels); super.init(); } + setBitrate(bitrate) { + this.encoder.applyEncoderCTL(this.ctl.BITRATE, Math.min(128, Math.max(16, bitrate)) * 1000); + } + setFEC(enabled) { this.encoder.applyEncoderCTL(this.ctl.FEC, enabled ? 1 : 0); } diff --git a/src/client/voice/opus/OpusEngineList.js b/src/client/voice/opus/OpusEngineList.js index 3b69cdaeb..e6ede9a5a 100644 --- a/src/client/voice/opus/OpusEngineList.js +++ b/src/client/voice/opus/OpusEngineList.js @@ -3,8 +3,6 @@ const list = [ require('./OpusScriptEngine'), ]; -let opusEngineFound; - function fetch(Encoder, engineOptions) { try { return new Encoder(engineOptions); @@ -25,10 +23,9 @@ exports.fetch = engineOptions => { const fetched = fetch(encoder, engineOptions); if (fetched) return fetched; } - return null; -}; exports.guaranteeOpusEngine = () => { if (typeof opusEngineFound === 'undefined') opusEngineFound = Boolean(exports.fetch()); if (!opusEngineFound) throw new Error('Couldn\'t find an Opus engine.'); + throw new Error('OPUS_ENGINE_MISSING'); }; diff --git a/src/client/voice/opus/OpusScriptEngine.js b/src/client/voice/opus/OpusScriptEngine.js index 81ca6206b..a5e046d40 100644 --- a/src/client/voice/opus/OpusScriptEngine.js +++ b/src/client/voice/opus/OpusScriptEngine.js @@ -10,10 +10,14 @@ class OpusScriptEngine extends OpusEngine { } catch (err) { throw err; } - this.encoder = new OpusScript(48000, 2); + this.encoder = new OpusScript(this.samplingRate, this.channels); super.init(); } + setBitrate(bitrate) { + this.encoder.encoderCTL(this.ctl.BITRATE, Math.min(128, Math.max(16, bitrate)) * 1000); + } + setFEC(enabled) { this.encoder.encoderCTL(this.ctl.FEC, enabled ? 1 : 0); } diff --git a/src/client/voice/player/AudioPlayer.js b/src/client/voice/player/AudioPlayer.js index afbc9a671..2a264f9e6 100644 --- a/src/client/voice/player/AudioPlayer.js +++ b/src/client/voice/player/AudioPlayer.js @@ -30,11 +30,6 @@ class AudioPlayer extends EventEmitter { * @type {Prism} */ this.prism = new Prism(); - /** - * The opus encoder that the player uses - * @type {NodeOpusEngine|OpusScriptEngine} - */ - this.opusEncoder = OpusEncoders.fetch(); this.streams = new Collection(); this.currentStream = {}; this.streamingData = { @@ -67,6 +62,7 @@ class AudioPlayer extends EventEmitter { destroy() { if (this.opusEncoder) this.opusEncoder.destroy(); + this.opusEncoder = null; } destroyCurrentStream() { @@ -83,13 +79,25 @@ class AudioPlayer extends EventEmitter { this.currentStream = {}; } - playUnknownStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) { - OpusEncoders.guaranteeOpusEngine(); - const options = { seek, volume, passes }; + /** + * Set the bitrate of the current Opus encoder. + * @param {number} value New bitrate, in kbps. + * If set to 'auto', the voice channel's bitrate will be used + */ + setBitrate(value) { + if (!value) return; + if (!this.opusEncoder) return; + const bitrate = value === 'auto' ? this.voiceConnection.channel.bitrate : value; + this.opusEncoder.setBitrate(bitrate); + } + + playUnknownStream(stream, options = {}) { + this.destroy(); + this.opusEncoder = OpusEncoders.fetch(options); const transcoder = this.prism.transcode({ type: 'ffmpeg', media: stream, - ffmpegArguments: ffmpegArguments.concat(['-ss', String(seek)]), + ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek || 0)]), }); this.destroyCurrentStream(); this.currentStream = { @@ -105,9 +113,10 @@ class AudioPlayer extends EventEmitter { return this.playPCMStream(transcoder.output, options, true); } - playPCMStream(stream, { seek = 0, volume = 1, passes = 1 } = {}, fromUnknown = false) { - OpusEncoders.guaranteeOpusEngine(); - const options = { seek, volume, passes }; + playPCMStream(stream, options = {}, fromUnknown = false) { + this.destroy(); + this.opusEncoder = OpusEncoders.fetch(options); + this.setBitrate(options.bitrate); const dispatcher = this.createDispatcher(stream, options); if (fromUnknown) { this.currentStream.dispatcher = dispatcher; @@ -122,8 +131,8 @@ class AudioPlayer extends EventEmitter { return dispatcher; } - playOpusStream(stream, { seek = 0, passes = 1 } = {}) { - const options = { seek, passes, opus: true }; + playOpusStream(stream, options = {}) { + options.opus = true; this.destroyCurrentStream(); const dispatcher = this.createDispatcher(stream, options); this.currentStream = { @@ -134,8 +143,7 @@ class AudioPlayer extends EventEmitter { return dispatcher; } - playBroadcast(broadcast, { volume = 1, passes = 1 } = {}) { - const options = { volume, passes }; + playBroadcast(broadcast, options) { this.destroyCurrentStream(); const dispatcher = this.createDispatcher(broadcast, options); this.currentStream = { @@ -148,7 +156,9 @@ class AudioPlayer extends EventEmitter { return dispatcher; } - createDispatcher(stream, options) { + createDispatcher(stream, { seek = 0, volume = 1, passes = 1 } = {}) { + const options = { seek, volume, passes }; + const dispatcher = new StreamDispatcher(this, stream, options); dispatcher.on('end', () => this.destroyCurrentStream()); dispatcher.on('error', () => this.destroyCurrentStream()); diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js index 617b4eee0..af69a1e00 100644 --- a/src/structures/VoiceChannel.js +++ b/src/structures/VoiceChannel.js @@ -25,7 +25,7 @@ class VoiceChannel extends GuildChannel { * The bitrate of this voice channel * @type {number} */ - this.bitrate = data.bitrate; + this.bitrate = data.bitrate * 0.001; /** * The maximum amount of users allowed in this channel - 0 means unlimited. @@ -76,16 +76,17 @@ class VoiceChannel extends GuildChannel { } /** - * Sets the bitrate of the channel. + * Sets the bitrate of the channel (in kbps). * @param {number} bitrate The new bitrate * @returns {Promise} * @example * // Set the bitrate of a voice channel - * voiceChannel.setBitrate(48000) - * .then(vc => console.log(`Set bitrate to ${vc.bitrate} for ${vc.name}`)) + * voiceChannel.setBitrate(48) + * .then(vc => console.log(`Set bitrate to ${vc.bitrate}kbps for ${vc.name}`)) * .catch(console.error); */ setBitrate(bitrate) { + bitrate *= 1000; return this.edit({ bitrate }); } From 2611efe9c1c592f33a27d419b06cc817214843f4 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Thu, 10 Aug 2017 01:21:34 +0200 Subject: [PATCH 70/99] No longer double increment the reaction count when the client reacts (#1755) --- 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 093da2be1..8e986114b 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -543,8 +543,10 @@ class Message { 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++; + if (!reaction.users.has(user.id)) { + reaction.users.set(user.id, user); + reaction.count++; + } return reaction; } From 5dc83a1b03deb1bd3643fdda74985a41c6b82b59 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Sat, 12 Aug 2017 11:28:47 +0200 Subject: [PATCH 71/99] Git pls --- src/client/voice/opus/OpusEngineList.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/client/voice/opus/OpusEngineList.js b/src/client/voice/opus/OpusEngineList.js index e6ede9a5a..01b6d289f 100644 --- a/src/client/voice/opus/OpusEngineList.js +++ b/src/client/voice/opus/OpusEngineList.js @@ -24,8 +24,5 @@ exports.fetch = engineOptions => { if (fetched) return fetched; } -exports.guaranteeOpusEngine = () => { - if (typeof opusEngineFound === 'undefined') opusEngineFound = Boolean(exports.fetch()); - if (!opusEngineFound) throw new Error('Couldn\'t find an Opus engine.'); throw new Error('OPUS_ENGINE_MISSING'); }; From cd4a69d009e9765f2f2ca87feede7435abdda902 Mon Sep 17 00:00:00 2001 From: Crawl Date: Sat, 12 Aug 2017 11:50:15 +0200 Subject: [PATCH 72/99] Add Guild#nameAcronym --- src/structures/Guild.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index b801ff6d9..2530029f5 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -263,6 +263,15 @@ class Guild { return Constants.Endpoints.Guild(this).Icon(this.client.options.http.cdn, this.icon); } + /** + * Gets the acronym that shows up in place of a guild icon + * @type {string} + * @readonly + */ + get nameAcronym() { + return this.name.replace(/\w+/g, name => name[0]).replace(/\s/g, ''); + } + /** * The URL to this guild's splash * @type {?string} From 2809eb74ca065cbb9b9cb3a3d968448b5b548658 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Thu, 17 Aug 2017 00:16:09 +0200 Subject: [PATCH 73/99] Always send a type now when updating own presence (#1785) --- src/structures/ClientUser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index ff7261d9d..8dcafb2b7 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -190,7 +190,7 @@ class ClientUser extends User { if (data.game) { game = data.game; - if (game.url) game.type = 1; + game.type = game.url ? 1 : 0; } else if (typeof data.game !== 'undefined') { game = null; } From b3216f26d658ea3ad5d4c4a603f63f200af506f9 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Sun, 20 Aug 2017 21:39:27 +0200 Subject: [PATCH 74/99] Update deps --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 31ec625d5..9129ca54c 100644 --- a/package.json +++ b/package.json @@ -41,16 +41,16 @@ "peerDependencies": { "bufferutil": "^3.0.0", "erlpack": "hammerandchisel/erlpack", - "node-opus": "^0.2.5", + "node-opus": "^0.2.0", "opusscript": "^0.0.3", - "sodium": "^2.0.1", - "libsodium-wrappers": "^0.5.1", - "uws": "^0.14.1" + "sodium": "^2.0.0", + "libsodium-wrappers": "^0.5.0", + "uws": "^0.14.0" }, "devDependencies": { "@types/node": "^7.0.0", "discord.js-docgen": "hydrabolt/discord.js-docgen", - "eslint": "^4.2.0", + "eslint": "^4.0.0", "parallel-webpack": "^2.0.0", "uglifyjs-webpack-plugin": "^1.0.0-beta.1", "webpack": "^3.0.0" From e76ebb4fcb90f7ba3b830be1035daf16ceac6b52 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sat, 19 Aug 2017 20:15:02 -0700 Subject: [PATCH 75/99] Guild/systemchannel (#1799) * add cool system channel * Update Guild.js * Update Guild.js * Update Guild.js --- src/structures/Guild.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 2530029f5..eee49677c 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -134,6 +134,12 @@ class Guild { */ this.afkChannelID = data.afk_channel_id; + /** + * The ID of the system channel + * @type {?Snowflake} + */ + this.systemChannelID = data.system_channel_id; + /** * Whether embedded images are enabled on this guild * @type {boolean} @@ -291,6 +297,24 @@ class Guild { return this.members.get(this.ownerID); } + /** + * AFK voice channel for this guild + * @type {?VoiceChannel} + * @readonly + */ + get afkChannel() { + return this.client.channels.get(this.afkChannelID); + } + + /** + * System channel for this guild + * @type {?GuildChannel} + * @readonly + */ + get systemChannel() { + return this.client.channels.get(this.systemChannelID); + } + /** * If the client is connected to any voice channel in this guild, this will be the relevant VoiceConnection * @type {?VoiceConnection} @@ -525,6 +549,7 @@ class Guild { if (data.region) _data.region = data.region; if (typeof data.verificationLevel !== 'undefined') _data.verification_level = Number(data.verificationLevel); if (data.afkChannel) _data.afk_channel_id = this.client.resolver.resolveChannel(data.afkChannel).id; + if (data.systemChannel) _data.system_channel_id = this.client.resolver.resolveChannel(data.systemChannel).id; if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout); if (data.icon) _data.icon = this.client.resolver.resolveBase64(data.icon); if (data.owner) _data.owner_id = this.client.resolver.resolveUser(data.owner).id; @@ -600,6 +625,16 @@ class Guild { return this.edit({ afkChannel }); } + /** + * Edit the system channel of the guild. + * @param {ChannelResolvable} systemChannel The new system channel + * @param {string} [reason] Reason for changing the guild's system channel + * @returns {Promise} + */ + setSystemChannel(systemChannel, reason) { + return this.edit({ systemChannel }, reason); + } + /** * Edit the AFK timeout of the guild. * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK From c33b78da232824dabd3f2bad80661bb2e9306c7f Mon Sep 17 00:00:00 2001 From: Crawl Date: Sun, 20 Aug 2017 22:01:17 +0200 Subject: [PATCH 76/99] Remove reasons for now --- src/structures/Guild.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index eee49677c..598dbac69 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -628,11 +628,10 @@ class Guild { /** * Edit the system channel of the guild. * @param {ChannelResolvable} systemChannel The new system channel - * @param {string} [reason] Reason for changing the guild's system channel * @returns {Promise} */ - setSystemChannel(systemChannel, reason) { - return this.edit({ systemChannel }, reason); + setSystemChannel(systemChannel) { + return this.edit({ systemChannel }); } /** From c7d1507e19ceccc2629c6d83c0bbf4a03c174b09 Mon Sep 17 00:00:00 2001 From: Crawl Date: Sun, 20 Aug 2017 22:08:37 +0200 Subject: [PATCH 77/99] Docs cleanup --- docs/examples/greeting.js | 6 +- src/client/ClientDataResolver.js | 6 +- src/client/voice/VoiceBroadcast.js | 24 +-- src/client/voice/VoiceConnection.js | 26 +-- .../voice/dispatcher/StreamDispatcher.js | 10 +- src/client/voice/opus/BaseOpusEngine.js | 8 +- src/client/voice/player/AudioPlayer.js | 2 +- src/client/voice/receiver/VoiceReceiver.js | 7 +- src/client/websocket/WebSocketConnection.js | 8 +- src/client/websocket/WebSocketManager.js | 6 +- src/sharding/Shard.js | 8 +- src/sharding/ShardClientUtil.js | 8 +- src/sharding/ShardingManager.js | 8 +- src/structures/Channel.js | 4 +- src/structures/ClientUser.js | 24 +-- src/structures/Emoji.js | 6 +- src/structures/Guild.js | 56 +++--- src/structures/GuildAuditLogs.js | 2 +- src/structures/GuildChannel.js | 22 +- src/structures/GuildMember.js | 2 +- src/structures/Message.js | 28 +-- src/structures/MessageCollector.js | 5 +- src/structures/ReactionCollector.js | 2 +- src/structures/Role.js | 34 ++-- src/structures/TextChannel.js | 6 +- src/structures/UserProfile.js | 2 +- src/structures/VoiceChannel.js | 12 +- src/structures/Webhook.js | 4 +- src/structures/interfaces/TextBasedChannel.js | 16 +- src/util/Constants.js | 190 ++++++++++-------- src/util/Permissions.js | 6 +- src/util/Util.js | 6 +- 32 files changed, 287 insertions(+), 267 deletions(-) diff --git a/docs/examples/greeting.js b/docs/examples/greeting.js index e9c92e076..55bf2d00d 100644 --- a/docs/examples/greeting.js +++ b/docs/examples/greeting.js @@ -19,11 +19,7 @@ client.on('ready', () => { // Create an event listener for new guild members client.on('guildMemberAdd', member => { - // Send the message to the guilds default channel (usually #general), mentioning the member - member.guild.defaultChannel.send(`Welcome to the server, ${member}!`); - - // If you want to send the message to a designated channel on a server instead - // you can do the following: + // Send the message to a designated channel on a server: const channel = member.guild.channels.find('name', 'member-log'); // Do nothing if the channel wasn't found on this server if (!channel) return; diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index a7f3e2508..adf28467a 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -28,7 +28,7 @@ class ClientDataResolver { /** * Data that resolves to give a User object. This can be: * * A User object - * * A user ID + * * A Snowflake * * A Message object (resolves to the message author) * * A Guild object (owner of the guild) * * A GuildMember object @@ -65,7 +65,7 @@ class ClientDataResolver { /** * Data that resolves to give a Guild object. This can be: * * A Guild object - * * A Guild ID + * * A Snowflake * @typedef {Guild|Snowflake} GuildResolvable */ @@ -106,7 +106,7 @@ class ClientDataResolver { * * A Channel object * * A Message object (the channel the message was sent in) * * A Guild object (the #general channel) - * * A channel ID + * * A Snowflake * @typedef {Channel|Guild|Message|Snowflake} ChannelResolvable */ diff --git a/src/client/voice/VoiceBroadcast.js b/src/client/voice/VoiceBroadcast.js index 76513d86a..3a78d87a7 100644 --- a/src/client/voice/VoiceBroadcast.js +++ b/src/client/voice/VoiceBroadcast.js @@ -18,7 +18,7 @@ const ffmpegArguments = [ * ```js * const broadcast = client.createVoiceBroadcast(); * broadcast.playFile('./music.mp3'); - * // play "music.mp3" in all voice connections that the client is in + * // Play "music.mp3" in all voice connections that the client is in * for (const connection of client.voiceConnections.values()) { * connection.playBroadcast(broadcast); * } @@ -136,12 +136,12 @@ class VoiceBroadcast extends VolumeInterface { * const broadcast = client.createVoiceBroadcast(); * * voiceChannel.join() - * .then(connection => { - * const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' }); - * broadcast.playStream(stream); - * const dispatcher = connection.playBroadcast(broadcast); - * }) - * .catch(console.error); + * .then(connection => { + * const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' }); + * broadcast.playStream(stream); + * const dispatcher = connection.playBroadcast(broadcast); + * }) + * .catch(console.error); */ playStream(stream, options = {}) { this.setVolume(options.volume || 1); @@ -158,11 +158,11 @@ class VoiceBroadcast extends VolumeInterface { * const broadcast = client.createVoiceBroadcast(); * * voiceChannel.join() - * .then(connection => { - * broadcast.playFile('C:/Users/Discord/Desktop/music.mp3'); - * const dispatcher = connection.playBroadcast(broadcast); - * }) - * .catch(console.error); + * .then(connection => { + * broadcast.playFile('C:/Users/Discord/Desktop/music.mp3'); + * const dispatcher = connection.playBroadcast(broadcast); + * }) + * .catch(console.error); */ playFile(file, options = {}) { this.setVolume(options.volume || 1); diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 9163db449..8dc09e5b7 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -11,9 +11,10 @@ const Prism = require('prism-media'); * Represents a connection to a guild's voice server. * ```js * // Obtained using: - * voiceChannel.join().then(connection => { + * voiceChannel.join() + * .then(connection => { * - * }); + * }); * ``` * @extends {EventEmitter} */ @@ -443,10 +444,10 @@ class VoiceConnection extends EventEmitter { * @example * // Play files natively * voiceChannel.join() - * .then(connection => { - * const dispatcher = connection.playFile('C:/Users/Discord/Desktop/music.mp3'); - * }) - * .catch(console.error); + * .then(connection => { + * const dispatcher = connection.playFile('C:/Users/Discord/Desktop/music.mp3'); + * }) + * .catch(console.error); */ playFile(file, options) { return this.player.playUnknownStream(`file:${file}`, options); @@ -472,11 +473,11 @@ class VoiceConnection extends EventEmitter { * const ytdl = require('ytdl-core'); * const streamOptions = { seek: 0, volume: 1 }; * voiceChannel.join() - * .then(connection => { - * const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' }); - * const dispatcher = connection.playStream(stream, streamOptions); - * }) - * .catch(console.error); + * .then(connection => { + * const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' }); + * const dispatcher = connection.playStream(stream, streamOptions); + * }) + * .catch(console.error); */ playStream(stream, options) { return this.player.playUnknownStream(stream, options); @@ -520,7 +521,8 @@ class VoiceConnection extends EventEmitter { } /** - * Creates a VoiceReceiver so you can start listening to voice data. It's recommended to only create one of these. + * Creates a VoiceReceiver so you can start listening to voice data. + * It's recommended to only create one of these. * @returns {VoiceReceiver} */ createReceiver() { diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index 24df9d069..beef7b9f4 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -89,12 +89,12 @@ class StreamDispatcher extends VolumeInterface { } /** - * Stops sending voice packets to the voice connection (stream may still progress however) + * Stops sending voice packets to the voice connection (stream may still progress however). */ pause() { this.setPaused(true); } /** - * Resumes sending voice packets to the voice connection (may be further on in the stream than when paused) + * Resumes sending voice packets to the voice connection (may be further on in the stream than when paused). */ resume() { this.setPaused(false); } @@ -122,7 +122,7 @@ class StreamDispatcher extends VolumeInterface { /** * Set the bitrate of the current Opus encoder. - * @param {number} bitrate New bitrate, in kbps. + * @param {number} bitrate New bitrate, in kbps * If set to 'auto', the voice channel's bitrate will be used */ setBitrate(bitrate) { @@ -140,7 +140,7 @@ class StreamDispatcher extends VolumeInterface { /** * Emitted whenever the dispatcher has debug information. * @event StreamDispatcher#debug - * @param {string} info the debug info + * @param {string} info The debug info */ this.setSpeaking(true); while (repeats--) { @@ -294,7 +294,7 @@ class StreamDispatcher extends VolumeInterface { this.emit(type, reason); /** * Emitted once the dispatcher ends. - * @param {string} [reason] the reason the dispatcher ended + * @param {string} [reason] The reason the dispatcher ended * @event StreamDispatcher#end */ if (type !== 'end') this.emit('end', `destroyed due to ${type} - ${reason}`); diff --git a/src/client/voice/opus/BaseOpusEngine.js b/src/client/voice/opus/BaseOpusEngine.js index 40c6204fd..a51044905 100644 --- a/src/client/voice/opus/BaseOpusEngine.js +++ b/src/client/voice/opus/BaseOpusEngine.js @@ -4,10 +4,10 @@ */ class BaseOpus { /** - * @param {Object} [options] The options to apply to the Opus engine. - * @param {number} [options.bitrate=48] The desired bitrate (kbps). - * @param {boolean} [options.fec=false] Whether to enable forward error correction. - * @param {number} [options.plp=0] The expected packet loss percentage. + * @param {Object} [options] The options to apply to the Opus engine + * @param {number} [options.bitrate=48] The desired bitrate (kbps) + * @param {boolean} [options.fec=false] Whether to enable forward error correction + * @param {number} [options.plp=0] The expected packet loss percentage */ constructor({ bitrate = 48, fec = false, plp = 0 } = {}) { this.ctl = { diff --git a/src/client/voice/player/AudioPlayer.js b/src/client/voice/player/AudioPlayer.js index 2a264f9e6..32bb070d1 100644 --- a/src/client/voice/player/AudioPlayer.js +++ b/src/client/voice/player/AudioPlayer.js @@ -81,7 +81,7 @@ class AudioPlayer extends EventEmitter { /** * Set the bitrate of the current Opus encoder. - * @param {number} value New bitrate, in kbps. + * @param {number} value New bitrate, in kbps * If set to 'auto', the voice channel's bitrate will be used */ setBitrate(value) { diff --git a/src/client/voice/receiver/VoiceReceiver.js b/src/client/voice/receiver/VoiceReceiver.js index 7b1b6c8e0..c568c6aed 100644 --- a/src/client/voice/receiver/VoiceReceiver.js +++ b/src/client/voice/receiver/VoiceReceiver.js @@ -10,9 +10,10 @@ nonce.fill(0); * Receives voice data from a voice connection. * ```js * // Obtained using: - * voiceChannel.join().then(connection => { - * const receiver = connection.createReceiver(); - * }); + * voiceChannel.join() + * .then(connection => { + * const receiver = connection.createReceiver(); + * }); * ``` * @extends {EventEmitter} */ diff --git a/src/client/websocket/WebSocketConnection.js b/src/client/websocket/WebSocketConnection.js index af4a20e8d..9567f5653 100644 --- a/src/client/websocket/WebSocketConnection.js +++ b/src/client/websocket/WebSocketConnection.js @@ -29,12 +29,12 @@ const WebSocket = (function findWebSocket() { class WebSocketConnection extends EventEmitter { /** * @param {WebSocketManager} manager The WebSocket manager - * @param {string} gateway WebSocket gateway to connect to + * @param {string} gateway The WebSocket gateway to connect to */ constructor(manager, gateway) { super(); /** - * WebSocket Manager of this connection + * The WebSocket Manager of this connection * @type {WebSocketManager} */ this.manager = manager; @@ -232,7 +232,7 @@ class WebSocketConnection extends EventEmitter { /** * Creates a connection to a gateway. - * @param {string} gateway Gateway to connect to + * @param {string} gateway The gateway to connect to * @param {number} [after=0] How long to wait before connecting * @param {boolean} [force=false] Whether or not to force a new connection even if one already exists * @returns {boolean} @@ -356,7 +356,7 @@ class WebSocketConnection extends EventEmitter { /** * Called whenever an error occurs with the WebSocket. - * @param {Error} error Error that occurred + * @param {Error} error The error that occurred */ onError(error) { if (error && error.message === 'uWs client connection error') { diff --git a/src/client/websocket/WebSocketManager.js b/src/client/websocket/WebSocketManager.js index aabea1d9b..9ef073f24 100644 --- a/src/client/websocket/WebSocketManager.js +++ b/src/client/websocket/WebSocketManager.js @@ -3,7 +3,7 @@ const Constants = require('../../util/Constants'); const WebSocketConnection = require('./WebSocketConnection'); /** - * WebSocket Manager of the client + * WebSocket Manager of the client. * @private */ class WebSocketManager extends EventEmitter { @@ -23,7 +23,7 @@ class WebSocketManager extends EventEmitter { } /** - * Sends a heartbeat on the available connection + * Sends a heartbeat on the available connection. * @returns {void} */ heartbeat() { @@ -67,7 +67,7 @@ class WebSocketManager extends EventEmitter { /** * Connects the client to a gateway. - * @param {string} gateway Gateway to connect to + * @param {string} gateway The gateway to connect to * @returns {boolean} */ connect(gateway) { diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js index a73447b85..a1069ee9f 100644 --- a/src/sharding/Shard.js +++ b/src/sharding/Shard.js @@ -69,9 +69,11 @@ class Shard { * @param {string} prop Name of the client property to get, using periods for nesting * @returns {Promise<*>} * @example - * shard.fetchClientValue('guilds.size').then(count => { - * console.log(`${count} guilds in shard ${shard.id}`); - * }).catch(console.error); + * shard.fetchClientValue('guilds.size') + * .then(count => { + * console.log(`${count} guilds in shard ${shard.id}`); + * }) + * .catch(console.error); */ fetchClientValue(prop) { if (this._fetches.has(prop)) return this._fetches.get(prop); diff --git a/src/sharding/ShardClientUtil.js b/src/sharding/ShardClientUtil.js index b41deb5d1..0d7d4412b 100644 --- a/src/sharding/ShardClientUtil.js +++ b/src/sharding/ShardClientUtil.js @@ -49,9 +49,11 @@ class ShardClientUtil { * @param {string} prop Name of the client property to get, using periods for nesting * @returns {Promise} * @example - * client.shard.fetchClientValues('guilds.size').then(results => { - * console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`); - * }).catch(console.error); + * client.shard.fetchClientValues('guilds.size') + * .then(results => { + * console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`); + * }) + * .catch(console.error); */ fetchClientValues(prop) { return new Promise((resolve, reject) => { diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js index e105f3e3f..aa40547bd 100644 --- a/src/sharding/ShardingManager.js +++ b/src/sharding/ShardingManager.js @@ -176,9 +176,11 @@ class ShardingManager extends EventEmitter { * @param {string} prop Name of the client property to get, using periods for nesting * @returns {Promise} * @example - * manager.fetchClientValues('guilds.size').then(results => { - * console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`); - * }).catch(console.error); + * manager.fetchClientValues('guilds.size') + * .then(results => { + * console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`); + * }) + * .catch(console.error); */ fetchClientValues(prop) { if (this.shards.size === 0) return Promise.reject(new Error('No shards have been spawned.')); diff --git a/src/structures/Channel.js b/src/structures/Channel.js index 3d492e451..5cf03a608 100644 --- a/src/structures/Channel.js +++ b/src/structures/Channel.js @@ -58,8 +58,8 @@ class Channel { * @example * // Delete the channel * channel.delete() - * .then() // Success - * .catch(console.error); // Log error + * .then() // Success + * .catch(console.error); // Log error */ delete() { return this.client.rest.methods.deleteChannel(this); diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 8dcafb2b7..1ae7d6a87 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -89,8 +89,8 @@ class ClientUser extends User { * @example * // Set username * client.user.setUsername('discordjs') - * .then(user => console.log(`My new username is ${user.username}`)) - * .catch(console.error); + * .then(user => console.log(`My new username is ${user.username}`)) + * .catch(console.error); */ setUsername(username, password) { return this.client.rest.methods.updateCurrentUser({ username }, password); @@ -105,8 +105,8 @@ class ClientUser extends User { * @example * // Set email * client.user.setEmail('bob@gmail.com', 'some amazing password 123') - * .then(user => console.log(`My new email is ${user.email}`)) - * .catch(console.error); + * .then(user => console.log(`My new email is ${user.email}`)) + * .catch(console.error); */ setEmail(email, password) { return this.client.rest.methods.updateCurrentUser({ email }, password); @@ -121,8 +121,8 @@ class ClientUser extends User { * @example * // Set password * client.user.setPassword('some new amazing password 456', 'some amazing password 123') - * .then(user => console.log('New password set!')) - * .catch(console.error); + * .then(user => console.log('New password set!')) + * .catch(console.error); */ setPassword(newPassword, oldPassword) { return this.client.rest.methods.updateCurrentUser({ password: newPassword }, oldPassword); @@ -135,8 +135,8 @@ class ClientUser extends User { * @example * // Set avatar * client.user.setAvatar('./avatar.png') - * .then(user => console.log(`New avatar set!`)) - * .catch(console.error); + * .then(user => console.log(`New avatar set!`)) + * .catch(console.error); */ setAvatar(avatar) { if (typeof avatar === 'string' && avatar.startsWith('data:')) { @@ -215,10 +215,10 @@ class ClientUser extends User { /** * A user's status. Must be one of: - * - `online` - * - `idle` - * - `invisible` - * - `dnd` (do not disturb) + * * `online` + * * `idle` + * * `invisible` + * * `dnd` (do not disturb) * @typedef {string} PresenceStatus */ diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js index 452dd6654..88d07fa78 100644 --- a/src/structures/Emoji.js +++ b/src/structures/Emoji.js @@ -114,10 +114,10 @@ class Emoji { * @param {EmojiEditData} data The new data for the emoji * @returns {Promise} * @example - * // Edit a emoji + * // Edit an emoji * emoji.edit({name: 'newemoji'}) - * .then(e => console.log(`Edited emoji ${e}`)) - * .catch(console.error); + * .then(e => console.log(`Edited emoji ${e}`)) + * .catch(console.error); */ edit(data) { return this.client.rest.methods.updateEmoji(this, data); diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 598dbac69..3946aa846 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -219,7 +219,8 @@ class Guild { if (!this.emojis) { /** - * 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(); @@ -270,7 +271,7 @@ class Guild { } /** - * Gets the acronym that shows up in place of a guild icon + * The acronym that shows up in place of a guild icon. * @type {string} * @readonly */ @@ -391,7 +392,8 @@ 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() { @@ -537,11 +539,11 @@ class Guild { * @example * // Set the guild name and region * guild.edit({ - * name: 'Discord Guild', - * region: 'london', + * name: 'Discord Guild', + * region: 'london', * }) - * .then(updated => console.log(`New guild name ${updated.name} in region ${updated.region}`)) - * .catch(console.error); + * .then(updated => console.log(`New guild name ${updated.name} in region ${updated.region}`)) + * .catch(console.error); */ edit(data) { const _data = {}; @@ -737,8 +739,8 @@ class Guild { * @example * // Ban a user by ID (or with a user/guild member object) * guild.ban('some user ID') - * .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`)) - * .catch(console.error); + * .then(user => console.log(`Banned ${user.username || user.id || user} from ${guild.name}`)) + * .catch(console.error); */ ban(user, options = {}) { if (typeof options === 'number') { @@ -757,8 +759,8 @@ class Guild { * @example * // Unban a user by ID (or with a user/guild member object) * guild.unban('some user ID') - * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`)) - * .catch(console.error); + * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`)) + * .catch(console.error); */ unban(user) { return this.client.rest.methods.unbanGuildMember(this, user); @@ -802,8 +804,8 @@ class Guild { * @example * // Create a new text channel * guild.createChannel('new-general', 'text') - * .then(channel => console.log(`Created new channel ${channel}`)) - * .catch(console.error); + * .then(channel => console.log(`Created new channel ${channel}`)) + * .catch(console.error); */ createChannel(name, type, overwrites) { return this.client.rest.methods.createChannel(this, name, type, overwrites); @@ -822,8 +824,8 @@ class Guild { * @returns {Promise} * @example * guild.updateChannels([{ channel: channelID, position: newChannelIndex }]) - * .then(guild => console.log(`Updated channel positions for ${guild.id}`)) - * .catch(console.error); + * .then(guild => console.log(`Updated channel positions for ${guild.id}`)) + * .catch(console.error); */ setChannelPositions(channelPositions) { return this.client.rest.methods.updateChannelPositions(this.id, channelPositions); @@ -836,16 +838,16 @@ class Guild { * @example * // Create a new role * guild.createRole() - * .then(role => console.log(`Created role ${role}`)) - * .catch(console.error); + * .then(role => console.log(`Created role ${role}`)) + * .catch(console.error); * @example * // Create a new role with data * guild.createRole({ * name: 'Super Cool People', * color: 'BLUE', * }) - * .then(role => console.log(`Created role ${role}`)) - * .catch(console.error) + * .then(role => console.log(`Created role ${role}`)) + * .catch(console.error) */ createRole(data = {}) { return this.client.rest.methods.createGuildRole(this, data); @@ -860,13 +862,13 @@ class Guild { * @example * // Create a new emoji from a url * guild.createEmoji('https://i.imgur.com/w3duR07.png', 'rip') - * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`)) - * .catch(console.error); + * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`)) + * .catch(console.error); * @example * // Create a new emoji from a file on your computer * guild.createEmoji('./memes/banana.png', 'banana') - * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`)) - * .catch(console.error); + * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`)) + * .catch(console.error); */ createEmoji(attachment, name, roles) { return new Promise(resolve => { @@ -897,8 +899,8 @@ class Guild { * @example * // Leave a guild * guild.leave() - * .then(g => console.log(`Left the guild ${g}`)) - * .catch(console.error); + * .then(g => console.log(`Left the guild ${g}`)) + * .catch(console.error); */ leave() { return this.client.rest.methods.leaveGuild(this); @@ -910,8 +912,8 @@ class Guild { * @example * // Delete a guild * guild.delete() - * .then(g => console.log(`Deleted the guild ${g}`)) - * .catch(console.error); + * .then(g => console.log(`Deleted the guild ${g}`)) + * .catch(console.error); */ delete() { return this.client.rest.methods.deleteGuild(this); diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 2db442870..e2be2401d 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -171,7 +171,7 @@ class GuildAuditLogsEntry { this.executor = guild.client.users.get(data.user_id); /** - * An entry in the audit log representing a specific change + * An entry in the audit log representing a specific change. * @typedef {object} AuditLogChange * @property {string} key The property that was changed, e.g. `nick` for nickname changes * @property {*} [old] The old value of the change, e.g. for nicknames, the old nickname diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 16c8ddd87..0fe2d974b 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -142,10 +142,10 @@ class GuildChannel extends Channel { * @example * // Overwrite permissions for a message author * message.channel.overwritePermissions(message.author, { - * SEND_MESSAGES: false + * SEND_MESSAGES: false * }) - * .then(() => console.log('Done!')) - * .catch(console.error); + * .then(() => console.log('Done!')) + * .catch(console.error); */ overwritePermissions(userOrRole, options) { const payload = { @@ -206,8 +206,8 @@ class GuildChannel extends Channel { * @example * // Edit a channel * channel.edit({name: 'new-channel'}) - * .then(c => console.log(`Edited channel ${c}`)) - * .catch(console.error); + * .then(c => console.log(`Edited channel ${c}`)) + * .catch(console.error); */ edit(data) { return this.client.rest.methods.updateChannel(this, data); @@ -220,8 +220,8 @@ class GuildChannel extends Channel { * @example * // Set a new channel name * channel.setName('not_general') - * .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`)) - * .catch(console.error); + * .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`)) + * .catch(console.error); */ setName(name) { return this.edit({ name }); @@ -235,8 +235,8 @@ class GuildChannel extends Channel { * @example * // Set a new channel position * channel.setPosition(2) - * .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`)) - * .catch(console.error); + * .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`)) + * .catch(console.error); */ setPosition(position, relative) { return this.guild.setChannelPosition(this, position, relative).then(() => this); @@ -249,8 +249,8 @@ class GuildChannel extends Channel { * @example * // Set a new channel topic * channel.setTopic('needs more rate limiting') - * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`)) - * .catch(console.error); + * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`)) + * .catch(console.error); */ setTopic(topic) { return this.client.rest.methods.updateChannel(this, { topic }); diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 0c096120d..444727976 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -481,7 +481,7 @@ class GuildMember { } /** - * Ban this guild member + * Ban this guild member. * @param {Object|number|string} [options] Ban options. If a number, the number of days to delete messages for, if a * string, the ban reason. Supplying an object allows you to do both. * @param {number} [options.days=0] Number of days of messages to delete diff --git a/src/structures/Message.js b/src/structures/Message.js index 8e986114b..eec937e98 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -57,8 +57,8 @@ 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 - * where the author is still a member. + * 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} */ this.member = this.guild ? this.guild.member(this.author) || null : null; @@ -209,8 +209,8 @@ class Message { } /** - * The message contents with all mentions replaced by the equivalent text. If mentions cannot be resolved to a name, - * the relevant mention in the message content will not be converted + * The message contents with all mentions replaced by the equivalent text. + * If mentions cannot be resolved to a name, the relevant mention in the message content will not be converted. * @type {string} * @readonly */ @@ -254,8 +254,8 @@ class Message { * @example * // Create a reaction collector * const collector = message.createReactionCollector( - * (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someID', - * { time: 15000 } + * (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someID', + * { time: 15000 } * ); * collector.on('collect', r => console.log(`Collected ${r.emoji.name}`)); * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); @@ -271,8 +271,8 @@ class Message { */ /** - * Similar to createCollector but in promise form. Resolves with a collection of reactions that pass the specified - * filter. + * Similar to createCollector but in promise form. + * Resolves with a collection of reactions that pass the specified filter. * @param {CollectorFilter} filter The filter function to use * @param {AwaitReactionsOptions} [options={}] Optional options to pass to the internal collector * @returns {Promise>} @@ -370,8 +370,8 @@ class Message { * @example * // Update the content of a message * message.edit('This is my new content!') - * .then(msg => console.log(`Updated the content of a message from ${msg.author}`)) - * .catch(console.error); + * .then(msg => console.log(`Updated the content of a message from ${msg.author}`)) + * .catch(console.error); */ edit(content, options) { if (!options && typeof content === 'object' && !(content instanceof Array)) { @@ -438,8 +438,8 @@ class Message { * @example * // Delete a message * message.delete() - * .then(msg => console.log(`Deleted message from ${msg.author}`)) - * .catch(console.error); + * .then(msg => console.log(`Deleted message from ${msg.author}`)) + * .catch(console.error); */ delete(timeout = 0) { if (timeout <= 0) { @@ -461,8 +461,8 @@ class Message { * @example * // Reply to a message * message.reply('Hey, I\'m a reply!') - * .then(msg => console.log(`Sent a reply to ${msg.author}`)) - * .catch(console.error); + * .then(msg => console.log(`Sent a reply to ${msg.author}`)) + * .catch(console.error); */ reply(content, options) { if (!options && typeof content === 'object' && !(content instanceof Array)) { diff --git a/src/structures/MessageCollector.js b/src/structures/MessageCollector.js index cc77a86c0..c0a51c813 100644 --- a/src/structures/MessageCollector.js +++ b/src/structures/MessageCollector.js @@ -22,7 +22,8 @@ class MessageCollector extends Collector { super(channel.client, filter, options); /** - * @type {TextBasedChannel} channel The channel + * The channel + * @type {TextBasedChannel} */ this.channel = channel; @@ -60,7 +61,7 @@ class MessageCollector extends Collector { /** * Handle an incoming message for possible collection. * @param {Message} message The message that could be collected - * @returns {?{key: Snowflake, value: Message}} Message data to collect + * @returns {?{key: Snowflake, value: Message}} * @private */ handle(message) { diff --git a/src/structures/ReactionCollector.js b/src/structures/ReactionCollector.js index a54687d32..ecdb37674 100644 --- a/src/structures/ReactionCollector.js +++ b/src/structures/ReactionCollector.js @@ -45,7 +45,7 @@ class ReactionCollector extends Collector { /** * Handle an incoming reaction for possible collection. * @param {MessageReaction} reaction The reaction to possibly collect - * @returns {?{key: Snowflake, value: MessageReaction}} Reaction data to collect + * @returns {?{key: Snowflake, value: MessageReaction}} * @private */ handle(reaction) { diff --git a/src/structures/Role.js b/src/structures/Role.js index d5ffb33b1..fb23fdff1 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -135,7 +135,7 @@ class Role { } /** - * Get an object mapping permission names to whether or not the role enables that permission + * Get an object mapping permission names to whether or not the role enables that permission. * @returns {Object} * @example * // Print the serialized role permissions @@ -206,8 +206,8 @@ class Role { * @example * // Edit a role * role.edit({name: 'new role'}) - * .then(r => console.log(`Edited role ${r}`)) - * .catch(console.error); + * .then(r => console.log(`Edited role ${r}`)) + * .catch(console.error); */ edit(data) { return this.client.rest.methods.updateGuildRole(this, data); @@ -220,8 +220,8 @@ class Role { * @example * // Set the name of the role * role.setName('new role') - * .then(r => console.log(`Edited name of role ${r}`)) - * .catch(console.error); + * .then(r => console.log(`Edited name of role ${r}`)) + * .catch(console.error); */ setName(name) { return this.edit({ name }); @@ -234,8 +234,8 @@ class Role { * @example * // Set the color of a role * role.setColor('#FF0000') - * .then(r => console.log(`Set color of role ${r}`)) - * .catch(console.error); + * .then(r => console.log(`Set color of role ${r}`)) + * .catch(console.error); */ setColor(color) { return this.edit({ color }); @@ -248,8 +248,8 @@ class Role { * @example * // Set the hoist of the role * role.setHoist(true) - * .then(r => console.log(`Role hoisted: ${r.hoist}`)) - * .catch(console.error); + * .then(r => console.log(`Role hoisted: ${r.hoist}`)) + * .catch(console.error); */ setHoist(hoist) { return this.edit({ hoist }); @@ -263,8 +263,8 @@ class Role { * @example * // Set the position of the role * role.setPosition(1) - * .then(r => console.log(`Role position: ${r.position}`)) - * .catch(console.error); + * .then(r => console.log(`Role position: ${r.position}`)) + * .catch(console.error); */ setPosition(position, relative) { return this.guild.setRolePosition(this, position, relative).then(() => this); @@ -277,8 +277,8 @@ class Role { * @example * // Set the permissions of the role * role.setPermissions(['KICK_MEMBERS', 'BAN_MEMBERS']) - * .then(r => console.log(`Role updated ${r}`)) - * .catch(console.error); + * .then(r => console.log(`Role updated ${r}`)) + * .catch(console.error); */ setPermissions(permissions) { return this.edit({ permissions }); @@ -291,8 +291,8 @@ class Role { * @example * // Make the role mentionable * role.setMentionable(true) - * .then(r => console.log(`Role updated ${r}`)) - * .catch(console.error); + * .then(r => console.log(`Role updated ${r}`)) + * .catch(console.error); */ setMentionable(mentionable) { return this.edit({ mentionable }); @@ -304,8 +304,8 @@ class Role { * @example * // Delete a role * role.delete() - * .then(r => console.log(`Deleted role ${r}`)) - * .catch(console.error); + * .then(r => console.log(`Deleted role ${r}`)) + * .catch(console.error); */ delete() { return this.client.rest.methods.deleteGuildRole(this); diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index d7f0a73f3..2d01517c5 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -63,9 +63,9 @@ class TextChannel extends GuildChannel { * @param {BufferResolvable|Base64Resolvable} avatar The avatar for the webhook * @returns {Promise} webhook The created webhook * @example - * channel.createWebhook('Snek', 'http://snek.s3.amazonaws.com/topSnek.png') - * .then(webhook => console.log(`Created webhook ${webhook}`)) - * .catch(console.error) + * channel.createWebhook('Snek', 'https://i.imgur.com/mI8XcpG.jpg') + * .then(webhook => console.log(`Created webhook ${webhook}`)) + * .catch(console.error) */ createWebhook(name, avatar) { return new Promise(resolve => { diff --git a/src/structures/UserProfile.js b/src/structures/UserProfile.js index 192bc1e63..a27d4d08d 100644 --- a/src/structures/UserProfile.js +++ b/src/structures/UserProfile.js @@ -13,7 +13,7 @@ class UserProfile { this.user = user; /** - * The client that created the instance of the the UserProfile. + * The client that created the instance of the UserProfile * @name UserProfile#client * @type {Client} * @readonly diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js index af69a1e00..081710de0 100644 --- a/src/structures/VoiceChannel.js +++ b/src/structures/VoiceChannel.js @@ -82,8 +82,8 @@ class VoiceChannel extends GuildChannel { * @example * // Set the bitrate of a voice channel * voiceChannel.setBitrate(48) - * .then(vc => console.log(`Set bitrate to ${vc.bitrate}kbps for ${vc.name}`)) - * .catch(console.error); + * .then(vc => console.log(`Set bitrate to ${vc.bitrate}kbps for ${vc.name}`)) + * .catch(console.error); */ setBitrate(bitrate) { bitrate *= 1000; @@ -97,8 +97,8 @@ class VoiceChannel extends GuildChannel { * @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); + * .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`)) + * .catch(console.error); */ setUserLimit(userLimit) { return this.edit({ userLimit }); @@ -110,8 +110,8 @@ class VoiceChannel extends GuildChannel { * @example * // Join a voice channel * voiceChannel.join() - * .then(connection => console.log('Connected!')) - * .catch(console.error); + * .then(connection => console.log('Connected!')) + * .catch(console.error); */ join() { if (this.client.browser) return Promise.reject(new Error('Voice connections are not available in browsers.')); diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 23ce1e6c4..563e37200 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -94,8 +94,8 @@ class Webhook { * @example * // Send a message * webhook.send('hello!') - * .then(message => console.log(`Sent message: ${message.content}`)) - * .catch(console.error); + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); */ send(content, options) { if (!options && typeof content === 'object' && !(content instanceof Array)) { diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index 6bf3a469f..07c2a7382 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -69,8 +69,8 @@ class TextBasedChannel { * @example * // Send a message * channel.send('hello!') - * .then(message => console.log(`Sent message: ${message.content}`)) - * .catch(console.error); + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); */ send(content, options) { if (!options && typeof content === 'object' && !(content instanceof Array)) { @@ -158,8 +158,8 @@ class TextBasedChannel { * @example * // Get messages * channel.fetchMessages({limit: 10}) - * .then(messages => console.log(`Received ${messages.size} messages`)) - * .catch(console.error); + * .then(messages => console.log(`Received ${messages.size} messages`)) + * .catch(console.error); */ fetchMessages(options = {}) { return this.client.rest.methods.getChannelMessages(this, options).then(data => { @@ -321,8 +321,8 @@ class TextBasedChannel { * @example * // Create a message collector * const collector = channel.createMessageCollector( - * m => m.content.includes('discord'), - * { time: 15000 } + * m => m.content.includes('discord'), + * { time: 15000 } * ); * collector.on('collect', m => console.log(`Collected ${m.content}`)); * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); @@ -348,8 +348,8 @@ class TextBasedChannel { * const filter = m => m.content.startsWith('!vote'); * // Errors: ['time'] treats ending because of the time limit as an error * channel.awaitMessages(filter, { max: 4, time: 60000, errors: ['time'] }) - * .then(collected => console.log(collected.size)) - * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`)); + * .then(collected => console.log(collected.size)) + * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`)); */ awaitMessages(filter, options = {}) { return new Promise((resolve, reject) => { diff --git a/src/util/Constants.js b/src/util/Constants.js index 562445376..132b90b98 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -220,12 +220,12 @@ const Endpoints = exports.Endpoints = { /** * The current status of the client. Here are the available statuses: - * - READY - * - CONNECTING - * - RECONNECTING - * - IDLE - * - NEARLY - * - DISCONNECTED + * * READY + * * CONNECTING + * * RECONNECTING + * * IDLE + * * NEARLY + * * DISCONNECTED * @typedef {number} Status */ exports.Status = { @@ -239,11 +239,11 @@ exports.Status = { /** * The current status of a voice connection. Here are the available statuses: - * - CONNECTED - * - CONNECTING - * - AUTHENTICATING - * - RECONNECTING - * - DISCONNECTED + * * CONNECTED + * * CONNECTING + * * AUTHENTICATING + * * RECONNECTING + * * DISCONNECTED * @typedef {number} VoiceStatus */ exports.VoiceStatus = { @@ -333,41 +333,41 @@ exports.Events = { /** * The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events: - * - READY - * - RESUMED - * - 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 - * - USER_SETTINGS_UPDATE - * - PRESENCE_UPDATE - * - VOICE_STATE_UPDATE - * - TYPING_START - * - VOICE_SERVER_UPDATE - * - RELATIONSHIP_ADD - * - RELATIONSHIP_REMOVE + * * READY + * * RESUMED + * * 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 + * * USER_SETTINGS_UPDATE + * * PRESENCE_UPDATE + * * VOICE_STATE_UPDATE + * * TYPING_START + * * VOICE_SERVER_UPDATE + * * RELATIONSHIP_ADD + * * RELATIONSHIP_REMOVE * @typedef {string} WSEventType */ exports.WSEvents = { @@ -409,6 +409,18 @@ exports.WSEvents = { RELATIONSHIP_REMOVE: 'RELATIONSHIP_REMOVE', }; +/** + * The type of a message, e.g. `DEFAULT`. Here are the available types: + * * DEFAULT + * * RECIPIENT_ADD + * * RECIPIENT_REMOVE + * * CALL + * * CHANNEL_NAME_CHANGE + * * CHANNEL_ICON_CHANGE + * * PINS_ADD + * * GUILD_MEMBER_JOIN + * @typedef {string} MessageType + */ exports.MessageTypes = [ 'DEFAULT', 'RECIPIENT_ADD', @@ -597,49 +609,49 @@ exports.Colors = { /** * An error encountered while performing an API request. Here are the potential errors: - * - UNKNOWN_ACCOUNT - * - UNKNOWN_APPLICATION - * - UNKNOWN_CHANNEL - * - UNKNOWN_GUILD - * - UNKNOWN_INTEGRATION - * - UNKNOWN_INVITE - * - UNKNOWN_MEMBER - * - UNKNOWN_MESSAGE - * - UNKNOWN_OVERWRITE - * - UNKNOWN_PROVIDER - * - UNKNOWN_ROLE - * - UNKNOWN_TOKEN - * - UNKNOWN_USER - * - UNKNOWN_EMOJI - * - BOT_PROHIBITED_ENDPOINT - * - BOT_ONLY_ENDPOINT - * - MAXIMUM_GUILDS - * - MAXIMUM_FRIENDS - * - MAXIMUM_PINS - * - MAXIMUM_ROLES - * - MAXIMUM_REACTIONS - * - UNAUTHORIZED - * - MISSING_ACCESS - * - INVALID_ACCOUNT_TYPE - * - CANNOT_EXECUTE_ON_DM - * - EMBED_DISABLED - * - CANNOT_EDIT_MESSAGE_BY_OTHER - * - CANNOT_SEND_EMPTY_MESSAGE - * - CANNOT_MESSAGE_USER - * - CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL - * - CHANNEL_VERIFICATION_LEVEL_TOO_HIGH - * - OAUTH2_APPLICATION_BOT_ABSENT - * - MAXIMUM_OAUTH2_APPLICATIONS - * - INVALID_OAUTH_STATE - * - MISSING_PERMISSIONS - * - INVALID_AUTHENTICATION_TOKEN - * - NOTE_TOO_LONG - * - INVALID_BULK_DELETE_QUANTITY - * - CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL - * - CANNOT_EXECUTE_ON_SYSTEM_MESSAGE - * - BULK_DELETE_MESSAGE_TOO_OLD - * - INVITE_ACCEPTED_TO_GUILD_NOT_CONTANING_BOT - * - REACTION_BLOCKED + * * UNKNOWN_ACCOUNT + * * UNKNOWN_APPLICATION + * * UNKNOWN_CHANNEL + * * UNKNOWN_GUILD + * * UNKNOWN_INTEGRATION + * * UNKNOWN_INVITE + * * UNKNOWN_MEMBER + * * UNKNOWN_MESSAGE + * * UNKNOWN_OVERWRITE + * * UNKNOWN_PROVIDER + * * UNKNOWN_ROLE + * * UNKNOWN_TOKEN + * * UNKNOWN_USER + * * UNKNOWN_EMOJI + * * BOT_PROHIBITED_ENDPOINT + * * BOT_ONLY_ENDPOINT + * * MAXIMUM_GUILDS + * * MAXIMUM_FRIENDS + * * MAXIMUM_PINS + * * MAXIMUM_ROLES + * * MAXIMUM_REACTIONS + * * UNAUTHORIZED + * * MISSING_ACCESS + * * INVALID_ACCOUNT_TYPE + * * CANNOT_EXECUTE_ON_DM + * * EMBED_DISABLED + * * CANNOT_EDIT_MESSAGE_BY_OTHER + * * CANNOT_SEND_EMPTY_MESSAGE + * * CANNOT_MESSAGE_USER + * * CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL + * * CHANNEL_VERIFICATION_LEVEL_TOO_HIGH + * * OAUTH2_APPLICATION_BOT_ABSENT + * * MAXIMUM_OAUTH2_APPLICATIONS + * * INVALID_OAUTH_STATE + * * MISSING_PERMISSIONS + * * INVALID_AUTHENTICATION_TOKEN + * * NOTE_TOO_LONG + * * INVALID_BULK_DELETE_QUANTITY + * * CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL + * * CANNOT_EXECUTE_ON_SYSTEM_MESSAGE + * * BULK_DELETE_MESSAGE_TOO_OLD + * * INVITE_ACCEPTED_TO_GUILD_NOT_CONTANING_BOT + * * REACTION_BLOCKED * @typedef {string} APIError */ exports.APIErrors = { diff --git a/src/util/Permissions.js b/src/util/Permissions.js index c24337672..bb5588f5f 100644 --- a/src/util/Permissions.js +++ b/src/util/Permissions.js @@ -103,7 +103,7 @@ class Permissions { } /** - * Gets an object mapping permission name (like `READ_MESSAGES`) to a {@link boolean} indicating whether the + * Gets an object mapping permission name (like `VIEW_CHANNEL`) to a {@link boolean} indicating whether the * permission is available. * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override * @returns {Object} @@ -152,8 +152,8 @@ class Permissions { /** * Data that can be resolved to give a permission number. This can be: - * - A string (see {@link Permissions.FLAGS}) - * - A permission number + * * A string (see {@link Permissions.FLAGS}) + * * A permission number * @typedef {string|number} PermissionResolvable */ diff --git a/src/util/Util.js b/src/util/Util.js index 9b7d536cd..c23051a65 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -66,9 +66,9 @@ class Util { /** * Parses emoji info out of a string. The string must be one of: - * - A UTF-8 emoji (no ID) - * - A URL-encoded UTF-8 emoji (no ID) - * - A Discord custom emoji (`<:name:id>`) + * * A UTF-8 emoji (no ID) + * * A URL-encoded UTF-8 emoji (no ID) + * * A Discord custom emoji (`<:name:id>`) * @param {string} text Emoji string to parse * @returns {Object} Object with `name` and `id` properties * @private From 2478092d44aac11c0a90cacbed1211af6cd4bdc6 Mon Sep 17 00:00:00 2001 From: Crawl Date: Sun, 20 Aug 2017 22:15:51 +0200 Subject: [PATCH 78/99] More docs cleanup --- src/client/voice/VoiceConnection.js | 2 +- src/structures/GroupDMChannel.js | 16 +++++++++------- src/structures/Guild.js | 2 +- src/structures/MessageMentions.js | 2 +- src/structures/User.js | 2 +- src/structures/Webhook.js | 2 +- src/util/Constants.js | 4 ++-- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 8dc09e5b7..456e8d09f 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -165,7 +165,7 @@ class VoiceConnection extends EventEmitter { } /** - * Set the token and endpoint required to connect to the the voice servers. + * Set the token and endpoint required to connect to the voice servers. * @param {string} token The voice token * @param {string} endpoint The voice endpoint * @returns {void} diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js index d7ee76b50..84ca62371 100644 --- a/src/structures/GroupDMChannel.js +++ b/src/structures/GroupDMChannel.js @@ -47,8 +47,8 @@ class GroupDMChannel extends Channel { this.name = data.name; /** - * A hash of the Group DM icon. - * @type {string} + * A hash of this Group DM icon + * @type {?string} */ this.icon = data.icon; @@ -70,11 +70,13 @@ class GroupDMChannel extends Channel { */ this.applicationID = data.application_id; - /** - * Nicknames for group members - * @type {?Collection} - */ - if (data.nicks) this.nicks = new Collection(data.nicks.map(n => [n.id, n.nick])); + if (data.nicks) { + /** + * Nicknames for group members + * @type {?Collection} + */ + this.nicks = new Collection(data.nicks.map(n => [n.id, n.nick])); + } if (!this.recipients) { /** diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 3946aa846..073c32383 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -18,7 +18,7 @@ const Snowflake = require('../util/Snowflake'); class Guild { constructor(client, data) { /** - * The client that created the instance of the the guild + * The client that created the instance of the guild * @name Guild#client * @type {Client} * @readonly diff --git a/src/structures/MessageMentions.js b/src/structures/MessageMentions.js index 0e8f6de83..99d45913b 100644 --- a/src/structures/MessageMentions.js +++ b/src/structures/MessageMentions.js @@ -102,7 +102,7 @@ class MessageMentions { /** * Any channels that were mentioned - * @type {?Collection} + * @type {Collection} * @readonly */ get channels() { diff --git a/src/structures/User.js b/src/structures/User.js index 4dbc0d84f..94344e4dd 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -10,7 +10,7 @@ const Snowflake = require('../util/Snowflake'); class User { constructor(client, data) { /** - * The client that created the instance of the the user + * The client that created the instance of the user * @name User#client * @type {} * @readonly diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 563e37200..de9a8e4c3 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -36,7 +36,7 @@ class Webhook { /** * The avatar for the webhook - * @type {string} + * @type {?string} */ this.avatar = data.avatar; diff --git a/src/util/Constants.js b/src/util/Constants.js index 132b90b98..08eb0091b 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -555,8 +555,8 @@ exports.UserSettingsMap = { explicit_content_filter: function explicitContentFilter(type) { // eslint-disable-line func-name-matching /** - * Safe direct messaging; force people's messages with images to be scanned before they are sent to you - * one of `DISABLED`, `NON_FRIENDS`, `FRIENDS_AND_NON_FRIENDS` + * Safe direct messaging; force people's messages with images to be scanned before they are sent to you. + * One of `DISABLED`, `NON_FRIENDS`, `FRIENDS_AND_NON_FRIENDS` * @name ClientUserSettings#explicitContentFilter * @type {string} */ From 29aef18de8170d70181f0920e552b8d596fa7ed8 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 10 Aug 2017 01:17:12 +0200 Subject: [PATCH 79/99] updated docs for .toString() so it now uses send instead sendMessage in example (#1761) --- src/structures/ReactionEmoji.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/ReactionEmoji.js b/src/structures/ReactionEmoji.js index c8e67b570..f550544c6 100644 --- a/src/structures/ReactionEmoji.js +++ b/src/structures/ReactionEmoji.js @@ -38,7 +38,7 @@ class ReactionEmoji { * 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}`); + * reaction.message.channel.send(`The emoji used is ${reaction.emoji}`); * @returns {string} */ toString() { From c01e9ad8281f2b8fdb4a4941bb7b8d41bb6e4572 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Thu, 10 Aug 2017 01:21:34 +0200 Subject: [PATCH 80/99] No longer double increment the reaction count when the client reacts (#1755) From a7e5e53e5dfe0f18978d1cc26412366fb9c124ad Mon Sep 17 00:00:00 2001 From: Crawl Date: Sun, 20 Aug 2017 22:18:40 +0200 Subject: [PATCH 81/99] Fix ClientUser#settings not showing up in the documentation (#1757) --- src/structures/ClientUser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 1ae7d6a87..e6171ebec 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -72,7 +72,7 @@ class ClientUser extends User { * This is only filled when using a user account. * @type {?ClientUserSettings} */ - if (data.user_settings) this.settings = new ClientUserSettings(this, data.user_settings); + this.settings = data.user_settings ? new ClientUserSettings(this, data.user_settings) : null; } edit(data) { From 618fa2b104cbaf0e5370454c8e95ab5f61734fcd Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Mon, 10 Jul 2017 03:34:54 +0200 Subject: [PATCH 82/99] functions for setTimeout should get the context bound and not applied (#1673) --- src/client/websocket/WebSocketConnection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/websocket/WebSocketConnection.js b/src/client/websocket/WebSocketConnection.js index 9567f5653..58878b3ea 100644 --- a/src/client/websocket/WebSocketConnection.js +++ b/src/client/websocket/WebSocketConnection.js @@ -444,7 +444,7 @@ class WebSocketConnection extends EventEmitter { * @returns {void} */ identify(after) { - if (after) return this.client.setTimeout(this.identify.apply(this), after); + if (after) return this.client.setTimeout(this.identify.bind(this), after); return this.sessionID ? this.identifyResume() : this.identifyNew(); } From be4ccb368697ce29701b6e3849e1b0c5f74f4621 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Mon, 21 Aug 2017 22:21:18 +0200 Subject: [PATCH 83/99] Backporting audit log reasons and createRole with position for 11.2 (#1810) * Backporting audit log reasons and createRole with position for 11.2 * Sending kick reason via header rather than via query --- src/client/rest/APIRequest.js | 4 +- src/client/rest/RESTManager.js | 4 +- src/client/rest/RESTMethods.js | 169 +++++++++++++------------ src/structures/Emoji.js | 10 +- src/structures/Guild.js | 89 +++++++------ src/structures/GuildChannel.js | 30 +++-- src/structures/GuildMember.js | 45 ++++--- src/structures/Invite.js | 5 +- src/structures/PermissionOverwrites.js | 5 +- src/structures/Role.js | 35 +++-- src/structures/TextChannel.js | 7 +- src/structures/VoiceChannel.js | 10 +- src/structures/Webhook.js | 5 +- 13 files changed, 238 insertions(+), 180 deletions(-) diff --git a/src/client/rest/APIRequest.js b/src/client/rest/APIRequest.js index 518017ae3..3492fa7a8 100644 --- a/src/client/rest/APIRequest.js +++ b/src/client/rest/APIRequest.js @@ -2,7 +2,7 @@ const snekfetch = require('snekfetch'); const Constants = require('../../util/Constants'); class APIRequest { - constructor(rest, method, path, auth, data, files) { + constructor(rest, method, path, auth, data, files, reason) { this.rest = rest; this.client = rest.client; this.method = method; @@ -11,6 +11,7 @@ class APIRequest { this.data = data; this.files = files; this.route = this.getRoute(this.path); + this.reason = reason; } getRoute(url) { @@ -36,6 +37,7 @@ class APIRequest { const API = `${this.client.options.http.host}/api/v${this.client.options.http.version}`; const request = snekfetch[this.method](`${API}${this.path}`); if (this.auth) request.set('Authorization', this.getAuth()); + if (this.reason) request.set('X-Audit-Log-Reason', encodeURIComponent(this.reason)); if (!this.rest.client.browser) request.set('User-Agent', this.rest.userAgentManager.userAgent); if (this.files) { for (const file of this.files) if (file && file.file) request.attach(file.name, file.file, file.name); diff --git a/src/client/rest/RESTManager.js b/src/client/rest/RESTManager.js index 512b3063e..37d5a9599 100644 --- a/src/client/rest/RESTManager.js +++ b/src/client/rest/RESTManager.js @@ -42,8 +42,8 @@ class RESTManager { } } - makeRequest(method, url, auth, data, file) { - const apiRequest = new APIRequest(this, method, url, auth, data, file); + makeRequest(method, url, auth, data, file, reason) { + const apiRequest = new APIRequest(this, method, url, auth, data, file, reason); if (!this.handlers[apiRequest.route]) { const RequestHandlerType = this.getRequestHandler(); this.handlers[apiRequest.route] = new RequestHandlerType(this, apiRequest.route); diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 01ac61f62..023a50a1b 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -252,13 +252,13 @@ class RESTMethods { }); } - createChannel(guild, channelName, channelType, overwrites) { + createChannel(guild, channelName, channelType, overwrites, reason) { if (overwrites instanceof Collection) overwrites = overwrites.array(); return this.rest.makeRequest('post', Endpoints.Guild(guild).channels, true, { name: channelName, type: channelType, permission_overwrites: overwrites, - }).then(data => this.client.actions.ChannelCreate.handle(data).channel); + }, undefined, reason).then(data => this.client.actions.ChannelCreate.handle(data).channel); } createDM(recipient) { @@ -300,14 +300,14 @@ class RESTMethods { }); } - updateChannel(channel, _data) { + updateChannel(channel, _data, reason) { 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', Endpoints.Channel(channel), true, data).then(newData => + return this.rest.makeRequest('patch', Endpoints.Channel(channel), true, data, undefined, reason).then(newData => this.client.actions.ChannelUpdate.handle(newData).updated ); } @@ -373,58 +373,57 @@ class RESTMethods { ); } - updateGuild(guild, _data) { - 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.client.resolver.resolveChannel(_data.afkChannel).id; - if (_data.afkTimeout) data.afk_timeout = Number(_data.afkTimeout); - 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', Endpoints.Guild(guild), true, data).then(newData => + updateGuild(guild, data, reason) { + return this.rest.makeRequest('patch', Endpoints.Guild(guild), true, data, undefined, reason).then(newData => this.client.actions.GuildUpdate.handle(newData).updated ); } kickGuildMember(guild, member, reason) { - const url = `${Endpoints.Guild(guild).Member(member)}?reason=${reason}`; - return this.rest.makeRequest('delete', url, true).then(() => - this.client.actions.GuildMemberRemove.handle({ - guild_id: guild.id, - user: member.user, - }).member - ); + return this.rest.makeRequest( + 'delete', Endpoints.Guild(guild).Member(member), true, + undefined, undefined, reason) + .then(() => + this.client.actions.GuildMemberRemove.handle({ + guild_id: guild.id, + user: member.user, + }).member + ); } - createGuildRole(guild, data) { + createGuildRole(guild, data, reason) { if (data.color) data.color = this.client.resolver.resolveColor(data.color); if (data.permissions) data.permissions = Permissions.resolve(data.permissions); - return this.rest.makeRequest('post', Endpoints.Guild(guild).roles, true, data).then(role => - this.client.actions.GuildRoleCreate.handle({ + return this.rest.makeRequest('post', Endpoints.Guild(guild).roles, true, data, undefined, reason).then(r => { + const { role } = this.client.actions.GuildRoleCreate.handle({ guild_id: guild.id, - role, - }).role - ); + role: r, + }); + if (data.position) return role.setPosition(data.position, reason); + return role; + }); } - deleteGuildRole(role) { - return this.rest.makeRequest('delete', Endpoints.Guild(role.guild).Role(role.id), true).then(() => - this.client.actions.GuildRoleDelete.handle({ - guild_id: role.guild.id, - role_id: role.id, - }).role - ); + deleteGuildRole(role, reason) { + return this.rest.makeRequest( + 'delete', Endpoints.Guild(role.guild).Role(role.id), true, + undefined, undefined, reason) + .then(() => + this.client.actions.GuildRoleDelete.handle({ + guild_id: role.guild.id, + role_id: role.id, + }).role + ); } setChannelOverwrite(channel, payload) { return this.rest.makeRequest('put', `${Endpoints.Channel(channel).permissions}/${payload.id}`, true, payload); } - deletePermissionOverwrites(overwrite) { + deletePermissionOverwrites(overwrite, reason) { return this.rest.makeRequest( - 'delete', `${Endpoints.Channel(overwrite.channel).permissions}/${overwrite.id}`, true + 'delete', `${Endpoints.Channel(overwrite.channel).permissions}/${overwrite.id}`, + true, undefined, undefined, reason ).then(() => overwrite); } @@ -465,7 +464,7 @@ class RESTMethods { }); } - updateGuildMember(member, data) { + updateGuildMember(member, data, reason) { if (data.channel) { data.channel_id = this.client.resolver.resolveChannel(data.channel).id; data.channel = null; @@ -481,12 +480,12 @@ class RESTMethods { } } - return this.rest.makeRequest('patch', endpoint, true, data).then(newData => + return this.rest.makeRequest('patch', endpoint, true, data, undefined, reason).then(newData => member.guild._updateMember(member, newData).mem ); } - addMemberRole(member, role) { + addMemberRole(member, role, reason) { return new Promise((resolve, reject) => { if (member._roles.includes(role.id)) return resolve(member); @@ -501,15 +500,16 @@ class RESTMethods { const timeout = this.client.setTimeout(() => this.client.removeListener(Constants.Events.GUILD_MEMBER_UPDATE, listener), 10e3); - return this.rest.makeRequest('put', Endpoints.Member(member).Role(role.id), true).catch(err => { - this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); - this.client.clearTimeout(timeout); - reject(err); - }); + return this.rest.makeRequest('put', Endpoints.Member(member).Role(role.id), true, undefined, undefined, reason) + .catch(err => { + this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); + this.client.clearTimeout(timeout); + reject(err); + }); }); } - removeMemberRole(member, role) { + removeMemberRole(member, role, reason) { return new Promise((resolve, reject) => { if (!member._roles.includes(role.id)) return resolve(member); @@ -524,11 +524,12 @@ class RESTMethods { const timeout = this.client.setTimeout(() => this.client.removeListener(Constants.Events.GUILD_MEMBER_UPDATE, listener), 10e3); - return this.rest.makeRequest('delete', Endpoints.Member(member).Role(role.id), true).catch(err => { - this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); - this.client.clearTimeout(timeout); - reject(err); - }); + return this.rest.makeRequest('delete', Endpoints.Member(member).Role(role.id), true, undefined, undefined, reason) + .catch(err => { + this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); + this.client.clearTimeout(timeout); + reject(err); + }); }); } @@ -552,7 +553,7 @@ class RESTMethods { }); } - unbanGuildMember(guild, member) { + unbanGuildMember(guild, member, reason) { return new Promise((resolve, reject) => { const id = this.client.resolver.resolveUserID(member); if (!id) throw new Error('Couldn\'t resolve the user ID to unban.'); @@ -571,11 +572,12 @@ class RESTMethods { reject(new Error('Took too long to receive the ban remove event.')); }, 10000); - this.rest.makeRequest('delete', `${Endpoints.Guild(guild).bans}/${id}`, true).catch(err => { - this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); - this.client.clearTimeout(timeout); - reject(err); - }); + this.rest.makeRequest('delete', `${Endpoints.Guild(guild).bans}/${id}`, true, undefined, undefined, reason) + .catch(err => { + this.client.removeListener(Constants.Events.GUILD_BAN_REMOVE, listener); + this.client.clearTimeout(timeout); + reject(err); + }); }); } @@ -591,7 +593,7 @@ class RESTMethods { ); } - updateGuildRole(role, _data) { + updateGuildRole(role, _data, reason) { const data = {}; data.name = _data.name || role.name; data.position = typeof _data.position !== 'undefined' ? _data.position : role.position; @@ -602,12 +604,13 @@ class RESTMethods { if (_data.permissions) data.permissions = Permissions.resolve(_data.permissions); else data.permissions = role.permissions; - return this.rest.makeRequest('patch', Endpoints.Guild(role.guild).Role(role.id), true, data).then(_role => - this.client.actions.GuildRoleUpdate.handle({ - role: _role, - guild_id: role.guild.id, - }).updated - ); + return this.rest.makeRequest('patch', Endpoints.Guild(role.guild).Role(role.id), true, data, undefined, reason) + .then(_role => + this.client.actions.GuildRoleUpdate.handle({ + role: _role, + guild_id: role.guild.id, + }).updated + ); } pinMessage(message) { @@ -624,17 +627,18 @@ class RESTMethods { return this.rest.makeRequest('get', Endpoints.Channel(channel).pins, true); } - createChannelInvite(channel, options) { + createChannelInvite(channel, options, reason) { const payload = {}; payload.temporary = options.temporary; payload.max_age = options.maxAge; payload.max_uses = options.maxUses; - return this.rest.makeRequest('post', Endpoints.Channel(channel).invites, true, payload) + return this.rest.makeRequest('post', Endpoints.Channel(channel).invites, true, payload, undefined, reason) .then(invite => new Invite(this.client, invite)); } - deleteInvite(invite) { - return this.rest.makeRequest('delete', Endpoints.Invite(invite.code), true).then(() => invite); + deleteInvite(invite, reason) { + return this.rest.makeRequest('delete', Endpoints.Invite(invite.code), true, undefined, undefined, reason) + .then(() => invite); } getInvite(code) { @@ -654,28 +658,31 @@ class RESTMethods { }); } - pruneGuildMembers(guild, days, dry) { - return this.rest.makeRequest(dry ? 'get' : 'post', `${Endpoints.Guild(guild).prune}?days=${days}`, true) + pruneGuildMembers(guild, days, dry, reason) { + return this.rest.makeRequest(dry ? + 'get' : + 'post', + `${Endpoints.Guild(guild).prune}?days=${days}`, true, undefined, undefined, reason) .then(data => data.pruned); } - createEmoji(guild, image, name, roles) { + createEmoji(guild, image, name, roles, reason) { const data = { image, name }; if (roles) data.roles = roles.map(r => r.id ? r.id : r); - return this.rest.makeRequest('post', Endpoints.Guild(guild).emojis, true, data) + return this.rest.makeRequest('post', Endpoints.Guild(guild).emojis, true, data, undefined, reason) .then(emoji => this.client.actions.GuildEmojiCreate.handle(guild, emoji).emoji); } - updateEmoji(emoji, _data) { + updateEmoji(emoji, _data, reason) { const data = {}; if (_data.name) data.name = _data.name; if (_data.roles) data.roles = _data.roles.map(r => r.id ? r.id : r); - return this.rest.makeRequest('patch', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true, data) + return this.rest.makeRequest('patch', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true, data, undefined, reason) .then(newEmoji => this.client.actions.GuildEmojiUpdate.handle(emoji, newEmoji).emoji); } - deleteEmoji(emoji) { - return this.rest.makeRequest('delete', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true) + deleteEmoji(emoji, reason) { + return this.rest.makeRequest('delete', Endpoints.Guild(emoji.guild).Emoji(emoji.id), true, undefined, reason) .then(() => this.client.actions.GuildEmojiDelete.handle(emoji).data); } @@ -718,8 +725,8 @@ class RESTMethods { }); } - createWebhook(channel, name, avatar) { - return this.rest.makeRequest('post', Endpoints.Channel(channel).webhooks, true, { name, avatar }) + createWebhook(channel, name, avatar, reason) { + return this.rest.makeRequest('post', Endpoints.Channel(channel).webhooks, true, { name, avatar }, undefined, reason) .then(data => new Webhook(this.client, data)); } @@ -734,8 +741,10 @@ class RESTMethods { }); } - deleteWebhook(webhook) { - return this.rest.makeRequest('delete', Endpoints.Webhook(webhook.id, webhook.token), false); + deleteWebhook(webhook, reason) { + return this.rest.makeRequest( + 'delete', Endpoints.Webhook(webhook.id, webhook.token), + false, undefined, undefined, reason); } sendWebhookMessage(webhook, content, { avatarURL, tts, disableEveryone, embeds, username } = {}, file = null) { diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js index 88d07fa78..246c95740 100644 --- a/src/structures/Emoji.js +++ b/src/structures/Emoji.js @@ -112,6 +112,7 @@ class Emoji { /** * Edits the emoji. * @param {EmojiEditData} data The new data for the emoji + * @param {string} [reason] Reason for editing this emoji * @returns {Promise} * @example * // Edit an emoji @@ -119,17 +120,18 @@ class Emoji { * .then(e => console.log(`Edited emoji ${e}`)) * .catch(console.error); */ - edit(data) { - return this.client.rest.methods.updateEmoji(this, data); + edit(data, reason) { + return this.client.rest.methods.updateEmoji(this, data, reason); } /** * Set the name of the emoji. * @param {string} name The new name for the emoji + * @param {string} [reason] The reason for changing the emoji's name * @returns {Promise} */ - setName(name) { - return this.edit({ name }); + setName(name, reason) { + return this.edit({ name }, reason); } /** diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 073c32383..baecfc7c3 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -535,6 +535,7 @@ class Guild { /** * Updates the guild with new information - e.g. a new name. * @param {GuildEditData} data The data to update the guild with + * @param {string} [reason] Reason for editing this guild * @returns {Promise} * @example * // Set the guild name and region @@ -545,7 +546,7 @@ class Guild { * .then(updated => console.log(`New guild name ${updated.name} in region ${updated.region}`)) * .catch(console.error); */ - edit(data) { + edit(data, reason) { const _data = {}; if (data.name) _data.name = data.name; if (data.region) _data.region = data.region; @@ -559,21 +560,23 @@ class Guild { if (typeof data.explicitContentFilter !== 'undefined') { _data.explicit_content_filter = Number(data.explicitContentFilter); } - return this.client.rest.methods.updateGuild(this, _data); + return this.client.rest.methods.updateGuild(this, _data, reason); } /** * Edit the level of the explicit content filter. * @param {number} explicitContentFilter The new level of the explicit content filter + * @param {string} [reason] Reason for changing the level of the guild's explicit content filter * @returns {Promise} */ - setExplicitContentFilter(explicitContentFilter) { - return this.edit({ explicitContentFilter }); + setExplicitContentFilter(explicitContentFilter, reason) { + return this.edit({ explicitContentFilter }, reason); } /** * Edit the name of the guild. * @param {string} name The new name of the guild + * @param {string} [reason] Reason for changing the guild's name * @returns {Promise} * @example * // Edit the guild name @@ -581,13 +584,14 @@ class Guild { * .then(updated => console.log(`Updated guild name to ${guild.name}`)) * .catch(console.error); */ - setName(name) { - return this.edit({ name }); + setName(name, reason) { + return this.edit({ name }, reason); } /** * Edit the region of the guild. * @param {string} region The new region of the guild + * @param {string} [reason] Reason for changing the guild's region * @returns {Promise} * @example * // Edit the guild region @@ -595,13 +599,14 @@ class Guild { * .then(updated => console.log(`Updated guild region to ${guild.region}`)) * .catch(console.error); */ - setRegion(region) { - return this.edit({ region }); + setRegion(region, reason) { + return this.edit({ region }, reason); } /** * Edit the verification level of the guild. * @param {number} verificationLevel The new verification level of the guild + * @param {string} [reason] Reason for changing the guild's verification level * @returns {Promise} * @example * // Edit the guild verification level @@ -609,13 +614,14 @@ class Guild { * .then(updated => console.log(`Updated guild verification level to ${guild.verificationLevel}`)) * .catch(console.error); */ - setVerificationLevel(verificationLevel) { - return this.edit({ verificationLevel }); + setVerificationLevel(verificationLevel, reason) { + return this.edit({ verificationLevel }, reason); } /** * Edit the AFK channel of the guild. * @param {ChannelResolvable} afkChannel The new AFK channel + * @param {string} [reason] Reason for changing the guild's AFK channel * @returns {Promise} * @example * // Edit the guild AFK channel @@ -623,22 +629,24 @@ class Guild { * .then(updated => console.log(`Updated guild AFK channel to ${guild.afkChannel}`)) * .catch(console.error); */ - setAFKChannel(afkChannel) { - return this.edit({ afkChannel }); + setAFKChannel(afkChannel, reason) { + return this.edit({ afkChannel }, reason); } /** * Edit the system channel of the guild. * @param {ChannelResolvable} systemChannel The new system channel + * @param {string} [reason] Reason for changing the guild's system channel * @returns {Promise} */ - setSystemChannel(systemChannel) { - return this.edit({ systemChannel }); + setSystemChannel(systemChannel, reason) { + return this.edit({ systemChannel }, reason); } /** * Edit the AFK timeout of the guild. * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK + * @param {string} [reason] Reason for changing the guild's AFK timeout * @returns {Promise} * @example * // Edit the guild AFK channel @@ -646,13 +654,14 @@ class Guild { * .then(updated => console.log(`Updated guild AFK timeout to ${guild.afkTimeout}`)) * .catch(console.error); */ - setAFKTimeout(afkTimeout) { - return this.edit({ afkTimeout }); + setAFKTimeout(afkTimeout, reason) { + return this.edit({ afkTimeout }, reason); } /** * Set a new guild icon. * @param {Base64Resolvable} icon The new icon of the guild + * @param {string} [reason] Reason for changing the guild's icon * @returns {Promise} * @example * // Edit the guild icon @@ -660,13 +669,14 @@ class Guild { * .then(updated => console.log('Updated the guild icon')) * .catch(console.error); */ - setIcon(icon) { - return this.edit({ icon }); + setIcon(icon, reason) { + return this.edit({ icon }, reason); } /** * Sets a new owner of the guild. * @param {GuildMemberResolvable} owner The new owner of the guild + * @param {string} [reason] Reason for setting the new owner * @returns {Promise} * @example * // Edit the guild owner @@ -674,13 +684,14 @@ class Guild { * .then(updated => console.log(`Updated the guild owner to ${updated.owner.username}`)) * .catch(console.error); */ - setOwner(owner) { - return this.edit({ owner }); + setOwner(owner, reason) { + return this.edit({ owner }, reason); } /** * Set a new guild splash screen. * @param {Base64Resolvable} splash The new splash screen of the guild + * @param {string} [reason] Reason for changing the guild's splash screen * @returns {Promise} * @example * // Edit the guild splash @@ -688,8 +699,8 @@ class Guild { * .then(updated => console.log('Updated the guild splash')) * .catch(console.error); */ - setSplash(splash) { - return this.edit({ splash }); + setSplash(splash, reason) { + return this.edit({ splash }, reason); } /** @@ -755,6 +766,7 @@ class Guild { /** * Unbans a user from the guild. * @param {UserResolvable} user The user to unban + * @param {string} [reason] Reason for unbanning the user * @returns {Promise} * @example * // Unban a user by ID (or with a user/guild member object) @@ -762,14 +774,15 @@ class Guild { * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`)) * .catch(console.error); */ - unban(user) { - return this.client.rest.methods.unbanGuildMember(this, user); + unban(user, reason) { + return this.client.rest.methods.unbanGuildMember(this, user, reason); } /** * Prunes members from the guild based on how long they have been inactive. * @param {number} days Number of days of inactivity required to kick * @param {boolean} [dry=false] If true, will return number of users that will be kicked, without actually doing it + * @param {string} [reason] Reason for this prune * @returns {Promise} The number of members that were/will be kicked * @example * // See how many members will be pruned @@ -782,9 +795,9 @@ class Guild { * .then(pruned => console.log(`I just pruned ${pruned} people!`)) * .catch(console.error); */ - pruneMembers(days, dry = false) { + pruneMembers(days, dry = false, reason) { if (typeof days !== 'number') throw new TypeError('Days must be a number.'); - return this.client.rest.methods.pruneGuildMembers(this, days, dry); + return this.client.rest.methods.pruneGuildMembers(this, days, dry, reason); } /** @@ -799,7 +812,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 Permission overwrites to apply to the new channel + * @param {Array} [overwrites] Permission overwrites to apply to the new channel + * @param {string} [reason] Reason for creating this channel * @returns {Promise} * @example * // Create a new text channel @@ -807,8 +821,8 @@ class Guild { * .then(channel => console.log(`Created new channel ${channel}`)) * .catch(console.error); */ - createChannel(name, type, overwrites) { - return this.client.rest.methods.createChannel(this, name, type, overwrites); + createChannel(name, type, overwrites, reason) { + return this.client.rest.methods.createChannel(this, name, type, overwrites, reason); } /** @@ -834,6 +848,7 @@ class Guild { /** * Creates a new role in the guild with given information * @param {RoleData} [data] The data to update the role with + * @param {string} [reason] Reason for creating this role * @returns {Promise} * @example * // Create a new role @@ -849,8 +864,8 @@ class Guild { * .then(role => console.log(`Created role ${role}`)) * .catch(console.error) */ - createRole(data = {}) { - return this.client.rest.methods.createGuildRole(this, data); + createRole(data = {}, reason) { + return this.client.rest.methods.createGuildRole(this, data, reason); } /** @@ -858,6 +873,7 @@ class Guild { * @param {BufferResolvable|Base64Resolvable} attachment The image for the emoji * @param {string} name The name for the emoji * @param {Collection|Role[]} [roles] Roles to limit the emoji to + * @param {string} [reason] Reason for creating the emoji * @returns {Promise} The created emoji * @example * // Create a new emoji from a url @@ -870,14 +886,14 @@ class Guild { * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`)) * .catch(console.error); */ - createEmoji(attachment, name, roles) { + createEmoji(attachment, name, roles, reason) { return new Promise(resolve => { if (typeof attachment === 'string' && attachment.startsWith('data:')) { - resolve(this.client.rest.methods.createEmoji(this, attachment, name, roles)); + resolve(this.client.rest.methods.createEmoji(this, attachment, name, roles, reason)); } else { this.client.resolver.resolveBuffer(attachment).then(data => { const dataURI = this.client.resolver.resolveBase64(data); - resolve(this.client.rest.methods.createEmoji(this, dataURI, name, roles)); + resolve(this.client.rest.methods.createEmoji(this, dataURI, name, roles, reason)); }); } }); @@ -886,11 +902,12 @@ class Guild { /** * Delete an emoji. * @param {Emoji|string} emoji The emoji to delete + * @param {string} [reason] Reason for deleting the emoji * @returns {Promise} */ - deleteEmoji(emoji) { + deleteEmoji(emoji, reason) { if (!(emoji instanceof Emoji)) emoji = this.emojis.get(emoji); - return this.client.rest.methods.deleteEmoji(emoji); + return this.client.rest.methods.deleteEmoji(emoji, reason); } /** diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 0fe2d974b..9b3e83c0e 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -138,6 +138,7 @@ class GuildChannel extends Channel { * Overwrites the permissions for a user or role in this channel. * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update * @param {PermissionOverwriteOptions} options The configuration for the update + * @param {string} [reason] Reason for creating/editing this overwrite * @returns {Promise} * @example * // Overwrite permissions for a message author @@ -147,7 +148,7 @@ class GuildChannel extends Channel { * .then(() => console.log('Done!')) * .catch(console.error); */ - overwritePermissions(userOrRole, options) { + overwritePermissions(userOrRole, options, reason) { const payload = { allow: 0, deny: 0, @@ -186,7 +187,7 @@ class GuildChannel extends Channel { } } - return this.client.rest.methods.setChannelOverwrite(this, payload); + return this.client.rest.methods.setChannelOverwrite(this, payload, reason); } /** @@ -202,6 +203,7 @@ class GuildChannel extends Channel { /** * Edits the channel. * @param {ChannelData} data The new data for the channel + * @param {string} [reason] Reason for editing this channel * @returns {Promise} * @example * // Edit a channel @@ -209,13 +211,14 @@ class GuildChannel extends Channel { * .then(c => console.log(`Edited channel ${c}`)) * .catch(console.error); */ - edit(data) { - return this.client.rest.methods.updateChannel(this, data); + edit(data, reason) { + return this.client.rest.methods.updateChannel(this, data, reason); } /** * Set a new name for the guild channel. * @param {string} name The new name for the guild channel + * @param {string} [reason] Reason for changing the guild channel's name * @returns {Promise} * @example * // Set a new channel name @@ -223,8 +226,8 @@ class GuildChannel extends Channel { * .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`)) * .catch(console.error); */ - setName(name) { - return this.edit({ name }); + setName(name, reason) { + return this.edit({ name }, reason); } /** @@ -245,6 +248,7 @@ class GuildChannel extends Channel { /** * Set a new topic for the guild channel. * @param {string} topic The new topic for the guild channel + * @param {string} [reason] Reason for changing the guild channel's topic * @returns {Promise} * @example * // Set a new channel topic @@ -252,8 +256,8 @@ class GuildChannel extends Channel { * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`)) * .catch(console.error); */ - setTopic(topic) { - return this.client.rest.methods.updateChannel(this, { topic }); + setTopic(topic, reason) { + return this.edit({ topic }, reason); } /** @@ -269,10 +273,11 @@ class GuildChannel extends Channel { * kicked after 24 hours if they have not yet received a role * @param {number} [options.maxAge=86400] How long the invite should last (in seconds, 0 for forever) * @param {number} [options.maxUses=0] Maximum number of uses + * @param {string} [reason] Reason for creating the invite * @returns {Promise} */ - createInvite(options = {}) { - return this.client.rest.methods.createChannelInvite(this, options); + createInvite(options = {}, reason) { + return this.client.rest.methods.createChannelInvite(this, options, reason); } /** @@ -280,10 +285,11 @@ class GuildChannel extends 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 * @param {boolean} [withTopic=true] Whether to clone the channel with this channel's topic + * @param {string} [reason] Reason for cloning this channel * @returns {Promise} */ - clone(name = this.name, withPermissions = true, withTopic = true) { - return this.guild.createChannel(name, this.type, withPermissions ? this.permissionOverwrites : []) + clone(name = this.name, withPermissions = true, withTopic = true, reason) { + return this.guild.createChannel(name, this.type, withPermissions ? this.permissionOverwrites : [], reason) .then(channel => withTopic ? channel.setTopic(this.topic) : channel); } diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js index 444727976..bf87bb9ef 100644 --- a/src/structures/GuildMember.js +++ b/src/structures/GuildMember.js @@ -345,28 +345,31 @@ class GuildMember { /** * Edit a guild member. * @param {GuildMemberEditData} data The data to edit the member with + * @param {string} [reason] Reason for editing this user * @returns {Promise} */ - edit(data) { - return this.client.rest.methods.updateGuildMember(this, data); + edit(data, reason) { + return this.client.rest.methods.updateGuildMember(this, data, reason); } /** * Mute/unmute a user. * @param {boolean} mute Whether or not the member should be muted + * @param {string} [reason] Reason for muting or unmuting * @returns {Promise} */ - setMute(mute) { - return this.edit({ mute }); + setMute(mute, reason) { + return this.edit({ mute }, reason); } /** * Deafen/undeafen a user. * @param {boolean} deaf Whether or not the member should be deafened + * @param {string} [reason] Reason for deafening or undeafening * @returns {Promise} */ - setDeaf(deaf) { - return this.edit({ deaf }); + setDeaf(deaf, reason) { + return this.edit({ deaf }, reason); } /** @@ -381,29 +384,32 @@ class GuildMember { /** * Sets the roles applied to the member. * @param {Collection|Role[]|Snowflake[]} roles The roles or role IDs to apply + * @param {string} [reason] Reason for applying the roles * @returns {Promise} */ - setRoles(roles) { - return this.edit({ roles }); + setRoles(roles, reason) { + return this.edit({ roles }, reason); } /** * Adds a single role to the member. * @param {Role|Snowflake} role The role or ID of the role to add + * @param {string} [reason] Reason for adding the role * @returns {Promise} */ - addRole(role) { + addRole(role, reason) { if (!(role instanceof Role)) role = this.guild.roles.get(role); if (!role) return Promise.reject(new TypeError('Supplied parameter was neither a Role nor a Snowflake.')); - return this.client.rest.methods.addMemberRole(this, role); + return this.client.rest.methods.addMemberRole(this, role, reason); } /** * Adds multiple roles to the member. * @param {Collection|Role[]|Snowflake[]} roles The roles or role IDs to add + * @param {string} [reason] Reason for adding the roles * @returns {Promise} */ - addRoles(roles) { + addRoles(roles, reason) { let allRoles; if (roles instanceof Collection) { allRoles = this._roles.slice(); @@ -411,26 +417,28 @@ class GuildMember { } else { allRoles = this._roles.concat(roles); } - return this.edit({ roles: allRoles }); + return this.edit({ roles: allRoles }, reason); } /** * Removes a single role from the member. * @param {Role|Snowflake} role The role or ID of the role to remove + * @param {string} [reason] Reason for removing the role * @returns {Promise} */ - removeRole(role) { + removeRole(role, reason) { if (!(role instanceof Role)) role = this.guild.roles.get(role); if (!role) return Promise.reject(new TypeError('Supplied parameter was neither a Role nor a Snowflake.')); - return this.client.rest.methods.removeMemberRole(this, role); + return this.client.rest.methods.removeMemberRole(this, role, reason); } /** * Removes multiple roles from the member. * @param {Collection|Role[]|Snowflake[]} roles The roles or role IDs to remove + * @param {string} [reason] Reason for removing the roles * @returns {Promise} */ - removeRoles(roles) { + removeRoles(roles, reason) { const allRoles = this._roles.slice(); if (roles instanceof Collection) { for (const role of roles.values()) { @@ -443,16 +451,17 @@ class GuildMember { if (index >= 0) allRoles.splice(index, 1); } } - return this.edit({ roles: allRoles }); + return this.edit({ roles: allRoles }, reason); } /** * Set the nickname for the guild member. * @param {string} nick The nickname for the guild member + * @param {string} [reason] Reason for setting the nickname * @returns {Promise} */ - setNickname(nick) { - return this.edit({ nick }); + setNickname(nick, reason) { + return this.edit({ nick }, reason); } /** diff --git a/src/structures/Invite.js b/src/structures/Invite.js index cd9324b44..5582f0941 100644 --- a/src/structures/Invite.js +++ b/src/structures/Invite.js @@ -141,10 +141,11 @@ class Invite { /** * Deletes this invite. + * @param {string} [reason] Reason for deleting this invite * @returns {Promise} */ - delete() { - return this.client.rest.methods.deleteInvite(this); + delete(reason) { + return this.client.rest.methods.deleteInvite(this, reason); } /** diff --git a/src/structures/PermissionOverwrites.js b/src/structures/PermissionOverwrites.js index 8044be45a..efe9e956b 100644 --- a/src/structures/PermissionOverwrites.js +++ b/src/structures/PermissionOverwrites.js @@ -33,10 +33,11 @@ class PermissionOverwrites { /** * Delete this Permission Overwrite. + * @param {string} [reason] Reason for deleting this overwrite * @returns {Promise} */ - delete() { - return this.channel.client.rest.methods.deletePermissionOverwrites(this); + delete(reason) { + return this.channel.client.rest.methods.deletePermissionOverwrites(this, reason); } } diff --git a/src/structures/Role.js b/src/structures/Role.js index fb23fdff1..87f5cd902 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -202,6 +202,7 @@ class Role { /** * Edits the role. * @param {RoleData} data The new data for the role + * @param {string} [reason] The reason for editing this role * @returns {Promise} * @example * // Edit a role @@ -209,13 +210,14 @@ class Role { * .then(r => console.log(`Edited role ${r}`)) * .catch(console.error); */ - edit(data) { - return this.client.rest.methods.updateGuildRole(this, data); + edit(data, reason) { + return this.client.rest.methods.updateGuildRole(this, data, reason); } /** * Set a new name for the role. * @param {string} name The new name of the role + * @param {string} [reason] Reason for changing the role's name * @returns {Promise} * @example * // Set the name of the role @@ -223,13 +225,14 @@ class Role { * .then(r => console.log(`Edited name of role ${r}`)) * .catch(console.error); */ - setName(name) { - return this.edit({ name }); + setName(name, reason) { + return this.edit({ name }, reason); } /** * Set a new color for the role. * @param {ColorResolvable} color The color of the role + * @param {string} [reason] Reason for changing the role's color * @returns {Promise} * @example * // Set the color of a role @@ -237,13 +240,14 @@ class Role { * .then(r => console.log(`Set color of role ${r}`)) * .catch(console.error); */ - setColor(color) { - return this.edit({ color }); + setColor(color, reason) { + return this.edit({ color }, reason); } /** * Set whether or not the role should be hoisted. * @param {boolean} hoist Whether or not to hoist the role + * @param {string} [reason] Reason for setting whether or not the role should be hoisted * @returns {Promise} * @example * // Set the hoist of the role @@ -251,8 +255,8 @@ class Role { * .then(r => console.log(`Role hoisted: ${r.hoist}`)) * .catch(console.error); */ - setHoist(hoist) { - return this.edit({ hoist }); + setHoist(hoist, reason) { + return this.edit({ hoist }, reason); } /** @@ -273,6 +277,7 @@ class Role { /** * Set the permissions of the role. * @param {string[]} permissions The permissions of the role + * @param {string} [reason] Reason for changing the role's permissions * @returns {Promise} * @example * // Set the permissions of the role @@ -280,13 +285,14 @@ class Role { * .then(r => console.log(`Role updated ${r}`)) * .catch(console.error); */ - setPermissions(permissions) { - return this.edit({ permissions }); + setPermissions(permissions, reason) { + return this.edit({ permissions }, reason); } /** * Set whether this role is mentionable. * @param {boolean} mentionable Whether this role should be mentionable + * @param {string} [reason] Reason for setting whether or not this role should be mentionable * @returns {Promise} * @example * // Make the role mentionable @@ -294,12 +300,13 @@ class Role { * .then(r => console.log(`Role updated ${r}`)) * .catch(console.error); */ - setMentionable(mentionable) { - return this.edit({ mentionable }); + setMentionable(mentionable, reason) { + return this.edit({ mentionable }, reason); } /** * Deletes the role. + * @param {string} [reason] Reason for deleting the role * @returns {Promise} * @example * // Delete a role @@ -307,8 +314,8 @@ class Role { * .then(r => console.log(`Deleted role ${r}`)) * .catch(console.error); */ - delete() { - return this.client.rest.methods.deleteGuildRole(this); + delete(reason) { + return this.client.rest.methods.deleteGuildRole(this, reason); } /** diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 2d01517c5..8ce79fb4b 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -61,19 +61,20 @@ class TextChannel extends GuildChannel { * Create a webhook for the channel. * @param {string} name The name of the webhook * @param {BufferResolvable|Base64Resolvable} avatar The avatar for the webhook + * @param {string} [reason] Reason for creating this webhook * @returns {Promise} webhook The created webhook * @example * channel.createWebhook('Snek', 'https://i.imgur.com/mI8XcpG.jpg') * .then(webhook => console.log(`Created webhook ${webhook}`)) * .catch(console.error) */ - createWebhook(name, avatar) { + createWebhook(name, avatar, reason) { return new Promise(resolve => { if (typeof avatar === 'string' && avatar.startsWith('data:')) { - resolve(this.client.rest.methods.createWebhook(this, name, avatar)); + resolve(this.client.rest.methods.createWebhook(this, name, avatar, reason)); } else { this.client.resolver.resolveBuffer(avatar).then(data => - resolve(this.client.rest.methods.createWebhook(this, name, data)) + resolve(this.client.rest.methods.createWebhook(this, name, data, reason)) ); } }); diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js index 081710de0..fe7d7ddbc 100644 --- a/src/structures/VoiceChannel.js +++ b/src/structures/VoiceChannel.js @@ -78,6 +78,7 @@ class VoiceChannel extends GuildChannel { /** * Sets the bitrate of the channel (in kbps). * @param {number} bitrate The new bitrate + * @param {string} [reason] Reason for changing the channel's bitrate * @returns {Promise} * @example * // Set the bitrate of a voice channel @@ -85,14 +86,15 @@ class VoiceChannel extends GuildChannel { * .then(vc => console.log(`Set bitrate to ${vc.bitrate}kbps for ${vc.name}`)) * .catch(console.error); */ - setBitrate(bitrate) { + setBitrate(bitrate, reason) { bitrate *= 1000; - return this.edit({ bitrate }); + return this.edit({ bitrate }, reason); } /** * Sets the user limit of the channel. * @param {number} userLimit The new user limit + * @param {string} [reason] Reason for changing the user limit * @returns {Promise} * @example * // Set the user limit of a voice channel @@ -100,8 +102,8 @@ class VoiceChannel extends GuildChannel { * .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`)) * .catch(console.error); */ - setUserLimit(userLimit) { - return this.edit({ userLimit }); + setUserLimit(userLimit, reason) { + return this.edit({ userLimit }, reason); } /** diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index de9a8e4c3..9a70ce833 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -216,10 +216,11 @@ class Webhook { /** * Delete the webhook. + * @param {string} [reason] Reason for deleting the webhook * @returns {Promise} */ - delete() { - return this.client.rest.methods.deleteWebhook(this); + delete(reason) { + return this.client.rest.methods.deleteWebhook(this, reason); } } From f7664b01a2d01e669f661ad36d8fcbe32d29be0d Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Mon, 21 Aug 2017 22:25:21 +0200 Subject: [PATCH 84/99] Backports (#1813) * Backported OAuth2Application https://github.com/hydrabolt/discord.js/commit/201ecd25a2e017c0be74ed2c68abc2c323813a87 * Backported retry on 500 https://github.com/hydrabolt/discord.js/commit/57b69803132355b506f9919f37a55915ccdf86d3 * Backported https://github.com/hydrabolt/discord.js/commit/b8034525e3da2a5bd05fab44841ee3b0149ff70c and https://github.com/hydrabolt/discord.js/commit/fa5c4efa2bc545bc4df6f449dde343a6dcfdcfe2 --- src/client/rest/RESTMethods.js | 7 +- src/client/rest/RequestHandlers/Burst.js | 6 ++ src/client/rest/RequestHandlers/Sequential.js | 3 + .../packets/WebSocketPacketManager.js | 1 + .../websocket/packets/handlers/Ready.js | 1 + .../handlers/UserGuildSettingsUpdate.js | 18 +++++ src/structures/ClientUser.js | 13 ++++ src/structures/ClientUserChannelOverride.js | 28 +++++++ src/structures/ClientUserGuildSettings.js | 58 ++++++++++++++ src/structures/Guild.js | 61 +++++++++++++++ src/structures/GuildChannel.js | 31 ++++++++ src/structures/OAuth2Application.js | 3 +- src/structures/User.js | 2 +- src/util/Constants.js | 77 ++++++++++++++++++- 14 files changed, 304 insertions(+), 5 deletions(-) create mode 100644 src/client/websocket/packets/handlers/UserGuildSettingsUpdate.js create mode 100644 src/structures/ClientUserChannelOverride.js create mode 100644 src/structures/ClientUserGuildSettings.js diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 023a50a1b..14a28ee5e 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -878,7 +878,8 @@ class RESTMethods { } resetApplication(id) { - return this.rest.makeRequest('post', Endpoints.OAUTH2.Application(id).reset, true) + return this.rest.makeRequest('post', Endpoints.OAUTH2.Application(id).resetToken, true) + .then(() => this.rest.makeRequest('post', Endpoints.OAUTH2.Application(id).resetSecret, true)) .then(app => new OAuth2Application(this.client, app)); } @@ -908,6 +909,10 @@ class RESTMethods { patchUserSettings(data) { return this.rest.makeRequest('patch', Constants.Endpoints.User('@me').settings, true, data); } + + patchClientUserGuildSettings(guildID, data) { + return this.rest.makeRequest('patch', Constants.Endpoints.User('@me').Guild(guildID).settings, true, data); + } } module.exports = RESTMethods; diff --git a/src/client/rest/RequestHandlers/Burst.js b/src/client/rest/RequestHandlers/Burst.js index e5a160246..f3311ab65 100644 --- a/src/client/rest/RequestHandlers/Burst.js +++ b/src/client/rest/RequestHandlers/Burst.js @@ -40,6 +40,12 @@ class BurstRequestHandler extends RequestHandler { this.handle(); this.resetTimeout = null; }, Number(res.headers['retry-after']) + this.client.options.restTimeOffset); + } else if (err.status >= 500 && err.status < 600) { + this.queue.unshift(item); + this.resetTimeout = this.client.setTimeout(() => { + this.handle(); + this.resetTimeout = null; + }, 1e3 + this.client.options.restTimeOffset); } else { item.reject(err.status === 400 ? new DiscordAPIError(res.body) : err); this.handle(); diff --git a/src/client/rest/RequestHandlers/Sequential.js b/src/client/rest/RequestHandlers/Sequential.js index 04f805237..42b07452e 100644 --- a/src/client/rest/RequestHandlers/Sequential.js +++ b/src/client/rest/RequestHandlers/Sequential.js @@ -64,6 +64,9 @@ class SequentialRequestHandler extends RequestHandler { resolve(); }, Number(res.headers['retry-after']) + this.restManager.client.options.restTimeOffset); if (res.headers['x-ratelimit-global']) this.globalLimit = true; + } else if (err.status >= 500 && err.status < 600) { + this.queue.unshift(item); + this.restManager.client.setTimeout(resolve, 1e3 + this.client.options.restTimeOffset); } else { item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.body) : err); resolve(err); diff --git a/src/client/websocket/packets/WebSocketPacketManager.js b/src/client/websocket/packets/WebSocketPacketManager.js index 079ef5d8c..efc42df4a 100644 --- a/src/client/websocket/packets/WebSocketPacketManager.js +++ b/src/client/websocket/packets/WebSocketPacketManager.js @@ -39,6 +39,7 @@ class WebSocketPacketManager { this.register(Constants.WSEvents.USER_UPDATE, require('./handlers/UserUpdate')); this.register(Constants.WSEvents.USER_NOTE_UPDATE, require('./handlers/UserNoteUpdate')); this.register(Constants.WSEvents.USER_SETTINGS_UPDATE, require('./handlers/UserSettingsUpdate')); + this.register(Constants.WSEvents.USER_GUILD_SETTINGS_UPDATE, require('./handlers/UserGuildSettingsUpdate')); 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')); diff --git a/src/client/websocket/packets/handlers/Ready.js b/src/client/websocket/packets/handlers/Ready.js index d7ee8ed11..8c2492abf 100644 --- a/src/client/websocket/packets/handlers/Ready.js +++ b/src/client/websocket/packets/handlers/Ready.js @@ -10,6 +10,7 @@ class ReadyHandler extends AbstractHandler { client.ws.heartbeat(); data.user.user_settings = data.user_settings; + data.user.user_guild_settings = data.user_guild_settings; const clientUser = new ClientUser(client, data.user); client.user = clientUser; diff --git a/src/client/websocket/packets/handlers/UserGuildSettingsUpdate.js b/src/client/websocket/packets/handlers/UserGuildSettingsUpdate.js new file mode 100644 index 000000000..1470a3c84 --- /dev/null +++ b/src/client/websocket/packets/handlers/UserGuildSettingsUpdate.js @@ -0,0 +1,18 @@ +const AbstractHandler = require('./AbstractHandler'); +const Constants = require('../../../../util/Constants'); + +class UserGuildSettingsUpdateHandler extends AbstractHandler { + handle(packet) { + const client = this.packetManager.client; + client.user.guildSettings.get(packet.d.guild_id).patch(packet.d); + client.emit(Constants.Events.USER_GUILD_SETTINGS_UPDATE, client.user.guildSettings.get(packet.d.guild_id)); + } +} + +/** + * Emitted whenever the client user's settings update. + * @event Client#clientUserGuildSettingsUpdate + * @param {ClientUserGuildSettings} clientUserGuildSettings The new client user guild settings + */ + +module.exports = UserGuildSettingsUpdateHandler; diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index e6171ebec..a612e1a2e 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -1,6 +1,7 @@ const User = require('./User'); const Collection = require('../util/Collection'); const ClientUserSettings = require('./ClientUserSettings'); +const ClientUserGuildSettings = require('./ClientUserGuildSettings'); const Constants = require('../util/Constants'); /** @@ -73,6 +74,18 @@ class ClientUser extends User { * @type {?ClientUserSettings} */ this.settings = data.user_settings ? new ClientUserSettings(this, data.user_settings) : null; + + /** + * All of the user's guild settings + * This is only filled when using a user account + * @type {Collection} + */ + this.guildSettings = new Collection(); + if (data.user_guild_settings) { + for (const settings of data.user_guild_settings) { + this.guildSettings.set(settings.guild_id, new ClientUserGuildSettings(settings, this.client)); + } + } } edit(data) { diff --git a/src/structures/ClientUserChannelOverride.js b/src/structures/ClientUserChannelOverride.js new file mode 100644 index 000000000..12790b9ac --- /dev/null +++ b/src/structures/ClientUserChannelOverride.js @@ -0,0 +1,28 @@ +const Constants = require('../util/Constants'); + +/** + * A wrapper around the ClientUser's channel overrides. + */ +class ClientUserChannelOverride { + constructor(data) { + this.patch(data); + } + + /** + * Patch the data contained in this class with new partial data. + * @param {Object} data Data to patch this with + */ + patch(data) { + for (const key of Object.keys(Constants.UserChannelOverrideMap)) { + const value = Constants.UserChannelOverrideMap[key]; + if (!data.hasOwnProperty(key)) continue; + if (typeof value === 'function') { + this[value.name] = value(data[key]); + } else { + this[value] = data[key]; + } + } + } +} + +module.exports = ClientUserChannelOverride; diff --git a/src/structures/ClientUserGuildSettings.js b/src/structures/ClientUserGuildSettings.js new file mode 100644 index 000000000..146be9872 --- /dev/null +++ b/src/structures/ClientUserGuildSettings.js @@ -0,0 +1,58 @@ +const Constants = require('../util/Constants'); +const Collection = require('../util/Collection'); +const ClientUserChannelOverride = require('./ClientUserChannelOverride'); + +/** + * A wrapper around the ClientUser's guild settings. + */ +class ClientUserGuildSettings { + constructor(data, client) { + /** + * The client that created the instance of the ClientUserGuildSettings + * @name ClientUserGuildSettings#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + /** + * The ID of the guild this settings are for + * @type {Snowflake} + */ + this.guildID = data.guild_id; + this.channelOverrides = new Collection(); + this.patch(data); + } + + /** + * Patch the data contained in this class with new partial data. + * @param {Object} data Data to patch this with + */ + patch(data) { + for (const key of Object.keys(Constants.UserGuildSettingsMap)) { + const value = Constants.UserGuildSettingsMap[key]; + if (!data.hasOwnProperty(key)) continue; + if (key === 'channel_overrides') { + for (const channel of data[key]) { + this.channelOverrides.set(channel.channel_id, + new ClientUserChannelOverride(channel)); + } + } else if (typeof value === 'function') { + this[value.name] = value(data[key]); + } else { + this[value] = data[key]; + } + } + } + + /** + * Update a specific property of the guild settings. + * @param {string} name Name of property + * @param {value} value Value to patch + * @returns {Promise} + */ + update(name, value) { + return this.client.rest.methods.patchClientUserGuildSettings(this.guildID, { [name]: value }); + } +} + +module.exports = ClientUserGuildSettings; diff --git a/src/structures/Guild.js b/src/structures/Guild.js index baecfc7c3..eb7d0f7ff 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -330,6 +330,7 @@ class Guild { * The position of this guild * This is only available when using a user account. * @type {?number} + * @readonly */ get position() { if (this.client.user.bot) return null; @@ -337,6 +338,66 @@ class Guild { return this.client.user.settings.guildPositions.indexOf(this.id); } + /** + * Whether the guild is muted + * This is only available when using a user account. + * @type {?boolean} + * @readonly + */ + get muted() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.id).muted; + } catch (err) { + return false; + } + } + + /** + * The type of message that should notify you + * This is only available when using a user account. + * @type {?MessageNotificationType} + * @readonly + */ + get messageNotifications() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.id).messageNotifications; + } catch (err) { + return null; + } + } + + /** + * Whether to receive mobile push notifications + * This is only available when using a user account. + * @type {?boolean} + * @readonly + */ + get mobilePush() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.id).mobilePush; + } catch (err) { + return false; + } + } + + /** + * Whether to suppress everyone messages + * This is only available when using a user account. + * @type {?boolean} + * @readonly + */ + get suppressEveryone() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.id).suppressEveryone; + } catch (err) { + return null; + } + } + /** * The `@everyone` role of the guild * @type {Role} diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 9b3e83c0e..2c728afbe 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -3,6 +3,7 @@ const Role = require('./Role'); const PermissionOverwrites = require('./PermissionOverwrites'); const Permissions = require('../util/Permissions'); const Collection = require('../util/Collection'); +const Constants = require('../util/Constants'); /** * Represents a guild channel (i.e. text channels and voice channels). @@ -328,6 +329,36 @@ class GuildChannel extends Channel { this.permissionsFor(this.client.user).has(Permissions.FLAGS.MANAGE_CHANNELS); } + /** + * Whether the channel is muted + * This is only available when using a user account. + * @type {?boolean} + * @readonly + */ + get muted() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.guild.id).channelOverrides.get(this.id).muted; + } catch (err) { + return false; + } + } + + /** + * The type of message that should notify you + * This is only available when using a user account. + * @type {?MessageNotificationType} + * @readonly + */ + get messageNotifications() { + if (this.client.user.bot) return null; + try { + return this.client.user.guildSettings.get(this.guild.id).channelOverrides.get(this.id).messageNotifications; + } catch (err) { + return Constants.MessageNotificationTypes[3]; + } + } + /** * When concatenated with a string, this automatically returns the channel's mention instead of the Channel object. * @returns {string} diff --git a/src/structures/OAuth2Application.js b/src/structures/OAuth2Application.js index 0e037ab7d..cfd657d95 100644 --- a/src/structures/OAuth2Application.js +++ b/src/structures/OAuth2Application.js @@ -37,7 +37,7 @@ class OAuth2Application { /** * The app's icon hash - * @type {string} + * @type {?string} */ this.icon = data.icon; @@ -124,6 +124,7 @@ class OAuth2Application { /** * Reset the app's secret and bot token. + * This is only available when using a user account. * @returns {OAuth2Application} */ reset() { diff --git a/src/structures/User.js b/src/structures/User.js index 94344e4dd..027eedbb6 100644 --- a/src/structures/User.js +++ b/src/structures/User.js @@ -12,7 +12,7 @@ class User { /** * The client that created the instance of the user * @name User#client - * @type {} + * @type {Client} * @readonly */ Object.defineProperty(this, 'client', { value: client }); diff --git a/src/util/Constants.js b/src/util/Constants.js index 08eb0091b..b299a58ef 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -102,7 +102,10 @@ const Endpoints = exports.Endpoints = { relationships: `${base}/relationships`, settings: `${base}/settings`, Relationship: uID => `${base}/relationships/${uID}`, - Guild: guildID => `${base}/guilds/${guildID}`, + Guild: guildID => ({ + toString: () => `${base}/guilds/${guildID}`, + settings: `${base}/guilds/${guildID}/settings`, + }), Note: id => `${base}/notes/${id}`, Mentions: (limit, roles, everyone, guildID) => `${base}/mentions?limit=${limit}&roles=${roles}&everyone=${everyone}${guildID ? `&guild_id=${guildID}` : ''}`, @@ -200,7 +203,8 @@ const Endpoints = exports.Endpoints = { const base = `/oauth2/applications/${appID}`; return { toString: () => base, - reset: `${base}/reset`, + resetSecret: `${base}/reset`, + resetToken: `${base}/bot/reset`, }; }, App: appID => `/oauth2/authorize?client_id=${appID}`, @@ -320,6 +324,7 @@ exports.Events = { USER_UPDATE: 'userUpdate', USER_NOTE_UPDATE: 'userNoteUpdate', USER_SETTINGS_UPDATE: 'clientUserSettingsUpdate', + USER_GUILD_SETTINGS_UPDATE: 'clientUserGuildSettingsUpdate', PRESENCE_UPDATE: 'presenceUpdate', VOICE_STATE_UPDATE: 'voiceStateUpdate', TYPING_START: 'typingStart', @@ -401,6 +406,7 @@ exports.WSEvents = { USER_UPDATE: 'USER_UPDATE', USER_NOTE_UPDATE: 'USER_NOTE_UPDATE', USER_SETTINGS_UPDATE: 'USER_SETTINGS_UPDATE', + USER_GUILD_SETTINGS_UPDATE: 'USER_GUILD_SETTINGS_UPDATE', PRESENCE_UPDATE: 'PRESENCE_UPDATE', VOICE_STATE_UPDATE: 'VOICE_STATE_UPDATE', TYPING_START: 'TYPING_START', @@ -432,6 +438,21 @@ exports.MessageTypes = [ 'GUILD_MEMBER_JOIN', ]; +/** + * The type of a message notification setting. Here are the available types: + * * EVERYTHING + * * MENTIONS + * * NOTHING + * * INHERIT (only for GuildChannel) + * @typedef {string} MessageNotificationType + */ +exports.MessageNotificationTypes = [ + 'EVERYTHING', + 'MENTIONS', + 'NOTHING', + 'INHERIT', +]; + exports.DefaultAvatars = { BLURPLE: '6debd47ed13483642cf09e832ed0bc1b', GREY: '322c936a8c8be1b803cd94861bdfa868', @@ -579,6 +600,58 @@ exports.UserSettingsMap = { }, }; +exports.UserGuildSettingsMap = { + message_notifications: function messageNotifications(type) { // eslint-disable-line func-name-matching + /** + * The type of message that should notify you + * @name ClientUserGuildSettings#messageNotifications + * @type {MessageNotificationType} + */ + return exports.MessageNotificationTypes[type]; + }, + /** + * Whether to receive mobile push notifications + * @name ClientUserGuildSettings#mobilePush + * @type {boolean} + */ + mobile_push: 'mobilePush', + /** + * Whether the guild is muted + * @name ClientUserGuildSettings#muted + * @type {boolean} + */ + muted: 'muted', + /** + * Whether to suppress everyone mention + * @name ClientUserGuildSettings#suppressEveryone + * @type {boolean} + */ + suppress_everyone: 'suppressEveryone', + /** + * A collection containing all the channel overrides + * @name ClientUserGuildSettings#channelOverrides + * @type {Collection} + */ + channel_overrides: 'channelOverrides', +}; + +exports.UserChannelOverrideMap = { + message_notifications: function messageNotifications(type) { // eslint-disable-line func-name-matching + /** + * The type of message that should notify you + * @name ClientUserChannelOverride#messageNotifications + * @type {MessageNotificationType} + */ + return exports.MessageNotificationTypes[type]; + }, + /** + * Whether the channel is muted + * @name ClientUserChannelOverride#muted + * @type {boolean} + */ + muted: 'muted', +}; + exports.Colors = { DEFAULT: 0x000000, AQUA: 0x1ABC9C, From 0b22d9a774f385d7fd9c6996616d37a6ba1b002d Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Tue, 22 Aug 2017 00:39:27 +0200 Subject: [PATCH 85/99] Backporting Attachments (#1817) --- src/client/ClientDataResolver.js | 27 +++++++ src/client/rest/RESTMethods.js | 43 ++++++----- src/index.js | 1 + src/structures/Attachment.js | 74 +++++++++++++++++++ src/structures/RichEmbed.js | 8 +- src/structures/Webhook.js | 64 +++++++++++++--- src/structures/interfaces/TextBasedChannel.js | 27 +++++-- 7 files changed, 208 insertions(+), 36 deletions(-) create mode 100644 src/structures/Attachment.js diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index adf28467a..751d3447b 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -234,6 +234,33 @@ class ClientDataResolver { return Promise.reject(new TypeError('The resource must be a string or Buffer.')); } + /** + * @external Stream + * @see {@link https://nodejs.org/api/stream.html} + */ + + /** + * Converts a Stream to a Buffer. + * @param {Stream} resource The stream to convert + * @returns {Promise} + */ + resolveFile(resource) { + return resource ? this.resolveBuffer(resource) + .catch(() => { + if (resource.pipe && typeof resource.pipe === 'function') { + return new Promise((resolve, reject) => { + const buffers = []; + resource.once('error', reject); + resource.on('data', data => buffers.push(data)); + resource.once('end', () => resolve(Buffer.concat(buffers))); + }); + } else { + throw new TypeError('The resource must be a string, Buffer or a valid file stream.'); + } + }) : + Promise.reject(new TypeError('The resource must be a string, Buffer or a valid file stream.')); + } + /** * Data that can be resolved to give an emoji identifier. This can be: * * The unicode representation of an emoji diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 14a28ee5e..9d67e9e8f 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -102,12 +102,12 @@ class RESTMethods { 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 ? files : null).then(message => { + const options = index === list.length - 1 ? { tts, embed, files } : { tts }; + chan.send(list[index], options).then(message => { messages.push(message); if (index >= list.length - 1) return resolve(messages); return sendChunk(list, ++index); - }); + }).catch(reject); }(content, 0)); } else { this.rest.makeRequest('post', Endpoints.Channel(chan).messages, true, { @@ -747,21 +747,30 @@ class RESTMethods { false, undefined, undefined, reason); } - sendWebhookMessage(webhook, content, { avatarURL, tts, disableEveryone, embeds, username } = {}, file = null) { - username = username || webhook.name; - if (typeof content !== 'undefined') content = this.client.resolver.resolveString(content); - if (content) { - if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) { - content = content.replace(/@(everyone|here)/g, '@\u200b$1'); + sendWebhookMessage(webhook, content, { avatarURL, tts, embeds, username } = {}, files = null) { + return new Promise((resolve, reject) => { + username = username || webhook.name; + + if (content instanceof Array) { + const messages = []; + (function sendChunk(list, index) { + const options = index === list.length - 1 ? { tts, embeds, files } : { tts }; + webhook.send(list[index], options).then(message => { + messages.push(message); + if (index >= list.length - 1) return resolve(messages); + return sendChunk(list, ++index); + }).catch(reject); + }(content, 0)); + } else { + this.rest.makeRequest('post', `${Endpoints.Webhook(webhook.id, webhook.token)}?wait=true`, false, { + username, + avatar_url: avatarURL, + content, + tts, + embeds, + }, files).then(resolve, reject); } - } - return this.rest.makeRequest('post', `${Endpoints.Webhook(webhook.id, webhook.token)}?wait=true`, false, { - username, - avatar_url: avatarURL, - content, - tts, - embeds, - }, file); + }); } sendSlackWebhookMessage(webhook, body) { diff --git a/src/index.js b/src/index.js index 37c06d96e..fb035c771 100644 --- a/src/index.js +++ b/src/index.js @@ -26,6 +26,7 @@ module.exports = { splitMessage: Util.splitMessage, // Structures + Attachment: require('./structures/Attachment'), Channel: require('./structures/Channel'), ClientUser: require('./structures/ClientUser'), ClientUserSettings: require('./structures/ClientUserSettings'), diff --git a/src/structures/Attachment.js b/src/structures/Attachment.js new file mode 100644 index 000000000..e4ccbb197 --- /dev/null +++ b/src/structures/Attachment.js @@ -0,0 +1,74 @@ +/** + * Represents an attachment in a message. + * @param {BufferResolvable|Stream} file The file + * @param {string} [name] The name of the file, if any + */ +class Attachment { + constructor(file, name) { + this.file = null; + if (name) this.setAttachment(file, name); + else this._attach(file); + } + + /** + * The name of the file + * @type {?string} + * @readonly + */ + get name() { + return this.file.name; + } + + /** + * The file + * @type {?BufferResolvable|Stream} + * @readonly + */ + get attachment() { + return this.file.attachment; + } + + /** + * Set the file of this attachment. + * @param {BufferResolvable|Stream} file The file + * @param {string} name The name of the file + * @returns {Attachment} This attachment + */ + setAttachment(file, name) { + this.file = { attachment: file, name }; + return this; + } + + /** + * Set the file of this attachment. + * @param {BufferResolvable|Stream} attachment The file + * @returns {Attachment} This attachment + */ + setFile(attachment) { + this.file = { attachment }; + return this; + } + + /** + * Set the name of this attachment. + * @param {string} name The name of the image + * @returns {Attachment} This attachment + */ + setName(name) { + this.file.name = name; + return this; + } + + /** + * Set the file of this attachment. + * @param {BufferResolvable|Stream} file The file + * @param {string} name The name of the file + * @private + */ + _attach(file, name) { + if (typeof file === 'string') this.file = file; + else this.setAttachment(file, name); + } +} + +module.exports = Attachment; diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index 62b40e185..8e4c6eb86 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -1,4 +1,5 @@ -const ClientDataResolver = require('../client/ClientDataResolver'); +const Attachment = require('./Attachment'); +let ClientDataResolver; /** * A rich embed to be sent with a message with a fluent interface for creation. @@ -113,6 +114,7 @@ class RichEmbed { * @returns {RichEmbed} This embed */ setColor(color) { + if (!ClientDataResolver) ClientDataResolver = require('../client/ClientDataResolver'); this.color = ClientDataResolver.resolveColor(color); return this; } @@ -203,11 +205,13 @@ class RichEmbed { /** * Sets the file to upload alongside the embed. This file can be accessed via `attachment://fileName.extension` when * setting an embed image or author/footer icons. Only one file may be attached. - * @param {FileOptions|string} file Local path or URL to the file to attach, or valid FileOptions for a file to attach + * @param {FileOptions|string|Attachment} file Local path or URL to the file to attach, + * or valid FileOptions for a file to attach * @returns {RichEmbed} This embed */ attachFile(file) { if (this.file) throw new RangeError('You may not upload more than one file at once.'); + if (file instanceof Attachment) file = file.file; this.file = file; return this; } diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 9a70ce833..68cec7c4b 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -1,4 +1,7 @@ const path = require('path'); +const Util = require('../util/Util'); +const Attachment = require('./Attachment'); +const RichEmbed = require('./RichEmbed'); /** * Represents a webhook. @@ -76,11 +79,12 @@ class Webhook { * @property {string} [avatarURL] Avatar URL override for the message * @property {boolean} [tts=false] Whether or not the message should be spoken aloud * @property {string} [nonce=''] The nonce for the message - * @property {Object[]} [embeds] An array of embeds for the message + * @property {Array} [embeds] An array of embeds 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 {FileOptions|string} [file] A file to send with the message + * @property {FileOptions|BufferResolvable|Attachment} [file] A file to send with the message **(deprecated)** + * @property {FileOptions[]|BufferResolvable[]|Attachment[]} [files] Files to send with the message * @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. @@ -89,7 +93,8 @@ class Webhook { /** * Send a message with this webhook. * @param {StringResolvable} content The content to send - * @param {WebhookMessageOptions} [options={}] The options to provide + * @param {WebhookMessageOptions|Attachment|RichEmbed} [options] The options to provide + * can also be just a RichEmbed or Attachment * @returns {Promise} * @example * // Send a message @@ -97,39 +102,78 @@ class Webhook { * .then(message => console.log(`Sent message: ${message.content}`)) * .catch(console.error); */ - send(content, options) { + send(content, options) { // eslint-disable-line complexity if (!options && typeof content === 'object' && !(content instanceof Array)) { options = content; content = ''; } else if (!options) { options = {}; } + + if (options instanceof Attachment) options = { files: [options] }; + if (options instanceof RichEmbed) options = { embeds: [options] }; + + if (content) { + content = this.client.resolver.resolveString(content); + let { split, code, disableEveryone } = options; + if (split && typeof split !== 'object') split = {}; + if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) { + content = Util.escapeMarkdown(content, true); + content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``; + if (split) { + split.prepend = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n`; + split.append = '\n```'; + } + } + if (disableEveryone || (typeof disableEveryone === 'undefined' && this.client.options.disableEveryone)) { + content = content.replace(/@(everyone|here)/g, '@\u200b$1'); + } + + if (split) content = Util.splitMessage(content, split); + } + if (options.file) { if (options.files) options.files.push(options.file); else options.files = [options.file]; } + if (options.embeds) { + const files = []; + for (const embed of options.embeds) { + if (embed.file) files.push(embed.file); + } + if (options.files) options.files.push(...files); + else options.files = files; + } + if (options.files) { for (let i = 0; i < options.files.length; i++) { let file = options.files[i]; - if (typeof file === 'string') file = { attachment: file }; + if (typeof file === 'string' || Buffer.isBuffer(file)) file = { attachment: file }; if (!file.name) { if (typeof file.attachment === 'string') { file.name = path.basename(file.attachment); } else if (file.attachment && file.attachment.path) { file.name = path.basename(file.attachment.path); + } else if (file instanceof Attachment) { + file = { attachment: file.file, name: path.basename(file.file) || 'file.jpg' }; } else { file.name = 'file.jpg'; } + } else if (file instanceof Attachment) { + file = file.file; } + options.files[i] = file; } - return this.client.resolver.resolveBuffer(options.file.attachment).then(file => - this.client.rest.methods.sendWebhookMessage(this, content, options, { - file, - name: options.file.name, + + return Promise.all(options.files.map(file => + this.client.resolver.resolveFile(file.attachment).then(resource => { + file.file = resource; + return file; }) - ); + )).then(files => this.client.rest.methods.sendWebhookMessage(this, content, options, files)); } + return this.client.rest.methods.sendWebhookMessage(this, content, options); } diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index 07c2a7382..16b0465ad 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -2,6 +2,8 @@ const path = require('path'); const Message = require('../Message'); const MessageCollector = require('../MessageCollector'); const Collection = require('../../util/Collection'); +const Attachment = require('../../structures/Attachment'); +const RichEmbed = require('../../structures/RichEmbed'); const util = require('util'); /** @@ -38,8 +40,8 @@ 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 **(deprecated)** - * @property {FileOptions[]|string[]} [files] Files to send with the message + * @property {FileOptions|BufferResolvable|Attachment} [file] A file to send with the message **(deprecated)** + * @property {FileOptions[]|BufferResolvable[]|Attachment[]} [files] Files to send with the message * @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 @@ -64,7 +66,8 @@ class TextBasedChannel { /** * Send a message to this channel. * @param {StringResolvable} [content] Text for the message - * @param {MessageOptions} [options={}] Options for the message + * @param {MessageOptions|Attachment|RichEmbed} [options] Options for the message, + * can also be just a RichEmbed or Attachment * @returns {Promise} * @example * // Send a message @@ -80,7 +83,13 @@ class TextBasedChannel { options = {}; } - if (options.embed && options.embed.file) options.file = options.embed.file; + if (options instanceof Attachment) options = { files: [options.file] }; + if (options instanceof RichEmbed) options = { embed: options }; + + if (options.embed && options.embed.file) { + if (options.files) options.files.push(options.embed.file); + else options.files = [options.embed.file]; + } if (options.file) { if (options.files) options.files.push(options.file); @@ -90,22 +99,26 @@ class TextBasedChannel { if (options.files) { for (let i = 0; i < options.files.length; i++) { let file = options.files[i]; - if (typeof file === 'string') file = { attachment: file }; + if (typeof file === 'string' || Buffer.isBuffer(file)) file = { attachment: file }; if (!file.name) { if (typeof file.attachment === 'string') { file.name = path.basename(file.attachment); } else if (file.attachment && file.attachment.path) { file.name = path.basename(file.attachment.path); + } else if (file instanceof Attachment) { + file = { attachment: file.file, name: path.basename(file.file) || 'file.jpg' }; } else { file.name = 'file.jpg'; } + } else if (file instanceof Attachment) { + file = file.file; } options.files[i] = file; } return Promise.all(options.files.map(file => - this.client.resolver.resolveBuffer(file.attachment).then(buffer => { - file.file = buffer; + this.client.resolver.resolveFile(file.attachment).then(resource => { + file.file = resource; return file; }) )).then(files => this.client.rest.methods.sendMessage(this, content, options, files)); From a85fc9163002d623e29fbb1e62c8ad3f23c44646 Mon Sep 17 00:00:00 2001 From: Crawl Date: Tue, 22 Aug 2017 19:57:59 +0200 Subject: [PATCH 86/99] make webpack over 9000 times better (#1816) * webpack stuff * even better * Update browser.js --- browser.js | 9 +++++++++ src/client/Client.js | 5 ++--- src/client/websocket/WebSocketConnection.js | 2 +- src/index.js | 2 -- webpack.config.js | 7 ++++++- 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 browser.js diff --git a/browser.js b/browser.js new file mode 100644 index 000000000..9f9341efc --- /dev/null +++ b/browser.js @@ -0,0 +1,9 @@ +const browser = typeof window !== 'undefined'; +const webpack = !!process.env.__DISCORD_WEBPACK__; + +const Discord = require('./'); + +module.exports = Discord; +if (browser && webpack) window.Discord = Discord; // eslint-disable-line no-undef +// eslint-disable-next-line no-console +else if (!browser) console.warn('Warning: Attempting to use browser version of Discord.js in a non-browser environment!'); diff --git a/src/client/Client.js b/src/client/Client.js index 97bbbdd96..54790ac7a 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -1,5 +1,4 @@ -const os = require('os'); -const EventEmitter = require('events').EventEmitter; +const EventEmitter = require('events'); const Constants = require('../util/Constants'); const Permissions = require('../util/Permissions'); const Util = require('../util/Util'); @@ -250,7 +249,7 @@ class Client extends EventEmitter { * @readonly */ get browser() { - return os.platform() === 'browser'; + return typeof window !== 'undefined'; } /** diff --git a/src/client/websocket/WebSocketConnection.js b/src/client/websocket/WebSocketConnection.js index 58878b3ea..c0581a208 100644 --- a/src/client/websocket/WebSocketConnection.js +++ b/src/client/websocket/WebSocketConnection.js @@ -1,4 +1,4 @@ -const browser = require('os').platform() === 'browser'; +const browser = typeof window !== 'undefined'; const EventEmitter = require('events'); const Constants = require('../../util/Constants'); const zlib = require('zlib'); diff --git a/src/index.js b/src/index.js index fb035c771..d2b38d14f 100644 --- a/src/index.js +++ b/src/index.js @@ -61,5 +61,3 @@ module.exports = { VoiceChannel: require('./structures/VoiceChannel'), Webhook: require('./structures/Webhook'), }; - -if (require('os').platform() === 'browser') window.Discord = module.exports; // eslint-disable-line no-undef diff --git a/webpack.config.js b/webpack.config.js index 55d0e12f6..db75a5e23 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,6 +12,11 @@ const createConfig = options => { const plugins = [ new webpack.DefinePlugin({ 'global.GENTLY': false }), new webpack.optimize.ModuleConcatenationPlugin(), + new webpack.DefinePlugin({ + 'process.env': { + __DISCORD_WEBPACK__: '"true"', + }, + }), ]; if (options.minify) plugins.push(new UglifyJSPlugin({ uglifyOptions: { output: { comments: false } } })); @@ -19,7 +24,7 @@ const createConfig = options => { const filename = `./webpack/discord${process.env.VERSIONED === 'false' ? '' : '.' + version}${options.minify ? '.min' : ''}.js`; // eslint-disable-line return { - entry: './src/index.js', + entry: './browser.js', output: { path: __dirname, filename, From 17d7f5c723b942a5bf8aec11fe2f1120b7666a55 Mon Sep 17 00:00:00 2001 From: Isabella Date: Tue, 22 Aug 2017 22:29:22 -0500 Subject: [PATCH 87/99] resolveImage backport (#1822) * add resolveImage * add groupDMChannel#setIcon + icon getter * doc fix * crawl no kill pls * *whistles* * channe --- src/client/ClientDataResolver.js | 76 ++++++++++++++++---------------- src/client/rest/RESTMethods.js | 7 +++ src/structures/ClientUser.js | 12 ++--- src/structures/GroupDMChannel.js | 32 ++++++++++++++ src/structures/Guild.js | 26 +++++------ src/structures/TextChannel.js | 2 +- src/structures/Webhook.js | 2 +- src/util/Constants.js | 2 + 8 files changed, 98 insertions(+), 61 deletions(-) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index 751d3447b..bc7d98726 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -174,6 +174,20 @@ class ClientDataResolver { return String(data); } + + /** + * Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image. + * @param {string|BufferResolvable|Base64Resolvable} image The image to be resolved + * @returns {Promise} + */ + resolveImage(image) { + if (!image) return Promise.resolve(null); + if (typeof image === 'string' && image.startsWith('data:')) { + return Promise.resolve(image); + } + return this.resolveFile(image).then(this.resolveBase64); + } + /** * Data that resolves to give a Base64 string, typically for image uploading. This can be: * * A Buffer @@ -192,19 +206,25 @@ class ClientDataResolver { } /** - * Data that can be resolved to give a Buffer. This can be: - * * A Buffer - * * The path to a local file - * * A URL - * @typedef {string|Buffer} BufferResolvable - */ + * Data that can be resolved to give a Buffer. This can be: + * * A Buffer + * * The path to a local file + * * A URL + * * A Stream + * @typedef {string|Buffer} BufferResolvable + */ /** - * Resolves a BufferResolvable to a Buffer. - * @param {BufferResolvable} resource The buffer resolvable to resolve - * @returns {Promise} - */ - resolveBuffer(resource) { + * @external Stream + * @see {@link https://nodejs.org/api/stream.html} + */ + + /** + * Resolves a BufferResolvable to a Buffer. + * @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve + * @returns {Promise} + */ + resolveFile(resource) { if (resource instanceof Buffer) return Promise.resolve(resource); if (this.client.browser && resource instanceof ArrayBuffer) return Promise.resolve(convertToBuffer(resource)); @@ -229,38 +249,18 @@ class ClientDataResolver { }); } }); + } else if (resource.pipe && typeof resource.pipe === 'function') { + return new Promise((resolve, reject) => { + const buffers = []; + resource.once('error', reject); + resource.on('data', data => buffers.push(data)); + resource.once('end', () => resolve(Buffer.concat(buffers))); + }); } return Promise.reject(new TypeError('The resource must be a string or Buffer.')); } - /** - * @external Stream - * @see {@link https://nodejs.org/api/stream.html} - */ - - /** - * Converts a Stream to a Buffer. - * @param {Stream} resource The stream to convert - * @returns {Promise} - */ - resolveFile(resource) { - return resource ? this.resolveBuffer(resource) - .catch(() => { - if (resource.pipe && typeof resource.pipe === 'function') { - return new Promise((resolve, reject) => { - const buffers = []; - resource.once('error', reject); - resource.on('data', data => buffers.push(data)); - resource.once('end', () => resolve(Buffer.concat(buffers))); - }); - } else { - throw new TypeError('The resource must be a string, Buffer or a valid file stream.'); - } - }) : - Promise.reject(new TypeError('The resource must be a string, Buffer or a valid file stream.')); - } - /** * Data that can be resolved to give an emoji identifier. This can be: * * The unicode representation of an emoji diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 9d67e9e8f..063442e37 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -285,6 +285,13 @@ class RESTMethods { .then(() => channel); } + updateGroupDMChannel(channel, _data) { + const data = {}; + data.name = _data.name; + data.icon = _data.icon; + return this.rest.makeRequest('patch', Endpoints.Channel(channel), true, data).then(() => channel); + } + getExistingDM(recipient) { return this.client.channels.find(channel => channel.recipient && channel.recipient.id === recipient.id diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index a612e1a2e..2009220c1 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -152,13 +152,9 @@ class ClientUser extends User { * .catch(console.error); */ setAvatar(avatar) { - if (typeof avatar === 'string' && avatar.startsWith('data:')) { - return this.client.rest.methods.updateCurrentUser({ avatar }); - } else { - return this.client.resolver.resolveBuffer(avatar).then(data => - this.client.rest.methods.updateCurrentUser({ avatar: data }) - ); - } + return this.client.resolver.resolveImage(avatar).then(data => + this.client.rest.methods.updateCurrentUser({ avatar: data }) + ); } /** @@ -337,7 +333,7 @@ class ClientUser extends User { }, reject) ); } else { - return this.client.resolver.resolveBuffer(icon) + return this.client.resolver.resolveFile(icon) .then(data => this.createGuild(name, { region, icon: this.client.resolver.resolveBase64(data) || null })); } } diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js index 84ca62371..c01ba84d8 100644 --- a/src/structures/GroupDMChannel.js +++ b/src/structures/GroupDMChannel.js @@ -1,6 +1,7 @@ const Channel = require('./Channel'); const TextBasedChannel = require('./interfaces/TextBasedChannel'); const Collection = require('../util/Collection'); +const Constants = require('../util/Constants'); /* { type: 3, @@ -105,6 +106,23 @@ class GroupDMChannel extends Channel { return this.client.users.get(this.ownerID); } + /** + * The URL to this guild's icon + * @type {?string} + * @readonly + */ + get iconURL() { + if (!this.icon) return null; + return Constants.Endpoints.Channel(this).Icon(this.client.options.http.cdn, this.icon); + } + + edit(data) { + const _data = {}; + if (data.name) _data.name = data.name; + if (data.icon) _data.icon = data.icon; + return this.client.rest.methods.updateGroupDMChannel(this, _data); + } + /** * 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 @@ -140,6 +158,20 @@ class GroupDMChannel extends Channel { }); } + /** + * Set a new GroupDMChannel icon. + * @param {Base64Resolvable|BufferResolvable} icon The new icon of the group dm + * @returns {Promise} + * @example + * // Edit the group dm icon + * channel.setIcon('./icon.png') + * .then(updated => console.log('Updated the channel icon')) + * .catch(console.error); + */ + setIcon(icon) { + return this.client.resolver.resolveImage(icon).then(data => this.edit({ icon: data })); + } + /** * When concatenated with a string, this automatically concatenates the channel's name instead of the Channel object. * @returns {string} diff --git a/src/structures/Guild.js b/src/structures/Guild.js index eb7d0f7ff..be064e7f3 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -615,9 +615,9 @@ class Guild { if (data.afkChannel) _data.afk_channel_id = this.client.resolver.resolveChannel(data.afkChannel).id; if (data.systemChannel) _data.system_channel_id = this.client.resolver.resolveChannel(data.systemChannel).id; if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout); - if (data.icon) _data.icon = this.client.resolver.resolveBase64(data.icon); + if (data.icon) _data.icon = 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); + if (data.splash) _data.splash = data.splash; if (typeof data.explicitContentFilter !== 'undefined') { _data.explicit_content_filter = Number(data.explicitContentFilter); } @@ -627,7 +627,7 @@ class Guild { /** * Edit the level of the explicit content filter. * @param {number} explicitContentFilter The new level of the explicit content filter - * @param {string} [reason] Reason for changing the level of the guild's explicit content filter + * @param {string} [reason] Reason for changing the level of the guild's explicit content filter * @returns {Promise} */ setExplicitContentFilter(explicitContentFilter, reason) { @@ -637,7 +637,7 @@ class Guild { /** * Edit the name of the guild. * @param {string} name The new name of the guild - * @param {string} [reason] Reason for changing the guild's name + * @param {string} [reason] Reason for changing the guild's name * @returns {Promise} * @example * // Edit the guild name @@ -697,7 +697,7 @@ class Guild { /** * Edit the system channel of the guild. * @param {ChannelResolvable} systemChannel The new system channel - * @param {string} [reason] Reason for changing the guild's system channel + * @param {string} [reason] Reason for changing the guild's system channel * @returns {Promise} */ setSystemChannel(systemChannel, reason) { @@ -721,17 +721,17 @@ class Guild { /** * Set a new guild icon. - * @param {Base64Resolvable} icon The new icon of the guild + * @param {Base64Resolvable|BufferResolvable} icon The new icon of the guild * @param {string} [reason] Reason for changing the guild's icon * @returns {Promise} * @example * // Edit the guild icon - * guild.setIcon(fs.readFileSync('./icon.png')) + * guild.setIcon('./icon.png') * .then(updated => console.log('Updated the guild icon')) * .catch(console.error); */ setIcon(icon, reason) { - return this.edit({ icon }, reason); + return this.client.resolver.resolveImage(icon).then(data => this.edit({ icon: data, reason })); } /** @@ -751,17 +751,17 @@ class Guild { /** * Set a new guild splash screen. - * @param {Base64Resolvable} splash The new splash screen of the guild + * @param {BufferResolvable|Base64Resolvable} splash The new splash screen of the guild * @param {string} [reason] Reason for changing the guild's splash screen * @returns {Promise} * @example * // Edit the guild splash - * guild.setIcon(fs.readFileSync('./splash.png')) + * guild.setSplash('./splash.png') * .then(updated => console.log('Updated the guild splash')) * .catch(console.error); */ - setSplash(splash, reason) { - return this.edit({ splash }, reason); + setSplash(splash) { + return this.client.resolver.resolveImage(splash).then(data => this.edit({ splash: data })); } /** @@ -952,7 +952,7 @@ class Guild { if (typeof attachment === 'string' && attachment.startsWith('data:')) { resolve(this.client.rest.methods.createEmoji(this, attachment, name, roles, reason)); } else { - this.client.resolver.resolveBuffer(attachment).then(data => { + this.client.resolver.resolveFile(attachment).then(data => { const dataURI = this.client.resolver.resolveBase64(data); resolve(this.client.rest.methods.createEmoji(this, dataURI, name, roles, reason)); }); diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 8ce79fb4b..8d9d91a95 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -73,7 +73,7 @@ class TextChannel extends GuildChannel { if (typeof avatar === 'string' && avatar.startsWith('data:')) { resolve(this.client.rest.methods.createWebhook(this, name, avatar, reason)); } else { - this.client.resolver.resolveBuffer(avatar).then(data => + this.client.resolver.resolveFile(avatar).then(data => resolve(this.client.rest.methods.createWebhook(this, name, data, reason)) ); } diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 68cec7c4b..f05d7e056 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -247,7 +247,7 @@ class Webhook { */ edit(name = this.name, avatar) { if (avatar) { - return this.client.resolver.resolveBuffer(avatar).then(file => { + return this.client.resolver.resolveFile(avatar).then(file => { const dataURI = this.client.resolver.resolveBase64(file); return this.client.rest.methods.editWebhook(this, name, dataURI); }); diff --git a/src/util/Constants.js b/src/util/Constants.js index b299a58ef..ed9335cf6 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -167,6 +167,7 @@ const Endpoints = exports.Endpoints = { webhooks: `${base}/webhooks`, search: `${base}/messages/search`, pins: `${base}/pins`, + Icon: (root, hash) => Endpoints.CDN(root).GDMIcon(channelID, hash), Pin: messageID => `${base}/pins/${messageID}`, Recipient: recipientID => `${base}/recipients/${recipientID}`, Message: messageID => { @@ -195,6 +196,7 @@ const Endpoints = exports.Endpoints = { Asset: name => `${root}/assets/${name}`, Avatar: (userID, hash) => `${root}/avatars/${userID}/${hash}.${hash.startsWith('a_') ? 'gif' : 'png'}?size=2048`, Icon: (guildID, hash) => `${root}/icons/${guildID}/${hash}.jpg`, + GDMIcon: (channelID, hash) => `${root}/channel-icons/${channelID}/${hash}.jpg?size=2048`, Splash: (guildID, hash) => `${root}/splashes/${guildID}/${hash}.jpg`, }; }, From 85d195da52135d7e40494323b645d7f939bfdaf4 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 22 Aug 2017 10:55:28 -0700 Subject: [PATCH 88/99] Fix docs on resolveImage --- 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 bc7d98726..bda336dbb 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -177,7 +177,7 @@ class ClientDataResolver { /** * Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image. - * @param {string|BufferResolvable|Base64Resolvable} image The image to be resolved + * @param {BufferResolvable|Base64Resolvable} image The image to be resolved * @returns {Promise} */ resolveImage(image) { From bce5b677adea4d64e7d6097e399c0b6042399957 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Thu, 24 Aug 2017 00:35:44 +0200 Subject: [PATCH 89/99] Backport passing a collection to a collector --- src/structures/interfaces/Collector.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/structures/interfaces/Collector.js b/src/structures/interfaces/Collector.js index 287256ebe..10a46b18e 100644 --- a/src/structures/interfaces/Collector.js +++ b/src/structures/interfaces/Collector.js @@ -5,7 +5,8 @@ const EventEmitter = require('events').EventEmitter; * Filter to be applied to the collector. * @typedef {Function} CollectorFilter * @param {...*} args Any arguments received by the listener - * @returns {boolean} To collect or not collect + * @param {Collection} collection The items collected by this collector + * @returns {boolean} */ /** @@ -78,7 +79,7 @@ class Collector extends EventEmitter { */ _handle(...args) { const collect = this.handle(...args); - if (!collect || !this.filter(...args)) return; + if (!collect || !this.filter(...args, this.collected)) return; this.collected.set(collect.key, collect.value); From 1fe201ae90d554feafbd476bdc12dd00190d8a50 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 25 Aug 2017 15:14:05 +0200 Subject: [PATCH 90/99] Backporting, doc/bug fixes as well deprecation (#1826) * Backporting, doc/bug fixes as well deprecation * Adress issue with not resettable icons/images --- src/client/ClientDataResolver.js | 2 +- src/client/rest/RESTMethods.js | 19 +++++++---- .../websocket/packets/handlers/Resumed.js | 2 +- src/structures/Attachment.js | 1 + src/structures/ClientUser.js | 34 ++++--------------- src/structures/ClientUserChannelOverride.js | 2 ++ src/structures/ClientUserGuildSettings.js | 2 ++ src/structures/ClientUserSettings.js | 5 ++- src/structures/DMChannel.js | 1 + src/structures/GroupDMChannel.js | 25 ++++++++++++-- src/structures/Guild.js | 33 ++++++++---------- src/structures/GuildChannel.js | 26 +++++++++----- src/structures/Message.js | 2 +- src/structures/Role.js | 2 +- src/structures/TextChannel.js | 18 +++++----- src/structures/Webhook.js | 14 +++----- src/structures/interfaces/TextBasedChannel.js | 11 ++++-- src/util/Constants.js | 11 ++++++ src/util/Permissions.js | 4 ++- 19 files changed, 125 insertions(+), 89 deletions(-) diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index bda336dbb..c725792b7 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -178,7 +178,7 @@ class ClientDataResolver { /** * Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image. * @param {BufferResolvable|Base64Resolvable} image The image to be resolved - * @returns {Promise} + * @returns {Promise} */ resolveImage(image) { if (!image) return Promise.resolve(null); diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 063442e37..c621eead4 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -285,6 +285,11 @@ class RESTMethods { .then(() => channel); } + removeUserFromGroupDM(channel, userId) { + return this.rest.makeRequest('delete', Endpoints.Channel(channel).Recipient(userId), true) + .then(() => channel); + } + updateGroupDMChannel(channel, _data) { const data = {}; data.name = _data.name; @@ -298,13 +303,14 @@ class RESTMethods { ); } - deleteChannel(channel) { + deleteChannel(channel, reason) { 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('delete', Endpoints.Channel(channel), true).then(data => { - data.id = channel.id; - return this.client.actions.ChannelDelete.handle(data).channel; - }); + return this.rest.makeRequest('delete', Endpoints.Channel(channel), true, undefined, undefined, reason) + .then(data => { + data.id = channel.id; + return this.client.actions.ChannelDelete.handle(data).channel; + }); } updateChannel(channel, _data, reason) { @@ -369,7 +375,7 @@ class RESTMethods { const user = this.client.user; const data = {}; data.username = _data.username || user.username; - data.avatar = this.client.resolver.resolveBase64(_data.avatar) || user.avatar; + data.avatar = typeof _data.avatar === 'undefined' ? user.avatar : this.client.resolver.resolveBase64(_data.avatar); if (!user.bot) { data.email = _data.email || user.email; data.password = password; @@ -639,6 +645,7 @@ class RESTMethods { payload.temporary = options.temporary; payload.max_age = options.maxAge; payload.max_uses = options.maxUses; + payload.unique = options.unique; return this.rest.makeRequest('post', Endpoints.Channel(channel).invites, true, payload, undefined, reason) .then(invite => new Invite(this.client, invite)); } diff --git a/src/client/websocket/packets/handlers/Resumed.js b/src/client/websocket/packets/handlers/Resumed.js index 7b4387092..0c796053b 100644 --- a/src/client/websocket/packets/handlers/Resumed.js +++ b/src/client/websocket/packets/handlers/Resumed.js @@ -14,7 +14,7 @@ class ResumedHandler extends AbstractHandler { const replayed = ws.sequence - ws.closeSequence; ws.debug(`RESUMED ${ws._trace.join(' -> ')} | replayed ${replayed} events.`); - client.emit('resume', replayed); + client.emit(Constants.Events.RESUME, replayed); ws.heartbeat(); } } diff --git a/src/structures/Attachment.js b/src/structures/Attachment.js index e4ccbb197..216b61c9d 100644 --- a/src/structures/Attachment.js +++ b/src/structures/Attachment.js @@ -63,6 +63,7 @@ class Attachment { * Set the file of this attachment. * @param {BufferResolvable|Stream} file The file * @param {string} name The name of the file + * @returns {void} * @private */ _attach(file, name) { diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 2009220c1..ef3398bd3 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -304,37 +304,17 @@ class ClientUser extends User { * Creates a guild. * 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 {string} [region] The region for the server * @param {BufferResolvable|Base64Resolvable} [icon=null] The icon for the guild * @returns {Promise} The guild that was created */ - - createGuild(name, { region, icon = null } = {}) { - if (!icon || (typeof icon === 'string' && icon.startsWith('data:'))) { - return new Promise((resolve, reject) => - this.client.api.guilds.post({ data: { name, region, icon } }) - .then(data => { - if (this.client.guilds.has(data.id)) return resolve(this.client.guilds.get(data.id)); - - const handleGuild = guild => { - if (guild.id === data.id) { - this.client.removeListener(Constants.Events.GUILD_CREATE, handleGuild); - this.client.clearTimeout(timeout); - resolve(guild); - } - }; - this.client.on(Constants.Events.GUILD_CREATE, handleGuild); - - const timeout = this.client.setTimeout(() => { - this.client.removeListener(Constants.Events.GUILD_CREATE, handleGuild); - resolve(this.client.dataManager.newGuild(data)); - }, 10000); - return undefined; - }, reject) - ); + createGuild(name, region, icon = null) { + if (typeof icon === 'string' && icon.startsWith('data:')) { + return this.client.rest.methods.createGuild({ name, icon, region }); } else { - return this.client.resolver.resolveFile(icon) - .then(data => this.createGuild(name, { region, icon: this.client.resolver.resolveBase64(data) || null })); + return this.client.resolver.resolveImage(icon).then(data => + this.client.rest.methods.createGuild({ name, icon: data, region }) + ); } } diff --git a/src/structures/ClientUserChannelOverride.js b/src/structures/ClientUserChannelOverride.js index 12790b9ac..93efa45ff 100644 --- a/src/structures/ClientUserChannelOverride.js +++ b/src/structures/ClientUserChannelOverride.js @@ -11,6 +11,8 @@ class ClientUserChannelOverride { /** * Patch the data contained in this class with new partial data. * @param {Object} data Data to patch this with + * @returns {void} + * @private */ patch(data) { for (const key of Object.keys(Constants.UserChannelOverrideMap)) { diff --git a/src/structures/ClientUserGuildSettings.js b/src/structures/ClientUserGuildSettings.js index 146be9872..5a28747ec 100644 --- a/src/structures/ClientUserGuildSettings.js +++ b/src/structures/ClientUserGuildSettings.js @@ -26,6 +26,8 @@ class ClientUserGuildSettings { /** * Patch the data contained in this class with new partial data. * @param {Object} data Data to patch this with + * @returns {void} + * @private */ patch(data) { for (const key of Object.keys(Constants.UserGuildSettingsMap)) { diff --git a/src/structures/ClientUserSettings.js b/src/structures/ClientUserSettings.js index 798f348c5..6b18c0394 100644 --- a/src/structures/ClientUserSettings.js +++ b/src/structures/ClientUserSettings.js @@ -13,6 +13,8 @@ class ClientUserSettings { /** * Patch the data contained in this class with new partial data. * @param {Object} data Data to patch this with + * @returns {void} + * @private */ patch(data) { for (const key of Object.keys(Constants.UserSettingsMap)) { @@ -29,7 +31,7 @@ class ClientUserSettings { /** * Update a specific property of of user settings. * @param {string} name Name of property - * @param {value} value Value to patch + * @param {*} value Value to patch * @returns {Promise} */ update(name, value) { @@ -37,6 +39,7 @@ class ClientUserSettings { } /** + * Sets the position at which this guild will appear in the Discord client. * @param {Guild} guild The guild to move * @param {number} position Absolute or relative position * @param {boolean} [relative=false] Whether to position relatively or absolutely diff --git a/src/structures/DMChannel.js b/src/structures/DMChannel.js index 9c74aad4e..063c07286 100644 --- a/src/structures/DMChannel.js +++ b/src/structures/DMChannel.js @@ -53,6 +53,7 @@ class DMChannel extends Channel { get typing() {} get typingCount() {} createCollector() {} + createMessageCollector() {} awaitMessages() {} // Doesn't work on DM channels; bulkDelete() {} acknowledge() {} diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js index c01ba84d8..efa20b2c2 100644 --- a/src/structures/GroupDMChannel.js +++ b/src/structures/GroupDMChannel.js @@ -119,7 +119,7 @@ class GroupDMChannel extends Channel { edit(data) { const _data = {}; if (data.name) _data.name = data.name; - if (data.icon) _data.icon = data.icon; + if (typeof data.icon !== 'undefined') _data.icon = data.icon; return this.client.rest.methods.updateGroupDMChannel(this, _data); } @@ -148,6 +148,7 @@ class GroupDMChannel extends Channel { * Add a user to the DM * @param {UserResolvable|string} accessTokenOrID Access token or user resolvable * @param {string} [nick] Permanent nickname to give the user (only available if a bot is creating the DM) + * @returns {Promise} */ addUser(accessTokenOrID, nick) { @@ -161,7 +162,7 @@ class GroupDMChannel extends Channel { /** * Set a new GroupDMChannel icon. * @param {Base64Resolvable|BufferResolvable} icon The new icon of the group dm - * @returns {Promise} + * @returns {Promise} * @example * // Edit the group dm icon * channel.setIcon('./icon.png') @@ -172,6 +173,25 @@ class GroupDMChannel extends Channel { return this.client.resolver.resolveImage(icon).then(data => this.edit({ icon: data })); } + /** + * Sets a new name for this Group DM. + * @param {string} name New name for this Group DM + * @returns {Promise} + */ + setName(name) { + return this.edit({ name }); + } + + /** + * Removes an user from this Group DM. + * @param {UserResolvable} user User to remove + * @returns {Promise} + */ + removeUser(user) { + const id = this.client.resolver.resolveUserID(user); + return this.client.rest.methods.removeUserFromGroupDM(this, id); + } + /** * When concatenated with a string, this automatically concatenates the channel's name instead of the Channel object. * @returns {string} @@ -203,6 +223,7 @@ class GroupDMChannel extends Channel { get typing() {} get typingCount() {} createCollector() {} + createMessageCollector() {} awaitMessages() {} // Doesn't work on Group DMs; bulkDelete() {} acknowledge() {} diff --git a/src/structures/Guild.js b/src/structures/Guild.js index be064e7f3..600e93aa2 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -512,7 +512,7 @@ class Guild { /** * Fetch a single guild member from a user. * @param {UserResolvable} user The user to fetch the member for - * @param {boolean} [cache=true] Insert the user into the users cache + * @param {boolean} [cache=true] Insert the member into the members cache * @returns {Promise} */ fetchMember(user, cache = true) { @@ -563,9 +563,7 @@ class Guild { * Performs a search within the entire guild. * This is only available when using a user account. * @param {MessageSearchOptions} [options={}] Options to pass to the search - * @returns {Promise>} - * An array containing arrays of messages. Each inner array is a search context cluster. - * The message which has triggered the result will have the `hit` property set to `true`. + * @returns {Promise} * @example * guild.search({ * content: 'discord.js', @@ -587,6 +585,7 @@ class Guild { * @property {number} [verificationLevel] The verification level of the guild * @property {number} [explicitContentFilter] The level of the explicit content filter * @property {ChannelResolvable} [afkChannel] The AFK channel of the guild + * @property {ChannelResolvable} [systemChannel] The system 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 @@ -596,7 +595,7 @@ class Guild { /** * Updates the guild with new information - e.g. a new name. * @param {GuildEditData} data The data to update the guild with - * @param {string} [reason] Reason for editing this guild + * @param {string} [reason] Reason for editing the guild * @returns {Promise} * @example * // Set the guild name and region @@ -615,9 +614,9 @@ class Guild { if (data.afkChannel) _data.afk_channel_id = this.client.resolver.resolveChannel(data.afkChannel).id; if (data.systemChannel) _data.system_channel_id = this.client.resolver.resolveChannel(data.systemChannel).id; if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout); - if (data.icon) _data.icon = data.icon; + if (typeof data.icon !== 'undefined') _data.icon = data.icon; if (data.owner) _data.owner_id = this.client.resolver.resolveUser(data.owner).id; - if (data.splash) _data.splash = data.splash; + if (typeof data.splash !== 'undefined') _data.splash = data.splash; if (typeof data.explicitContentFilter !== 'undefined') { _data.explicit_content_filter = Number(data.explicitContentFilter); } @@ -789,6 +788,7 @@ class Guild { /** * Allow direct messages from guild members. + * This is only available when using a user account. * @param {boolean} allow Whether to allow direct messages * @returns {Promise} */ @@ -827,7 +827,7 @@ class Guild { /** * Unbans a user from the guild. * @param {UserResolvable} user The user to unban - * @param {string} [reason] Reason for unbanning the user + * @param {string} [reason] Reason for unbanning the user * @returns {Promise} * @example * // Unban a user by ID (or with a user/guild member object) @@ -948,16 +948,13 @@ class Guild { * .catch(console.error); */ createEmoji(attachment, name, roles, reason) { - return new Promise(resolve => { - if (typeof attachment === 'string' && attachment.startsWith('data:')) { - resolve(this.client.rest.methods.createEmoji(this, attachment, name, roles, reason)); - } else { - this.client.resolver.resolveFile(attachment).then(data => { - const dataURI = this.client.resolver.resolveBase64(data); - resolve(this.client.rest.methods.createEmoji(this, dataURI, name, roles, reason)); - }); - } - }); + if (typeof attachment === 'string' && attachment.startsWith('data:')) { + return this.client.rest.methods.createEmoji(this, attachment, name, roles, reason); + } else { + return this.client.resolver.resolveImage(attachment).then(data => + this.client.rest.methods.createEmoji(this, data, name, roles, reason) + ); + } } /** diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 2c728afbe..b1ad692b9 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -137,7 +137,7 @@ class GuildChannel extends Channel { /** * Overwrites the permissions for a user or role in this channel. - * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {Role|Snowflake|UserResolvable} userOrRole The user or role to update * @param {PermissionOverwriteOptions} options The configuration for the update * @param {string} [reason] Reason for creating/editing this overwrite * @returns {Promise} @@ -261,19 +261,15 @@ class GuildChannel extends Channel { return this.edit({ topic }, reason); } - /** - * Options given when creating a guild channel invite. - * @typedef {Object} InviteOptions - - */ - /** * Create an invite to this guild channel. - * @param {InviteOptions} [options={}] Options for the invite + * This is only available when using a bot account. + * @param {Object} [options={}] Options for the invite * @param {boolean} [options.temporary=false] Whether members that joined via the invite should be automatically * kicked after 24 hours if they have not yet received a role * @param {number} [options.maxAge=86400] How long the invite should last (in seconds, 0 for forever) * @param {number} [options.maxUses=0] Maximum number of uses + * @param {boolean} [options.unique=false] Create a unique invite, or use an existing one with similar settings * @param {string} [reason] Reason for creating the invite * @returns {Promise} */ @@ -294,6 +290,20 @@ class GuildChannel extends Channel { .then(channel => withTopic ? channel.setTopic(this.topic) : channel); } + /** + * Deletes this channel. + * @param {string} [reason] Reason for deleting this channel + * @returns {Promise} + * @example + * // Delete the channel + * channel.delete('making room for new channels') + * .then(channel => console.log(`Deleted ${channel.name} to make room for new channels`)) + * .catch(console.error); // Log error + */ + delete(reason) { + return this.client.rest.methods.deleteChannel(this, reason); + } + /** * 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/Message.js b/src/structures/Message.js index eec937e98..d39c3b9c6 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -271,7 +271,7 @@ class Message { */ /** - * Similar to createCollector but in promise form. + * Similar to createMessageCollector but in promise form. * Resolves with a collection of reactions that pass the specified filter. * @param {CollectorFilter} filter The filter function to use * @param {AwaitReactionsOptions} [options={}] Optional options to pass to the internal collector diff --git a/src/structures/Role.js b/src/structures/Role.js index 87f5cd902..d7a787f0d 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -195,7 +195,7 @@ class Role { * @property {ColorResolvable} [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 {PermissionResolvable[]|number} [permissions] The permissions of the role * @property {boolean} [mentionable] Whether or not the role should be mentionable */ diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 8d9d91a95..da0696840 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -60,7 +60,7 @@ class TextChannel extends GuildChannel { /** * Create a webhook for the channel. * @param {string} name The name of the webhook - * @param {BufferResolvable|Base64Resolvable} avatar The avatar for the webhook + * @param {BufferResolvable|Base64Resolvable} [avatar] The avatar for the webhook * @param {string} [reason] Reason for creating this webhook * @returns {Promise} webhook The created webhook * @example @@ -69,15 +69,13 @@ class TextChannel extends GuildChannel { * .catch(console.error) */ createWebhook(name, avatar, reason) { - return new Promise(resolve => { - if (typeof avatar === 'string' && avatar.startsWith('data:')) { - resolve(this.client.rest.methods.createWebhook(this, name, avatar, reason)); - } else { - this.client.resolver.resolveFile(avatar).then(data => - resolve(this.client.rest.methods.createWebhook(this, name, data, reason)) - ); - } - }); + if (typeof avatar === 'string' && avatar.startsWith('data:')) { + return this.client.rest.methods.createWebhook(this, name, avatar, reason); + } else { + return this.client.resolver.resolveImage(avatar).then(data => + this.client.rest.methods.createWebhook(this, name, data, reason) + ); + } } // 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 f05d7e056..1b72530e7 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -242,20 +242,16 @@ class Webhook { /** * Edit the webhook. * @param {string} name The new name for the webhook - * @param {BufferResolvable} 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 => { - const dataURI = this.client.resolver.resolveBase64(file); - return this.client.rest.methods.editWebhook(this, name, dataURI); - }); + return this.client.resolver.resolveImage(avatar).then(data => + this.client.rest.methods.editWebhook(this, name, data) + ); } - return this.client.rest.methods.editWebhook(this, name).then(data => { - this.setup(data); - return this; - }); + return this.client.rest.methods.editWebhook(this, name); } /** diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index 16b0465ad..37c106a25 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -230,13 +230,18 @@ class TextBasedChannel { * @property {boolean} [nsfw=false] Include results from NSFW channels */ + /** + * @typedef {Object} MessageSearchResult + * @property {number} totalResults Total result count + * @property {Message[][]} messages Array of message results + * The message which has triggered the result will have the `hit` property set to `true` + */ + /** * Performs a search within the channel. * This is only available when using a user account. * @param {MessageSearchOptions} [options={}] Options to pass to the search - * @returns {Promise>} - * An array containing arrays of messages. Each inner array is a search context cluster - * The message which has triggered the result will have the `hit` property set to `true` + * @returns {Promise} * @example * channel.search({ * content: 'discord.js', diff --git a/src/util/Constants.js b/src/util/Constants.js index ed9335cf6..36b15c61c 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -29,6 +29,7 @@ exports.Package = require('../../package.json'); * 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 + * @property {HTTPOptions} [http] HTTP options */ exports.DefaultOptions = { apiRequestMethod: 'sequential', @@ -63,6 +64,15 @@ exports.DefaultOptions = { }, version: 6, }, + + /** + * HTTP options + * @typedef {Object} HTTPOptions + * @property {number} [version=7] API version to use + * @property {string} [api='https://discordapp.com/api'] Base url of the API + * @property {string} [cdn='https://cdn.discordapp.com'] Base url of the CDN + * @property {string} [invite='https://discord.gg'] Base url of invites + */ http: { version: 7, host: 'https://discordapp.com', @@ -293,6 +303,7 @@ exports.VoiceOPCodes = { exports.Events = { READY: 'ready', + RESUME: 'resume', GUILD_CREATE: 'guildCreate', GUILD_DELETE: 'guildDelete', GUILD_UPDATE: 'guildUpdate', diff --git a/src/util/Permissions.js b/src/util/Permissions.js index bb5588f5f..c2d604dbc 100644 --- a/src/util/Permissions.js +++ b/src/util/Permissions.js @@ -180,7 +180,8 @@ class Permissions { * - `MANAGE_GUILD` (edit the guild information, region, etc.) * - `ADD_REACTIONS` (add new reactions to messages) * - `VIEW_AUDIT_LOG` - * - `READ_MESSAGES` + * - `VIEW_CHANNEL` + * - `READ_MESSAGES` **(deprecated)** * - `SEND_MESSAGES` * - `SEND_TTS_MESSAGES` * - `MANAGE_MESSAGES` (delete messages and reactions) @@ -215,6 +216,7 @@ Permissions.FLAGS = { ADD_REACTIONS: 1 << 6, VIEW_AUDIT_LOG: 1 << 7, + VIEW_CHANNEL: 1 << 10, READ_MESSAGES: 1 << 10, SEND_MESSAGES: 1 << 11, SEND_TTS_MESSAGES: 1 << 12, From 56fe70266e20f98cc97da4caa803731b8367d1b1 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 25 Aug 2017 19:50:01 +0200 Subject: [PATCH 91/99] Allow Message#edit to accept a RichEmbed and fixed RichEmbed#file's type (#1829) --- src/structures/Message.js | 4 +++- src/structures/RichEmbed.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index d39c3b9c6..1110513b1 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -1,6 +1,7 @@ const Mentions = require('./MessageMentions'); const Attachment = require('./MessageAttachment'); const Embed = require('./MessageEmbed'); +const RichEmbed = require('./RichEmbed'); const MessageReaction = require('./MessageReaction'); const ReactionCollector = require('./ReactionCollector'); const Util = require('../util/Util'); @@ -365,7 +366,7 @@ class Message { /** * Edit the content of the message. * @param {StringResolvable} [content] The new content for the message - * @param {MessageEditOptions} [options] The options to provide + * @param {MessageEditOptions|RichEmbed} [options] The options to provide * @returns {Promise} * @example * // Update the content of a message @@ -380,6 +381,7 @@ class Message { } else if (!options) { options = {}; } + if (options instanceof RichEmbed) options = { embed: options }; return this.client.rest.methods.updateMessage(this, content, options); } diff --git a/src/structures/RichEmbed.js b/src/structures/RichEmbed.js index 8e4c6eb86..8842f976d 100644 --- a/src/structures/RichEmbed.js +++ b/src/structures/RichEmbed.js @@ -69,7 +69,7 @@ class RichEmbed { /** * File to upload alongside this Embed - * @type {string} + * @type {FileOptions|string|Attachment} */ this.file = data.file; } From 425efe1fe4b8d679e0a8b1ab24c52345bb78b127 Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Wed, 30 Aug 2017 02:15:24 +0200 Subject: [PATCH 92/99] Consistently store message reactions keyed under their unicode (#1852) --- src/structures/Message.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 1110513b1..ce83c63a9 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -536,7 +536,7 @@ class Message { } _addReaction(emoji, user) { - const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : encodeURIComponent(emoji.name); + const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name; let reaction; if (this.reactions.has(emojiID)) { reaction = this.reactions.get(emojiID); @@ -553,7 +553,7 @@ class Message { } _removeReaction(emoji, user) { - const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : encodeURIComponent(emoji.name); + const emojiID = emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name; if (this.reactions.has(emojiID)) { const reaction = this.reactions.get(emojiID); if (reaction.users.has(user.id)) { From 95e22c2f12519fc6659c3f1664af11ff3175f7fe Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 1 Sep 2017 16:05:22 +0200 Subject: [PATCH 93/99] feat/fix: add DiscordAPIError#path and fixed Burst request handler handling api errors (#1867) --- src/client/rest/DiscordAPIError.js | 8 +++++++- src/client/rest/RequestHandlers/Burst.js | 2 +- src/client/rest/RequestHandlers/Sequential.js | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/client/rest/DiscordAPIError.js b/src/client/rest/DiscordAPIError.js index be1b82786..1b9c194d9 100644 --- a/src/client/rest/DiscordAPIError.js +++ b/src/client/rest/DiscordAPIError.js @@ -3,12 +3,18 @@ * @extends Error */ class DiscordAPIError extends Error { - constructor(error) { + constructor(path, error) { super(); const flattened = this.constructor.flattenErrors(error.errors || error).join('\n'); this.name = 'DiscordAPIError'; this.message = error.message && flattened ? `${error.message}\n${flattened}` : error.message || flattened; + /** + * The path of the request relative to the HTTP endpoint + * @type {string} + */ + this.path = path; + /** * HTTP error code returned by Discord * @type {number} diff --git a/src/client/rest/RequestHandlers/Burst.js b/src/client/rest/RequestHandlers/Burst.js index f3311ab65..1f941af31 100644 --- a/src/client/rest/RequestHandlers/Burst.js +++ b/src/client/rest/RequestHandlers/Burst.js @@ -47,7 +47,7 @@ class BurstRequestHandler extends RequestHandler { this.resetTimeout = null; }, 1e3 + this.client.options.restTimeOffset); } else { - item.reject(err.status === 400 ? new DiscordAPIError(res.body) : err); + item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.request.path, res.body) : err); this.handle(); } } else { diff --git a/src/client/rest/RequestHandlers/Sequential.js b/src/client/rest/RequestHandlers/Sequential.js index 42b07452e..5f026f7bb 100644 --- a/src/client/rest/RequestHandlers/Sequential.js +++ b/src/client/rest/RequestHandlers/Sequential.js @@ -68,7 +68,7 @@ class SequentialRequestHandler extends RequestHandler { this.queue.unshift(item); this.restManager.client.setTimeout(resolve, 1e3 + this.client.options.restTimeOffset); } else { - item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.body) : err); + item.reject(err.status >= 400 && err.status < 500 ? new DiscordAPIError(res.request.path, res.body) : err); resolve(err); } } else { From 4df2adc801b6a17acbd7146a824247b4c452c8ab Mon Sep 17 00:00:00 2001 From: SpaceEEC Date: Fri, 1 Sep 2017 16:14:20 +0200 Subject: [PATCH 94/99] Backporting #1863, allowing the afk and systemchannel to be set to null (#1865) * fix(Guild): Allow the afk and system channel to be set to null. * make the getter return null --- src/structures/Guild.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 600e93aa2..fac0141b5 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -304,7 +304,7 @@ class Guild { * @readonly */ get afkChannel() { - return this.client.channels.get(this.afkChannelID); + return this.client.channels.get(this.afkChannelID) || null; } /** @@ -313,7 +313,7 @@ class Guild { * @readonly */ get systemChannel() { - return this.client.channels.get(this.systemChannelID); + return this.client.channels.get(this.systemChannelID) || null; } /** @@ -611,8 +611,12 @@ class Guild { if (data.name) _data.name = data.name; if (data.region) _data.region = data.region; if (typeof data.verificationLevel !== 'undefined') _data.verification_level = Number(data.verificationLevel); - if (data.afkChannel) _data.afk_channel_id = this.client.resolver.resolveChannel(data.afkChannel).id; - if (data.systemChannel) _data.system_channel_id = this.client.resolver.resolveChannel(data.systemChannel).id; + if (typeof data.afkChannel !== 'undefined') { + _data.afk_channel_id = this.client.resolver.resolveChannelID(data.afkChannel); + } + if (typeof data.systemChannel !== 'undefined') { + _data.system_channel_id = this.client.resolver.resolveChannelID(data.systemChannel); + } if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout); if (typeof data.icon !== 'undefined') _data.icon = data.icon; if (data.owner) _data.owner_id = this.client.resolver.resolveUser(data.owner).id; From 0d6b7ce6419ea8c7fad55d515cac37c45aba8a98 Mon Sep 17 00:00:00 2001 From: bdistin Date: Sun, 3 Sep 2017 10:34:50 -0500 Subject: [PATCH 95/99] Fix deny administrator edge case bug (backport from permissions cleanup) --- src/structures/GuildChannel.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index b1ad692b9..f54e60297 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -73,6 +73,9 @@ class GuildChannel extends Channel { const roles = member.roles; for (const role of roles.values()) permissions |= role.permissions; + const admin = Boolean(permissions & Permissions.FLAGS.ADMINISTRATOR); + if (admin) return new Permissions(Permissions.ALL); + const overwrites = this.overwritesFor(member, true, roles); if (overwrites.everyone) { @@ -92,9 +95,6 @@ class GuildChannel extends Channel { permissions |= overwrites.member.allow; } - const admin = Boolean(permissions & Permissions.FLAGS.ADMINISTRATOR); - if (admin) permissions = Permissions.ALL; - return new Permissions(member, permissions); } From a47c30e31a1d3c880d9bcaa7c03bcf6dc67c562c Mon Sep 17 00:00:00 2001 From: iCrawl Date: Sun, 3 Sep 2017 17:58:46 +0200 Subject: [PATCH 96/99] update submodule --- typings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings b/typings index 0967675a2..697fc933d 160000 --- a/typings +++ b/typings @@ -1 +1 @@ -Subproject commit 0967675a2f8e6fa46ab543f955af82a82230d17a +Subproject commit 697fc933de90209b81b69bd0fe87883e3c7a217d From 18de265fcc8035d785ce4a03f618a27a72fc8ad8 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Sun, 3 Sep 2017 18:03:13 +0200 Subject: [PATCH 97/99] lock version numbers, prepare release --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 9129ca54c..e536e7287 100644 --- a/package.json +++ b/package.json @@ -34,26 +34,26 @@ "dependencies": { "long": "^3.2.0", "prism-media": "^0.0.1", - "snekfetch": "^3.2.0", + "snekfetch": "^3.3.0", "tweetnacl": "^1.0.0", - "ws": "^3.0.0" + "ws": "^3.1.0" }, "peerDependencies": { - "bufferutil": "^3.0.0", + "bufferutil": "^3.0.2", "erlpack": "hammerandchisel/erlpack", - "node-opus": "^0.2.0", + "node-opus": "^0.2.6", "opusscript": "^0.0.3", - "sodium": "^2.0.0", - "libsodium-wrappers": "^0.5.0", - "uws": "^0.14.0" + "sodium": "^2.0.1", + "libsodium-wrappers": "^0.5.4", + "uws": "^0.14.5" }, "devDependencies": { - "@types/node": "^7.0.0", + "@types/node": "^7.0.43", "discord.js-docgen": "hydrabolt/discord.js-docgen", - "eslint": "^4.0.0", - "parallel-webpack": "^2.0.0", + "eslint": "^4.6.0", + "parallel-webpack": "^2.1.0", "uglifyjs-webpack-plugin": "^1.0.0-beta.1", - "webpack": "^3.0.0" + "webpack": "^3.5.5" }, "engines": { "node": ">=6.0.0" From 4778a47cd4843c5efcb9b33aaf922c97d6109630 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Sun, 3 Sep 2017 18:40:49 +0200 Subject: [PATCH 98/99] Update docs landing page --- docs/general/updating.md | 12 ++++++++---- docs/general/welcome.md | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/general/updating.md b/docs/general/updating.md index cc2c7399f..4c0bca98d 100644 --- a/docs/general/updating.md +++ b/docs/general/updating.md @@ -1,3 +1,7 @@ +# Version 11.2.0 +v11.2.0 features fixes a lot of bugs we encountered along the 11.1.0 release, as well as support for new features such as Message Attachments and UserGuildSettings. +See [the changelog](https://github.com/hydrabolt/discord.js/releases/tag/11.2.0) for a full list of changes, including information about deprecations. + # Version 11.1.0 v11.1.0 features improved voice and gateway stability, as well as support for new features such as audit logs and searching for messages. See [the changelog](https://github.com/hydrabolt/discord.js/releases/tag/11.1.0) for a full list of changes, including @@ -118,9 +122,9 @@ The guild parameter that has been dropped from the guild-related events can stil ## 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` +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. @@ -149,7 +153,7 @@ A couple more important details: * `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. +Version 9 eschews callbacks in favour of Promises. This means all code relying on callbacks must be changed. For example, the following code: ```js diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 01a3d6717..62a4fd121 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -17,8 +17,8 @@ # Welcome! -Welcome to the discord.js v11.1.0 documentation. -v11.1.0 features improved voice and gateway stability, as well as support for new features such as audit logs and searching for messages. +Welcome to the discord.js v11.2.0 documentation. +v11.2.0 features fixes a lot of bugs we encountered along the 11.1.0 release, as well as support for new features such as Message Attachments and UserGuildSettings. ## About discord.js is a powerful [node.js](https://nodejs.org) module that allows you to interact with the @@ -30,11 +30,11 @@ discord.js is a powerful [node.js](https://nodejs.org) module that allows you to - 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, as they're all 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` +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 @@ -79,7 +79,7 @@ client.login('your token'); ## 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). +[documentation](https://discord.js.org/#/docs). See [the contribution guide](https://github.com/hydrabolt/discord.js/blob/master/.github/CONTRIBUTING.md) if you'd like to submit a PR. ## Help From 77c54a94f2e5f5726effeecca62e9c92cc889611 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Sun, 3 Sep 2017 18:42:57 +0200 Subject: [PATCH 99/99] Fix typo in landing page --- docs/general/updating.md | 2 +- docs/general/welcome.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/general/updating.md b/docs/general/updating.md index 4c0bca98d..8c54e8fa7 100644 --- a/docs/general/updating.md +++ b/docs/general/updating.md @@ -1,5 +1,5 @@ # Version 11.2.0 -v11.2.0 features fixes a lot of bugs we encountered along the 11.1.0 release, as well as support for new features such as Message Attachments and UserGuildSettings. +v11.2.0 fixes a lot of bugs we encountered along the 11.1.0 release, as well as support for new features such as Message Attachments and UserGuildSettings. See [the changelog](https://github.com/hydrabolt/discord.js/releases/tag/11.2.0) for a full list of changes, including information about deprecations. # Version 11.1.0 diff --git a/docs/general/welcome.md b/docs/general/welcome.md index 62a4fd121..d93f83924 100644 --- a/docs/general/welcome.md +++ b/docs/general/welcome.md @@ -18,7 +18,7 @@ # Welcome! Welcome to the discord.js v11.2.0 documentation. -v11.2.0 features fixes a lot of bugs we encountered along the 11.1.0 release, as well as support for new features such as Message Attachments and UserGuildSettings. +v11.2.0 fixes a lot of bugs we encountered along the 11.1.0 release, as well as support for new features such as Message Attachments and UserGuildSettings. ## About discord.js is a powerful [node.js](https://nodejs.org) module that allows you to interact with the