From cd08a3b5a4569e98aaa9c954d578ac9c12fba826 Mon Sep 17 00:00:00 2001
From: SpaceEEC
Date: Thu, 26 Oct 2017 09:52:03 +0200
Subject: [PATCH 01/75] refactor(Actions): remove obsolete user and guild
member get actions (#2061)
The UserGetAction was never used.
The GuildMemberGetAction was only once used.
Easily replaced with a shorter and more comprehensible line. (Also consistent with the rest of the library)
---
src/client/actions/ActionsManager.js | 2 --
src/client/actions/GuildMemberGet.js | 10 ----------
src/client/actions/UserGet.js | 11 -----------
src/structures/Guild.js | 2 +-
4 files changed, 1 insertion(+), 24 deletions(-)
delete mode 100644 src/client/actions/GuildMemberGet.js
delete mode 100644 src/client/actions/UserGet.js
diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js
index 8341e7453..9708d17b2 100644
--- a/src/client/actions/ActionsManager.js
+++ b/src/client/actions/ActionsManager.js
@@ -14,13 +14,11 @@ class ActionsManager {
this.register(require('./ChannelUpdate'));
this.register(require('./GuildDelete'));
this.register(require('./GuildUpdate'));
- this.register(require('./GuildMemberGet'));
this.register(require('./GuildMemberRemove'));
this.register(require('./GuildBanRemove'));
this.register(require('./GuildRoleCreate'));
this.register(require('./GuildRoleDelete'));
this.register(require('./GuildRoleUpdate'));
- this.register(require('./UserGet'));
this.register(require('./UserUpdate'));
this.register(require('./UserNoteUpdate'));
this.register(require('./GuildSync'));
diff --git a/src/client/actions/GuildMemberGet.js b/src/client/actions/GuildMemberGet.js
deleted file mode 100644
index 5bf2aafec..000000000
--- a/src/client/actions/GuildMemberGet.js
+++ /dev/null
@@ -1,10 +0,0 @@
-const Action = require('./Action');
-
-class GuildMemberGetAction extends Action {
- handle(guild, data) {
- const member = guild.members.create(data);
- return { member };
- }
-}
-
-module.exports = GuildMemberGetAction;
diff --git a/src/client/actions/UserGet.js b/src/client/actions/UserGet.js
deleted file mode 100644
index 4a135dd58..000000000
--- a/src/client/actions/UserGet.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const Action = require('./Action');
-
-class UserGetAction extends Action {
- handle(data) {
- const client = this.client;
- const user = client.users.create(data);
- return { user };
- }
-}
-
-module.exports = UserGetAction;
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index d96329be0..b3ea8535d 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -542,7 +542,7 @@ class Guild extends Base {
}
}
return this.client.api.guilds(this.id).members(user.id).put({ data: options })
- .then(data => this.client.actions.GuildMemberGet.handle(this, data).member);
+ .then(data => this.members.create(data));
}
/**
From bc30fdd8671d3a82ca90ede3c7df571ca3f0ecf2 Mon Sep 17 00:00:00 2001
From: Gus Caplan
Date: Thu, 26 Oct 2017 14:07:27 -0500
Subject: [PATCH 02/75] Fix Avatar URL generation bug (#2063)
---
src/util/Constants.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/util/Constants.js b/src/util/Constants.js
index aed65b8ee..7a0b262d3 100644
--- a/src/util/Constants.js
+++ b/src/util/Constants.js
@@ -114,6 +114,7 @@ exports.Endpoints = {
Asset: name => `${root}/assets/${name}`,
DefaultAvatar: number => `${root}/embed/avatars/${number}.png`,
Avatar: (userID, hash, format = 'default', size) => {
+ if (userID === '1') return hash;
if (format === 'default') format = hash.startsWith('a_') ? 'gif' : 'webp';
return makeImageUrl(`${root}/avatars/${userID}/${hash}`, { format, size });
},
From cd54e9317ff0396a34bafacce2ff2c056a4b0cee Mon Sep 17 00:00:00 2001
From: Gus Caplan
Date: Fri, 27 Oct 2017 08:36:53 -0500
Subject: [PATCH 03/75] Time Difference in REST (#2057)
---
src/rest/RESTManager.js | 10 ++++++++++
src/rest/handlers/RequestHandler.js | 7 +++----
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/rest/RESTManager.js b/src/rest/RESTManager.js
index 48476cca6..d62423979 100644
--- a/src/rest/RESTManager.js
+++ b/src/rest/RESTManager.js
@@ -12,12 +12,22 @@ class RESTManager {
this.globallyRateLimited = false;
this.tokenPrefix = tokenPrefix;
this.versioned = true;
+ this.timeDifferences = [];
}
get api() {
return routeBuilder(this);
}
+ get timeDifference() {
+ return Math.round(this.timeDifferences.reduce((a, b) => a + b, 0) / this.timeDifferences.length);
+ }
+
+ set timeDifference(ms) {
+ this.timeDifferences.unshift(ms);
+ if (this.timeDifferences.length > 5) this.timeDifferences.length = 5;
+ }
+
getAuth() {
const token = this.client.token || this.client.accessToken;
const prefixed = !!this.client.application || (this.client.user && this.client.user.bot);
diff --git a/src/rest/handlers/RequestHandler.js b/src/rest/handlers/RequestHandler.js
index aaeed9d69..64e9ea72b 100644
--- a/src/rest/handlers/RequestHandler.js
+++ b/src/rest/handlers/RequestHandler.js
@@ -9,7 +9,6 @@ class RequestHandler {
this.limit = Infinity;
this.resetTime = null;
this.remaining = 1;
- this.timeDifference = 0;
this.queue = [];
}
@@ -32,7 +31,7 @@ class RequestHandler {
const finish = timeout => {
if (timeout || this.limited) {
if (!timeout) {
- timeout = this.resetTime - Date.now() + this.timeDifference + this.client.options.restTimeOffset;
+ timeout = this.resetTime - Date.now() + this.manager.timeDifference + this.client.options.restTimeOffset;
}
// eslint-disable-next-line prefer-promise-reject-errors
reject({ timeout });
@@ -50,7 +49,7 @@ class RequestHandler {
this.client.emit(RATE_LIMIT, {
timeout,
limit: this.limit,
- timeDifference: this.timeDifference,
+ timeDifference: this.manager.timeDifference,
method: item.request.method,
path: item.request.path,
route: item.request.route,
@@ -66,7 +65,7 @@ class RequestHandler {
this.limit = Number(res.headers['x-ratelimit-limit']);
this.resetTime = Number(res.headers['x-ratelimit-reset']) * 1000;
this.remaining = Number(res.headers['x-ratelimit-remaining']);
- this.timeDifference = Date.now() - new Date(res.headers.date).getTime();
+ this.manager.timeDifference = Date.now() - new Date(res.headers.date).getTime();
}
if (err) {
if (err.status === 429) {
From 0fc9459450341ae894dcbf62939f43bbca0d6c79 Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Fri, 27 Oct 2017 10:34:18 -0400
Subject: [PATCH 04/75] Add TextChannel#setNSFW method (#2050)
* Add TextChannel#setNSFW method
* Doesn't look like anything to me
* butts
---
src/structures/GuildChannel.js | 2 ++
src/structures/TextChannel.js | 10 ++++++++++
2 files changed, 12 insertions(+)
diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js
index 66efd8c2b..a1e8d53a0 100644
--- a/src/structures/GuildChannel.js
+++ b/src/structures/GuildChannel.js
@@ -248,6 +248,7 @@ class GuildChannel extends Channel {
* @property {string} [name] The name of the channel
* @property {number} [position] The position of the channel
* @property {string} [topic] The topic of the text channel
+ * @property {boolean} [nsfw] Whether the channel is NSFW
* @property {number} [bitrate] The bitrate of the voice channel
* @property {number} [userLimit] The user limit of the voice channel
* @property {Snowflake} [parentID] The parent ID of the channel
@@ -290,6 +291,7 @@ class GuildChannel extends Channel {
data: {
name: (data.name || this.name).trim(),
topic: data.topic,
+ nsfw: data.nsfw,
bitrate: data.bitrate || (this.bitrate ? this.bitrate * 1000 : undefined),
user_limit: data.userLimit != null ? data.userLimit : this.userLimit, // eslint-disable-line eqeqeq
parent_id: data.parentID,
diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js
index 6529f46b9..5679f841a 100644
--- a/src/structures/TextChannel.js
+++ b/src/structures/TextChannel.js
@@ -38,6 +38,16 @@ class TextChannel extends GuildChannel {
if (data.messages) for (const message of data.messages) this.messages.create(message);
}
+ /**
+ * Sets whether this channel is flagged as NSFW.
+ * @param {boolean} nsfw Whether the channel should be considered NSFW
+ * @param {string} [reason] Reason for changing the channel's NSFW flag
+ * @returns {Promise}
+ */
+ setNSFW(nsfw, reason) {
+ return this.edit({ nsfw }, reason);
+ }
+
/**
* Fetches all webhooks for the channel.
* @returns {Promise>}
From 291af7e845126d10a5fd95c6a6576e0e40a9e0ac Mon Sep 17 00:00:00 2001
From: Johnson Chen
Date: Sat, 28 Oct 2017 20:35:38 +1100
Subject: [PATCH 05/75] Change recent to timestamp because DiscordAPIError
(#2065)
are lovely arn't they?
---
src/structures/shared/Search.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/structures/shared/Search.js b/src/structures/shared/Search.js
index 971f2483d..a852a5086 100644
--- a/src/structures/shared/Search.js
+++ b/src/structures/shared/Search.js
@@ -11,7 +11,7 @@ const { TypeError } = require('../../errors');
* @property {ChannelResolvable} [channel] Channel to limit search to (only for guild search endpoint)
* @property {UserResolvable} [author] Author to limit search
* @property {string} [authorType] One of `user`, `bot`, `webhook`, or add `-` to negate (e.g. `-webhook`)
- * @property {string} [sortBy='recent'] `recent` or `relevant`
+ * @property {string} [sortBy='timestamp'] `timestamp` or `relevant`
* @property {string} [sortOrder='descending'] `ascending` or `descending`
* @property {number} [contextSize=2] How many messages to get around the matched message (0 to 2)
* @property {number} [limit=25] Maximum number of results to get (1 to 25)
From 0a05761b49dbd3970234ddaf80484fc78a1afbda Mon Sep 17 00:00:00 2001
From: SpaceEEC
Date: Sat, 28 Oct 2017 18:55:59 +0200
Subject: [PATCH 06/75] Add new exports and remove a deprecated one (#2068)
* add new exports and remove a deprecated one
* fix incorrect require path
---
src/index.js | 24 +++++++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/src/index.js b/src/index.js
index 746aab001..7bea36f1b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -15,7 +15,6 @@ module.exports = {
DataResolver: require('./util/DataResolver'),
DataStore: require('./stores/DataStore'),
DiscordAPIError: require('./rest/DiscordAPIError'),
- EvaluatedPermissions: require('./util/Permissions'),
Permissions: require('./util/Permissions'),
Snowflake: require('./util/Snowflake'),
SnowflakeUtil: require('./util/Snowflake'),
@@ -23,15 +22,32 @@ module.exports = {
util: Util,
version: require('../package.json').version,
+ // Stores
+ ChannelStore: require('./stores/ChannelStore'),
+ ClientPresenceStore: require('./stores/ClientPresenceStore'),
+ EmojiStore: require('./stores/EmojiStore'),
+ GuildChannelStore: require('./stores/GuildChannelStore'),
+ GuildMemberStore: require('./stores/GuildMemberStore'),
+ GuildStore: require('./stores/GuildStore'),
+ MessageStore: require('./stores/MessageStore'),
+ PresenceStore: require('./stores/PresenceStore'),
+ RoleStore: require('./stores/RoleStore'),
+ UserStore: require('./stores/UserStore'),
+
// Shortcuts to Util methods
escapeMarkdown: Util.escapeMarkdown,
fetchRecommendedShards: Util.fetchRecommendedShards,
splitMessage: Util.splitMessage,
// Structures
+ Base: require('./structures/Base'),
Activity: require('./structures/Presence').Activity,
+ CategoryChannel: require('./structures/CategoryChannel'),
Channel: require('./structures/Channel'),
+ ClientApplication: require('./structures/ClientApplication'),
ClientUser: require('./structures/ClientUser'),
+ ClientUserChannelOverride: require('./structures/ClientUserChannelOverride'),
+ ClientUserGuildSettings: require('./structures/ClientUserGuildSettings'),
ClientUserSettings: require('./structures/ClientUserSettings'),
Collector: require('./structures/interfaces/Collector'),
DMChannel: require('./structures/DMChannel'),
@@ -48,15 +64,17 @@ module.exports = {
MessageEmbed: require('./structures/MessageEmbed'),
MessageMentions: require('./structures/MessageMentions'),
MessageReaction: require('./structures/MessageReaction'),
- ClientApplication: require('./structures/ClientApplication'),
PermissionOverwrites: require('./structures/PermissionOverwrites'),
Presence: require('./structures/Presence').Presence,
- ReactionEmoji: require('./structures/ReactionEmoji'),
ReactionCollector: require('./structures/ReactionCollector'),
+ ReactionEmoji: require('./structures/ReactionEmoji'),
+ RichPresenceAssets: require('./structures/Presence').RichPresenceAssets,
Role: require('./structures/Role'),
TextChannel: require('./structures/TextChannel'),
User: require('./structures/User'),
+ UserConnection: require('./structures/UserConnection'),
VoiceChannel: require('./structures/VoiceChannel'),
+ VoiceRegion: require('./structures/VoiceRegion'),
Webhook: require('./structures/Webhook'),
WebSocket: require('./WebSocket'),
From cd3d3344e8c34e0950584b20bbe984b411fc23c5 Mon Sep 17 00:00:00 2001
From: SpaceEEC
Date: Sat, 28 Oct 2017 18:57:16 +0200
Subject: [PATCH 07/75] fix(GuildMember#hasPermission): pass correct parameters
to Permissions#has (#2070)
Also removed deprecated parameter of the method itself.
---
src/structures/GuildMember.js | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index c4c2d6a81..935ba1084 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -294,19 +294,13 @@ class GuildMember extends Base {
/**
* Checks if any of the member's roles have a permission.
* @param {PermissionResolvable|PermissionResolvable[]} permission Permission(s) to check for
- * @param {boolean} [explicit=false] Whether to require the role to explicitly have the exact permission
- * **(deprecated)**
- * @param {boolean} [checkAdmin] Whether to allow the administrator permission to override
- * (takes priority over `explicit`)
- * @param {boolean} [checkOwner] Whether to allow being the guild's owner to override
- * (takes priority over `explicit`)
+ * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
+ * @param {boolean} [checkOwner=true] Whether to allow being the guild's owner to override
* @returns {boolean}
*/
- hasPermission(permission, explicit = false, checkAdmin, checkOwner) {
- if (typeof checkAdmin === 'undefined') checkAdmin = !explicit;
- if (typeof checkOwner === 'undefined') checkOwner = !explicit;
+ hasPermission(permission, checkAdmin = true, checkOwner = true) {
if (checkOwner && this.user.id === this.guild.ownerID) return true;
- return this.roles.some(r => r.permissions.has(permission, undefined, checkAdmin));
+ return this.roles.some(r => r.permissions.has(permission, checkAdmin));
}
/**
From cda408534ada7f50b2d3a3a613b912b3548723d6 Mon Sep 17 00:00:00 2001
From: bdistin
Date: Sat, 28 Oct 2017 11:57:50 -0500
Subject: [PATCH 08/75] user.patch shouldn't try to touch the token (#2072)
---
src/structures/ClientUser.js | 2 ++
src/structures/User.js | 2 --
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js
index cc19ad58b..ce71d9bcf 100644
--- a/src/structures/ClientUser.js
+++ b/src/structures/ClientUser.js
@@ -88,6 +88,8 @@ class ClientUser extends User {
this.guildSettings.set(settings.guild_id, new ClientUserGuildSettings(this.client, settings));
}
}
+
+ if (data.token) this.client.token = data.token;
}
/**
diff --git a/src/structures/User.js b/src/structures/User.js
index 814d3a90c..f12d67ffa 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -63,8 +63,6 @@ class User extends Base {
* @type {?Message}
*/
this.lastMessage = null;
-
- if (data.token) this.client.token = data.token;
}
/**
From b255af0825776eed430bbba0f96c4b8546bab926 Mon Sep 17 00:00:00 2001
From: bdistin
Date: Sat, 28 Oct 2017 11:58:27 -0500
Subject: [PATCH 09/75] Fix user.bot (#2073)
* fix user.bot
* user.avatar is nullable (docs)
---
src/structures/User.js | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/structures/User.js b/src/structures/User.js
index f12d67ffa..2a184b1d4 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -20,6 +20,13 @@ class User extends Base {
*/
this.id = data.id;
+ /**
+ * Whether or not the user is a bot
+ * @type {boolean}
+ * @name User#bot
+ */
+ this.bot = Boolean(data.bot);
+
this._patch(data);
}
@@ -40,18 +47,11 @@ class User extends Base {
/**
* The ID of the user's avatar
- * @type {string}
+ * @type {?string}
* @name User#avatar
*/
if (typeof data.avatar !== 'undefined') this.avatar = data.avatar;
- /**
- * Whether or not the user is a bot
- * @type {boolean}
- * @name User#bot
- */
- if (typeof this.bot === 'undefined' && typeof data.bot !== 'undefined') this.bot = Boolean(data.bot);
-
/**
* The ID of the last message sent by the user, if one was sent
* @type {?Snowflake}
From c495ea025a37b0d6e1f645407719573a1e6b9083 Mon Sep 17 00:00:00 2001
From: Gus Caplan
Date: Sat, 28 Oct 2017 11:58:46 -0500
Subject: [PATCH 10/75] fix raw event (#2074)
---
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 a30be35a8..4e3835ef0 100644
--- a/src/client/websocket/WebSocketConnection.js
+++ b/src/client/websocket/WebSocketConnection.js
@@ -272,7 +272,7 @@ class WebSocketConnection extends EventEmitter {
try {
const packet = WebSocket.unpack(this.inflate.result);
this.onPacket(packet);
- if (this.client.listenerCount('raw')) this.client.emit('raw', data);
+ if (this.client.listenerCount('raw')) this.client.emit('raw', packet);
} catch (err) {
this.client.emit('debug', err);
}
From 88719f0f4209fce2a4d08cecc90fa4c8e2bdde8d Mon Sep 17 00:00:00 2001
From: William Tran
Date: Sat, 28 Oct 2017 10:01:17 -0700
Subject: [PATCH 11/75] Typos in docs (#2055)
* Typo in Guild.createRole docs
Added missing semicolon in example code.
* consistent periods in docs
---
src/client/voice/VoiceBroadcast.js | 2 +-
src/structures/Guild.js | 4 ++--
src/structures/Presence.js | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/client/voice/VoiceBroadcast.js b/src/client/voice/VoiceBroadcast.js
index 678045dca..5a2c54c73 100644
--- a/src/client/voice/VoiceBroadcast.js
+++ b/src/client/voice/VoiceBroadcast.js
@@ -236,7 +236,7 @@ class VoiceBroadcast extends VolumeInterface {
}
/**
- * Plays an arbitrary input that can be [handled by ffmpeg](https://ffmpeg.org/ffmpeg-protocols.html#Description)
+ * Plays an arbitrary input that can be [handled by ffmpeg](https://ffmpeg.org/ffmpeg-protocols.html#Description).
* @param {string} input The arbitrary input
* @param {StreamOptions} [options] Options for playing the stream
* @returns {VoiceBroadcast}
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index b3ea8535d..f3edd46ab 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -979,7 +979,7 @@ class Guild extends Base {
}
/**
- * Creates a new role in the guild with given information
+ * Creates a new role in the guild with given information.
* The position will silently reset to 1 if an invalid one is provided, or none.
* @param {Object} [options] Options
* @param {RoleData} [options.data] The data to update the role with
@@ -1000,7 +1000,7 @@ class Guild extends Base {
* reason: 'we needed a role for Super Cool People',
* })
* .then(role => console.log(`Created role ${role}`))
- * .catch(console.error)
+ * .catch(console.error);
*/
createRole({ data = {}, reason } = {}) {
if (data.color) data.color = Util.resolveColor(data.color);
diff --git a/src/structures/Presence.js b/src/structures/Presence.js
index 1eba8170c..91bee3ac3 100644
--- a/src/structures/Presence.js
+++ b/src/structures/Presence.js
@@ -38,7 +38,7 @@ class Presence {
}
/**
- * Whether this presence is equal to another
+ * Whether this presence is equal to another.
* @param {Presence} presence The presence to compare with
* @returns {boolean}
*/
From 50ad66f5139b52bf057de0d2745d7ae73e4fa8f8 Mon Sep 17 00:00:00 2001
From: Gus Caplan
Date: Sat, 28 Oct 2017 12:02:12 -0500
Subject: [PATCH 12/75] clean up readme a bit (#2054)
* clean up readme a bit
* Update README.md
---
README.md | 30 ++++++++++++++++--------------
1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/README.md b/README.md
index 6b88f4370..989d2d652 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,7 @@
-
+
@@ -31,9 +30,9 @@ discord.js is a powerful [node.js](https://nodejs.org) module that allows you to
**Node.js 8.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`
-With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm install discord.js opusscript --save`
+Without voice support: `npm i discord.js`
+With voice support ([node-opus](https://www.npmjs.com/package/node-opus)): `npm i discord.js node-opus`
+With voice support ([opusscript](https://www.npmjs.com/package/opusscript)): `npm i discord.js opusscript`
### Audio engines
The preferred audio engine is node-opus, as it performs significantly better than opusscript. When both are available, discord.js will automatically choose node-opus.
@@ -41,13 +40,13 @@ Using opusscript is only recommended for development environments where node-opu
For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers.
### Optional packages
-- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for significantly faster WebSocket data inflation (`npm install zlib-sync`)
-- [bufferutil](https://www.npmjs.com/package/bufferutil) to greatly speed up the WebSocket when *not* using uws (`npm install bufferutil --save`)
-- [erlpack](https://github.com/hammerandchisel/erlpack) for significantly faster WebSocket data (de)serialisation (`npm install discordapp/erlpack --save`)
+- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for significantly faster WebSocket data inflation (`npm i zlib-sync`)
+- [erlpack](https://github.com/discordapp/erlpack) for significantly faster WebSocket data (de)serialisation (`npm i discordapp/erlpack`)
- One of the following packages can be installed for faster voice packet encryption and decryption:
- - [sodium](https://www.npmjs.com/package/sodium) (`npm install sodium --save`)
- - [libsodium.js](https://www.npmjs.com/package/libsodium-wrappers) (`npm install libsodium-wrappers --save`)
-- [uws](https://www.npmjs.com/package/uws) for a much faster WebSocket connection (`npm install uws --save`)
+ - [sodium](https://www.npmjs.com/package/sodium) (`npm i sodium`)
+ - [libsodium.js](https://www.npmjs.com/package/libsodium-wrappers) (`npm i libsodium-wrappers`)
+- [uws](https://www.npmjs.com/package/uws) for a much faster WebSocket connection (`npm i uws`)
+- [bufferutil](https://www.npmjs.com/package/bufferutil) for a much faster WebSocket connection when *not* using uws (`npm i bufferutil`)
## Example usage
```js
@@ -70,11 +69,14 @@ client.login('your token');
## Links
* [Website](https://discord.js.org/) ([source](https://github.com/hydrabolt/discord.js-site))
* [Documentation](https://discord.js.org/#/docs)
-* [Discord.js server](https://discord.gg/bRCvFy9)
-* [Discord API server](https://discord.gg/rV4BwdK)
+* [Discord.js Discord server](https://discord.gg/bRCvFy9)
+* [Discord API Discord server](https://discord.gg/discord-api)
* [GitHub](https://github.com/hydrabolt/discord.js)
* [NPM](https://www.npmjs.com/package/discord.js)
-* [Related libraries](https://discordapi.com/unofficial/libs.html) (see also [discord-rpc](https://www.npmjs.com/package/discord-rpc))
+* [Related libraries](https://discordapi.com/unofficial/libs.html)
+
+### Extensions
+* [discord-rpc](https://www.npmjs.com/package/discord-rpc) ([github](https://github.com/devsnek/discord-rpc))
## Contributing
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
From a62d1e954d9e8be0a8acf3802dd4f87c367ac2a5 Mon Sep 17 00:00:00 2001
From: SpaceEEC
Date: Sat, 28 Oct 2017 19:03:27 +0200
Subject: [PATCH 13/75] fix(Presence): pass client and default to offline
(#2071)
---
src/structures/GuildMember.js | 2 +-
src/structures/Presence.js | 2 +-
src/structures/User.js | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index 935ba1084..8b83796cf 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -125,7 +125,7 @@ class GuildMember extends Base {
* @readonly
*/
get presence() {
- return this.frozenPresence || this.guild.presences.get(this.id) || new Presence();
+ return this.frozenPresence || this.guild.presences.get(this.id) || new Presence(this.client);
}
/**
diff --git a/src/structures/Presence.js b/src/structures/Presence.js
index 91bee3ac3..e788124fc 100644
--- a/src/structures/Presence.js
+++ b/src/structures/Presence.js
@@ -19,7 +19,7 @@ class Presence {
* * **`dnd`** - user is in Do Not Disturb
* @type {string}
*/
- this.status = data.status || this.status;
+ this.status = data.status || this.status || 'offline';
const activity = data.game || data.activity;
/**
diff --git a/src/structures/User.js b/src/structures/User.js
index 2a184b1d4..859cdeeb2 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -93,7 +93,7 @@ class User extends Base {
for (const guild of this.client.guilds.values()) {
if (guild.presences.has(this.id)) return guild.presences.get(this.id);
}
- return new Presence();
+ return new Presence(this.client);
}
/**
From 0101392334e48d144dda3bc1a127d1e9feb2555d Mon Sep 17 00:00:00 2001
From: SpaceEEC
Date: Sat, 28 Oct 2017 19:04:03 +0200
Subject: [PATCH 14/75] Documentation improvements (#2069)
* docs: fix documentation in various places
All stores: resolveID returns a nullable Snowflake
GuildAuditLogs: ActionType also can be ALL
MessageEmbed: make files property show up in the docs
ClientApplication: resetSecret and resetToken return a promise
ClientManager: status is readonly
Guild: features is an array of strings and ban no longer accepts a number or string
Guild: ban method no longer accepts a string or number
GuildMember: ^
RichPresenceAssets: small and large Image are Snowflakes, also fixed parameter documentation for small and large image url method
WebhookMessageOptions: file property is no longer a thing
* docs: improve GuildAuditLogs documentation
Prefix types with AuditLog to avoid confusion
Document GuildAuditLogs' static Targets and Actions properties and add necessary typedefs
Use typdefs over primitives where possible.
* fix documentation for Guild#defaultRole
---
src/client/ClientManager.js | 1 +
src/stores/ChannelStore.js | 2 +-
src/stores/DataStore.js | 2 +-
src/stores/EmojiStore.js | 2 +-
src/stores/GuildChannelStore.js | 2 +-
src/stores/GuildMemberStore.js | 2 +-
src/stores/GuildStore.js | 2 +-
src/stores/MessageStore.js | 2 +-
src/stores/PresenceStore.js | 2 +-
src/stores/ReactionStore.js | 2 +-
src/stores/RoleStore.js | 2 +-
src/stores/UserStore.js | 2 +-
src/structures/ClientApplication.js | 4 +-
src/structures/Guild.js | 6 +-
src/structures/GuildAuditLogs.js | 93 +++++++++++++++++++++--------
src/structures/GuildMember.js | 2 +-
src/structures/MessageEmbed.js | 12 ++--
src/structures/Presence.js | 14 +++--
src/structures/Webhook.js | 1 -
19 files changed, 100 insertions(+), 55 deletions(-)
diff --git a/src/client/ClientManager.js b/src/client/ClientManager.js
index 9e0d570d2..ad006b8fe 100644
--- a/src/client/ClientManager.js
+++ b/src/client/ClientManager.js
@@ -22,6 +22,7 @@ class ClientManager {
/**
* The status of the client
+ * @readonly
* @type {number}
*/
get status() {
diff --git a/src/stores/ChannelStore.js b/src/stores/ChannelStore.js
index 5491b8e59..2ceee8000 100644
--- a/src/stores/ChannelStore.js
+++ b/src/stores/ChannelStore.js
@@ -95,7 +95,7 @@ class ChannelStore extends DataStore {
* @memberof ChannelStore
* @instance
* @param {ChannelResolvable} channel The channel resolvable to resolve
- * @returns {?string}
+ * @returns {?Snowflake}
*/
}
diff --git a/src/stores/DataStore.js b/src/stores/DataStore.js
index 85ce17c90..398910d50 100644
--- a/src/stores/DataStore.js
+++ b/src/stores/DataStore.js
@@ -37,7 +37,7 @@ class DataStore extends Collection {
/**
* Resolves a data entry to a instance ID.
* @param {string|Instance} idOrInstance The id or instance of something in this DataStore
- * @returns {?string}
+ * @returns {?Snowflake}
*/
resolveID(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance.id;
diff --git a/src/stores/EmojiStore.js b/src/stores/EmojiStore.js
index 83b4df812..035cc3d4d 100644
--- a/src/stores/EmojiStore.js
+++ b/src/stores/EmojiStore.js
@@ -38,7 +38,7 @@ class EmojiStore extends DataStore {
/**
* Resolves a EmojiResolvable to a Emoji ID string.
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
- * @returns {?string}
+ * @returns {?Snowflake}
*/
resolveID(emoji) {
if (emoji instanceof ReactionEmoji) return emoji.id;
diff --git a/src/stores/GuildChannelStore.js b/src/stores/GuildChannelStore.js
index 3e03c8110..0bc3e8c4e 100644
--- a/src/stores/GuildChannelStore.js
+++ b/src/stores/GuildChannelStore.js
@@ -42,7 +42,7 @@ class GuildChannelStore extends DataStore {
* @memberof GuildChannelStore
* @instance
* @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve
- * @returns {?string}
+ * @returns {?Snowflake}
*/
}
diff --git a/src/stores/GuildMemberStore.js b/src/stores/GuildMemberStore.js
index ba8cac903..ad0acbed9 100644
--- a/src/stores/GuildMemberStore.js
+++ b/src/stores/GuildMemberStore.js
@@ -41,7 +41,7 @@ class GuildMemberStore extends DataStore {
/**
* Resolves a GuildMemberResolvable to an member ID string.
* @param {GuildMemberResolvable} member The user that is part of the guild
- * @returns {?string}
+ * @returns {?Snowflake}
*/
resolveID(member) {
const memberResolveable = super.resolveID(member);
diff --git a/src/stores/GuildStore.js b/src/stores/GuildStore.js
index 5e3e792a4..45e9c9cfc 100644
--- a/src/stores/GuildStore.js
+++ b/src/stores/GuildStore.js
@@ -33,7 +33,7 @@ class GuildStore extends DataStore {
* @memberof GuildStore
* @instance
* @param {GuildResolvable} guild The guild resolvable to identify
- * @returns {?string}
+ * @returns {?Snowflake}
*/
}
diff --git a/src/stores/MessageStore.js b/src/stores/MessageStore.js
index 75d01620c..cc0ff60b0 100644
--- a/src/stores/MessageStore.js
+++ b/src/stores/MessageStore.js
@@ -112,7 +112,7 @@ class MessageStore extends DataStore {
* @memberof MessageStore
* @instance
* @param {MessageResolvable} message The message resolvable to resolve
- * @returns {?string}
+ * @returns {?Snowflake}
*/
}
diff --git a/src/stores/PresenceStore.js b/src/stores/PresenceStore.js
index 1c2649712..8322c9c65 100644
--- a/src/stores/PresenceStore.js
+++ b/src/stores/PresenceStore.js
@@ -39,7 +39,7 @@ class PresenceStore extends DataStore {
/**
* Resolves a PresenceResolvable to a Presence ID string.
* @param {PresenceResolvable} presence The presence resolvable to resolve
- * @returns {?string}
+ * @returns {?Snowflake}
*/
resolveID(presence) {
const presenceResolveable = super.resolveID(presence);
diff --git a/src/stores/ReactionStore.js b/src/stores/ReactionStore.js
index bcbca72ac..c11b4e176 100644
--- a/src/stores/ReactionStore.js
+++ b/src/stores/ReactionStore.js
@@ -38,7 +38,7 @@ class ReactionStore extends DataStore {
* @memberof ReactionStore
* @instance
* @param {MessageReactionResolvable} role The role resolvable to resolve
- * @returns {?string}
+ * @returns {?Snowflake}
*/
}
diff --git a/src/stores/RoleStore.js b/src/stores/RoleStore.js
index 7501cb459..bb8cd749d 100644
--- a/src/stores/RoleStore.js
+++ b/src/stores/RoleStore.js
@@ -38,7 +38,7 @@ class RoleStore extends DataStore {
* @memberof RoleStore
* @instance
* @param {RoleResolvable} role The role resolvable to resolve
- * @returns {?string}
+ * @returns {?Snowflake}
*/
}
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index 6ee4909a3..432a0768a 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -35,7 +35,7 @@ class UserStore extends DataStore {
/**
* Resolves a UserResolvable to a user ID string.
* @param {UserResolvable} user The UserResolvable to identify
- * @returns {?string}
+ * @returns {?Snowflake}
*/
resolveID(user) {
if (user instanceof GuildMember) return user.user.id;
diff --git a/src/structures/ClientApplication.js b/src/structures/ClientApplication.js
index 0d2646cee..494d8c1c3 100644
--- a/src/structures/ClientApplication.js
+++ b/src/structures/ClientApplication.js
@@ -177,7 +177,7 @@ class ClientApplication extends Base {
/**
* Resets the app's secret.
* This is only available when using a user account.
- * @returns {ClientApplication}
+ * @returns {Promise}
*/
resetSecret() {
return this.client.api.oauth2.applications[this.id].reset.post()
@@ -187,7 +187,7 @@ class ClientApplication extends Base {
/**
* Resets the app's bot token.
* This is only available when using a user account.
- * @returns {ClientApplication}
+ * @returns {Promise}
*/
resetToken() {
return this.client.api.oauth2.applications[this.id].bot.reset.post()
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index f3edd46ab..1b9463b51 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -115,7 +115,7 @@ class Guild extends Base {
/**
* An array of guild features
- * @type {Object[]}
+ * @type {string[]}
*/
this.features = data.features;
@@ -401,7 +401,7 @@ class Guild extends Base {
}
}
- /*
+ /**
* The `@everyone` role of the guild
* @type {Role}
* @readonly
@@ -802,7 +802,7 @@ class Guild extends Base {
/**
* Bans a user from the guild.
* @param {UserResolvable} user The user to ban
- * @param {Object|number|string} [options] Ban options. If a number, the number of days to delete messages for, if a
+ * @param {Object} [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
* @param {string} [options.reason] Reason for banning
diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js
index c48da00c0..d43a48b29 100644
--- a/src/structures/GuildAuditLogs.js
+++ b/src/structures/GuildAuditLogs.js
@@ -2,6 +2,24 @@ const Collection = require('../util/Collection');
const Snowflake = require('../util/Snowflake');
const Webhook = require('./Webhook');
+/**
+ * The target type of an entry, e.g. `GUILD`. Here are the available types:
+ * * GUILD
+ * * CHANNEL
+ * * USER
+ * * ROLE
+ * * INVITE
+ * * WEBHOOK
+ * * EMOJI
+ * * MESSAGE
+ * @typedef {string} AuditLogTargetType
+ */
+
+/**
+ * Key mirror of all available audit log targets.
+ * @name GuildAuditLogs.Targets
+ * @type {AuditLogTargetType}
+ */
const Targets = {
ALL: 'ALL',
GUILD: 'GUILD',
@@ -15,6 +33,43 @@ const Targets = {
UNKNOWN: 'UNKNOWN',
};
+/**
+ * The action of an entry. Here are the available actions:
+ * * ALL: null
+ * * GUILD_UPDATE: 1
+ * * CHANNEL_CREATE: 10
+ * * CHANNEL_UPDATE: 11
+ * * CHANNEL_DELETE: 12
+ * * CHANNEL_OVERWRITE_CREATE: 13
+ * * CHANNEL_OVERWRITE_UPDATE: 14
+ * * CHANNEL_OVERWRITE_DELETE: 15
+ * * MEMBER_KICK: 20
+ * * MEMBER_PRUNE: 21
+ * * MEMBER_BAN_ADD: 22
+ * * MEMBER_BAN_REMOVE: 23
+ * * MEMBER_UPDATE: 24
+ * * MEMBER_ROLE_UPDATE: 25
+ * * ROLE_CREATE: 30
+ * * ROLE_UPDATE: 31
+ * * ROLE_DELETE: 32
+ * * INVITE_CREATE: 40
+ * * INVITE_UPDATE: 41
+ * * INVITE_DELETE: 42
+ * * WEBHOOK_CREATE: 50
+ * * WEBHOOK_UPDATE: 51
+ * * WEBHOOK_DELETE: 50
+ * * EMOJI_CREATE: 60
+ * * EMOJI_UPDATE: 61
+ * * EMOJI_DELETE: 62
+ * * MESSAGE_DELETE: 72
+ * @typedef {?number|string} AuditLogAction
+ */
+
+/**
+ * All available actions keyed under their names to their numeric values.
+ * @name GuildAuditLogs.Actions
+ * @type {AuditLogAction}
+ */
const Actions = {
ALL: null,
GUILD_UPDATE: 1,
@@ -85,20 +140,7 @@ class GuildAuditLogs {
}
/**
- * The target type of an entry, e.g. `GUILD`. Here are the available types:
- * * GUILD
- * * CHANNEL
- * * USER
- * * ROLE
- * * INVITE
- * * WEBHOOK
- * * EMOJI
- * * MESSAGE
- * @typedef {string} TargetType
- */
-
- /**
- * The target for an audit log entry. It can be one of:
+ * The target of an entry. It can be one of:
* * A guild
* * A user
* * A role
@@ -106,13 +148,13 @@ class GuildAuditLogs {
* * An invite
* * A webhook
* * An object where the keys represent either the new value or the old value
- * @typedef {?Object|Guild|User|Role|Emoji|Invite|Webhook} EntryTarget
+ * @typedef {?Object|Guild|User|Role|Emoji|Invite|Webhook} AuditLogEntryTarget
*/
/**
* Finds the target type from the entry action.
- * @param {number} target The action target
- * @returns {?string}
+ * @param {AuditLogAction} target The action target
+ * @returns {AuditLogTargetType}
*/
static targetType(target) {
if (target < 10) return Targets.GUILD;
@@ -131,13 +173,14 @@ class GuildAuditLogs {
* * CREATE
* * DELETE
* * UPDATE
- * @typedef {string} ActionType
+ * * ALL
+ * @typedef {string} AuditLogActionType
*/
/**
* Finds the action type from the entry action.
- * @param {string} action The action target
- * @returns {string}
+ * @param {AuditLogAction} action The action target
+ * @returns {AuditLogActionType}
*/
static actionType(action) {
if ([
@@ -187,19 +230,19 @@ class GuildAuditLogsEntry {
const targetType = GuildAuditLogs.targetType(data.action_type);
/**
* The target type of this entry
- * @type {TargetType}
+ * @type {AuditLogTargetType}
*/
this.targetType = targetType;
/**
* The action type of this entry
- * @type {ActionType}
+ * @type {AuditLogActionType}
*/
this.actionType = GuildAuditLogs.actionType(data.action_type);
/**
- * Specific action type of this entry
- * @type {string}
+ * Specific action type of this entry in its string presentation
+ * @type {AuditLogAction}
*/
this.action = Object.keys(Actions).find(k => Actions[k] === data.action_type);
@@ -271,7 +314,7 @@ class GuildAuditLogsEntry {
if (targetType === Targets.UNKNOWN) {
/**
* The target of this entry
- * @type {EntryTarget}
+ * @type {AuditLogEntryTarget}
*/
this.target = this.changes.reduce((o, c) => {
o[c.key] = c.new || c.old;
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index 8b83796cf..010775e29 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -515,7 +515,7 @@ class GuildMember extends Base {
/**
* Bans this guild member.
- * @param {Object|number|string} [options] Ban options. If a number, the number of days to delete messages for, if a
+ * @param {Object} [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
* @param {string} [options.reason] Reason for banning
diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js
index da55d6cee..b7f430b75 100644
--- a/src/structures/MessageEmbed.js
+++ b/src/structures/MessageEmbed.js
@@ -132,12 +132,12 @@ class MessageEmbed {
proxyIconURL: data.footer.proxyIconURL || data.footer.proxy_icon_url,
} : null;
- /**
- * The files of this embed
- * @type {?Object}
- * @property {Array} files Files to attach
- */
if (data.files) {
+ /**
+ * The files of this embed
+ * @type {?Object}
+ * @property {Array} files Files to attach
+ */
this.files = data.files.map(file => {
if (file instanceof MessageAttachment) {
return typeof file.file === 'string' ? file.file : Util.cloneObject(file.file);
@@ -158,7 +158,7 @@ class MessageEmbed {
/**
* The hexadecimal version of the embed color, with a leading hash
- * @type {string}
+ * @type {?string}
* @readonly
*/
get hexColor() {
diff --git a/src/structures/Presence.js b/src/structures/Presence.js
index e788124fc..535c7d11d 100644
--- a/src/structures/Presence.js
+++ b/src/structures/Presence.js
@@ -160,21 +160,22 @@ class RichPresenceAssets {
/**
* ID of the large image asset
- * @type {?string}
+ * @type {?Snowflake}
*/
this.largeImage = assets.large_image || null;
/**
* ID of the small image asset
- * @type {?string}
+ * @type {?Snowflake}
*/
this.smallImage = assets.small_image || null;
}
/**
* Gets the URL of the small image asset
- * @param {string} format Format of the image
- * @param {number} size Size of the image
+ * @param {Object} [options] Options for the image url
+ * @param {string} [options.format] Format of the image
+ * @param {number} [options.size] Size of the image
* @returns {?string} The small image URL
*/
smallImageURL({ format, size } = {}) {
@@ -185,8 +186,9 @@ class RichPresenceAssets {
/**
* Gets the URL of the large image asset
- * @param {string} format Format of the image
- * @param {number} size Size of the image
+ * @param {Object} [options] Options for the image url
+ * @param {string} [options.format] Format of the image
+ * @param {number} [options.size] Size of the image
* @returns {?string} The large image URL
*/
largeImageURL({ format, size } = {}) {
diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js
index a781a25a8..f1a5c6a11 100644
--- a/src/structures/Webhook.js
+++ b/src/structures/Webhook.js
@@ -79,7 +79,6 @@ class Webhook {
* (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|BufferResolvable} [file] A file to send with the message
* @property {FileOptions[]|string[]} [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
From 1a8e8c7a67b59242537a6c97e383bc6aa4e8c8dd Mon Sep 17 00:00:00 2001
From: Sanctuary
Date: Sat, 28 Oct 2017 14:06:26 -0300
Subject: [PATCH 15/75] docs: Add/normalize .toString() docs on all classes
(#2042)
* docs: Add/normalize .toString() examples on all classes
* docs: Remove exclamation point on ClientApplication#toString example
* docs: Normalize .toString() descriptions on all classes
* Use "returns" instead of "concatenates"
---
src/structures/ClientApplication.js | 6 +++++-
src/structures/DMChannel.js | 7 +++++--
src/structures/Emoji.js | 2 +-
src/structures/GroupDMChannel.js | 6 ++----
src/structures/Guild.js | 5 +----
src/structures/GuildChannel.js | 7 ++-----
src/structures/GuildMember.js | 4 ++--
src/structures/ReactionEmoji.js | 7 ++++---
src/structures/Role.js | 5 ++++-
src/structures/User.js | 4 ++--
10 files changed, 28 insertions(+), 25 deletions(-)
diff --git a/src/structures/ClientApplication.js b/src/structures/ClientApplication.js
index 494d8c1c3..ccca3f7e0 100644
--- a/src/structures/ClientApplication.js
+++ b/src/structures/ClientApplication.js
@@ -195,8 +195,12 @@ class ClientApplication extends Base {
}
/**
- * When concatenated with a string, this automatically concatenates the app name rather than the app object.
+ * When concatenated with a string, this automatically returns the application's name instead of the
+ * ClientApplication object.
* @returns {string}
+ * @example
+ * // Logs: Application name: My App
+ * console.log(`Application name: ${application}`);
*/
toString() {
return this.name;
diff --git a/src/structures/DMChannel.js b/src/structures/DMChannel.js
index cdd4d2db8..fe6ba57c6 100644
--- a/src/structures/DMChannel.js
+++ b/src/structures/DMChannel.js
@@ -27,9 +27,12 @@ class DMChannel extends Channel {
}
/**
- * When concatenated with a string, this automatically concatenates the recipient's mention instead of the
- * DM channel object.
+ * When concatenated with a string, this automatically returns the recipient's mention instead of the
+ * DMChannel object.
* @returns {string}
+ * @example
+ * // Logs: Hello from <@123456789012345678>!
+ * console.log(`Hello from ${channel}!`);
*/
toString() {
return this.recipient.toString();
diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js
index 4b1ea2680..d1cca2843 100644
--- a/src/structures/Emoji.js
+++ b/src/structures/Emoji.js
@@ -190,7 +190,7 @@ class Emoji extends Base {
}
/**
- * When concatenated with a string, this automatically returns the emoji mention rather than the object.
+ * When concatenated with a string, this automatically concatenates the emoji's mention instead of the Emoji object.
* @returns {string}
* @example
* // Send an emoji:
diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js
index e142b070d..2ade06b06 100644
--- a/src/structures/GroupDMChannel.js
+++ b/src/structures/GroupDMChannel.js
@@ -203,14 +203,12 @@ class GroupDMChannel extends Channel {
}
/**
- * When concatenated with a string, this automatically concatenates the channel's name instead of the Channel object.
+ * When concatenated with a string, this automatically returns the channel's name instead of the
+ * GroupDMChannel object.
* @returns {string}
* @example
* // Logs: Hello from My Group DM!
* console.log(`Hello from ${channel}!`);
- * @example
- * // Logs: Hello from My Group DM!
- * console.log(`Hello from ' + channel + '!');
*/
toString() {
return this.name;
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index 1b9463b51..73adbe1df 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -1123,14 +1123,11 @@ class Guild extends Base {
}
/**
- * When concatenated with a string, this automatically concatenates the guild's name instead of the guild object.
+ * When concatenated with a string, this automatically returns the guild's name instead of the Guild object.
* @returns {string}
* @example
* // Logs: Hello from My Guild!
* console.log(`Hello from ${guild}!`);
- * @example
- * // Logs: Hello from My Guild!
- * console.log('Hello from ' + guild + '!');
*/
toString() {
return this.name;
diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js
index a1e8d53a0..896e2bdf8 100644
--- a/src/structures/GuildChannel.js
+++ b/src/structures/GuildChannel.js
@@ -493,11 +493,8 @@ class GuildChannel extends Channel {
* When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
* @returns {string}
* @example
- * // Outputs: Hello from #general
- * console.log(`Hello from ${channel}`);
- * @example
- * // Outputs: Hello from #general
- * console.log('Hello from ' + channel);
+ * // Logs: Hello from <#123456789012345678>!
+ * console.log(`Hello from ${channel}!`);
*/
toString() {
return `<#${this.id}>`;
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index 010775e29..7ab2597fd 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -529,10 +529,10 @@ class GuildMember extends Base {
}
/**
- * When concatenated with a string, this automatically concatenates the user's mention instead of the Member object.
+ * When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object.
* @returns {string}
* @example
- * // Logs: Hello from <@123456789>!
+ * // Logs: Hello from <@123456789012345678>!
* console.log(`Hello from ${member}!`);
*/
toString() {
diff --git a/src/structures/ReactionEmoji.js b/src/structures/ReactionEmoji.js
index f550544c6..94ea38930 100644
--- a/src/structures/ReactionEmoji.js
+++ b/src/structures/ReactionEmoji.js
@@ -35,11 +35,12 @@ class ReactionEmoji {
}
/**
- * Creates the text required to form a graphical emoji on Discord.
+ * When concatenated with a string, this automatically returns the text required to form a graphical emoji on Discord
+ * instead of the ReactionEmoji object.
+ * @returns {string}
* @example
* // Send the emoji used in a reaction to the channel the reaction is part of
- * reaction.message.channel.send(`The emoji used is ${reaction.emoji}`);
- * @returns {string}
+ * reaction.message.channel.send(`The emoji used was: ${reaction.emoji}`);
*/
toString() {
return this.id ? `<:${this.name}:${this.id}>` : this.name;
diff --git a/src/structures/Role.js b/src/structures/Role.js
index eb60185db..39c28e11b 100644
--- a/src/structures/Role.js
+++ b/src/structures/Role.js
@@ -332,8 +332,11 @@ class Role extends Base {
}
/**
- * When concatenated with a string, this automatically concatenates the role mention rather than the Role object.
+ * When concatenated with a string, this automatically returns the role's mention instead of the Role object.
* @returns {string}
+ * @example
+ * // Logs: Role: <@&123456789012345678>
+ * console.log(`Role: ${role}`);
*/
toString() {
if (this.id === this.guild.id) return '@everyone';
diff --git a/src/structures/User.js b/src/structures/User.js
index 859cdeeb2..001a999be 100644
--- a/src/structures/User.js
+++ b/src/structures/User.js
@@ -248,10 +248,10 @@ class User extends Base {
}
/**
- * When concatenated with a string, this automatically concatenates the user's mention instead of the User object.
+ * When concatenated with a string, this automatically returns the user's mention instead of the User object.
* @returns {string}
* @example
- * // logs: Hello from <@123456789>!
+ * // Logs: Hello from <@123456789012345678>!
* console.log(`Hello from ${user}!`);
*/
toString() {
From 94a4a068b9400000a3b057a1ae781cb9eb585bc4 Mon Sep 17 00:00:00 2001
From: SpaceEEC
Date: Sun, 29 Oct 2017 14:53:34 +0100
Subject: [PATCH 16/75] fix(TextBasedChannel): return a promise in startTyping
and clarify count parameter (#2047)
* fix(TextBasedChannel): return a promise in startTyping
This fixes #2040
Calling TextBasedChannel#startTyping now returns a promise.
This promise resolves when the bot stops typing (TextBasedChannel#stopTyping) or rejects when an error occurs.
Calling the method again returns the same promise as long the bot is still typing.
* move code into the promise' executor
* Clarify the purpose of the returned Promise
* inverse if and clarify count parameter
---
src/structures/interfaces/TextBasedChannel.js | 48 +++++++++++++------
1 file changed, 34 insertions(+), 14 deletions(-)
diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js
index ce997ae37..77bf65935 100644
--- a/src/structures/interfaces/TextBasedChannel.js
+++ b/src/structures/interfaces/TextBasedChannel.js
@@ -158,26 +158,45 @@ class TextBasedChannel {
/**
* Starts a typing indicator in the channel.
- * @param {number} [count] The number of times startTyping should be considered to have been called
+ * @param {number} [count=1] The number of times startTyping should be considered to have been called
+ * @returns {Promise} Resolves once the bot stops typing gracefully, or rejects when an error occurs
* @example
- * // Start typing in a channel
+ * // Start typing in a channel, or increase the typing count by one
* channel.startTyping();
+ * @example
+ * // Start typing in a channel with a typing count of five, or set it to five
+ * channel.startTyping(5);
*/
startTyping(count) {
if (typeof count !== 'undefined' && count < 1) throw new RangeError('TYPING_COUNT');
- if (!this.client.user._typing.has(this.id)) {
- const endpoint = this.client.api.channels[this.id].typing;
- this.client.user._typing.set(this.id, {
- count: count || 1,
- interval: this.client.setInterval(() => {
- endpoint.post();
- }, 9000),
- });
- endpoint.post();
- } else {
+ if (this.client.user._typing.has(this.id)) {
const entry = this.client.user._typing.get(this.id);
entry.count = count || entry.count + 1;
+ return entry.promise;
}
+
+ const entry = {};
+ entry.promise = new Promise((resolve, reject) => {
+ const endpoint = this.client.api.channels[this.id].typing;
+ Object.assign(entry, {
+ count: count || 1,
+ interval: this.client.setInterval(() => {
+ endpoint.post().catch(error => {
+ this.client.clearInterval(entry.interval);
+ this.client.user._typing.delete(this.id);
+ reject(error);
+ });
+ }, 9000),
+ resolve,
+ });
+ endpoint.post().catch(error => {
+ this.client.clearInterval(entry.interval);
+ this.client.user._typing.delete(this.id);
+ reject(error);
+ });
+ this.client.user._typing.set(this.id, entry);
+ });
+ return entry.promise;
}
/**
@@ -186,10 +205,10 @@ class TextBasedChannel {
* It can take a few seconds for the client user to stop typing.
* @param {boolean} [force=false] Whether or not to reset the call count and force the indicator to stop
* @example
- * // Stop typing in a channel
+ * // Reduce the typing count by one and stop typing if it reached 0
* channel.stopTyping();
* @example
- * // Force typing to fully stop in a channel
+ * // Force typing to fully stop regardless of typing count
* channel.stopTyping(true);
*/
stopTyping(force = false) {
@@ -199,6 +218,7 @@ class TextBasedChannel {
if (entry.count <= 0 || force) {
this.client.clearInterval(entry.interval);
this.client.user._typing.delete(this.id);
+ entry.resolve();
}
}
}
From 29a81eab733f91c2574979e5b2b3e62163abe9de Mon Sep 17 00:00:00 2001
From: Gus Caplan
Date: Sun, 29 Oct 2017 08:54:00 -0500
Subject: [PATCH 17/75] standardize message object creation (#1986)
* standardize message object creation so i don't flip out again
* fix stuff
* Update Message.js
* Update index.js
* Update SendMessage.js
* Update Message.js
---
src/structures/Message.js | 35 ++----
src/structures/Webhook.js | 113 ++----------------
src/structures/interfaces/TextBasedChannel.js | 56 +--------
src/structures/shared/CreateMessage.js | 111 +++++++++++++++++
src/structures/shared/SendMessage.js | 63 +---------
src/structures/shared/index.js | 1 +
6 files changed, 135 insertions(+), 244 deletions(-)
create mode 100644 src/structures/shared/CreateMessage.js
diff --git a/src/structures/Message.js b/src/structures/Message.js
index ee4c9b662..a409e411a 100644
--- a/src/structures/Message.js
+++ b/src/structures/Message.js
@@ -8,9 +8,9 @@ const Collection = require('../util/Collection');
const ReactionStore = require('../stores/ReactionStore');
const { MessageTypes } = require('../util/Constants');
const Permissions = require('../util/Permissions');
-const GuildMember = require('./GuildMember');
const Base = require('./Base');
const { Error, TypeError } = require('../errors');
+const { createMessage } = require('./shared');
/**
* Represents a message on Discord.
@@ -368,41 +368,22 @@ class Message extends Base {
* .then(msg => console.log(`Updated the content of a message from ${msg.author}`))
* .catch(console.error);
*/
- edit(content, options) {
+ async edit(content, options) {
if (!options && typeof content === 'object' && !(content instanceof Array)) {
options = content;
- content = '';
+ content = null;
} else if (!options) {
options = {};
}
- if (options instanceof Embed) options = { embed: options };
+ if (!options.content) options.content = content;
- if (typeof options.content !== 'undefined') content = options.content;
-
- if (typeof content !== 'undefined') content = Util.resolveString(content);
-
- let { embed, code, reply } = options;
-
- if (embed) embed = new Embed(embed)._apiTransform();
-
- // Wrap everything in a code block
- if (typeof code !== 'undefined' && (typeof code !== 'boolean' || code === true)) {
- content = Util.escapeMarkdown(Util.resolveString(content), true);
- content = `\`\`\`${typeof code !== 'boolean' ? code || '' : ''}\n${content}\n\`\`\``;
- }
-
- // Add the reply prefix
- if (reply && this.channel.type !== 'dm') {
- const id = this.client.users.resolveID(reply);
- const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
- content = `${mention}${content ? `, ${content}` : ''}`;
- }
+ const { data, files } = await createMessage(this, options);
return this.client.api.channels[this.channel.id].messages[this.id]
- .patch({ data: { content, embed } })
- .then(data => {
+ .patch({ data, files })
+ .then(d => {
const clone = this._clone();
- clone._patch(data);
+ clone._patch(d);
return clone;
});
}
diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js
index f1a5c6a11..3601b9ca4 100644
--- a/src/structures/Webhook.js
+++ b/src/structures/Webhook.js
@@ -1,9 +1,5 @@
-const Util = require('../util/Util');
const DataResolver = require('../util/DataResolver');
-const Embed = require('./MessageEmbed');
-const MessageAttachment = require('./MessageAttachment');
-const MessageEmbed = require('./MessageEmbed');
-const { browser } = require('../util/Constants');
+const { createMessage } = require('./shared');
/**
* Represents a webhook.
@@ -98,115 +94,24 @@ class Webhook {
* .catch(console.error);
*/
/* eslint-enable max-len */
- send(content, options) { // eslint-disable-line complexity
+ async send(content, options) { // eslint-disable-line complexity
if (!options && typeof content === 'object' && !(content instanceof Array)) {
options = content;
- content = '';
+ content = null;
} else if (!options) {
options = {};
}
+ if (!options.content) options.content = content;
- if (options instanceof MessageAttachment) options = { files: [options.file] };
- if (options instanceof MessageEmbed) options = { embeds: [options] };
- if (options.embed) options = { embeds: [options.embed] };
-
- if (content instanceof Array || options instanceof Array) {
- const which = content instanceof Array ? content : options;
- const attachments = which.filter(item => item instanceof MessageAttachment);
- const embeds = which.filter(item => item instanceof MessageEmbed);
- if (attachments.length) options = { files: attachments };
- if (embeds.length) options = { embeds };
- if ((embeds.length || attachments.length) && content instanceof Array) content = '';
- }
-
- if (!options.username) options.username = this.name;
- if (options.avatarURL) {
- options.avatar_url = options.avatarURL;
- options.avatarURL = null;
- }
-
- if (content) {
- content = Util.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);
- }
- options.content = content;
-
- if (options.embeds) options.embeds = options.embeds.map(embed => new Embed(embed)._apiTransform());
-
- if (options.files) {
- for (let i = 0; i < options.files.length; i++) {
- let file = options.files[i];
- if (typeof file === 'string' || (!browser && Buffer.isBuffer(file))) file = { attachment: file };
- if (!file.name) {
- if (typeof file.attachment === 'string') {
- file.name = Util.basename(file.attachment);
- } else if (file.attachment && file.attachment.path) {
- file.name = Util.basename(file.attachment.path);
- } else if (file instanceof MessageAttachment) {
- file = { attachment: file.file, name: Util.basename(file.file) || 'file.jpg' };
- } else {
- file.name = 'file.jpg';
- }
- } else if (file instanceof MessageAttachment) {
- file = file.file;
- }
- options.files[i] = file;
- }
-
- return Promise.all(options.files.map(file =>
- DataResolver.resolveFile(file.attachment).then(resource => {
- file.file = resource;
- return file;
- })
- )).then(files => this.client.api.webhooks(this.id, this.token).post({
- data: options,
- query: { wait: true },
- files,
- auth: false,
- }));
- }
-
- if (content instanceof Array) {
- return new Promise((resolve, reject) => {
- const messages = [];
- (function sendChunk() {
- const opt = content.length ? null : { embeds: options.embeds, files: options.files };
- this.client.api.webhooks(this.id, this.token).post({
- data: { content: content.shift(), opt },
- query: { wait: true },
- auth: false,
- })
- .then(message => {
- messages.push(message);
- if (content.length === 0) return resolve(messages);
- return sendChunk.call(this);
- })
- .catch(reject);
- }.call(this));
- });
- }
+ const { data, files } = await createMessage(this, options);
return this.client.api.webhooks(this.id, this.token).post({
- data: options,
+ data, files,
query: { wait: true },
auth: false,
- }).then(data => {
- if (!this.client.channels) return data;
- return this.client.channels.get(data.channel_id).messages.create(data, false);
+ }).then(d => {
+ if (!this.client.channels) return d;
+ return this.client.channels.get(d.channel_id).messages.create(d, false);
});
}
diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js
index 77bf65935..39aac0937 100644
--- a/src/structures/interfaces/TextBasedChannel.js
+++ b/src/structures/interfaces/TextBasedChannel.js
@@ -1,12 +1,7 @@
const MessageCollector = require('../MessageCollector');
const Shared = require('../shared');
-const Util = require('../../util/Util');
-const { browser } = require('../../util/Constants');
const Snowflake = require('../../util/Snowflake');
const Collection = require('../../util/Collection');
-const DataResolver = require('../../util/DataResolver');
-const MessageAttachment = require('../../structures/MessageAttachment');
-const MessageEmbed = require('../../structures/MessageEmbed');
const { RangeError, TypeError } = require('../../errors');
/**
@@ -80,61 +75,12 @@ class TextBasedChannel {
send(content, options) { // eslint-disable-line complexity
if (!options && typeof content === 'object' && !(content instanceof Array)) {
options = content;
- content = '';
+ content = null;
} else if (!options) {
options = {};
}
-
- if (options instanceof MessageEmbed) options = { embed: options };
- if (options instanceof MessageAttachment) options = { files: [options.file] };
-
- if (content instanceof Array || options instanceof Array) {
- const which = content instanceof Array ? content : options;
- const attachments = which.filter(item => item instanceof MessageAttachment);
- if (attachments.length) {
- options = { files: attachments };
- if (content instanceof Array) content = '';
- }
- }
-
if (!options.content) options.content = content;
- if (options.embed && options.embed.files) {
- if (options.files) options.files = options.files.concat(options.embed.files);
- else options.files = options.embed.files;
- }
-
- if (options.files) {
- for (let i = 0; i < options.files.length; i++) {
- let file = options.files[i];
- if (typeof file === 'string' || (!browser && Buffer.isBuffer(file))) file = { attachment: file };
- if (!file.name) {
- if (typeof file.attachment === 'string') {
- file.name = Util.basename(file.attachment);
- } else if (file.attachment && file.attachment.path) {
- file.name = Util.basename(file.attachment.path);
- } else if (file instanceof MessageAttachment) {
- file = { attachment: file.file, name: Util.basename(file.file) || 'file.jpg' };
- } else {
- file.name = 'file.jpg';
- }
- } else if (file instanceof MessageAttachment) {
- file = file.file;
- }
- options.files[i] = file;
- }
-
- return Promise.all(options.files.map(file =>
- DataResolver.resolveFile(file.attachment).then(resource => {
- file.file = resource;
- return file;
- })
- )).then(files => {
- options.files = files;
- return Shared.sendMessage(this, options);
- });
- }
-
return Shared.sendMessage(this, options);
}
diff --git a/src/structures/shared/CreateMessage.js b/src/structures/shared/CreateMessage.js
new file mode 100644
index 000000000..65c7c2ca3
--- /dev/null
+++ b/src/structures/shared/CreateMessage.js
@@ -0,0 +1,111 @@
+const Embed = require('../MessageEmbed');
+const DataResolver = require('../../util/DataResolver');
+const MessageEmbed = require('../MessageEmbed');
+const MessageAttachment = require('../MessageAttachment');
+const { browser } = require('../../util/Constants');
+const Util = require('../../util/Util');
+
+// eslint-disable-next-line complexity
+module.exports = async function createMessage(channel, options) {
+ const User = require('../User');
+ const GuildMember = require('../GuildMember');
+ const Webhook = require('../Webhook');
+
+ const webhook = channel instanceof Webhook;
+
+ if (typeof options.nonce !== 'undefined') {
+ options.nonce = parseInt(options.nonce);
+ if (isNaN(options.nonce) || options.nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE');
+ }
+
+ if (options instanceof MessageEmbed) options = webhook ? { embeds: [options] } : { embed: options };
+ if (options instanceof MessageAttachment) options = { files: [options.file] };
+
+ if (options.reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') {
+ const id = channel.client.users.resolveID(options.reply);
+ const mention = `<@${options.reply instanceof GuildMember && options.reply.nickname ? '!' : ''}${id}>`;
+ if (options.split) options.split.prepend = `${mention}, ${options.split.prepend || ''}`;
+ options.content = `${mention}${typeof options.content !== 'undefined' ? `, ${options.content}` : ''}`;
+ }
+
+ if (options.content) {
+ options.content = Util.resolveString(options.content);
+ if (options.split && typeof options.split !== 'object') options.split = {};
+ // Wrap everything in a code block
+ if (typeof options.code !== 'undefined' && (typeof options.code !== 'boolean' || options.code === true)) {
+ options.content = Util.escapeMarkdown(options.content, true);
+ options.content =
+ `\`\`\`${typeof options.code !== 'boolean' ? options.code || '' : ''}\n${options.content}\n\`\`\``;
+ if (options.split) {
+ options.split.prepend = `\`\`\`${typeof options.code !== 'boolean' ? options.code || '' : ''}\n`;
+ options.split.append = '\n```';
+ }
+ }
+
+ // Add zero-width spaces to @everyone/@here
+ if (options.disableEveryone ||
+ (typeof options.disableEveryone === 'undefined' && channel.client.options.disableEveryone)) {
+ options.content = options.content.replace(/@(everyone|here)/g, '@\u200b$1');
+ }
+
+ if (options.split) options.content = Util.splitMessage(options.content, options.split);
+ }
+
+ if (options.embed && options.embed.files) {
+ if (options.files) options.files = options.files.concat(options.embed.files);
+ else options.files = options.embed.files;
+ }
+
+ if (options.embed && webhook) options.embeds = [new Embed(options.embed)._apiTransform()];
+ else if (options.embed) options.embed = new Embed(options.embed)._apiTransform();
+ else if (options.embeds) options.embeds = options.embeds.map(e => new Embed(e)._apiTransform());
+
+ let files;
+
+ if (options.files) {
+ for (let i = 0; i < options.files.length; i++) {
+ let file = options.files[i];
+ if (typeof file === 'string' || (!browser && Buffer.isBuffer(file))) file = { attachment: file };
+ if (!file.name) {
+ if (typeof file.attachment === 'string') {
+ file.name = Util.basename(file.attachment);
+ } else if (file.attachment && file.attachment.path) {
+ file.name = Util.basename(file.attachment.path);
+ } else if (file instanceof MessageAttachment) {
+ file = { attachment: file.file, name: Util.basename(file.file) || 'file.jpg' };
+ } else {
+ file.name = 'file.jpg';
+ }
+ } else if (file instanceof MessageAttachment) {
+ file = file.file;
+ }
+ options.files[i] = file;
+ }
+
+ files = await Promise.all(options.files.map(file =>
+ DataResolver.resolveFile(file.attachment).then(resource => {
+ file.file = resource;
+ return file;
+ })
+ ));
+ delete options.files;
+ }
+
+ if (webhook) {
+ if (!options.username) options.username = this.name;
+ if (options.avatarURL) {
+ options.avatar_url = options.avatarURL;
+ options.avatarURL = null;
+ }
+ }
+
+ return { data: {
+ content: options.content,
+ tts: options.tts,
+ nonce: options.nonce,
+ embed: options.embed,
+ embeds: options.embeds,
+ username: options.username,
+ avatar_url: options.avatarURL,
+ }, files };
+};
diff --git a/src/structures/shared/SendMessage.js b/src/structures/shared/SendMessage.js
index 560ece728..5007a0af5 100644
--- a/src/structures/shared/SendMessage.js
+++ b/src/structures/shared/SendMessage.js
@@ -1,65 +1,12 @@
-const Util = require('../../util/Util');
-const Embed = require('../MessageEmbed');
-const { RangeError } = require('../../errors');
+const createMessage = require('./CreateMessage');
-module.exports = function sendMessage(channel, options) { // eslint-disable-line complexity
+module.exports = async function sendMessage(channel, options) { // eslint-disable-line complexity
const User = require('../User');
const GuildMember = require('../GuildMember');
if (channel instanceof User || channel instanceof GuildMember) return channel.createDM().then(dm => dm.send(options));
- let { content, nonce, reply, code, disableEveryone, tts, embed, files, split } = options;
- if (embed) embed = new Embed(embed)._apiTransform();
+ const { data, files } = await createMessage(channel, options);
- if (typeof nonce !== 'undefined') {
- nonce = parseInt(nonce);
- if (isNaN(nonce) || nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE');
- }
-
- // Add the reply prefix
- if (reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') {
- const id = channel.client.users.resolveID(reply);
- const mention = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>`;
- if (split) split.prepend = `${mention}, ${split.prepend || ''}`;
- content = `${mention}${typeof content !== 'undefined' ? `, ${content}` : ''}`;
- }
-
- if (content) {
- content = Util.resolveString(content);
- if (split && typeof split !== 'object') split = {};
- // Wrap everything in a code block
- 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```';
- }
- }
-
- // Add zero-width spaces to @everyone/@here
- if (disableEveryone || (typeof disableEveryone === 'undefined' && channel.client.options.disableEveryone)) {
- content = content.replace(/@(everyone|here)/g, '@\u200b$1');
- }
-
- if (split) content = Util.splitMessage(content, split);
- }
-
- if (content instanceof Array) {
- return new Promise((resolve, reject) => {
- const messages = [];
- (function sendChunk() {
- const opt = content.length ? { tts } : { tts, embed, files };
- channel.send(content.shift(), opt).then(message => {
- messages.push(message);
- if (content.length === 0) return resolve(messages);
- return sendChunk();
- }).catch(reject);
- }());
- });
- }
-
- return channel.client.api.channels[channel.id].messages.post({
- data: { content, tts, nonce, embed },
- files,
- }).then(data => channel.client.actions.MessageCreate.handle(data).message);
+ return channel.client.api.channels[channel.id].messages.post({ data, files })
+ .then(d => channel.client.actions.MessageCreate.handle(d).message);
};
diff --git a/src/structures/shared/index.js b/src/structures/shared/index.js
index 67eed7f83..67a09646b 100644
--- a/src/structures/shared/index.js
+++ b/src/structures/shared/index.js
@@ -1,4 +1,5 @@
module.exports = {
search: require('./Search'),
sendMessage: require('./SendMessage'),
+ createMessage: require('./CreateMessage'),
};
From 21d09f338e8dc2ad3cbc74d1e3fd56994b92de01 Mon Sep 17 00:00:00 2001
From: SpaceEEC
Date: Sun, 5 Nov 2017 18:52:33 +0100
Subject: [PATCH 18/75] fix(Guild): correctly resolve user in Guild#addMember
(#2090)
---
src/structures/Guild.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index 73adbe1df..e00151f47 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -528,7 +528,9 @@ class Guild extends Base {
* @returns {Promise}
*/
addMember(user, options) {
- if (this.members.has(user.id)) return Promise.resolve(this.members.get(user.id));
+ user = this.client.users.resolveID(user);
+ if (!user) return Promise.reject(new TypeError('INVALID_TYPE', 'user', 'UserResolvable'));
+ if (this.members.has(user)) return Promise.resolve(this.members.get(user));
options.access_token = options.accessToken;
if (options.roles) {
const roles = [];
@@ -541,7 +543,7 @@ class Guild extends Base {
roles.push(role.id);
}
}
- return this.client.api.guilds(this.id).members(user.id).put({ data: options })
+ return this.client.api.guilds(this.id).members(user).put({ data: options })
.then(data => this.members.create(data));
}
From 05a41b5ca44cfa39db2413284a93bd34c402806d Mon Sep 17 00:00:00 2001
From: SpaceEEC
Date: Mon, 6 Nov 2017 02:42:24 +0100
Subject: [PATCH 19/75] fix(Split/Webhook): readd message chunk sending and fix
webhook avatar/username (#2085)
---
src/structures/Webhook.js | 13 +++++++++++++
src/structures/shared/CreateMessage.js | 11 ++++-------
src/structures/shared/SendMessage.js | 11 +++++++++++
3 files changed, 28 insertions(+), 7 deletions(-)
diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js
index 3601b9ca4..7ee44745e 100644
--- a/src/structures/Webhook.js
+++ b/src/structures/Webhook.js
@@ -105,6 +105,19 @@ class Webhook {
const { data, files } = await createMessage(this, options);
+ if (data.content instanceof Array) {
+ const messages = [];
+ for (let i = 0; i < data.content.length; i++) {
+ const opt = i === data.content.length - 1 ? { embeds: data.embeds, files } : {};
+ Object.assign(opt, { avatarURL: data.avatar_url, content: data.content[i], username: data.username });
+ // eslint-disable-next-line no-await-in-loop
+ const message = await this.send(data.content[i], opt);
+ messages.push(message);
+ }
+ return messages;
+ }
+
+
return this.client.api.webhooks(this.id, this.token).post({
data, files,
query: { wait: true },
diff --git a/src/structures/shared/CreateMessage.js b/src/structures/shared/CreateMessage.js
index 65c7c2ca3..5abf0799e 100644
--- a/src/structures/shared/CreateMessage.js
+++ b/src/structures/shared/CreateMessage.js
@@ -10,8 +10,9 @@ module.exports = async function createMessage(channel, options) {
const User = require('../User');
const GuildMember = require('../GuildMember');
const Webhook = require('../Webhook');
+ const WebhookClient = require('../../client/WebhookClient');
- const webhook = channel instanceof Webhook;
+ const webhook = channel instanceof Webhook || channel instanceof WebhookClient;
if (typeof options.nonce !== 'undefined') {
options.nonce = parseInt(options.nonce);
@@ -88,15 +89,11 @@ module.exports = async function createMessage(channel, options) {
return file;
})
));
- delete options.files;
}
if (webhook) {
if (!options.username) options.username = this.name;
- if (options.avatarURL) {
- options.avatar_url = options.avatarURL;
- options.avatarURL = null;
- }
+ if (options.avatarURL) options.avatar_url = options.avatarURL;
}
return { data: {
@@ -106,6 +103,6 @@ module.exports = async function createMessage(channel, options) {
embed: options.embed,
embeds: options.embeds,
username: options.username,
- avatar_url: options.avatarURL,
+ avatar_url: options.avatar_url,
}, files };
};
diff --git a/src/structures/shared/SendMessage.js b/src/structures/shared/SendMessage.js
index 5007a0af5..95ea49ca3 100644
--- a/src/structures/shared/SendMessage.js
+++ b/src/structures/shared/SendMessage.js
@@ -7,6 +7,17 @@ module.exports = async function sendMessage(channel, options) { // eslint-disabl
const { data, files } = await createMessage(channel, options);
+ if (data.content instanceof Array) {
+ const messages = [];
+ for (let i = 0; i < data.content.length; i++) {
+ const opt = i === data.content.length - 1 ? { tts: data.tts, embed: data.embed, files } : { tts: data.tts };
+ // eslint-disable-next-line no-await-in-loop
+ const message = await channel.send(data.content[i], opt);
+ messages.push(message);
+ }
+ return messages;
+ }
+
return channel.client.api.channels[channel.id].messages.post({ data, files })
.then(d => channel.client.actions.MessageCreate.handle(d).message);
};
From 62544905a06152ca769c4e6d5f16337b134ac67e Mon Sep 17 00:00:00 2001
From: Yukine
Date: Fri, 10 Nov 2017 01:30:13 +0100
Subject: [PATCH 20/75] enhanced setUserLimit to reset when passing null to
stay consistent with other methods (#2083)
* added a new check to setUserLimit so it won't silently fail anymore if you put a wrong type in
* adapt spaces idea of converting null to 0
* this way it looks cleaner
* and i need to remove this
* need to do it that way because like Gus said null will not change anyhting
* space prooved me wrong and idk why ist working now
---
src/structures/GuildChannel.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js
index 896e2bdf8..a84ad0847 100644
--- a/src/structures/GuildChannel.js
+++ b/src/structures/GuildChannel.js
@@ -293,7 +293,7 @@ class GuildChannel extends Channel {
topic: data.topic,
nsfw: data.nsfw,
bitrate: data.bitrate || (this.bitrate ? this.bitrate * 1000 : undefined),
- user_limit: data.userLimit != null ? data.userLimit : this.userLimit, // eslint-disable-line eqeqeq
+ user_limit: typeof data.userLimit !== 'undefined' ? data.userLimit : this.userLimit,
parent_id: data.parentID,
lock_permissions: data.lockPermissions,
permission_overwrites: data.permissionOverwrites,
From 5cd42695aeb974e998d4b220cba1c27b30a55628 Mon Sep 17 00:00:00 2001
From: Isabella
Date: Tue, 14 Nov 2017 02:11:44 -0600
Subject: [PATCH 21/75] refactor(MessageReaction): ReactionUserStore (#2078)
* refactor(MessageReactions): fetchUsers() is now users.fetch()
made a lovely class for it and all
happify linter
stuff
i know how to code i swear
i lied
* bdistin suggestions
* space suggestions, rename store
* fix count
* documentation update
---
src/index.js | 1 +
src/stores/ReactionUserStore.js | 33 +++++++++++++++++++++++++++++++
src/structures/MessageReaction.js | 26 +++---------------------
3 files changed, 37 insertions(+), 23 deletions(-)
create mode 100644 src/stores/ReactionUserStore.js
diff --git a/src/index.js b/src/index.js
index 7bea36f1b..de6a6e66c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -29,6 +29,7 @@ module.exports = {
GuildChannelStore: require('./stores/GuildChannelStore'),
GuildMemberStore: require('./stores/GuildMemberStore'),
GuildStore: require('./stores/GuildStore'),
+ ReactionUserStore: require('./stores/ReactionUserStore'),
MessageStore: require('./stores/MessageStore'),
PresenceStore: require('./stores/PresenceStore'),
RoleStore: require('./stores/RoleStore'),
diff --git a/src/stores/ReactionUserStore.js b/src/stores/ReactionUserStore.js
new file mode 100644
index 000000000..b3c3ec012
--- /dev/null
+++ b/src/stores/ReactionUserStore.js
@@ -0,0 +1,33 @@
+const DataStore = require('./DataStore');
+/**
+ * A data store to store User models who reacted to a MessageReaction.
+ * @extends {DataStore}
+ */
+class ReactionUserStore extends DataStore {
+ constructor(client, iterable, reaction) {
+ super(client, iterable, require('../structures/User'));
+ this.reaction = reaction;
+ }
+
+ /**
+ * Fetches all the users that gave this reaction. Resolves with a collection of users, mapped by their IDs.
+ * @param {Object} [options] Options for fetching the users
+ * @param {number} [options.limit=100] The maximum amount of users to fetch, defaults to 100
+ * @param {Snowflake} [options.before] Limit fetching users to those with an id lower than the supplied id
+ * @param {Snowflake} [options.after] Limit fetching users to those with an id greater than the supplied id
+ * @returns {Promise>}
+ */
+ async fetch({ limit = 100, after, before } = {}) {
+ const message = this.reaction.message;
+ const users = await this.client.api.channels[message.channel.id].messages[message.id]
+ .reactions[this.reaction.emoji.identifier]
+ .get({ query: { limit, before, after } });
+ for (const rawUser of users) {
+ const user = this.client.users.create(rawUser);
+ this.set(user.id, user);
+ }
+ return this;
+ }
+}
+
+module.exports = ReactionUserStore;
diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js
index 2b273f84b..28a320a63 100644
--- a/src/structures/MessageReaction.js
+++ b/src/structures/MessageReaction.js
@@ -1,6 +1,6 @@
-const Collection = require('../util/Collection');
const Emoji = require('./Emoji');
const ReactionEmoji = require('./ReactionEmoji');
+const ReactionUserStore = require('../stores/ReactionUserStore');
const { Error } = require('../errors');
/**
@@ -28,9 +28,9 @@ class MessageReaction {
/**
* The users that have given this reaction, mapped by their ID
- * @type {Collection}
+ * @type {ReactionUserStore}
*/
- this.users = new Collection();
+ this.users = new ReactionUserStore(client, undefined, this);
this._emoji = new ReactionEmoji(this, data.emoji.name, data.emoji.id);
}
@@ -77,26 +77,6 @@ class MessageReaction {
);
}
- /**
- * Fetches all the users that gave this reaction. Resolves with a collection of users, mapped by their IDs.
- * @param {Object} [options] Options for fetching the users
- * @param {number} [options.limit=100] The maximum amount of users to fetch, defaults to 100
- * @param {Snowflake} [options.after] Limit fetching users to those with an id greater than the supplied id
- * @returns {Promise>}
- */
- async fetchUsers({ limit = 100, after } = {}) {
- const message = this.message;
- const users = await message.client.api.channels[message.channel.id].messages[message.id]
- .reactions[this.emoji.identifier]
- .get({ query: { limit, after } });
- for (const rawUser of users) {
- const user = message.client.users.create(rawUser);
- this.users.set(user.id, user);
- }
- this.count = this.users.size;
- return this.users;
- }
-
_add(user) {
if (!this.users.has(user.id)) {
this.users.set(user.id, user);
From 2d8e26c24c360148083cacb3468a58c99320198a Mon Sep 17 00:00:00 2001
From: Isabella
Date: Thu, 16 Nov 2017 08:24:27 -0600
Subject: [PATCH 22/75] docs: add Guild#features type (#2105)
* docs: add Guild#features type
* fixed spacing
* make it a list, and add MORE_EMOJI
---
src/structures/Guild.js | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index e00151f47..b84117e91 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -114,8 +114,18 @@ class Guild extends Base {
this.large = Boolean('large' in data ? data.large : this.large);
/**
- * An array of guild features
- * @type {string[]}
+ * An array of enabled guild features, here are the possible values:
+ * * INVITE_SPLASH
+ * * MORE_EMOJI
+ * * VERIFIED
+ * * VIP_REGIONS
+ * * VANITY_URL
+ * @typedef {string} Features
+ */
+
+ /**
+ * An array of guild features partnered guilds have enabled
+ * @type {Features[]}
*/
this.features = data.features;
From 09315ae9dbcef978721a10dde4d00971fba0561c Mon Sep 17 00:00:00 2001
From: Will Nelson
Date: Thu, 16 Nov 2017 06:24:53 -0800
Subject: [PATCH 23/75] emit ReactionCollector#remove on all unreactions
(#2096)
* emit ReactionCollector#remove on all unreactions
this will emit an event when a user removes a collected reaction. this
is in addition to Collector#dispose, which will only fire when all users
have unreacted to the same emoji.
* emit only collected removals
---
src/structures/ReactionCollector.js | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/structures/ReactionCollector.js b/src/structures/ReactionCollector.js
index 1ebab746c..60bdabc48 100644
--- a/src/structures/ReactionCollector.js
+++ b/src/structures/ReactionCollector.js
@@ -83,7 +83,17 @@ class ReactionCollector extends Collector {
* @returns {?Snowflake|string}
*/
dispose(reaction) {
- return reaction.message.id === this.message.id && !reaction.count ? ReactionCollector.key(reaction) : null;
+ if (reaction.message.id !== this.message.id) return null;
+
+ /**
+ * Emitted whenever a reaction is removed from a message. Will emit on all reaction removals,
+ * as opposed to {@link Collector#dispose} which will only be emitted when the entire reaction
+ * is removed.
+ * @event ReactionCollector#remove
+ * @param {MessageReaction} reaction The reaction that was removed
+ */
+ if (this.collected.has(reaction)) this.emit('remove', reaction);
+ return reaction.count ? null : ReactionCollector.key(reaction);
}
/**
From 196cf7652e1a9c9009578a0c532bf6f0ddd7451e Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Thu, 16 Nov 2017 22:49:38 -0500
Subject: [PATCH 24/75] Add Shard#ready property and related events
---
src/sharding/Shard.js | 70 ++++++++++++++++++++++++++++-----
src/sharding/ShardClientUtil.js | 9 +++--
src/sharding/ShardingManager.js | 4 +-
3 files changed, 69 insertions(+), 14 deletions(-)
diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js
index e67bece78..76da23527 100644
--- a/src/sharding/Shard.js
+++ b/src/sharding/Shard.js
@@ -8,9 +8,9 @@ const { Error } = require('../errors');
*/
class Shard {
/**
- * @param {ShardingManager} manager The sharding manager
- * @param {number} id The ID of this shard
- * @param {Array} [args=[]] Command line arguments to pass to the script
+ * @param {ShardingManager} manager Manager that is spawning this shard
+ * @param {number} id ID of this shard
+ * @param {string[]} [args=[]] Command line arguments to pass to the script
*/
constructor(manager, id, args = []) {
/**
@@ -26,7 +26,7 @@ class Shard {
this.id = id;
/**
- * The environment variables for the shard
+ * Environment variables for the shard's process
* @type {Object}
*/
this.env = Object.assign({}, process.env, {
@@ -41,14 +41,33 @@ class Shard {
*/
this.process = childProcess.fork(path.resolve(this.manager.file), args, {
env: this.env,
- });
- this.process.on('message', this._handleMessage.bind(this));
- this.process.once('exit', () => {
- if (this.manager.respawn) this.manager.createShard(this.id);
- });
+ }).on('message', this._handleMessage.bind(this));
+ /**
+ * Whether the shard's {@link Client} is ready
+ * @type {boolean}
+ */
+ this.ready = false;
+
+ /**
+ * Ongoing promises for calls to {@link Shard#eval}, mapped by the `script` they were called with
+ * @type {Map}
+ * @private
+ */
this._evals = new Map();
+
+ /**
+ * Ongoing promises for calls to {@link Shard#fetchClientValue}, mapped by the `prop` they were called with
+ * @type {Map}
+ * @private
+ */
this._fetches = new Map();
+
+ // Handle the death of the process
+ this.process.once('exit', () => {
+ this.ready = false;
+ if (this.manager.respawn) this.manager.createShard(this.id).catch(err => { this.manager.emit('error', err); });
+ });
}
/**
@@ -134,6 +153,39 @@ class Shard {
*/
_handleMessage(message) {
if (message) {
+ // Shard is ready
+ if (message._ready) {
+ this.ready = true;
+ /**
+ * Emitted upon the shard's {@link Client#ready} event.
+ * @event Shard#ready
+ */
+ this.emit('ready');
+ return;
+ }
+
+ // Shard has disconnected
+ if (message._disconnect) {
+ this.ready = false;
+ /**
+ * Emitted upon the shard's {@link Client#disconnect} event.
+ * @event Shard#disconnect
+ */
+ this.emit('disconnect');
+ return;
+ }
+
+ // Shard is attempting to reconnect
+ if (message._reconnecting) {
+ this.ready = false;
+ /**
+ * Emitted upon the shard's {@link Client#reconnecting} event.
+ * @event Shard#reconnecting
+ */
+ this.emit('reconnecting');
+ return;
+ }
+
// Shard is requesting a property fetch
if (message._sFetchProp) {
this.manager.fetchClientValues(message._sFetchProp).then(
diff --git a/src/sharding/ShardClientUtil.js b/src/sharding/ShardClientUtil.js
index aceb85f2d..70e625824 100644
--- a/src/sharding/ShardClientUtil.js
+++ b/src/sharding/ShardClientUtil.js
@@ -7,11 +7,14 @@ const { Error } = require('../errors');
*/
class ShardClientUtil {
/**
- * @param {Client} client The client of the current shard
+ * @param {Client} client Client of the current shard
*/
constructor(client) {
this.client = client;
process.on('message', this._handleMessage.bind(this));
+ client.on('ready', () => { process.send({ _ready: true }); });
+ client.on('disconnect', () => { process.send({ _disconnect: true }); });
+ client.on('reconnecting', () => { process.send({ _reconnecting: true }); });
}
/**
@@ -49,7 +52,7 @@ class ShardClientUtil {
/**
* Fetches a client property value of each shard.
* @param {string} prop Name of the client property to get, using periods for nesting
- * @returns {Promise}
+ * @returns {Promise>}
* @example
* client.shard.fetchClientValues('guilds.size')
* .then(results => {
@@ -76,7 +79,7 @@ class ShardClientUtil {
/**
* Evaluates a script on all shards, in the context of the Clients.
* @param {string} script JavaScript to run on each shard
- * @returns {Promise} Results of the script execution
+ * @returns {Promise>} Results of the script execution
*/
broadcastEval(script) {
return new Promise((resolve, reject) => {
diff --git a/src/sharding/ShardingManager.js b/src/sharding/ShardingManager.js
index 7a18898be..117fca4f9 100644
--- a/src/sharding/ShardingManager.js
+++ b/src/sharding/ShardingManager.js
@@ -168,7 +168,7 @@ class ShardingManager extends EventEmitter {
/**
* Evaluates a script on all shards, in the context of the Clients.
* @param {string} script JavaScript to run on each shard
- * @returns {Promise} Results of the script execution
+ * @returns {Promise>} Results of the script execution
*/
broadcastEval(script) {
const promises = [];
@@ -179,7 +179,7 @@ class ShardingManager extends EventEmitter {
/**
* Fetches a client property value of each shard.
* @param {string} prop Name of the client property to get, using periods for nesting
- * @returns {Promise}
+ * @returns {Promise>}
* @example
* manager.fetchClientValues('guilds.size')
* .then(results => {
From 6fa4fc532cf9525838b55287a5565be4a4ec07d2 Mon Sep 17 00:00:00 2001
From: SpaceEEC
Date: Fri, 17 Nov 2017 08:49:57 +0100
Subject: [PATCH 25/75] fix(Shard): extend EventEmitter to be able to emit
events (#2112)
---
src/sharding/Shard.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/sharding/Shard.js b/src/sharding/Shard.js
index 76da23527..7e2a51ac7 100644
--- a/src/sharding/Shard.js
+++ b/src/sharding/Shard.js
@@ -1,4 +1,5 @@
const childProcess = require('child_process');
+const EventEmitter = require('events');
const path = require('path');
const Util = require('../util/Util');
const { Error } = require('../errors');
@@ -6,13 +7,14 @@ const { Error } = require('../errors');
/**
* Represents a Shard spawned by the ShardingManager.
*/
-class Shard {
+class Shard extends EventEmitter {
/**
* @param {ShardingManager} manager Manager that is spawning this shard
* @param {number} id ID of this shard
* @param {string[]} [args=[]] Command line arguments to pass to the script
*/
constructor(manager, id, args = []) {
+ super();
/**
* Manager that created the shard
* @type {ShardingManager}
From 0cd4a92fb8d7e041bbf600c5b7fdb3af55460680 Mon Sep 17 00:00:00 2001
From: Frangu Vlad
Date: Fri, 17 Nov 2017 15:20:57 +0200
Subject: [PATCH 26/75] docs: Fixed some missing docstrings or incorrect return
types (#2093)
* Fix some missing doc strings
Mainly just readonly tags
* Return an error when guild#allowDMs is ran from a bot account, and fix some return types
* WebhookClient implements Webhook, doesn't extend it
* Fix Client#rateLimit docs not showing what it returns
Cause I wanted to handle this event only to see no return props :thinking:
* Actually make Client#rateLimit show the right info
Its an object with all the info
---
src/client/BaseClient.js | 1 +
src/client/Client.js | 1 +
src/client/WebhookClient.js | 2 +-
src/rest/handlers/RequestHandler.js | 13 +++++++------
src/structures/ClientUser.js | 2 +-
src/structures/Guild.js | 5 +++--
src/structures/GuildMember.js | 6 ++++++
src/util/Constants.js | 2 +-
8 files changed, 21 insertions(+), 11 deletions(-)
diff --git a/src/client/BaseClient.js b/src/client/BaseClient.js
index 365e9dcb7..f2c91ccdf 100644
--- a/src/client/BaseClient.js
+++ b/src/client/BaseClient.js
@@ -42,6 +42,7 @@ class BaseClient extends EventEmitter {
/**
* API shortcut
* @type {Object}
+ * @readonly
* @private
*/
get api() {
diff --git a/src/client/Client.js b/src/client/Client.js
index b26b73e2e..4b335e88a 100644
--- a/src/client/Client.js
+++ b/src/client/Client.js
@@ -163,6 +163,7 @@ class Client extends BaseClient {
/**
* Timestamp of the latest ping's start time
* @type {number}
+ * @readonly
* @private
*/
get _pingTimestamp() {
diff --git a/src/client/WebhookClient.js b/src/client/WebhookClient.js
index bc413cef6..c4c297879 100644
--- a/src/client/WebhookClient.js
+++ b/src/client/WebhookClient.js
@@ -3,7 +3,7 @@ const BaseClient = require('./BaseClient');
/**
* The webhook client.
- * @extends {Webhook}
+ * @implements {Webhook}
* @extends {BaseClient}
*/
class WebhookClient extends BaseClient {
diff --git a/src/rest/handlers/RequestHandler.js b/src/rest/handlers/RequestHandler.js
index 64e9ea72b..c4226a45c 100644
--- a/src/rest/handlers/RequestHandler.js
+++ b/src/rest/handlers/RequestHandler.js
@@ -39,12 +39,13 @@ class RequestHandler {
/**
* Emitted when the client hits a rate limit while making a request
* @event Client#rateLimit
- * @prop {number} timeout Timeout in ms
- * @prop {number} limit Number of requests that can be made to this endpoint
- * @prop {number} timeDifference Delta-T in ms between your system and Discord servers
- * @prop {string} method HTTP method used for request that triggered this event
- * @prop {string} path Path used for request that triggered this event
- * @prop {string} route Route used for request that triggered this event
+ * @param {Object} rateLimitInfo Object containing the rate limit info
+ * @param {number} rateLimitInfo.timeout Timeout in ms
+ * @param {number} rateLimitInfo.limit Number of requests that can be made to this endpoint
+ * @param {number} rateLimitInfo.timeDifference Delta-T in ms between your system and Discord servers
+ * @param {string} rateLimitInfo.method HTTP method used for request that triggered this event
+ * @param {string} rateLimitInfo.path Path used for request that triggered this event
+ * @param {string} rateLimitInfo.route Route used for request that triggered this event
*/
this.client.emit(RATE_LIMIT, {
timeout,
diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js
index ce71d9bcf..ef6433924 100644
--- a/src/structures/ClientUser.js
+++ b/src/structures/ClientUser.js
@@ -248,7 +248,7 @@ class ClientUser extends User {
/**
* Fetches messages that mentioned the client's user.
* This is only available when using a user account.
- * @param {Object} [options] Options for the fetch
+ * @param {Object} [options={}] Options for the fetch
* @param {number} [options.limit=25] Maximum number of mentions to retrieve
* @param {boolean} [options.roles=true] Whether to include role mentions
* @param {boolean} [options.everyone=true] Whether to include everyone/here mentions
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index b84117e91..8c796d337 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -321,7 +321,7 @@ class Guild extends Base {
/**
* System channel for this guild
- * @type {?GuildChannel}
+ * @type {?TextChannel}
* @readonly
*/
get systemChannel() {
@@ -806,6 +806,7 @@ class Guild extends Base {
* @returns {Promise}
*/
allowDMs(allow) {
+ if (this.client.user.bot) return Promise.reject(new Error('FEATURE_USER_ONLY'));
const settings = this.client.user.settings;
if (allow) return settings.removeRestrictedGuild(this);
else return settings.addRestrictedGuild(this);
@@ -818,7 +819,7 @@ class Guild extends Base {
* string, the ban reason. Supplying an object allows you to do both.
* @param {number} [options.days=0] Number of days of messages to delete
* @param {string} [options.reason] Reason for banning
- * @returns {Promise} Result object will be resolved as specifically as possible.
+ * @returns {Promise} Result object will be resolved as specifically as possible.
* If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot
* be resolved, the user ID will be the result.
* @example
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index 7ab2597fd..05266e1b8 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -77,36 +77,42 @@ class GuildMember extends Base {
/**
* Whether this member is deafened server-wide
* @type {boolean}
+ * @readonly
*/
get serverDeaf() { return this.voiceState.deaf; }
/**
* Whether this member is muted server-wide
* @type {boolean}
+ * @readonly
*/
get serverMute() { return this.voiceState.mute; }
/**
* Whether this member is self-muted
* @type {boolean}
+ * @readonly
*/
get selfMute() { return this.voiceState.self_mute; }
/**
* Whether this member is self-deafened
* @type {boolean}
+ * @readonly
*/
get selfDeaf() { return this.voiceState.self_deaf; }
/**
* The voice session ID of this member (if any)
* @type {?Snowflake}
+ * @readonly
*/
get voiceSessionID() { return this.voiceState.session_id; }
/**
* The voice channel ID of this member, (if any)
* @type {?Snowflake}
+ * @readonly
*/
get voiceChannelID() { return this.voiceState.channel_id; }
diff --git a/src/util/Constants.js b/src/util/Constants.js
index 7a0b262d3..efeeb29fa 100644
--- a/src/util/Constants.js
+++ b/src/util/Constants.js
@@ -52,7 +52,7 @@ exports.DefaultOptions = {
* WebSocket options (these are left as snake_case to match the API)
* @typedef {Object} WebsocketOptions
* @property {number} [large_threshold=250] Number of members in a guild to be considered large
- * @property {boolean} [compress=true] Whether to compress data sent on the connection
+ * @property {boolean} [compress=false] Whether to compress data sent on the connection
* (defaults to `false` for browsers)
*/
ws: {
From 8237bc054ce2d705ea5432dfbcf07c4fbd452d1a Mon Sep 17 00:00:00 2001
From: Drahcirius
Date: Fri, 17 Nov 2017 08:37:07 -0500
Subject: [PATCH 27/75] So long, long (#1994)
* refactor: remove long dep
* fix linter issue
* remove file extensions
* optimize methods
---
package.json | 1 -
src/structures/shared/Search.js | 10 +++----
src/util/Snowflake.js | 13 ++++----
src/util/Util.js | 53 +++++++++++++++++++++++++++++++--
4 files changed, 61 insertions(+), 16 deletions(-)
diff --git a/package.json b/package.json
index 50fb6bb59..08e2b200d 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,6 @@
"homepage": "https://github.com/hydrabolt/discord.js#readme",
"runkitExampleFilename": "./docs/examples/ping.js",
"dependencies": {
- "long": "^3.0.0",
"pako": "^1.0.0",
"prism-media": "^0.0.2",
"snekfetch": "^3.0.0",
diff --git a/src/structures/shared/Search.js b/src/structures/shared/Search.js
index a852a5086..3adca7fcc 100644
--- a/src/structures/shared/Search.js
+++ b/src/structures/shared/Search.js
@@ -1,4 +1,4 @@
-const long = require('long');
+const Util = require('../../util/Util');
const { TypeError } = require('../../errors');
/**
@@ -40,17 +40,17 @@ module.exports = function search(target, options) {
if (typeof options === 'string') options = { content: options };
if (options.before) {
if (!(options.before instanceof Date)) options.before = new Date(options.before);
- options.maxID = long.fromNumber(options.before.getTime() - 14200704e5).shiftLeft(22).toString();
+ options.maxID = Util.binaryToID((options.before.getTime() - 14200704e5).toString(2) + '0'.repeat(22));
}
if (options.after) {
if (!(options.after instanceof Date)) options.after = new Date(options.after);
- options.minID = long.fromNumber(options.after.getTime() - 14200704e5).shiftLeft(22).toString();
+ options.minID = Util.binaryToID((options.after.getTime() - 14200704e5).toString(2) + '0'.repeat(22));
}
if (options.during) {
if (!(options.during instanceof Date)) options.during = new Date(options.during);
const t = options.during.getTime() - 14200704e5;
- options.minID = long.fromNumber(t).shiftLeft(22).toString();
- options.maxID = long.fromNumber(t + 864e5).shiftLeft(22).toString();
+ options.minID = Util.binaryToID(t.toString(2) + '0'.repeat(22));
+ options.maxID = Util.binaryToID((t + 864e5).toString(2) + '0'.repeat(22));
}
if (options.channel) options.channel = target.client.channels.resolveID(options.channel);
if (options.author) options.author = target.client.users.resolveID(options.author);
diff --git a/src/util/Snowflake.js b/src/util/Snowflake.js
index f16839108..27f9f7440 100644
--- a/src/util/Snowflake.js
+++ b/src/util/Snowflake.js
@@ -1,4 +1,4 @@
-const Long = require('long');
+const Util = require('../util/Util');
// Discord epoch (2015-01-01T00:00:00.000Z)
const EPOCH = 1420070400000;
@@ -31,8 +31,9 @@ class SnowflakeUtil {
*/
static generate() {
if (INCREMENT >= 4095) INCREMENT = 0;
- const BINARY = `${pad((Date.now() - EPOCH).toString(2), 42)}0000100000${pad((INCREMENT++).toString(2), 12)}`;
- return Long.fromString(BINARY, 2).toString();
+ // eslint-disable-next-line max-len
+ const BINARY = `${(Date.now() - EPOCH).toString(2).padStart(42, '0')}0000100000${(INCREMENT++).toString(2).padStart(12, '0')}`;
+ return Util.binaryToID(BINARY);
}
/**
@@ -52,7 +53,7 @@ class SnowflakeUtil {
* @returns {DeconstructedSnowflake} Deconstructed snowflake
*/
static deconstruct(snowflake) {
- const BINARY = pad(Long.fromString(snowflake).toString(2), 64);
+ const BINARY = Util.idToBinary(snowflake).toString(2).padStart(64, '0');
const res = {
timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH,
workerID: parseInt(BINARY.substring(42, 47), 2),
@@ -68,8 +69,4 @@ class SnowflakeUtil {
}
}
-function pad(v, n, c = '0') {
- return String(v).length >= n ? String(v) : (String(c).repeat(n) + v).slice(-n);
-}
-
module.exports = SnowflakeUtil;
diff --git a/src/util/Util.js b/src/util/Util.js
index ecc1b93e1..46ce22367 100644
--- a/src/util/Util.js
+++ b/src/util/Util.js
@@ -1,4 +1,3 @@
-const Long = require('long');
const snekfetch = require('snekfetch');
const { Colors, DefaultOptions, Endpoints } = require('./Constants');
const { Error: DiscordError, RangeError, TypeError } = require('../errors');
@@ -299,7 +298,9 @@ class Util {
*/
static discordSort(collection) {
return collection
- .sort((a, b) => a.rawPosition - b.rawPosition || Long.fromString(a.id).sub(Long.fromString(b.id)).toNumber());
+ .sort((a, b) => a.rawPosition - b.rawPosition ||
+ parseInt(a.id.slice(0, -10)) - parseInt(b.id.slice(0, -10)) ||
+ parseInt(a.id.slice(10)) - parseInt(b.id.slice(10)));
}
static setPosition(item, position, relative, sorted, route, reason) {
@@ -316,6 +317,54 @@ class Util {
}
return f;
}
+
+ /**
+ * Transform a snowflake from a decimal string to a bit string
+ * @param {string} num Snowflake to be transformed
+ * @returns {string}
+ * @private
+ */
+ static idToBinary(num) {
+ let bin = '';
+ let high = parseInt(num.slice(0, -10)) || 0;
+ let low = parseInt(num.slice(-10));
+ while (low > 0 || high > 0) {
+ bin = String(low & 1) + bin;
+ low = Math.floor(low / 2);
+ if (high > 0) {
+ low += 5000000000 * (high % 2);
+ high = Math.floor(high / 2);
+ }
+ }
+ return bin;
+ }
+
+
+ /**
+ * Transform a snowflake from a bit string to a decimal string
+ * @param {string} num Bit string to be transformed
+ * @returns {string}
+ * @private
+ */
+ static binaryToID(num) {
+ let dec = '';
+
+ while (num.length > 50) {
+ const high = parseInt(num.slice(0, -32), 2);
+ const low = parseInt((high % 10).toString(2) + num.slice(-32), 2);
+
+ dec = (low % 10).toString() + dec;
+ num = Math.floor(high / 10).toString(2) + Math.floor(low / 10).toString(2).padStart(32, '0');
+ }
+
+ num = parseInt(num, 2);
+ while (num > 0) {
+ dec = (num % 10).toString() + dec;
+ num = Math.floor(num / 10);
+ }
+
+ return dec;
+ }
}
module.exports = Util;
From 0f4ca39fa358be6aee3a2f295eba6852b3d66bb1 Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Sat, 18 Nov 2017 17:12:27 -0500
Subject: [PATCH 28/75] Add Node 9 to Travis
---
.travis.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.travis.yml b/.travis.yml
index fb2f6214c..0c886cf8c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,7 @@
language: node_js
node_js:
- "8"
+ - "9"
cache:
directories:
- node_modules
From b7c4df5dc1c98d10c11a96db625c8924f4a9cc12 Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Sat, 18 Nov 2017 17:19:34 -0500
Subject: [PATCH 29/75] Fix trailing space
---
src/structures/Guild.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index 8c796d337..16bda06e0 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -115,7 +115,7 @@ class Guild extends Base {
/**
* An array of enabled guild features, here are the possible values:
- * * INVITE_SPLASH
+ * * INVITE_SPLASH
* * MORE_EMOJI
* * VERIFIED
* * VIP_REGIONS
From 547b9cc2f3814f7ad3affd91bd962d443088acd2 Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Sat, 18 Nov 2017 17:23:18 -0500
Subject: [PATCH 30/75] Only run Node 8 build for test stage
---
.travis.yml | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 0c886cf8c..25878d575 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,4 @@
language: node_js
-node_js:
- - "8"
- - "9"
cache:
directories:
- node_modules
@@ -10,8 +7,12 @@ jobs:
include:
- stage: test
script: bash ./travis/test.sh
+ node_js:
+ - 8
+ - 9
- stage: deploy
script: bash ./travis/deploy.sh
+ node_js: 9
env:
global:
- ENCRYPTION_LABEL: "af862fa96d3e"
From 4a15ccab0f4df072c7794ce0baad7b837467a2eb Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Sat, 18 Nov 2017 17:26:03 -0500
Subject: [PATCH 31/75] Maybe fix dumb Travis behaviour
---
.travis.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 25878d575..a82715f55 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,7 @@
language: node_js
+node_js:
+ - 8
+ - 9
cache:
directories:
- node_modules
@@ -7,9 +10,6 @@ jobs:
include:
- stage: test
script: bash ./travis/test.sh
- node_js:
- - 8
- - 9
- stage: deploy
script: bash ./travis/deploy.sh
node_js: 9
From 297ac4e4dbf7134130d9278a9d6bcf910feb3fd0 Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Sat, 18 Nov 2017 17:33:54 -0500
Subject: [PATCH 32/75] Still mucking about with Travis
---
.travis.yml | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index a82715f55..89d0baf36 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,14 +2,10 @@ language: node_js
node_js:
- 8
- 9
-cache:
- directories:
- - node_modules
install: npm install
+script: bash ./travis/test.sh
jobs:
include:
- - stage: test
- script: bash ./travis/test.sh
- stage: deploy
script: bash ./travis/deploy.sh
node_js: 9
@@ -17,5 +13,8 @@ env:
global:
- ENCRYPTION_LABEL: "af862fa96d3e"
- COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com"
+cache:
+ directories:
+ - node_modules
dist: trusty
sudo: false
From 127d87dca82d560d488295510755fabc041dc7b7 Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Sat, 18 Nov 2017 17:45:06 -0500
Subject: [PATCH 33/75] Hopefully remove unnecessary work from Travis tests
---
.travis.yml | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 89d0baf36..675e03ebc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,12 +7,11 @@ script: bash ./travis/test.sh
jobs:
include:
- stage: deploy
- script: bash ./travis/deploy.sh
node_js: 9
-env:
- global:
- - ENCRYPTION_LABEL: "af862fa96d3e"
- - COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com"
+ script: bash ./travis/deploy.sh
+ env:
+ - ENCRYPTION_LABEL: "af862fa96d3e"
+ - COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com"
cache:
directories:
- node_modules
From abd6156a90de362ac751c1d39aa4fa9474d2f5c9 Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Sat, 18 Nov 2017 17:50:30 -0500
Subject: [PATCH 34/75] Maybe fix Travis some more
---
.travis.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 675e03ebc..3b89d2afa 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,8 +10,8 @@ jobs:
node_js: 9
script: bash ./travis/deploy.sh
env:
- - ENCRYPTION_LABEL: "af862fa96d3e"
- - COMMIT_AUTHOR_EMAIL: "amishshah.2k@gmail.com"
+ - ENCRYPTION_LABEL="af862fa96d3e"
+ - COMMIT_AUTHOR_EMAIL="amishshah.2k@gmail.com"
cache:
directories:
- node_modules
From c622143e3944afd3baae20e213a43e929fe6b890 Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Sat, 18 Nov 2017 17:54:50 -0500
Subject: [PATCH 35/75] Remove Node version check from deploy script
---
travis/deploy.sh | 7 -------
1 file changed, 7 deletions(-)
diff --git a/travis/deploy.sh b/travis/deploy.sh
index 340087312..ab6116008 100644
--- a/travis/deploy.sh
+++ b/travis/deploy.sh
@@ -24,12 +24,6 @@ else
SOURCE_TYPE="branch"
fi
-# For Node != 8, do nothing
-if [ "$TRAVIS_NODE_VERSION" != "8" ]; then
- echo -e "\e[36m\e[1mBuild triggered with Node v${TRAVIS_NODE_VERSION} - doing nothing."
- exit 0
-fi
-
# Run the build
npm run docs
VERSIONED=false npm run webpack
@@ -88,4 +82,3 @@ git config user.name "Travis CI"
git config user.email "$COMMIT_AUTHOR_EMAIL"
git commit -m "Webpack build for ${SOURCE_TYPE} ${SOURCE}: ${SHA}" || true
git push $SSH_REPO $TARGET_BRANCH
-
From b859501b6f980c0f6117a27e21c38a3e4db3e2bc Mon Sep 17 00:00:00 2001
From: Schuyler Cebulskie
Date: Sat, 18 Nov 2017 20:30:13 -0500
Subject: [PATCH 36/75] Document and clean up some garbage
---
src/structures/CategoryChannel.js | 2 +-
src/structures/Guild.js | 43 ++++++++++++++++++-------
src/util/Util.js | 53 ++++++++++++++++++-------------
3 files changed, 63 insertions(+), 35 deletions(-)
diff --git a/src/structures/CategoryChannel.js b/src/structures/CategoryChannel.js
index 2c063f73d..d7121a32b 100644
--- a/src/structures/CategoryChannel.js
+++ b/src/structures/CategoryChannel.js
@@ -6,7 +6,7 @@ const GuildChannel = require('./GuildChannel');
*/
class CategoryChannel extends GuildChannel {
/**
- * The channels that are part of this category
+ * Channels that are part of this category
* @type {?Collection}
* @readonly
*/
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index 16bda06e0..1eff773a2 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -1067,8 +1067,7 @@ class Guild extends Base {
.then(emoji => this.client.actions.GuildEmojiCreate.handle(this, emoji).emoji);
}
- return DataResolver.resolveImage(attachment)
- .then(image => this.createEmoji(image, name, { roles, reason }));
+ return DataResolver.resolveImage(attachment).then(image => this.createEmoji(image, name, { roles, reason }));
}
/**
@@ -1146,6 +1145,34 @@ class Guild extends Base {
return this.name;
}
+ /**
+ * Creates a collection of this guild's roles, sorted by their position and IDs.
+ * @returns {Collection}
+ * @private
+ */
+ _sortedRoles() {
+ return Util.discordSort(this.roles);
+ }
+
+ /**
+ * Creates a collection of this guild's or a specific category's channels, sorted by their position and IDs.
+ * @param {GuildChannel} [channel] Category to get the channels of
+ * @returns {Collection}
+ * @private
+ */
+ _sortedChannels(channel) {
+ const category = channel.type === ChannelTypes.CATEGORY;
+ return Util.discordSort(this.channels.filter(c =>
+ c.type === channel.type && (category || c.parent === channel.parent)
+ ));
+ }
+
+ /**
+ * Handles a user speaking update in a voice channel.
+ * @param {Snowflake} user ID of the user that the update is for
+ * @param {boolean} speaking Whether the user is speaking
+ * @private
+ */
_memberSpeakUpdate(user, speaking) {
const member = this.members.get(user);
if (member && member.speaking !== speaking) {
@@ -1159,23 +1186,15 @@ class Guild extends Base {
this.client.emit(Events.GUILD_MEMBER_SPEAKING, member, speaking);
}
}
-
- _sortedRoles() {
- return Util.discordSort(this.roles);
- }
-
- _sortedChannels(channel) {
- const category = channel.type === ChannelTypes.CATEGORY;
- return Util.discordSort(this.channels.filter(c =>
- c.type === channel.type && (category || c.parent === channel.parent)));
- }
}
+// TODO: Document this thing
class VoiceStateCollection extends Collection {
constructor(guild) {
super();
this.guild = guild;
}
+
set(id, voiceState) {
const member = this.guild.members.get(id);
if (member) {
diff --git a/src/util/Util.js b/src/util/Util.js
index 46ce22367..26df930ce 100644
--- a/src/util/Util.js
+++ b/src/util/Util.js
@@ -21,9 +21,7 @@ class Util {
static splitMessage(text, { maxLength = 1950, char = '\n', prepend = '', append = '' } = {}) {
if (text.length <= maxLength) return text;
const splitText = text.split(char);
- if (splitText.length === 1) {
- throw new RangeError('SPLIT_MAX_LEN');
- }
+ if (splitText.length === 1) throw new RangeError('SPLIT_MAX_LEN');
const messages = [''];
let msg = 0;
for (let i = 0; i < splitText.length; i++) {
@@ -83,10 +81,7 @@ class Util {
const [name, id] = text.split(':');
return { name, id };
} else {
- return {
- name: text,
- id: null,
- };
+ return { name: text, id: null };
}
}
@@ -225,7 +220,6 @@ class Util {
* @param {StringResolvable} data The string resolvable to resolve
* @returns {string}
*/
-
static resolveString(data) {
if (typeof data === 'string') return data;
if (data instanceof Array) return data.join('\n');
@@ -273,7 +267,6 @@ class Util {
* @param {ColorResolvable} color Color to resolve
* @returns {number} A color
*/
-
static resolveColor(color) {
if (typeof color === 'string') {
if (color === 'RANDOM') return Math.floor(Math.random() * (0xFFFFFF + 1));
@@ -292,17 +285,29 @@ class Util {
}
/**
- * Sorts by Discord's position and then by ID.
+ * Sorts by Discord's position and ID.
* @param {Collection} collection Collection of objects to sort
* @returns {Collection}
*/
static discordSort(collection) {
- return collection
- .sort((a, b) => a.rawPosition - b.rawPosition ||
- parseInt(a.id.slice(0, -10)) - parseInt(b.id.slice(0, -10)) ||
- parseInt(a.id.slice(10)) - parseInt(b.id.slice(10)));
+ return collection.sort((a, b) =>
+ a.rawPosition - b.rawPosition ||
+ parseInt(a.id.slice(0, -10)) - parseInt(b.id.slice(0, -10)) ||
+ parseInt(a.id.slice(10)) - parseInt(b.id.slice(10))
+ );
}
+ /**
+ * Sets the position of a Channel or Role.
+ * @param {Channel|Role} item Object to set the position of
+ * @param {number} position New position for the object
+ * @param {boolean} relative Whether `position` is relative to its current position
+ * @param {Collection} sorted A collection of the objects sorted properly
+ * @param {APIRouter} route Route to call PATCH on
+ * @param {string} [reason] Reason for the change
+ * @returns {Promise