diff --git a/src/client/ClientDataResolver.js b/src/client/ClientDataResolver.js index bda336dbb..c725792b7 100644 --- a/src/client/ClientDataResolver.js +++ b/src/client/ClientDataResolver.js @@ -178,7 +178,7 @@ class ClientDataResolver { /** * Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image. * @param {BufferResolvable|Base64Resolvable} image The image to be resolved - * @returns {Promise} + * @returns {Promise} */ resolveImage(image) { if (!image) return Promise.resolve(null); diff --git a/src/client/rest/RESTMethods.js b/src/client/rest/RESTMethods.js index 063442e37..c621eead4 100644 --- a/src/client/rest/RESTMethods.js +++ b/src/client/rest/RESTMethods.js @@ -285,6 +285,11 @@ class RESTMethods { .then(() => channel); } + removeUserFromGroupDM(channel, userId) { + return this.rest.makeRequest('delete', Endpoints.Channel(channel).Recipient(userId), true) + .then(() => channel); + } + updateGroupDMChannel(channel, _data) { const data = {}; data.name = _data.name; @@ -298,13 +303,14 @@ class RESTMethods { ); } - deleteChannel(channel) { + deleteChannel(channel, reason) { if (channel instanceof User || channel instanceof GuildMember) channel = this.getExistingDM(channel); if (!channel) return Promise.reject(new Error('No channel to delete.')); - return this.rest.makeRequest('delete', Endpoints.Channel(channel), true).then(data => { - data.id = channel.id; - return this.client.actions.ChannelDelete.handle(data).channel; - }); + return this.rest.makeRequest('delete', Endpoints.Channel(channel), true, undefined, undefined, reason) + .then(data => { + data.id = channel.id; + return this.client.actions.ChannelDelete.handle(data).channel; + }); } updateChannel(channel, _data, reason) { @@ -369,7 +375,7 @@ class RESTMethods { const user = this.client.user; const data = {}; data.username = _data.username || user.username; - data.avatar = this.client.resolver.resolveBase64(_data.avatar) || user.avatar; + data.avatar = typeof _data.avatar === 'undefined' ? user.avatar : this.client.resolver.resolveBase64(_data.avatar); if (!user.bot) { data.email = _data.email || user.email; data.password = password; @@ -639,6 +645,7 @@ class RESTMethods { payload.temporary = options.temporary; payload.max_age = options.maxAge; payload.max_uses = options.maxUses; + payload.unique = options.unique; return this.rest.makeRequest('post', Endpoints.Channel(channel).invites, true, payload, undefined, reason) .then(invite => new Invite(this.client, invite)); } diff --git a/src/client/websocket/packets/handlers/Resumed.js b/src/client/websocket/packets/handlers/Resumed.js index 7b4387092..0c796053b 100644 --- a/src/client/websocket/packets/handlers/Resumed.js +++ b/src/client/websocket/packets/handlers/Resumed.js @@ -14,7 +14,7 @@ class ResumedHandler extends AbstractHandler { const replayed = ws.sequence - ws.closeSequence; ws.debug(`RESUMED ${ws._trace.join(' -> ')} | replayed ${replayed} events.`); - client.emit('resume', replayed); + client.emit(Constants.Events.RESUME, replayed); ws.heartbeat(); } } diff --git a/src/structures/Attachment.js b/src/structures/Attachment.js index e4ccbb197..216b61c9d 100644 --- a/src/structures/Attachment.js +++ b/src/structures/Attachment.js @@ -63,6 +63,7 @@ class Attachment { * Set the file of this attachment. * @param {BufferResolvable|Stream} file The file * @param {string} name The name of the file + * @returns {void} * @private */ _attach(file, name) { diff --git a/src/structures/ClientUser.js b/src/structures/ClientUser.js index 2009220c1..ef3398bd3 100644 --- a/src/structures/ClientUser.js +++ b/src/structures/ClientUser.js @@ -304,37 +304,17 @@ class ClientUser extends User { * Creates a guild. * This is only available when using a user account. * @param {string} name The name of the guild - * @param {string} region The region for the server + * @param {string} [region] The region for the server * @param {BufferResolvable|Base64Resolvable} [icon=null] The icon for the guild * @returns {Promise} The guild that was created */ - - createGuild(name, { region, icon = null } = {}) { - if (!icon || (typeof icon === 'string' && icon.startsWith('data:'))) { - return new Promise((resolve, reject) => - this.client.api.guilds.post({ data: { name, region, icon } }) - .then(data => { - if (this.client.guilds.has(data.id)) return resolve(this.client.guilds.get(data.id)); - - const handleGuild = guild => { - if (guild.id === data.id) { - this.client.removeListener(Constants.Events.GUILD_CREATE, handleGuild); - this.client.clearTimeout(timeout); - resolve(guild); - } - }; - this.client.on(Constants.Events.GUILD_CREATE, handleGuild); - - const timeout = this.client.setTimeout(() => { - this.client.removeListener(Constants.Events.GUILD_CREATE, handleGuild); - resolve(this.client.dataManager.newGuild(data)); - }, 10000); - return undefined; - }, reject) - ); + createGuild(name, region, icon = null) { + if (typeof icon === 'string' && icon.startsWith('data:')) { + return this.client.rest.methods.createGuild({ name, icon, region }); } else { - return this.client.resolver.resolveFile(icon) - .then(data => this.createGuild(name, { region, icon: this.client.resolver.resolveBase64(data) || null })); + return this.client.resolver.resolveImage(icon).then(data => + this.client.rest.methods.createGuild({ name, icon: data, region }) + ); } } diff --git a/src/structures/ClientUserChannelOverride.js b/src/structures/ClientUserChannelOverride.js index 12790b9ac..93efa45ff 100644 --- a/src/structures/ClientUserChannelOverride.js +++ b/src/structures/ClientUserChannelOverride.js @@ -11,6 +11,8 @@ class ClientUserChannelOverride { /** * Patch the data contained in this class with new partial data. * @param {Object} data Data to patch this with + * @returns {void} + * @private */ patch(data) { for (const key of Object.keys(Constants.UserChannelOverrideMap)) { diff --git a/src/structures/ClientUserGuildSettings.js b/src/structures/ClientUserGuildSettings.js index 146be9872..5a28747ec 100644 --- a/src/structures/ClientUserGuildSettings.js +++ b/src/structures/ClientUserGuildSettings.js @@ -26,6 +26,8 @@ class ClientUserGuildSettings { /** * Patch the data contained in this class with new partial data. * @param {Object} data Data to patch this with + * @returns {void} + * @private */ patch(data) { for (const key of Object.keys(Constants.UserGuildSettingsMap)) { diff --git a/src/structures/ClientUserSettings.js b/src/structures/ClientUserSettings.js index 798f348c5..6b18c0394 100644 --- a/src/structures/ClientUserSettings.js +++ b/src/structures/ClientUserSettings.js @@ -13,6 +13,8 @@ class ClientUserSettings { /** * Patch the data contained in this class with new partial data. * @param {Object} data Data to patch this with + * @returns {void} + * @private */ patch(data) { for (const key of Object.keys(Constants.UserSettingsMap)) { @@ -29,7 +31,7 @@ class ClientUserSettings { /** * Update a specific property of of user settings. * @param {string} name Name of property - * @param {value} value Value to patch + * @param {*} value Value to patch * @returns {Promise} */ update(name, value) { @@ -37,6 +39,7 @@ class ClientUserSettings { } /** + * Sets the position at which this guild will appear in the Discord client. * @param {Guild} guild The guild to move * @param {number} position Absolute or relative position * @param {boolean} [relative=false] Whether to position relatively or absolutely diff --git a/src/structures/DMChannel.js b/src/structures/DMChannel.js index 9c74aad4e..063c07286 100644 --- a/src/structures/DMChannel.js +++ b/src/structures/DMChannel.js @@ -53,6 +53,7 @@ class DMChannel extends Channel { get typing() {} get typingCount() {} createCollector() {} + createMessageCollector() {} awaitMessages() {} // Doesn't work on DM channels; bulkDelete() {} acknowledge() {} diff --git a/src/structures/GroupDMChannel.js b/src/structures/GroupDMChannel.js index c01ba84d8..efa20b2c2 100644 --- a/src/structures/GroupDMChannel.js +++ b/src/structures/GroupDMChannel.js @@ -119,7 +119,7 @@ class GroupDMChannel extends Channel { edit(data) { const _data = {}; if (data.name) _data.name = data.name; - if (data.icon) _data.icon = data.icon; + if (typeof data.icon !== 'undefined') _data.icon = data.icon; return this.client.rest.methods.updateGroupDMChannel(this, _data); } @@ -148,6 +148,7 @@ class GroupDMChannel extends Channel { * Add a user to the DM * @param {UserResolvable|string} accessTokenOrID Access token or user resolvable * @param {string} [nick] Permanent nickname to give the user (only available if a bot is creating the DM) + * @returns {Promise} */ addUser(accessTokenOrID, nick) { @@ -161,7 +162,7 @@ class GroupDMChannel extends Channel { /** * Set a new GroupDMChannel icon. * @param {Base64Resolvable|BufferResolvable} icon The new icon of the group dm - * @returns {Promise} + * @returns {Promise} * @example * // Edit the group dm icon * channel.setIcon('./icon.png') @@ -172,6 +173,25 @@ class GroupDMChannel extends Channel { return this.client.resolver.resolveImage(icon).then(data => this.edit({ icon: data })); } + /** + * Sets a new name for this Group DM. + * @param {string} name New name for this Group DM + * @returns {Promise} + */ + setName(name) { + return this.edit({ name }); + } + + /** + * Removes an user from this Group DM. + * @param {UserResolvable} user User to remove + * @returns {Promise} + */ + removeUser(user) { + const id = this.client.resolver.resolveUserID(user); + return this.client.rest.methods.removeUserFromGroupDM(this, id); + } + /** * When concatenated with a string, this automatically concatenates the channel's name instead of the Channel object. * @returns {string} @@ -203,6 +223,7 @@ class GroupDMChannel extends Channel { get typing() {} get typingCount() {} createCollector() {} + createMessageCollector() {} awaitMessages() {} // Doesn't work on Group DMs; bulkDelete() {} acknowledge() {} diff --git a/src/structures/Guild.js b/src/structures/Guild.js index be064e7f3..600e93aa2 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -512,7 +512,7 @@ class Guild { /** * Fetch a single guild member from a user. * @param {UserResolvable} user The user to fetch the member for - * @param {boolean} [cache=true] Insert the user into the users cache + * @param {boolean} [cache=true] Insert the member into the members cache * @returns {Promise} */ fetchMember(user, cache = true) { @@ -563,9 +563,7 @@ class Guild { * Performs a search within the entire guild. * This is only available when using a user account. * @param {MessageSearchOptions} [options={}] Options to pass to the search - * @returns {Promise>} - * An array containing arrays of messages. Each inner array is a search context cluster. - * The message which has triggered the result will have the `hit` property set to `true`. + * @returns {Promise} * @example * guild.search({ * content: 'discord.js', @@ -587,6 +585,7 @@ class Guild { * @property {number} [verificationLevel] The verification level of the guild * @property {number} [explicitContentFilter] The level of the explicit content filter * @property {ChannelResolvable} [afkChannel] The AFK channel of the guild + * @property {ChannelResolvable} [systemChannel] The system channel of the guild * @property {number} [afkTimeout] The AFK timeout of the guild * @property {Base64Resolvable} [icon] The icon of the guild * @property {GuildMemberResolvable} [owner] The owner of the guild @@ -596,7 +595,7 @@ class Guild { /** * Updates the guild with new information - e.g. a new name. * @param {GuildEditData} data The data to update the guild with - * @param {string} [reason] Reason for editing this guild + * @param {string} [reason] Reason for editing the guild * @returns {Promise} * @example * // Set the guild name and region @@ -615,9 +614,9 @@ class Guild { if (data.afkChannel) _data.afk_channel_id = this.client.resolver.resolveChannel(data.afkChannel).id; if (data.systemChannel) _data.system_channel_id = this.client.resolver.resolveChannel(data.systemChannel).id; if (data.afkTimeout) _data.afk_timeout = Number(data.afkTimeout); - if (data.icon) _data.icon = data.icon; + if (typeof data.icon !== 'undefined') _data.icon = data.icon; if (data.owner) _data.owner_id = this.client.resolver.resolveUser(data.owner).id; - if (data.splash) _data.splash = data.splash; + if (typeof data.splash !== 'undefined') _data.splash = data.splash; if (typeof data.explicitContentFilter !== 'undefined') { _data.explicit_content_filter = Number(data.explicitContentFilter); } @@ -789,6 +788,7 @@ class Guild { /** * Allow direct messages from guild members. + * This is only available when using a user account. * @param {boolean} allow Whether to allow direct messages * @returns {Promise} */ @@ -827,7 +827,7 @@ class Guild { /** * Unbans a user from the guild. * @param {UserResolvable} user The user to unban - * @param {string} [reason] Reason for unbanning the user + * @param {string} [reason] Reason for unbanning the user * @returns {Promise} * @example * // Unban a user by ID (or with a user/guild member object) @@ -948,16 +948,13 @@ class Guild { * .catch(console.error); */ createEmoji(attachment, name, roles, reason) { - return new Promise(resolve => { - if (typeof attachment === 'string' && attachment.startsWith('data:')) { - resolve(this.client.rest.methods.createEmoji(this, attachment, name, roles, reason)); - } else { - this.client.resolver.resolveFile(attachment).then(data => { - const dataURI = this.client.resolver.resolveBase64(data); - resolve(this.client.rest.methods.createEmoji(this, dataURI, name, roles, reason)); - }); - } - }); + if (typeof attachment === 'string' && attachment.startsWith('data:')) { + return this.client.rest.methods.createEmoji(this, attachment, name, roles, reason); + } else { + return this.client.resolver.resolveImage(attachment).then(data => + this.client.rest.methods.createEmoji(this, data, name, roles, reason) + ); + } } /** diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index 2c728afbe..b1ad692b9 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -137,7 +137,7 @@ class GuildChannel extends Channel { /** * Overwrites the permissions for a user or role in this channel. - * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {Role|Snowflake|UserResolvable} userOrRole The user or role to update * @param {PermissionOverwriteOptions} options The configuration for the update * @param {string} [reason] Reason for creating/editing this overwrite * @returns {Promise} @@ -261,19 +261,15 @@ class GuildChannel extends Channel { return this.edit({ topic }, reason); } - /** - * Options given when creating a guild channel invite. - * @typedef {Object} InviteOptions - - */ - /** * Create an invite to this guild channel. - * @param {InviteOptions} [options={}] Options for the invite + * This is only available when using a bot account. + * @param {Object} [options={}] Options for the invite * @param {boolean} [options.temporary=false] Whether members that joined via the invite should be automatically * kicked after 24 hours if they have not yet received a role * @param {number} [options.maxAge=86400] How long the invite should last (in seconds, 0 for forever) * @param {number} [options.maxUses=0] Maximum number of uses + * @param {boolean} [options.unique=false] Create a unique invite, or use an existing one with similar settings * @param {string} [reason] Reason for creating the invite * @returns {Promise} */ @@ -294,6 +290,20 @@ class GuildChannel extends Channel { .then(channel => withTopic ? channel.setTopic(this.topic) : channel); } + /** + * Deletes this channel. + * @param {string} [reason] Reason for deleting this channel + * @returns {Promise} + * @example + * // Delete the channel + * channel.delete('making room for new channels') + * .then(channel => console.log(`Deleted ${channel.name} to make room for new channels`)) + * .catch(console.error); // Log error + */ + delete(reason) { + return this.client.rest.methods.deleteChannel(this, reason); + } + /** * Checks if this channel has the same type, topic, position, name, overwrites and ID as another channel. * In most cases, a simple `channel.id === channel2.id` will do, and is much faster too. diff --git a/src/structures/Message.js b/src/structures/Message.js index eec937e98..d39c3b9c6 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -271,7 +271,7 @@ class Message { */ /** - * Similar to createCollector but in promise form. + * Similar to createMessageCollector but in promise form. * Resolves with a collection of reactions that pass the specified filter. * @param {CollectorFilter} filter The filter function to use * @param {AwaitReactionsOptions} [options={}] Optional options to pass to the internal collector diff --git a/src/structures/Role.js b/src/structures/Role.js index 87f5cd902..d7a787f0d 100644 --- a/src/structures/Role.js +++ b/src/structures/Role.js @@ -195,7 +195,7 @@ class Role { * @property {ColorResolvable} [color] The color of the role, either a hex string or a base 10 number * @property {boolean} [hoist] Whether or not the role should be hoisted * @property {number} [position] The position of the role - * @property {string[]} [permissions] The permissions of the role + * @property {PermissionResolvable[]|number} [permissions] The permissions of the role * @property {boolean} [mentionable] Whether or not the role should be mentionable */ diff --git a/src/structures/TextChannel.js b/src/structures/TextChannel.js index 8d9d91a95..da0696840 100644 --- a/src/structures/TextChannel.js +++ b/src/structures/TextChannel.js @@ -60,7 +60,7 @@ class TextChannel extends GuildChannel { /** * Create a webhook for the channel. * @param {string} name The name of the webhook - * @param {BufferResolvable|Base64Resolvable} avatar The avatar for the webhook + * @param {BufferResolvable|Base64Resolvable} [avatar] The avatar for the webhook * @param {string} [reason] Reason for creating this webhook * @returns {Promise} webhook The created webhook * @example @@ -69,15 +69,13 @@ class TextChannel extends GuildChannel { * .catch(console.error) */ createWebhook(name, avatar, reason) { - return new Promise(resolve => { - if (typeof avatar === 'string' && avatar.startsWith('data:')) { - resolve(this.client.rest.methods.createWebhook(this, name, avatar, reason)); - } else { - this.client.resolver.resolveFile(avatar).then(data => - resolve(this.client.rest.methods.createWebhook(this, name, data, reason)) - ); - } - }); + if (typeof avatar === 'string' && avatar.startsWith('data:')) { + return this.client.rest.methods.createWebhook(this, name, avatar, reason); + } else { + return this.client.resolver.resolveImage(avatar).then(data => + this.client.rest.methods.createWebhook(this, name, data, reason) + ); + } } // These are here only for documentation purposes - they are implemented by TextBasedChannel diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index f05d7e056..1b72530e7 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -242,20 +242,16 @@ class Webhook { /** * Edit the webhook. * @param {string} name The new name for the webhook - * @param {BufferResolvable} avatar The new avatar for the webhook + * @param {BufferResolvable} [avatar] The new avatar for the webhook * @returns {Promise} */ edit(name = this.name, avatar) { if (avatar) { - return this.client.resolver.resolveFile(avatar).then(file => { - const dataURI = this.client.resolver.resolveBase64(file); - return this.client.rest.methods.editWebhook(this, name, dataURI); - }); + return this.client.resolver.resolveImage(avatar).then(data => + this.client.rest.methods.editWebhook(this, name, data) + ); } - return this.client.rest.methods.editWebhook(this, name).then(data => { - this.setup(data); - return this; - }); + return this.client.rest.methods.editWebhook(this, name); } /** diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index 16b0465ad..37c106a25 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -230,13 +230,18 @@ class TextBasedChannel { * @property {boolean} [nsfw=false] Include results from NSFW channels */ + /** + * @typedef {Object} MessageSearchResult + * @property {number} totalResults Total result count + * @property {Message[][]} messages Array of message results + * The message which has triggered the result will have the `hit` property set to `true` + */ + /** * Performs a search within the channel. * This is only available when using a user account. * @param {MessageSearchOptions} [options={}] Options to pass to the search - * @returns {Promise>} - * An array containing arrays of messages. Each inner array is a search context cluster - * The message which has triggered the result will have the `hit` property set to `true` + * @returns {Promise} * @example * channel.search({ * content: 'discord.js', diff --git a/src/util/Constants.js b/src/util/Constants.js index ed9335cf6..36b15c61c 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -29,6 +29,7 @@ exports.Package = require('../../package.json'); * 100% certain you don't need, as many are important, but not obviously so. The safest one to disable with the * most impact is typically `TYPING_START`. * @property {WebsocketOptions} [ws] Options for the WebSocket + * @property {HTTPOptions} [http] HTTP options */ exports.DefaultOptions = { apiRequestMethod: 'sequential', @@ -63,6 +64,15 @@ exports.DefaultOptions = { }, version: 6, }, + + /** + * HTTP options + * @typedef {Object} HTTPOptions + * @property {number} [version=7] API version to use + * @property {string} [api='https://discordapp.com/api'] Base url of the API + * @property {string} [cdn='https://cdn.discordapp.com'] Base url of the CDN + * @property {string} [invite='https://discord.gg'] Base url of invites + */ http: { version: 7, host: 'https://discordapp.com', @@ -293,6 +303,7 @@ exports.VoiceOPCodes = { exports.Events = { READY: 'ready', + RESUME: 'resume', GUILD_CREATE: 'guildCreate', GUILD_DELETE: 'guildDelete', GUILD_UPDATE: 'guildUpdate', diff --git a/src/util/Permissions.js b/src/util/Permissions.js index bb5588f5f..c2d604dbc 100644 --- a/src/util/Permissions.js +++ b/src/util/Permissions.js @@ -180,7 +180,8 @@ class Permissions { * - `MANAGE_GUILD` (edit the guild information, region, etc.) * - `ADD_REACTIONS` (add new reactions to messages) * - `VIEW_AUDIT_LOG` - * - `READ_MESSAGES` + * - `VIEW_CHANNEL` + * - `READ_MESSAGES` **(deprecated)** * - `SEND_MESSAGES` * - `SEND_TTS_MESSAGES` * - `MANAGE_MESSAGES` (delete messages and reactions) @@ -215,6 +216,7 @@ Permissions.FLAGS = { ADD_REACTIONS: 1 << 6, VIEW_AUDIT_LOG: 1 << 7, + VIEW_CHANNEL: 1 << 10, READ_MESSAGES: 1 << 10, SEND_MESSAGES: 1 << 11, SEND_TTS_MESSAGES: 1 << 12,