diff --git a/index.d.ts b/index.d.ts index 948f50163..6f805f9e9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -154,10 +154,13 @@ declare namespace Eris { largeThreshold?: number; lastShardID?: number; latencyThreshold?: number; + maxReconnectAttempts?: number; + maxResumeAttempts?: number; maxShards?: number | "auto"; messageLimit?: number; opusOnly?: boolean; - reconnectAttempts?: number; + rateLimiterOffset?: number; + requestTimeout?: number; reconnectDelay?: ReconnectDelayFunction; restMode?: boolean; seedVoiceConnections?: boolean; @@ -365,6 +368,7 @@ declare namespace Eris { embeds: Embed[]; mentionedBy?: any; mentions: string[]; + pinned: boolean; roleMentions: string[]; tts: boolean; } @@ -516,6 +520,7 @@ declare namespace Eris { icon?: string; region?: string; roles?: PartialRole[]; + systemChannelID: string; verificationLevel?: number; } interface GetPruneOptions { @@ -1306,6 +1311,7 @@ declare namespace Eris { ): Promise; editGuildIntegration(guildID: string, integrationID: string, options: IntegrationOptions): Promise; editGuildMember(guildID: string, memberID: string, options: MemberOptions, reason?: string): Promise; + editGuildWidget(guildID: string, options: Widget): Promise editMessage(channelID: string, messageID: string, content: MessageContent): Promise; editNickname(guildID: string, nick: string, reason?: string): Promise; editRole(guildID: string, roleID: string, options: RoleOptions, reason?: string): Promise; // TODO not all options are available? @@ -1348,6 +1354,7 @@ declare namespace Eris { getGuildPreview(guildID: string): Promise; getGuildVanity(guildID: string): Promise<{ code?: string; uses?: number }>; getGuildWebhooks(guildID: string): Promise; + getGuildWidget(guildID: string): Promise; getInvite(inviteID: string, withCounts?: false): Promise>; getInvite(inviteID: string, withCounts: true): Promise>; getMessage(channelID: string, messageID: string): Promise; @@ -1683,7 +1690,6 @@ declare namespace Eris { position: number; type: Exclude; constructor(data: BaseData, guild: Guild); - createInvite(options?: CreateInviteOptions, reason?: string): Promise>; delete(reason?: string): Promise; deletePermission(overwriteID: string, reason?: string): Promise; edit(options: Omit, reason?: string): Promise; @@ -1825,8 +1831,8 @@ declare namespace Eris { edit(content: MessageContent): Promise>; getReaction(reaction: string, limit?: number, before?: string, after?: string): Promise; pin(): Promise; - removeReactionEmoji(reaction: string): Promise; removeReaction(reaction: string, userID?: string): Promise; + removeReactionEmoji(reaction: string): Promise; removeReactions(): Promise; unpin(): Promise; } @@ -2095,9 +2101,9 @@ declare namespace Eris { type: 2; userLimit?: number; voiceMembers: Collection; - getInvites(): Promise<(Invite & InviteWithMetadata)[]>; createInvite(options?: CreateInviteOptions, reason?: string): Promise>; - join(options: VoiceResourceOptions): Promise; + getInvites(): Promise<(Invite & InviteWithMetadata)[]>; + join(options: { opusOnly?: boolean; shared?: boolean }): Promise; leave(): void; } diff --git a/lib/Client.js b/lib/Client.js index a9f548235..20022f037 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -49,77 +49,80 @@ const sleep = (ms) => new Promise((res) => setTimeout(res, ms)); /** * Represents the main Eris client * @extends EventEmitter -* @prop {String} token The bot user token * @prop {Boolean?} bot Whether the bot user belongs to an OAuth2 application -* @prop {Object} options Eris options * @prop {Object} channelGuildMap Object mapping channel IDs to guild IDs -* @prop {Collection} shards Collection of shards Eris is using +* @prop {String} gatewayURL The URL for the discord gateway +* @prop {Collection} groupChannels Collection of group channels the bot is in (user accounts only) * @prop {Collection} guilds Collection of guilds the bot is in +* @prop {Object} guildShardMap Object mapping guild IDs to shard IDs +* @prop {Object} notes Object mapping user IDs to user notes (user accounts only) +* @prop {Object} options Eris options * @prop {Object} privateChannelMap Object mapping user IDs to private channel IDs * @prop {Collection} privateChannels Collection of private channels the bot is in -* @prop {Collection} groupChannels Collection of group channels the bot is in (user accounts only) -* @prop {Collection} voiceConnections Extended collection of active VoiceConnections the bot has -* @prop {Object} guildShardMap Object mapping guild IDs to shard IDs +* @prop {Collection} relationships Collection of relationships the bot user has (user accounts only) +* @prop {RequestHandler} requestHandler The request handler the client will use +* @prop {Collection} shards Collection of shards Eris is using * @prop {Number} startTime Timestamp of bot ready event +* @prop {String} token The bot user token * @prop {Collection} unavailableGuilds Collection of unavailable guilds the bot is in * @prop {Number} uptime How long in milliseconds the bot has been up for * @prop {ExtendedUser} user The bot user -* @prop {Collection} users Collection of users the bot sees -* @prop {Collection} relationships Collection of relationships the bot user has (user accounts only) * @prop {Object} userGuildSettings Object mapping guild IDs to individual guild settings for the bot user (user accounts only) +* @prop {Collection} users Collection of users the bot sees * @prop {Object} userSettings Object containing the user account settings (user accounts only) -* @prop {Object} notes Object mapping user IDs to user notes (user accounts only) +* @prop {Collection} voiceConnections Extended collection of active VoiceConnections the bot has */ class Client extends EventEmitter { /** * Create a Client * @arg {String} token bot token * @arg {Object} [options] Eris options (all options are optional) - * @arg {Boolean} [options.autoreconnect=true] Have Eris autoreconnect when connection is lost - * @arg {Boolean} [options.compress=false] Whether to request WebSocket data to be compressed or not - * @arg {Number} [options.connectionTimeout=30000] How long in milliseconds to wait for the connection to handshake with the server - * @arg {Object} [options.disableEvents] If disableEvents[eventName] is true, the WS event will not be processed. This can cause significant performance increase on large bots. [A full list of the WS event names can be found on the docs reference page](/Eris/docs/reference#ws-event-names) + * @arg {Object} [options.agent] A HTTP Agent used to proxy requests * @arg {Object} [options.allowedMentions] A list of mentions to allow by default in createMessage/editMessage * @arg {Boolean} [options.allowedMentions.everyone] Whether or not to allow @everyone/@here. * @arg {Boolean | Array} [options.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. * @arg {Boolean | Array} [options.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. + * @arg {Boolean} [options.autoreconnect=true] Have Eris autoreconnect when connection is lost + * @arg {Boolean} [options.compress=false] Whether to request WebSocket data to be compressed or not + * @arg {Number} [options.connectionTimeout=30000] How long in milliseconds to wait for the connection to handshake with the server + * @arg {String} [options.defaultImageFormat="jpg"] The default format to provide user avatars, guild icons, and group icons in. Can be "jpg", "png", "gif", or "webp" + * @arg {Number} [options.defaultImageSize=128] The default size to return user avatars, guild icons, banners, splashes, and group icons. Can be any power of two between 16 and 2048. If the height and width are different, the width will be the value specified, and the height relative to that + * @arg {Object} [options.disableEvents] If disableEvents[eventName] is true, the WS event will not be processed. This can cause significant performance increase on large bots. [A full list of the WS event names can be found on the docs reference page](/Eris/docs/reference#ws-event-names) * @arg {Number} [options.firstShardID=0] The ID of the first shard to run for this client * @arg {Boolean} [options.getAllUsers=false] Get all the users in every guild. Ready time will be severely delayed * @arg {Number} [options.guildCreateTimeout=2000] How long in milliseconds to wait for a GUILD_CREATE before "ready" is fired. Increase this value if you notice missing guilds * @arg {Boolean} [options.guildSubscriptions=true] If false, disables some guild subscription events, including typing and presence events. This will reduce processing load, but will also result in inconsistent member caching + * @arg {Number | String[]} [options.intents] A list of intents, or raw bitmask value describing the intents to subscribe to. "presence" intent must be enabled on your application's page to be used. * @arg {Number} [options.largeThreshold=250] The maximum number of offline users per guild during initial guild data transmission - * @arg {Number} [options.latencyThreshold=30000] The average request latency at which Eris will start emitting latency errors * @arg {Number} [options.lastShardID=options.maxShards - 1] The ID of the last shard to run for this client + * @arg {Number} [options.latencyThreshold=30000] The average request latency at which Eris will start emitting latency errors + * @arg {Number} [options.maxReconnectAttempts=Infinity] The maximum amount of times that the client is allowed to try to reconnect to Discord. + * @arg {Number} [options.maxResumeAttempts=10] The maximum amount of times a shard can attempt to resume a session before considering that session invalid. * @arg {Number | String} [options.maxShards=1] The total number of shards you want to run. If "auto" Eris will use Discord's recommended shard count. * @arg {Number} [options.messageLimit=100] The maximum size of a channel message cache * @arg {Boolean} [options.opusOnly=false] Whether to suppress the node-opus not found error or not * @arg {Number} [options.ratelimiterOffset=0] A number of milliseconds to offset the ratelimit timing calculations by * @arg {Number} [options.requestTimeout=15000] A number of milliseconds before requests are considered timed out + * @arg {Function} [options.reconnectDelay] A function which returns how long the bot should wait until reconnecting to Discord. * @arg {Boolean} [options.restMode=false] Whether to enable getting objects over REST. This should only be enabled if you are not connecting to the gateway. Bot tokens must be prefixed manually in REST mode * @arg {Boolean} [options.seedVoiceConnections=false] Whether to populate bot.voiceConnections with existing connections the bot account has during startup. Note that this will disconnect connections from other bot sessions - * @arg {String} [options.defaultImageFormat="jpg"] The default format to provide user avatars, guild icons, and group icons in. Can be "jpg", "png", "gif", or "webp" - * @arg {Number} [options.defaultImageSize=128] The default size to return user avatars, guild icons, banners, splashes, and group icons. Can be any power of two between 16 and 2048. If the height and width are different, the width will be the value specified, and the height relative to that * @arg {Object} [options.ws] An object of WebSocket options to pass to the shard WebSocket constructors - * @arg {Object} [options.agent] A HTTP Agent used to proxy requests - * @arg {Number} [options.maxReconnectAttempts=Infinity] The maximum amount of times that the client is allowed to try to reconnect to Discord. - * @arg {Number} [options.maxResumeAttempts=10] The maximum amount of times a shard can attempt to resume a session before considering that session invalid. - * @arg {Function} [options.reconnectDelay] A function which returns how long the bot should wait until reconnecting to Discord. - * @arg {Number | String[]} [options.intents] A list of intents, or raw bitmask value describing the intents to subscribe to. "presence" intent must be enabled on your application's page to be used. */ constructor(token, options) { super(); this.options = Object.assign({ + agent: null, + allowedMentions: { + users: true, + roles: true + }, autoreconnect: true, compress: false, connectionTimeout: 30000, defaultImageFormat: "jpg", defaultImageSize: 128, disableEvents: {}, - allowedMentions: { - users: true, - roles: true - }, firstShardID: 0, getAllUsers: false, guildCreateTimeout: 2000, @@ -136,7 +139,6 @@ class Client extends EventEmitter { restMode: false, seedVoiceConnections: false, ws: {}, - agent: null, reconnectDelay: (lastDelay, attempts) => Math.pow(attempts + 1, 0.7) * 20000 }, options); this.options.allowedMentions = this._formatAllowedMentions(this.options.allowedMentions); @@ -260,25 +262,6 @@ class Client extends EventEmitter { } } - /** - * Get info on connecting to the Discord gateway - * @returns {Promise} Resolves with an object containing gateway connection info - */ - getGateway() { - return this.requestHandler.request("GET", Endpoints.GATEWAY); - } - - /** - * Get general and bot-specific info on connecting to the Discord gateway (e.g. connection ratelimit) - * @returns {Promise} Resolves with an object containing gateway connection info - */ - getBotGateway() { - if(!this.token.startsWith("Bot ")) { - this.token = "Bot " + this.token; - } - return this.requestHandler.request("GET", Endpoints.GATEWAY_BOT, true); - } - /** * Disconnects all shards * @arg {Object?} [options] Shard disconnect options @@ -293,109 +276,110 @@ class Client extends EventEmitter { } /** - * Join a voice channel. If joining a group call, the voice connection ID will be stored in voiceConnections as "call". Otherwise, it will be the guild ID - * @arg {String} channelID The ID of the voice channel - * @arg {Object} [options] VoiceConnection constructor options - * @arg {Object} [options.shared] Whether the VoiceConnection will be part of a SharedStream or not - * @arg {Object} [options.opusOnly] Skip opus encoder initialization. You should not enable this unless you know what you are doing - * @returns {Promise} Resolves with a VoiceConnection + * [USER ACCOUNT] Accept an invite + * @arg {String} inviteID The ID of the invite + * @returns {Promise} */ - joinVoiceChannel(channelID, options = {}) { - const channel = this.getChannel(channelID); - if(!channel) { - return Promise.reject(new Error("Channel not found")); - } - if(channel.guild && !(channel.permissionsOf(this.user.id).allow & Constants.Permissions.voiceConnect)) { - return Promise.reject(new Error("Insufficient permission to connect to voice channel")); - } - this.shards.get(this.guildShardMap[this.channelGuildMap[channelID]] || 0).sendWS(Constants.GatewayOPCodes.VOICE_STATE_UPDATE, { - guild_id: this.channelGuildMap[channelID] || null, - channel_id: channelID || null, - self_mute: false, - self_deaf: false - }); - if(options.opusOnly === undefined) { - options.opusOnly = this.options.opusOnly; - } - return this.voiceConnections.join(this.channelGuildMap[channelID] || "call", channelID, options); + acceptInvite(inviteID) { + return this.requestHandler.request("POST", Endpoints.INVITE(inviteID), true).then((invite) => new Invite(invite, this)); } /** - * Leaves a voice channel - * @arg {String} channelID The ID of the voice channel + * [USER ACCOUNT] Add a user to a group + * @arg {String} groupID The ID of the target group + * @arg {String} userID The ID of the target user + * @returns {Promise} */ - leaveVoiceChannel(channelID) { - if(!channelID || !this.channelGuildMap[channelID]) { - return; - } - this.closeVoiceConnection(this.channelGuildMap[channelID]); + addGroupRecipient(groupID, userID) { + return this.requestHandler.request("PUT", Endpoints.CHANNEL_RECIPIENT(groupID, userID), true); } /** - * Closes a voice connection with a guild ID + * Add a role to a guild member * @arg {String} guildID The ID of the guild + * @arg {String} memberID The ID of the member + * @arg {String} roleID The ID of the role + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - closeVoiceConnection(guildID) { - this.shards.get(this.guildShardMap[guildID] || 0).sendWS(Constants.GatewayOPCodes.VOICE_STATE_UPDATE, { - guild_id: guildID || null, - channel_id: null, - self_mute: false, - self_deaf: false + addGuildMemberRole(guildID, memberID, roleID, reason) { + return this.requestHandler.request("PUT", Endpoints.GUILD_MEMBER_ROLE(guildID, memberID, roleID), true, { + reason }); - this.voiceConnections.leave(guildID || "call"); } /** - * Update the bot's AFK status. Setting this to true will enable push notifications for userbots. - * @arg {Boolean} afk Whether the bot user is AFK or not + * Add a reaction to a message + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @arg {String} [userID="@me"] The ID of the user to react as + * @returns {Promise} */ - editAFK(afk) { - this.presence.afk = !!afk; + addMessageReaction(channelID, messageID, reaction, userID) { + if(reaction === decodeURI(reaction)) { + reaction = encodeURIComponent(reaction); + } + return this.requestHandler.request("PUT", Endpoints.CHANNEL_MESSAGE_REACTION_USER(channelID, messageID, reaction, userID || "@me"), true); + } - this.shards.forEach((shard) => { - shard.editAFK(afk); + /** + * [USER ACCOUNT] Create a relationship with a user + * @arg {String} userID The ID of the target user + * @arg {Boolean} [block=false] If true, block the user. Otherwise, add the user as a friend + * @returns {Promise} + */ + addRelationship(userID, block) { + return this.requestHandler.request("PUT", Endpoints.USER_RELATIONSHIP("@me", userID), true, { + type: block ? 2 : undefined }); } /** - * Update the bot's status on all guilds - * @arg {String} [status] Sets the bot's status, either "online", "idle", "dnd", or "invisible" - * @arg {Object} [game] Sets the bot's active game, null to clear - * @arg {String} game.name Sets the name of the bot's active game - * @arg {Number} [game.type] The type of game. 0 is playing, 1 is streaming (Twitch only), 2 is listening, 3 is watching - * @arg {String} [game.url] Sets the url of the shard's active game + * [USER ACCOUNT] Purchase a premium subscription (Nitro) for the current user + * You must get a Stripe card token from the Stripe API for this to work + * @arg {String} token The Stripe credit card token + * @arg {String} plan The plan to purchase, either "premium_month" or "premium_year" + * @returns {Promise} */ - editStatus(status, game) { - if(game === undefined && typeof status === "object") { - game = status; - status = undefined; - } - if(status) { - this.presence.status = status; - } - if(game !== undefined) { - this.presence.game = game; - } - - this.shards.forEach((shard) => { - shard.editStatus(status, game); + addSelfPremiumSubscription(token, plan) { + return this.requestHandler.request("PUT", Endpoints.USER_BILLING_PREMIUM_SUBSCRIPTION("@me"), true, { + token: token, + payment_gateway: "stripe", + plan: plan }); } /** - * Get a Channel object from a channel ID - * @arg {String} channelID The ID of the channel - * @returns {CategoryChannel | GroupChannel | PrivateChannel | TextChannel | VoiceChannel | NewsChannel} + * Ban a user from a guild + * @arg {String} guildID The ID of the guild + * @arg {String} userID The ID of the user + * @arg {Number} [deleteMessageDays=0] Number of days to delete messages for, between 0-7 inclusive + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - getChannel(channelID) { - if(!channelID) { - throw new Error(`Invalid channel ID: ${channelID}`); + banGuildMember(guildID, userID, deleteMessageDays, reason) { + if(!isNaN(deleteMessageDays) && (deleteMessageDays < 0 || deleteMessageDays > 7)) { + return Promise.reject(new Error(`Invalid deleteMessageDays value (${deleteMessageDays}), should be a number between 0-7 inclusive`)); } + return this.requestHandler.request("PUT", Endpoints.GUILD_BAN(guildID, userID), true, { + delete_message_days: deleteMessageDays || 0, + reason: reason + }); + } - if(this.channelGuildMap[channelID] && this.guilds.get(this.channelGuildMap[channelID])) { - return this.guilds.get(this.channelGuildMap[channelID]).channels.get(channelID); - } - return this.privateChannels.get(channelID) || this.groupChannels.get(channelID); + /** + * Closes a voice connection with a guild ID + * @arg {String} guildID The ID of the guild + */ + closeVoiceConnection(guildID) { + this.shards.get(this.guildShardMap[guildID] || 0).sendWS(Constants.GatewayOPCodes.VOICE_STATE_UPDATE, { + guild_id: guildID || null, + channel_id: null, + self_mute: false, + self_deaf: false + }); + this.voiceConnections.leave(guildID || "call"); } /** @@ -404,13 +388,13 @@ class Client extends EventEmitter { * @arg {String} name The name of the channel * @arg {String} [type=0] The type of the channel, either 0 (text), 2 (voice), or 4 (category) * @arg {Object | String} [options] The properties the channel should have. If `options` is a string, it will be treated as `options.parentID` (see below). Passing a string is deprecated and will not be supported in future versions. - * @arg {String} [options.topic] The topic of the channel (text channels only) - * @arg {Boolean} [options.nsfw] The nsfw status of the channel * @arg {Number} [options.bitrate] The bitrate of the channel (voice channels only) + * @arg {Boolean} [options.nsfw] The nsfw status of the channel * @arg {String?} [options.parentID] The ID of the parent category channel for this channel * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (does not affect bots or users with manageMessages/manageChannel permissions) (text channels only) * @arg {String} [options.reason] The reason to be displayed in audit logs + * @arg {String} [options.topic] The topic of the channel (text channels only) * @arg {Number} [options.userLimit] The channel user limit (voice channels only) * @returns {Promise} */ @@ -432,118 +416,198 @@ class Client extends EventEmitter { return this.requestHandler.request("POST", Endpoints.GUILD_CHANNELS(guildID), true, { name: name, type: type, - reason: options.reason, - topic: options.topic, - nsfw: options.nsfw, bitrate: options.bitrate, - user_limit: options.userLimit, - rate_limit_per_user: options.rateLimitPerUser, + nsfw: options.nsfw, parent_id: options.parentID, - permission_overwrites: options.permissionOverwrites + permission_overwrites: options.permissionOverwrites, + rate_limit_per_user: options.rateLimitPerUser, + reason: options.reason, + topic: options.topic, + user_limit: options.userLimit }).then((channel) => Channel.from(channel, this)); } /** - * Edit a channel's properties + * Create an invite for a channel * @arg {String} channelID The ID of the channel - * @arg {Object} options The properties to edit - * @arg {String} [options.name] The name of the channel - * @arg {String} [options.icon] The icon of the channel as a base64 data URI (group channels only). Note: base64 strings alone are not base64 data URI strings - * @arg {String} [options.ownerID] The ID of the channel owner (group channels only) - * @arg {String} [options.topic] The topic of the channel (guild text channels only) - * @arg {Boolean} [options.nsfw] The nsfw status of the channel (guild channels only) - * @arg {Number} [options.bitrate] The bitrate of the channel (guild voice channels only) - * @arg {Number} [options.userLimit] The channel user limit (guild voice channels only) - * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (does not affect bots or users with manageMessages/manageChannel permissions) (guild text channels only) - * @arg {String?} [options.parentID] The ID of the parent channel category for this channel (guild text/voice channels only) + * @arg {Object} [options] Invite generation options + * @arg {Number} [options.maxAge] How long the invite should last in seconds + * @arg {Number} [options.maxUses] How many uses the invite should last for + * @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not + * @arg {Boolean} [options.unique] Whether the invite is unique or not * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * @returns {Promise} */ - editChannel(channelID, options, reason) { - return this.requestHandler.request("PATCH", Endpoints.CHANNEL(channelID), true, { - name: options.name, - icon: options.icon, - owner_id: options.ownerID, - topic: options.topic, - nsfw: options.nsfw, - bitrate: options.bitrate, - user_limit: options.userLimit, - rate_limit_per_user: options.rateLimitPerUser, - parent_id: options.parentID, + createChannelInvite(channelID, options = {}, reason) { + return this.requestHandler.request("POST", Endpoints.CHANNEL_INVITES(channelID), true, { + max_age: options.maxAge, + max_uses: options.maxUses, + temporary: options.temporary, + unique: options.unique, reason: reason - }).then((channel) => Channel.from(channel, this)); + }).then((invite) => new Invite(invite, this)); } /** - * Edit a guild channel's position. Note that channel position numbers are lowest on top and highest at the bottom. - * @arg {String} channelID The ID of the channel - * @arg {Number} position The new position of the channel - * @returns {Promise} + * Create a channel webhook + * @arg {String} channelID The ID of the channel to create the webhook in + * @arg {Object} options Webhook options + * @arg {String} options.name The default name + * @arg {String} options.avatar The default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} Resolves with a webhook object */ - editChannelPosition(channelID, position) { - let channels = this.guilds.get(this.channelGuildMap[channelID]).channels; - const channel = channels.get(channelID); - if(!channel) { - return Promise.reject(new Error(`Channel ${channelID} not found`)); - } - if(channel.position === position) { - return Promise.resolve(); - } - const min = Math.min(position, channel.position); - const max = Math.max(position, channel.position); - channels = channels.filter((chan) => { - return chan.type === channel.type - && min <= chan.position - && chan.position <= max - && chan.id !== channelID; - }).sort((a, b) => a.position - b.position); - if(position > channel.position) { - channels.push(channel); - } else { - channels.unshift(channel); + createChannelWebhook(channelID, options, reason) { + options.reason = reason; + return this.requestHandler.request("POST", Endpoints.CHANNEL_WEBHOOKS(channelID), true, options); + } + + /** + * [USER ACCOUNT] Create a group channel with other users + * @arg {String[]} userIDs The IDs of the other users + * @returns {Promise} + */ + createGroupChannel(userIDs) { + return this.requestHandler.request("POST", Endpoints.USER_CHANNELS("@me"), true, { + recipients: userIDs, + type: 3 + }).then((privateChannel) => new GroupChannel(privateChannel, this)); + } + + /** + * Create a guild + * @arg {String} name The name of the guild + * @arg {Object} options The properties of the guild + * @arg {String} [options.afkChannelID] The ID of the AFK voice channel + * @arg {Number} [options.afkTimeout] The AFK timeout in seconds + * @arg {Array} [options.channels] The new channels of the guild. IDs are placeholders which allow use of category channels. + * @arg {Number} [options.defaultNotifications] The default notification settings for the guild. 0 is "All Messages", 1 is "Only @mentions". + * @arg {Number} [options.explicitContentFilter] The level of the explicit content filter for messages/images in the guild. 0 disables message scanning, 1 enables scanning the messages of members without roles, 2 enables scanning for all messages. + * @arg {String} [options.icon] The guild icon as a base64 data URI. Note: base64 strings alone are not base64 data URI strings + * @arg {String} [options.region] The region of the guild + * @arg {Array} [options.roles] The new roles of the guild, the first one is the @everyone role. IDs are placeholders which allow channel overwrites. + * @arg {String} [options.systemChannelID] The ID of the system channel + * @arg {Number} [options.verificationLevel] The guild verification level + * @returns {Promise} + */ + createGuild(name, options) { + if(this.guilds.size > 9) { + throw new Error("This method can't be used when in 10 or more guilds."); } - return this.requestHandler.request("PATCH", Endpoints.GUILD_CHANNELS(this.channelGuildMap[channelID]), true, channels.map((channel, index) => ({ - id: channel.id, - position: index + min - }))); + + return this.requestHandler.request("POST", Endpoints.GUILDS, true, { + name: name, + region: options.region, + icon: options.icon, + verification_level: options.verificationLevel, + default_message_notifications: options.defaultNotifications, + explicit_content_filter: options.explicitContentFilter, + system_channel_id: options.systemChannelID, + afk_channel_id: options.afkChannelID, + afk_timeout: options.afkTimeout, + roles: options.roles, + channels: options.channels + }).then((guild) => new Guild(guild, this)); } /** - * Delete a guild channel, or leave a private or group channel - * @arg {String} channelID The ID of the channel + * Create a guild emoji object + * @arg {String} guildID The ID of the guild to create the emoji in + * @arg {Object} options Emoji options + * @arg {String} options.image The base 64 encoded string + * @arg {String} options.name The name of emoji + * @arg {Array} [options.roles] An array containing authorized role IDs * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * @returns {Promise} A guild emoji object */ - deleteChannel(channelID, reason) { - return this.requestHandler.request("DELETE", Endpoints.CHANNEL(channelID), true, { - reason - }); + createGuildEmoji(guildID, options, reason) { + options.reason = reason; + return this.requestHandler.request("POST", Endpoints.GUILD_EMOJIS(guildID), true, options); } /** - * Send typing status in a channel + * Create a message in a channel + * Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel for a user * @arg {String} channelID The ID of the channel - * @returns {Promise} + * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: + * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) + * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. + * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. + * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. + * @arg {String} content.content A content string + * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure + * @arg {Boolean} [content.tts] Set the message TTS flag + * @arg {Object | Object[]} [file] A file object (or an Array of them) + * @arg {Buffer} file.file A buffer containing file data + * @arg {String} file.name What to name the file + * @returns {Promise} */ - sendChannelTyping(channelID) { - return this.requestHandler.request("POST", Endpoints.CHANNEL_TYPING(channelID), true); + createMessage(channelID, content, file) { + if(content !== undefined) { + if(typeof content !== "object" || content === null) { + content = { + content: "" + content + }; + } else if(content.content !== undefined && typeof content.content !== "string") { + content.content = "" + content.content; + } else if(content.content === undefined && !content.embed && !file) { + return Promise.reject(new Error("No content, file, or embed")); + } + content.allowed_mentions = this._formatAllowedMentions(content.allowedMentions); + } else if(!file) { + return Promise.reject(new Error("No content, file, or embed")); + } + return this.requestHandler.request("POST", Endpoints.CHANNEL_MESSAGES(channelID), true, content, file).then((message) => new Message(message, this)); } /** - * Create a channel permission overwrite - * @arg {String} channelID The ID of channel - * @arg {String} overwriteID The ID of the overwritten user or role (everyone role ID = guild ID) - * @arg {Number} allow The permissions number for allowed permissions - * @arg {Number} deny The permissions number for denied permissions - * @arg {String} type The object type of the overwrite, either "member" or "role" + * Create a guild role + * @arg {String} guildID The ID of the guild to create the role in + * @arg {Object|Role} [options] An object or Role containing the properties to set + * @arg {Number} [options.color] The hex color of the role, in number form (ex: 0x3d15b3 or 4040115) + * @arg {Boolean} [options.hoist] Whether to hoist the role in the user list or not + * @arg {Boolean} [options.mentionable] Whether the role is mentionable or not + * @arg {String} [options.name] The name of the role + * @arg {Number} [options.permissions] The role permissions number + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + createRole(guildID, options, reason) { + return this.requestHandler.request("POST", Endpoints.GUILD_ROLES(guildID), true, { + name: options.name, + permissions: options.permissions instanceof Permission ? options.permissions.allow : options.permissions, + color: options.color, + hoist: options.hoist, + mentionable: options.mentionable, + reason: reason + }).then((role) => { + const guild = this.guilds.get(guildID); + if(guild) { + return guild.roles.add(role, guild); + } else { + return new Role(role); + } + }); + } + + /** + * Crosspost (publish) a message to subscribed channels + * @arg {String} channelID The ID of the NewsChannel + * @arg {String} messageID The ID of the message + * @returns {Promise} + */ + crosspostMessage(channelID, messageID) { + return this.requestHandler.request("POST", Endpoints.CHANNEL_CROSSPOST(channelID, messageID), true).then((message) => new Message(message, this)); + } + + /** + * Delete a guild channel, or leave a private or group channel + * @arg {String} channelID The ID of the channel * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - editChannelPermission(channelID, overwriteID, allow, deny, type, reason) { - return this.requestHandler.request("PUT", Endpoints.CHANNEL_PERMISSION(channelID, overwriteID), true, { - allow, - deny, - type, + deleteChannel(channelID, reason) { + return this.requestHandler.request("DELETE", Endpoints.CHANNEL(channelID), true, { reason }); } @@ -562,133 +626,132 @@ class Client extends EventEmitter { } /** - * Get all invites in a channel - * @arg {String} channelID The ID of the channel - * @returns {Promise} + * Delete a guild (bot user must be owner) + * @arg {String} guildID The ID of the guild + * @returns {Promise} */ - getChannelInvites(channelID) { - return this.requestHandler.request("GET", Endpoints.CHANNEL_INVITES(channelID), true).then((invites) => invites.map((invite) => new Invite(invite, this))); + deleteGuild(guildID) { + return this.requestHandler.request("DELETE", Endpoints.GUILD(guildID), true); } /** - * Create an invite for a channel - * @arg {String} channelID The ID of the channel - * @arg {Object} [options] Invite generation options - * @arg {Number} [options.maxAge] How long the invite should last in seconds - * @arg {Number} [options.maxUses] How many uses the invite should last for - * @arg {Boolean} [options.temporary] Whether the invite grants temporary membership or not - * @arg {Boolean} [options.unique] Whether the invite is unique or not + * Delete a guild emoji object + * @arg {String} guildID The ID of the guild to delete the emoji in + * @arg {String} emojiID The ID of the emoji * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * @returns {Promise} */ - createChannelInvite(channelID, options = {}, reason) { - return this.requestHandler.request("POST", Endpoints.CHANNEL_INVITES(channelID), true, { - max_age: options.maxAge, - max_uses: options.maxUses, - temporary: options.temporary, - unique: options.unique, - reason: reason - }).then((invite) => new Invite(invite, this)); + deleteGuildEmoji(guildID, emojiID, reason) { + return this.requestHandler.request("DELETE", Endpoints.GUILD_EMOJI(guildID, emojiID), true, { + reason + }); } /** - * Get all the webhooks in a channel - * @arg {String} channelID The ID of the channel to get webhooks for - * @returns {Promise} Resolves with an array of webhook objects + * Delete a guild integration + * @arg {String} guildID The ID of the guild + * @arg {String} integrationID The ID of the integration + * @returns {Promise} */ - getChannelWebhooks(channelID) { - return this.requestHandler.request("GET", Endpoints.CHANNEL_WEBHOOKS(channelID), true); + deleteGuildIntegration(guildID, integrationID) { + return this.requestHandler.request("DELETE", Endpoints.GUILD_INTEGRATION(guildID, integrationID), true); } /** - * Get a webhook - * @arg {String} webhookID The ID of the webhook - * @arg {String} [token] The token of the webhook, used instead of the Bot Authorization token - * @returns {Promise} Resolves with a webhook object + * Delete an invite + * @arg {String} inviteID The ID of the invite + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - getWebhook(webhookID, token) { - return this.requestHandler.request("GET", token ? Endpoints.WEBHOOK_TOKEN(webhookID, token) : Endpoints.WEBHOOK(webhookID), !token); + deleteInvite(inviteID, reason) { + return this.requestHandler.request("DELETE", Endpoints.INVITE(inviteID), true, { + reason + }); } /** - * Create a channel webhook - * @arg {String} channelID The ID of the channel to create the webhook in - * @arg {Object} options Webhook options - * @arg {String} options.name The default name - * @arg {String} options.avatar The default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings + * Delete a message + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} Resolves with a webhook object + * @returns {Promise} */ - createChannelWebhook(channelID, options, reason) { - options.reason = reason; - return this.requestHandler.request("POST", Endpoints.CHANNEL_WEBHOOKS(channelID), true, options); + deleteMessage(channelID, messageID, reason) { + return this.requestHandler.request("DELETE", Endpoints.CHANNEL_MESSAGE(channelID, messageID), true, { + reason + }); } /** - * Edit a webhook - * @arg {String} webhookID The ID of the webhook - * @arg {Object} options Webhook options - * @arg {String} [options.name] The new default name - * @arg {String} [options.avatar] The new default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings - * @arg {String} [token] The token of the webhook, used instead of the Bot Authorization token + * Bulk delete messages (bot accounts only) + * @arg {String} channelID The ID of the channel + * @arg {String[]} messageIDs Array of message IDs to delete * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} Resolves with a webhook object + * @returns {Promise} */ - editWebhook(webhookID, options, token, reason) { - options.reason = reason; - return this.requestHandler.request("PATCH", token ? Endpoints.WEBHOOK_TOKEN(webhookID, token) : Endpoints.WEBHOOK(webhookID), !token, options); + deleteMessages(channelID, messageIDs, reason) { + if(messageIDs.length === 0) { + return Promise.resolve(); + } + if(messageIDs.length === 1) { + return this.deleteMessage(channelID, messageIDs[0]); + } + + const oldestAllowedSnowflake = (Date.now() - 1421280000000) * 4194304; + const invalidMessage = messageIDs.find((messageID) => messageID < oldestAllowedSnowflake); + if(invalidMessage) { + return Promise.reject(new Error(`Message ${invalidMessage} is more than 2 weeks old.`)); + } + + if(messageIDs.length > 100) { + return this.requestHandler.request("POST", Endpoints.CHANNEL_BULK_DELETE(channelID), true, { + messages: messageIDs.splice(0, 100), + reason: reason + }).then(() => this.deleteMessages(channelID, messageIDs)); + } + return this.requestHandler.request("POST", Endpoints.CHANNEL_BULK_DELETE(channelID), true, { + messages: messageIDs, + reason: reason + }); } /** - * Execute a webhook - * @arg {String} webhookID The ID of the webhook - * @arg {String} token The token of the webhook - * @arg {Object} options Webhook execution options - * @arg {Boolean} [options.auth=false] Whether or not to authorize the request with the bot token (allowing custom emotes from other guilds) - * @arg {String} [options.content=""] A content string - * @arg {Object | Object[]} [options.file] A file object (or an Array of them) - * @arg {Buffer} options.file.file A buffer containing file data - * @arg {String} options.file.name What to name the file - * @arg {Object[]} [options.embeds] An array of Discord embeds - * @arg {String} [options.username] A custom username, defaults to webhook default username if not specified - * @arg {String} [options.avatarURL] A URL for a custom avatar, defaults to webhook default avatar if not specified - * @arg {Boolean} [options.tts=false] Whether the message should be a TTS message or not - * @arg {Boolean} [options.wait=false] Whether to wait for the server to confirm the message create or not - * @arg {Object} [options.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [options.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [options.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [options.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @returns {Promise} + * Delete a guild role + * @arg {String} guildID The ID of the guild to create the role in + * @arg {String} roleID The ID of the role + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - executeWebhook(webhookID, token, options) { - if(!options.content && !options.file && !options.embeds) { - return Promise.reject(new Error("No content, file, or embeds")); - } - return this.requestHandler.request("POST", Endpoints.WEBHOOK_TOKEN(webhookID, token) + (options.wait ? "?wait=true" : ""), !!options.auth, { - content: options.content, - embeds: options.embeds, - username: options.username, - avatar_url: options.avatarURL, - tts: options.tts, - allowed_mentions: this._formatAllowedMentions(options.allowedMentions) - }, options.file).then((response) => options.wait ? new Message(response, this) : undefined); + deleteRole(guildID, roleID, reason) { + return this.requestHandler.request("DELETE", Endpoints.GUILD_ROLE(guildID, roleID), true, { + reason + }); } /** - * Execute a slack-style webhook - * @arg {String} webhookID The ID of the webhook - * @arg {String} token The token of the webhook - * @arg {Object} options Slack webhook options - * @arg {Boolean} [options.wait=false] Whether to wait for the server to confirm the message create or not - * @arg {Boolean} [options.auth=false] Whether or not to authorize the request with the bot token (allowing custom emotes from other guilds) + * [USER ACCOUNT] Delete a connection for the current user + * @arg {String} platform The connection platform (e.g. "twitch", "reddit") + * @arg {String} id The connection ID * @returns {Promise} */ - executeSlackWebhook(webhookID, token, options) { - const wait = !!options.wait; - options.wait = undefined; - const auth = !!options.auth; - options.auth = undefined; - return this.requestHandler.request("POST", Endpoints.WEBHOOK_TOKEN_SLACK(webhookID, token) + (wait ? "?wait=true" : ""), auth, options); + deleteSelfConnection(platform, id) { + return this.requestHandler.request("DELETE", Endpoints.USER_CONNECTION_PLATFORM("@me", platform, id), true); + } + + /** + * [USER ACCOUNT] Cancel the premium subscription (Nitro) for the current user + * @returns {Promise} + */ + deleteSelfPremiumSubscription() { + return this.requestHandler.request("DELETE", Endpoints.USER_BILLING_PREMIUM_SUBSCRIPTION("@me"), true); + } + + /** + * [USER ACCOUNT] Delete the current user's note for another user + * @returns {Promise} + */ + deleteUserNote(userID) { + return this.requestHandler.request("DELETE", Endpoints.USER_NOTE("@me", userID), true); } /** @@ -705,106 +768,269 @@ class Client extends EventEmitter { } /** - * Get all the webhooks in a guild - * @arg {String} guildID The ID of the guild to get webhooks for - * @returns {Promise} Resolves with an array of webhook objects - */ - getGuildWebhooks(guildID) { - return this.requestHandler.request("GET", Endpoints.GUILD_WEBHOOKS(guildID), true); - } - - /** - * Get the audit logs for a guild - * @arg {String} guildID The ID of the guild to get audit logs for - * @arg {Number} [limit=50] The maximum number of entries to return - * @arg {String} [before] Get entries before this entry ID - * @arg {Number} [actionType] Filter entries by action type - * @returns {Promise} Resolves with {users: Users[], entries: GuildAuditLogEntry[]} + * [USER ACCOUNT] Disable TOTP authentication for the current user + * @arg {String} code The timed auth code for the current user + * @returns {Promise} An object containing the user's new authorization token */ - getGuildAuditLogs(guildID, limit, before, actionType) { - return this.requestHandler.request("GET", Endpoints.GUILD_AUDIT_LOGS(guildID), true, { - limit: limit || 50, - before: before, - action_type: actionType + disableSelfMFATOTP(code) { + return this.requestHandler.request("POST", Endpoints.USER_MFA_TOTP_DISABLE("@me"), true, { + code }).then((data) => { - const guild = this.guilds.get(guildID); - return { - users: data.users.map((user) => this.users.add(user, this)), - entries: data.audit_log_entries.map((entry) => new GuildAuditLogEntry(entry, guild)) - }; + if(data.token) { + this.token = data.token; + } }); } /** - * Create a guild emoji object - * @arg {String} guildID The ID of the guild to create the emoji in - * @arg {Object} options Emoji options - * @arg {String} options.name The name of emoji - * @arg {String} options.image The base 64 encoded string - * @arg {Array} [options.roles] An array containing authorized role IDs - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} A guild emoji object + * Update the bot's AFK status. Setting this to true will enable push notifications for userbots. + * @arg {Boolean} afk Whether the bot user is AFK or not */ - createGuildEmoji(guildID, options, reason) { - options.reason = reason; - return this.requestHandler.request("POST", Endpoints.GUILD_EMOJIS(guildID), true, options); + editAFK(afk) { + this.presence.afk = !!afk; + + this.shards.forEach((shard) => { + shard.editAFK(afk); + }); } /** - * Edit a guild emoji object - * @arg {String} guildID The ID of the guild to edit the emoji in - * @arg {String} emojiID The ID of the emoji you want to modify - * @arg {Object} options Emoji options - * @arg {String} [options.name] The name of emoji - * @arg {Array} [options.roles] An array containing authorized role IDs + * Edit a channel's properties + * @arg {String} channelID The ID of the channel + * @arg {Object} options The properties to edit + * @arg {Number} [options.bitrate] The bitrate of the channel (guild voice channels only) + * @arg {String} [options.icon] The icon of the channel as a base64 data URI (group channels only). Note: base64 strings alone are not base64 data URI strings + * @arg {String} [options.name] The name of the channel + * @arg {Boolean} [options.nsfw] The nsfw status of the channel (guild channels only) + * @arg {String} [options.ownerID] The ID of the channel owner (group channels only) + * @arg {String?} [options.parentID] The ID of the parent channel category for this channel (guild text/voice channels only) + * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (does not affect bots or users with manageMessages/manageChannel permissions) (guild text channels only) + * @arg {String} [options.topic] The topic of the channel (guild text channels only) + * @arg {Number} [options.userLimit] The channel user limit (guild voice channels only) * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} A guild emoji object + * @returns {Promise} */ - editGuildEmoji(guildID, emojiID, options, reason) { - options.reason = reason; - return this.requestHandler.request("PATCH", Endpoints.GUILD_EMOJI(guildID, emojiID), true, options); + editChannel(channelID, options, reason) { + return this.requestHandler.request("PATCH", Endpoints.CHANNEL(channelID), true, { + bitrate: options.bitrate, + icon: options.icon, + name: options.name, + nsfw: options.nsfw, + owner_id: options.ownerID, + parent_id: options.parentID, + rate_limit_per_user: options.rateLimitPerUser, + topic: options.topic, + user_limit: options.userLimit, + reason: reason + }).then((channel) => Channel.from(channel, this)); } /** - * Delete a guild emoji object - * @arg {String} guildID The ID of the guild to delete the emoji in - * @arg {String} emojiID The ID of the emoji + * Create a channel permission overwrite + * @arg {String} channelID The ID of channel + * @arg {String} overwriteID The ID of the overwritten user or role (everyone role ID = guild ID) + * @arg {Number} allow The permissions number for allowed permissions + * @arg {Number} deny The permissions number for denied permissions + * @arg {String} type The object type of the overwrite, either "member" or "role" * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - deleteGuildEmoji(guildID, emojiID, reason) { - return this.requestHandler.request("DELETE", Endpoints.GUILD_EMOJI(guildID, emojiID), true, { + editChannelPermission(channelID, overwriteID, allow, deny, type, reason) { + return this.requestHandler.request("PUT", Endpoints.CHANNEL_PERMISSION(channelID, overwriteID), true, { + allow, + deny, + type, reason }); } /** - * Create a guild role - * @arg {String} guildID The ID of the guild to create the role in - * @arg {Object|Role} [options] An object or Role containing the properties to set - * @arg {String} [options.name] The name of the role - * @arg {Number} [options.permissions] The role permissions number - * @arg {Number} [options.color] The hex color of the role, in number form (ex: 0x3d15b3 or 4040115) - * @arg {Boolean} [options.hoist] Whether to hoist the role in the user list or not - * @arg {Boolean} [options.mentionable] Whether the role is mentionable or not - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - createRole(guildID, options, reason) { - return this.requestHandler.request("POST", Endpoints.GUILD_ROLES(guildID), true, { + * Edit a guild channel's position. Note that channel position numbers are lowest on top and highest at the bottom. + * @arg {String} channelID The ID of the channel + * @arg {Number} position The new position of the channel + * @returns {Promise} + */ + editChannelPosition(channelID, position) { + let channels = this.guilds.get(this.channelGuildMap[channelID]).channels; + const channel = channels.get(channelID); + if(!channel) { + return Promise.reject(new Error(`Channel ${channelID} not found`)); + } + if(channel.position === position) { + return Promise.resolve(); + } + const min = Math.min(position, channel.position); + const max = Math.max(position, channel.position); + channels = channels.filter((chan) => { + return chan.type === channel.type + && min <= chan.position + && chan.position <= max + && chan.id !== channelID; + }).sort((a, b) => a.position - b.position); + if(position > channel.position) { + channels.push(channel); + } else { + channels.unshift(channel); + } + return this.requestHandler.request("PATCH", Endpoints.GUILD_CHANNELS(this.channelGuildMap[channelID]), true, channels.map((channel, index) => ({ + id: channel.id, + position: index + min + }))); + } + + /** + * Edit a guild + * @arg {String} guildID The ID of the guild + * @arg {Object} options The properties to edit + * @arg {String} [options.afkChannelID] The ID of the AFK voice channel + * @arg {Number} [options.afkTimeout] The AFK timeout in seconds + * @arg {String} [options.banner] The guild banner image as a base64 data URI (VIP only). Note: base64 strings alone are not base64 data URI strings + * @arg {Number} [options.defaultNotifications] The default notification settings for the guild. 0 is "All Messages", 1 is "Only @mentions". + * @arg {String} [options.description] The description for the guild (VIP only) + * @arg {Number} [options.explicitContentFilter] The level of the explicit content filter for messages/images in the guild. 0 disables message scanning, 1 enables scanning the messages of members without roles, 2 enables scanning for all messages. + * @arg {String} [options.icon] The guild icon as a base64 data URI. Note: base64 strings alone are not base64 data URI strings + * @arg {String} [options.name] The ID of the guild + * @arg {String} [options.ownerID] The ID of the user to transfer server ownership to (bot user must be owner) + * @arg {String} [options.preferredLocale] Preferred "PUBLIC" guild language used in server discovery and notices from Discord + * @arg {String} [options.publicUpdatesChannelID] The id of the channel where admins and moderators of "PUBLIC" guilds receive notices from Discord + * @arg {String} [options.region] The region of the guild + * @arg {String} [options.rulesChannelID] The id of the channel where "PUBLIC" guilds display rules and/or guidelines + * @arg {String} [options.splash] The guild splash image as a base64 data URI (VIP only). Note: base64 strings alone are not base64 data URI strings + * @arg {String} [options.systemChannelID] The ID of the system channel + * @arg {Number} [options.verificationLevel] The guild verification level + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + editGuild(guildID, options, reason) { + return this.requestHandler.request("PATCH", Endpoints.GUILD(guildID), true, { name: options.name, - permissions: options.permissions instanceof Permission ? options.permissions.allow : options.permissions, - color: options.color, - hoist: options.hoist, - mentionable: options.mentionable, + region: options.region, + icon: options.icon, + verification_level: options.verificationLevel, + default_message_notifications: options.defaultNotifications, + explicit_content_filter: options.explicitContentFilter, + system_channel_id: options.systemChannelID, + rules_channel_id: options.rulesChannelID, + public_updates_channel_id: options.publicUpdatesChannelID, + preferred_locale: options.preferredLocale, + afk_channel_id: options.afkChannelID, + afk_timeout: options.afkTimeout, + owner_id: options.ownerID, + splash: options.splash, + banner: options.banner, + description: options.description, reason: reason - }).then((role) => { - const guild = this.guilds.get(guildID); - if(guild) { - return guild.roles.add(role, guild); - } else { - return new Role(role); + }).then((guild) => new Guild(guild, this)); + } + + /** + * Edit a guild emoji object + * @arg {String} guildID The ID of the guild to edit the emoji in + * @arg {String} emojiID The ID of the emoji you want to modify + * @arg {Object} options Emoji options + * @arg {String} [options.name] The name of emoji + * @arg {Array} [options.roles] An array containing authorized role IDs + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} A guild emoji object + */ + editGuildEmoji(guildID, emojiID, options, reason) { + options.reason = reason; + return this.requestHandler.request("PATCH", Endpoints.GUILD_EMOJI(guildID, emojiID), true, options); + } + + /** + * Edit a guild integration + * @arg {String} guildID The ID of the guild + * @arg {String} integrationID The ID of the integration + * @arg {Object} options The properties to edit + * @arg {String} [options.enableEmoticons] Whether to enable integration emoticons or not + * @arg {String} [options.expireBehavior] What to do when a user's subscription runs out + * @arg {String} [options.expireGracePeriod] How long before the integration's role is removed from an unsubscribed user + * @returns {Promise} + */ + editGuildIntegration(guildID, integrationID, options) { + return this.requestHandler.request("PATCH", Endpoints.GUILD_INTEGRATION(guildID, integrationID), true, { + expire_behavior: options.expireBehavior, + expire_grace_period: options.expireGracePeriod, + enable_emoticons: options.enableEmoticons + }); + } + + /** + * Edit a guild member + * @arg {String} guildID The ID of the guild + * @arg {String} memberID The ID of the member + * @arg {Object} options The properties to edit + * @arg {String} [options.channelID] The ID of the voice channel to move the member to (must be in voice) + * @arg {Boolean} [options.deaf] Server deafen the member + * @arg {Boolean} [options.mute] Server mute the member + * @arg {String} [options.nick] Set the member's server nickname, "" to remove + * @arg {String[]} [options.roles] The array of role IDs the member should have + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + editGuildMember(guildID, memberID, options, reason) { + return this.requestHandler.request("PATCH", Endpoints.GUILD_MEMBER(guildID, memberID), true, { + roles: options.roles && options.roles.filter((roleID, index) => options.roles.indexOf(roleID) === index), + nick: options.nick, + mute: options.mute, + deaf: options.deaf, + channel_id: options.channelID, + reason: reason + }); + } + + /** + * Modify a guild's widget + * @arg {String} guildID The ID of the guild + * @arg {Object} options The widget object to modify (https://discord.com/developers/docs/resources/guild#modify-guild-widget) + * @returns {Promise} A guild widget object + */ + editGuildWidget(guildID, options) { + return this.requestHandler.request("PATCH", Endpoints.GUILD_WIDGET(guildID), true, options); + } + + /** + * Edit a message + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message + * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: + * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) + * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. + * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. + * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. + * @arg {String} content.content A content string + * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure + * @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#message-object-message-flags) for flags reference + * @returns {Promise} + */ + editMessage(channelID, messageID, content) { + if(content !== undefined) { + if(typeof content !== "object" || content === null) { + content = { + content: "" + content + }; + } else if(content.content !== undefined && typeof content.content !== "string") { + content.content = "" + content.content; + } else if(content.content === undefined && !content.embed && content.flags === undefined) { + return Promise.reject(new Error("No content, embed or flags")); } + content.allowed_mentions = this._formatAllowedMentions(content.allowedMentions); + } + return this.requestHandler.request("PATCH", Endpoints.CHANNEL_MESSAGE(channelID, messageID), true, content).then((message) => new Message(message, this)); + } + + /** + * Edit the bot's nickname in a guild + * @arg {String} guildID The ID of the guild + * @arg {String} nick The nickname + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + editNickname(guildID, nick, reason) { + return this.requestHandler.request("PATCH", Endpoints.GUILD_MEMBER_NICK(guildID, "@me"), true, { + nick, + reason }); } @@ -813,11 +1039,11 @@ class Client extends EventEmitter { * @arg {String} guildID The ID of the guild the role is in * @arg {String} roleID The ID of the role * @arg {Object} options The properties to edit - * @arg {String} [options.name] The name of the role - * @arg {Number} [options.permissions] The role permissions number * @arg {Number} [options.color] The hex color of the role, in number form (ex: 0x3da5b3 or 4040115) * @arg {Boolean} [options.hoist] Whether to hoist the role in the user list or not * @arg {Boolean} [options.mentionable] Whether the role is mentionable or not + * @arg {String} [options.name] The name of the role + * @arg {Number} [options.permissions] The role permissions number * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ @@ -860,420 +1086,209 @@ class Client extends EventEmitter { } /** - * Delete a guild role - * @arg {String} guildID The ID of the guild to create the role in - * @arg {String} roleID The ID of the role - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Edit properties of the bot user + * @arg {Object} options The properties to edit + * @arg {String} [options.username] The new username + * @arg {String} [options.avatar] The new avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings + * @returns {Promise} */ - deleteRole(guildID, roleID, reason) { - return this.requestHandler.request("DELETE", Endpoints.GUILD_ROLE(guildID, roleID), true, { - reason - }); + editSelf(options) { + return this.requestHandler.request("PATCH", Endpoints.USER("@me"), true, options).then((data) => new ExtendedUser(data, this)); } /** - * Get the prune count for a guild - * @arg {String} guildID The ID of the guild - * @arg {Number} [options] The options to use to get number of prune members - * @arg {Number} [options.days=7] The number of days of inactivity to prune for - * @arg {Array} [options.includeRoles] An array of role IDs that members must have to be considered for pruning - * @returns {Promise} Resolves with the number of members that would be pruned + * [USER ACCOUNT] Edit a connection for the current user + * @arg {String} platform The connection platform (e.g. "twitch", "reddit") + * @arg {String} id The connection ID + * @arg {Object} data The connection data + * @arg {Boolean} [data.friendSync] Whether to sync friends from the connection or not + * @arg {Number} [data.visibility] The visibility level for the connection. 0 = hidden, 1 = shown on profile + * @returns {Promise} The updated connection data */ - getPruneCount(guildID, options = {}) { - return this.requestHandler.request("GET", Endpoints.GUILD_PRUNE(guildID), true, { - days: options.days, - include_roles: options.includeRoles - }).then((data) => data.pruned); + editSelfConnection(platform, id, data) { + return this.requestHandler.request("PATCH", Endpoints.USER_CONNECTION_PLATFORM("@me", platform, id), true, { + visibility: data.visibility, + friend_sync: data.friendSync + }); } /** - * Begin pruning a guild - * @arg {String} guildID The ID of the guild - * @arg {Number} [options] The options to pass to prune members - * @arg {Number} [options.days=7] The number of days of inactivity to prune for - * @arg {Boolean} [options.computePruneCount=true] Whether or not the number of pruned members should be returned. Discord discourages setting this to true for larger guilds - * @arg {Array} [options.includeRoles] An array of role IDs that members must have to be considered for pruning - * @arg {String} [options.reason] The reason to be displayed in audit logs - * @returns {Promise} If computePruneCount was true, resolves with the number of pruned members - */ - pruneMembers(guildID, options = {}) { - return this.requestHandler.request("POST", Endpoints.GUILD_PRUNE(guildID), true, { - days: options.days, - compute_prune_count: options.computePruneCount, - include_roles: options.includeRoles, - reason: options.reason - }).then((data) => data.pruned); - } - - /** - * Get a list of general/guild-specific voice regions - * @arg {String} [guildID] The ID of the guild - * @returns {Promise} Resolves with an array of voice region objects - */ - getVoiceRegions(guildID) { - return guildID ? this.requestHandler.request("GET", Endpoints.GUILD_VOICE_REGIONS(guildID), true) : this.requestHandler.request("GET", Endpoints.VOICE_REGIONS, true); - } - - /** - * Get info on an invite - * @arg {String} inviteID The ID of the invite - * @arg {Boolean} [withCounts] Whether to fetch additional invite info or not (approximate member counts, approximate presences, channel counts, etc.) - * @returns {Promise} - */ - getInvite(inviteID, withCounts) { - return this.requestHandler.request("GET", Endpoints.INVITE(inviteID), true, { - with_counts: withCounts - }).then((invite) => new Invite(invite, this)); - } - - /** - * [USER ACCOUNT] Accept an invite - * @arg {String} inviteID The ID of the invite - * @returns {Promise} - */ - acceptInvite(inviteID) { - return this.requestHandler.request("POST", Endpoints.INVITE(inviteID), true).then((invite) => new Invite(invite, this)); - } - - /** - * Delete an invite - * @arg {String} inviteID The ID of the invite - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - deleteInvite(inviteID, reason) { - return this.requestHandler.request("DELETE", Endpoints.INVITE(inviteID), true, { - reason - }); - } - - /** - * Get properties of the bot user - * @returns {Promise} - */ - getSelf() { - return this.requestHandler.request("GET", Endpoints.USER("@me"), true).then((data) => new ExtendedUser(data, this)); - } - - /** - * Edit properties of the bot user - * @arg {Object} options The properties to edit - * @arg {String} [options.username] The new username - * @arg {String} [options.avatar] The new avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings - * @returns {Promise} - */ - editSelf(options) { - return this.requestHandler.request("PATCH", Endpoints.USER("@me"), true, options).then((data) => new ExtendedUser(data, this)); - } - - /** - * Get a DM channel with a user, or create one if it does not exist - * @arg {String} userID The ID of the user - * @returns {Promise} - */ - getDMChannel(userID) { - if(this.privateChannelMap[userID]) { - return Promise.resolve(this.privateChannels.get(this.privateChannelMap[userID])); - } - return this.requestHandler.request("POST", Endpoints.USER_CHANNELS("@me"), true, { - recipients: [userID], - type: 1 - }).then((privateChannel) => new PrivateChannel(privateChannel, this)); - } - - /** - * [USER ACCOUNT] Create a group channel with other users - * @arg {String[]} userIDs The IDs of the other users - * @returns {Promise} - */ - createGroupChannel(userIDs) { - return this.requestHandler.request("POST", Endpoints.USER_CHANNELS("@me"), true, { - recipients: userIDs, - type: 3 - }).then((privateChannel) => new GroupChannel(privateChannel, this)); - } - - /** - * Get a previous message in a channel - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - getMessage(channelID, messageID) { - return this.requestHandler.request("GET", Endpoints.CHANNEL_MESSAGE(channelID, messageID), true).then((message) => new Message(message, this)); - } - - /** - * Get previous messages in a channel - * @arg {String} channelID The ID of the channel - * @arg {Number} [limit=50] The max number of messages to get - * @arg {String} [before] Get messages before this message ID - * @arg {String} [after] Get messages after this message ID - * @arg {String} [around] Get messages around this message ID (does not work with limit > 100) - * @returns {Promise} + * [USER ACCOUNT] Edit settings for the current user + * @arg {Object} data The user settings data + * @arg {Boolean} [data.convertEmoticons] Whether to convert emoticons or not (e.g. :D => 😄) + * @arg {Boolean} [data.detectPlatformAccounts] Whether to automatically detect accounts from other platforms or not (Blizzard, Skype, etc.) + * @arg {Boolean} [data.developerMode] Whether to enable developer mode or not + * @arg {Boolean} [data.enableTTSCommand] Whether to respect usage of the TTS command or not + * @arg {Object} [data.friendSourceFlags] An object representing allowed friend request sources + * @arg {Boolean} [data.friendSourceFlags.all] Whether to allow friends requests from anywhere or not + * @arg {Boolean} [data.friendSourceFlags.mutualFriends] Whether to allow friend requests from people with mutual friends or not + * @arg {Boolean} [data.friendSourceFlags.mutualGuilds] Whether to allow friend requests from people in mutual guilds or not + * @arg {Array} [data.guildPositions] An ordered array of guild IDs representing the guild list order in the Discord client + * @arg {Boolean} [data.inlineAttachmentMedia] Whether to show attachment previews or not + * @arg {Boolean} [data.inlineEmbedMedia] Whether to show embed images or not + * @arg {String} [data.locale] The locale to use for the Discord UI + * @arg {Boolean} [data.messageDisplayCompact] Whether to use compact mode or not + * @arg {Boolean} [data.renderEmbeds] Whether to show embeds or not + * @arg {Boolean} [data.renderReactions] Whether to show reactions or not + * @arg {Array} [data.restrictedGuilds] An array of guild IDs where direct messages from guild members are disallowed + * @arg {Boolean} [data.showCurrentGame] Whether to set the user's status to the current game or not + * @arg {String} [data.status] The status of the user, either "invisible", "dnd", "away", or "online" + * @arg {String} [data.theme] The theme to use for the Discord UI, either "dark" or "light" + * @returns {Promise} The user's settings data. */ - async getMessages(channelID, limit = 50, before, after, around) { - if(limit && limit > 100) { - let logs = []; - const get = async (_before, _after) => { - const messages = await this.requestHandler.request("GET", Endpoints.CHANNEL_MESSAGES(channelID), true, { - limit: 100, - before: _before || undefined, - after: _after || undefined - }); - if(limit <= messages.length) { - return (_after ? messages.slice(messages.length - limit, messages.length).map((message) => new Message(message, this)).concat(logs) : logs.concat(messages.slice(0, limit).map((message) => new Message(message, this)))); - } - limit -= messages.length; - logs = (_after ? messages.map((message) => new Message(message, this)).concat(logs) : logs.concat(messages.map((message) => new Message(message, this)))); - if(messages.length < 100) { - return logs; - } - this.emit("debug", `Getting ${limit} more messages during getMessages for ${channelID}: ${_before} ${_after}`, -1); - return get((_before || !_after) && messages[messages.length - 1].id, _after && messages[0].id); - }; - return get(before, after); - } - const messages = await this.requestHandler.request("GET", Endpoints.CHANNEL_MESSAGES(channelID), true, { - limit, - before, - after, - around - }); - return messages.map((message) => { - try { - return new Message(message, this); - } catch(err) { - this.emit("error", `Error creating message from channel messages\n${err.stack}\n${JSON.stringify(messages)}`); - return null; + editSelfSettings(data) { + let friendSourceFlags = undefined; + if(data.friendSourceFlags) { + friendSourceFlags = {}; + if(data.friendSourceFlags.all) { + friendSourceFlags.all = true; } - }); - } - - /** - * Get all the pins in a channel - * @arg {String} channelID The ID of the channel - * @returns {Promise} - */ - getPins(channelID) { - return this.requestHandler.request("GET", Endpoints.CHANNEL_PINS(channelID), true).then((messages) => messages.map((message) => new Message(message, this))); - } - - /** - * Create a message in a channel - * Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel for a user - * @arg {String} channelID The ID of the channel - * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: - * @arg {String} content.content A content string - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Boolean} [content.tts] Set the message TTS flag - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Object | Object[]} [file] A file object (or an Array of them) - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - createMessage(channelID, content, file) { - if(content !== undefined) { - if(typeof content !== "object" || content === null) { - content = { - content: "" + content - }; - } else if(content.content !== undefined && typeof content.content !== "string") { - content.content = "" + content.content; - } else if(content.content === undefined && !content.embed && !file) { - return Promise.reject(new Error("No content, file, or embed")); + if(data.friendSourceFlags.mutualFriends) { + friendSourceFlags.mutual_friends = true; } - content.allowed_mentions = this._formatAllowedMentions(content.allowedMentions); - } else if(!file) { - return Promise.reject(new Error("No content, file, or embed")); - } - return this.requestHandler.request("POST", Endpoints.CHANNEL_MESSAGES(channelID), true, content, file).then((message) => new Message(message, this)); - } - - /** - * Edit a message - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message - * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: - * @arg {String} content.content A content string - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#message-object-message-flags) for flags reference - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @returns {Promise} - */ - editMessage(channelID, messageID, content) { - if(content !== undefined) { - if(typeof content !== "object" || content === null) { - content = { - content: "" + content - }; - } else if(content.content !== undefined && typeof content.content !== "string") { - content.content = "" + content.content; - } else if(content.content === undefined && !content.embed && content.flags === undefined) { - return Promise.reject(new Error("No content, embed or flags")); + if(data.friendSourceFlags.mutualGuilds) { + friendSourceFlags.mutual_guilds = true; } - content.allowed_mentions = this._formatAllowedMentions(content.allowedMentions); } - return this.requestHandler.request("PATCH", Endpoints.CHANNEL_MESSAGE(channelID, messageID), true, content).then((message) => new Message(message, this)); - } - - /** - * Pin a message - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - pinMessage(channelID, messageID) { - return this.requestHandler.request("PUT", Endpoints.CHANNEL_PIN(channelID, messageID), true); - } - - /** - * Unpin a message - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - unpinMessage(channelID, messageID) { - return this.requestHandler.request("DELETE", Endpoints.CHANNEL_PIN(channelID, messageID), true); + return this.requestHandler.request("PATCH", Endpoints.USER_SETTINGS("@me"), true, { + convert_emoticons: data.convertEmoticons, + detect_platform_accounts: data.detectPlatformAccounts, + developer_mode: data.developerMode, + enable_tts_command: data.enableTTSCommand, + friend_source_flags: friendSourceFlags, + guild_positions: data.guildPositions, + inline_attachment_media: data.inlineAttachmentMedia, + inline_embed_media: data.inlineEmbedMedia, + locale: data.locale, + message_display_compact: data.messageDisplayCompact, + render_embeds: data.renderEmbeds, + render_reactions: data.renderReactions, + restricted_guilds: data.restrictedGuilds, + show_current_game: data.showCurrentGame, + status: data.status, + theme: data.theme + }); } /** - * Get a list of users who reacted with a specific reaction - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {Number} [limit=100] The maximum number of users to get - * @arg {String} [before] Get users before this user ID - * @arg {String} [after] Get users after this user ID - * @returns {Promise} + * Update the bot's status on all guilds + * @arg {String} [status] Sets the bot's status, either "online", "idle", "dnd", or "invisible" + * @arg {Object} [game] Sets the bot's active game, null to clear + * @arg {String} game.name Sets the name of the bot's active game + * @arg {Number} [game.type] The type of game. 0 is playing, 1 is streaming (Twitch only), 2 is listening, 3 is watching + * @arg {String} [game.url] Sets the url of the shard's active game */ - getMessageReaction(channelID, messageID, reaction, limit, before, after) { - if(reaction === decodeURI(reaction)) { - reaction = encodeURIComponent(reaction); + editStatus(status, game) { + if(game === undefined && typeof status === "object") { + game = status; + status = undefined; } - return this.requestHandler.request("GET", Endpoints.CHANNEL_MESSAGE_REACTION(channelID, messageID, reaction), true, { - limit: limit || 100, - before: before, - after: after - }).then((users) => users.map((user) => new User(user, this))); - } - - /** - * Add a reaction to a message - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {String} [userID="@me"] The ID of the user to react as - * @returns {Promise} - */ - addMessageReaction(channelID, messageID, reaction, userID) { - if(reaction === decodeURI(reaction)) { - reaction = encodeURIComponent(reaction); + if(status) { + this.presence.status = status; } - return this.requestHandler.request("PUT", Endpoints.CHANNEL_MESSAGE_REACTION_USER(channelID, messageID, reaction, userID || "@me"), true); - } - - /** - * Remove a reaction from a message - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {String} [userID="@me"] The ID of the user to remove the reaction for - * @returns {Promise} - */ - removeMessageReaction(channelID, messageID, reaction, userID) { - if(reaction === decodeURI(reaction)) { - reaction = encodeURIComponent(reaction); + if(game !== undefined) { + this.presence.game = game; } - return this.requestHandler.request("DELETE", Endpoints.CHANNEL_MESSAGE_REACTION_USER(channelID, messageID, reaction, userID || "@me"), true); + + this.shards.forEach((shard) => { + shard.editStatus(status, game); + }); } /** - * Remove all reactions from a message - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message + * [USER ACCOUNT] Edit the current user's note for another user + * @arg {String} userID The ID of the target user + * @arg {String} note The note * @returns {Promise} */ - removeMessageReactions(channelID, messageID) { - return this.requestHandler.request("DELETE", Endpoints.CHANNEL_MESSAGE_REACTIONS(channelID, messageID), true); + editUserNote(userID, note) { + return this.requestHandler.request("PUT", Endpoints.USER_NOTE("@me", userID), true, { + note + }); } /** - * Remove all reactions from a message for a single emoji. - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @returns {Promise} - */ - removeMessageReactionEmoji(channelID, messageID, reaction) { - if(reaction === decodeURI(reaction)) { - reaction = encodeURIComponent(reaction); - } - return this.requestHandler.request("DELETE", Endpoints.CHANNEL_MESSAGE_REACTION(channelID, messageID, reaction), true); + * Edit a webhook + * @arg {String} webhookID The ID of the webhook + * @arg {Object} options Webhook options + * @arg {String} [options.name] The new default name + * @arg {String} [options.avatar] The new default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings + * @arg {String} [token] The token of the webhook, used instead of the Bot Authorization token + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} Resolves with a webhook object + */ + editWebhook(webhookID, options, token, reason) { + options.reason = reason; + return this.requestHandler.request("PATCH", token ? Endpoints.WEBHOOK_TOKEN(webhookID, token) : Endpoints.WEBHOOK(webhookID), !token, options); } /** - * Delete a message - * @arg {String} channelID The ID of the channel - * @arg {String} messageID The ID of the message - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * [USER ACCOUNT] Enable TOTP authentication for the current user + * @arg {String} secret The TOTP secret used to generate the auth code + * @arg {String} code The timed auth code for the current user + * @returns {Promise} An object containing the user's new authorization token and backup codes */ - deleteMessage(channelID, messageID, reason) { - return this.requestHandler.request("DELETE", Endpoints.CHANNEL_MESSAGE(channelID, messageID), true, { - reason + enableSelfMFATOTP(secret, code) { + return this.requestHandler.request("POST", Endpoints.USER_MFA_TOTP_ENABLE("@me"), true, { + secret, + code + }).then((data) => { + if(data.token) { + this.token = data.token; + } }); } /** - * Bulk delete messages (bot accounts only) - * @arg {String} channelID The ID of the channel - * @arg {String[]} messageIDs Array of message IDs to delete - * @arg {String} [reason] The reason to be displayed in audit logs + * Execute a slack-style webhook + * @arg {String} webhookID The ID of the webhook + * @arg {String} token The token of the webhook + * @arg {Object} options Slack webhook options + * @arg {Boolean} [options.auth=false] Whether or not to authorize the request with the bot token (allowing custom emotes from other guilds) + * @arg {Boolean} [options.wait=false] Whether to wait for the server to confirm the message create or not * @returns {Promise} */ - deleteMessages(channelID, messageIDs, reason) { - if(messageIDs.length === 0) { - return Promise.resolve(); - } - if(messageIDs.length === 1) { - return this.deleteMessage(channelID, messageIDs[0]); - } - - const oldestAllowedSnowflake = (Date.now() - 1421280000000) * 4194304; - const invalidMessage = messageIDs.find((messageID) => messageID < oldestAllowedSnowflake); - if(invalidMessage) { - return Promise.reject(new Error(`Message ${invalidMessage} is more than 2 weeks old.`)); - } - - if(messageIDs.length > 100) { - return this.requestHandler.request("POST", Endpoints.CHANNEL_BULK_DELETE(channelID), true, { - messages: messageIDs.splice(0, 100), - reason: reason - }).then(() => this.deleteMessages(channelID, messageIDs)); - } - return this.requestHandler.request("POST", Endpoints.CHANNEL_BULK_DELETE(channelID), true, { - messages: messageIDs, - reason: reason - }); + executeSlackWebhook(webhookID, token, options) { + const wait = !!options.wait; + options.wait = undefined; + const auth = !!options.auth; + options.auth = undefined; + return this.requestHandler.request("POST", Endpoints.WEBHOOK_TOKEN_SLACK(webhookID, token) + (wait ? "?wait=true" : ""), auth, options); } /** - * Crosspost (publish) a message to subscribed channels - * @arg {String} channelID The ID of the NewsChannel - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - crosspostMessage(channelID, messageID) { - return this.requestHandler.request("POST", Endpoints.CHANNEL_CROSSPOST(channelID, messageID), true).then((message) => new Message(message, this)); + * Execute a webhook + * @arg {String} webhookID The ID of the webhook + * @arg {String} token The token of the webhook + * @arg {Object} options Webhook execution options + * @arg {Object} [options.allowedMentions] A list of mentions to allow (overrides default) + * @arg {Boolean} [options.allowedMentions.everyone] Whether or not to allow @everyone/@here. + * @arg {Boolean | Array} [options.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. + * @arg {Boolean | Array} [options.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. + * @arg {Boolean} [options.auth=false] Whether or not to authorize the request with the bot token (allowing custom emotes from other guilds) + * @arg {String} [options.avatarURL] A URL for a custom avatar, defaults to webhook default avatar if not specified + * @arg {String} [options.content=""] A content string + * @arg {Object[]} [options.embeds] An array of Discord embeds + * @arg {Object | Object[]} [options.file] A file object (or an Array of them) + * @arg {Buffer} options.file.file A buffer containing file data + * @arg {String} options.file.name What to name the file + * @arg {Boolean} [options.tts=false] Whether the message should be a TTS message or not + * @arg {String} [options.username] A custom username, defaults to webhook default username if not specified + * @arg {Boolean} [options.wait=false] Whether to wait for the server to confirm the message create or not + * @returns {Promise} + */ + executeWebhook(webhookID, token, options) { + if(!options.content && !options.file && !options.embeds) { + return Promise.reject(new Error("No content, file, or embeds")); + } + return this.requestHandler.request("POST", Endpoints.WEBHOOK_TOKEN(webhookID, token) + (options.wait ? "?wait=true" : ""), !!options.auth, { + content: options.content, + embeds: options.embeds, + username: options.username, + avatar_url: options.avatarURL, + tts: options.tts, + allowed_mentions: this._formatAllowedMentions(options.allowedMentions) + }, options.file).then((response) => options.wait ? new Message(response, this) : undefined); } /** @@ -1287,156 +1302,139 @@ class Client extends EventEmitter { } /** - * Purge previous messages in a channel with an optional filter (bot accounts only) + * Get general and bot-specific info on connecting to the Discord gateway (e.g. connection ratelimit) + * @returns {Promise} Resolves with an object containing gateway connection info + */ + getBotGateway() { + if(!this.token.startsWith("Bot ")) { + this.token = "Bot " + this.token; + } + return this.requestHandler.request("GET", Endpoints.GATEWAY_BOT, true); + } + + /** + * Get a Channel object from a channel ID * @arg {String} channelID The ID of the channel - * @arg {Number} limit The max number of messages to search through, -1 for no limit - * @arg {function} [filter] Optional filter function that returns a boolean when passed a Message object - * @arg {String} [before] Get messages before this message ID - * @arg {String} [after] Get messages after this message ID - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} Resolves with the number of messages deleted + * @returns {CategoryChannel | GroupChannel | PrivateChannel | TextChannel | VoiceChannel | NewsChannel} */ - async purgeChannel(channelID, limit, filter, before, after, reason) { - if(typeof filter === "string") { - filter = (msg) => msg.content.includes(filter); + getChannel(channelID) { + if(!channelID) { + throw new Error(`Invalid channel ID: ${channelID}`); } - if(limit !== -1 && limit <= 0) { - return 0; + + if(this.channelGuildMap[channelID] && this.guilds.get(this.channelGuildMap[channelID])) { + return this.guilds.get(this.channelGuildMap[channelID]).channels.get(channelID); } - const toDelete = []; - let deleted = 0; - let done = false; - const checkToDelete = async () => { - const messageIDs = (done && toDelete) || (toDelete.length >= 100 && toDelete.splice(0, 100)); - if(messageIDs) { - deleted += messageIDs.length; - await this.deleteMessages(channelID, messageIDs, reason); - if(done) { - return deleted; - } - await sleep(1000); - return checkToDelete(); - } else if(done) { - return deleted; - } else { - await sleep(250); - return checkToDelete(); - } - }; - const del = async (_before, _after) => { - const messages = await this.getMessages(channelID, 100, _before, _after); - if(limit !== -1 && limit <= 0) { - done = true; - return; - } - for(const message of messages) { - if(limit !== -1 && limit <= 0) { - break; - } - if(message.timestamp < Date.now() - 1209600000) { // 14d * 24h * 60m * 60s * 1000ms - done = true; - return; - } - if(!filter || filter(message)) { - toDelete.push(message.id); - } - if(limit !== -1) { - limit--; - } - } - if((limit !== -1 && limit <= 0) || messages.length < 100) { - done = true; - return; - } - await del((_before || !_after) && messages[messages.length - 1].id, _after && messages[0].id); - }; - await del(before, after); - return checkToDelete(); + return this.privateChannels.get(channelID) || this.groupChannels.get(channelID); } /** - * [DEPRECATED] Get a guild's embed object - * @arg {String} guildID The ID of the guild - * @returns {Promise} A guild embed object + * Get all invites in a channel + * @arg {String} channelID The ID of the channel + * @returns {Promise} */ - getGuildEmbed(guildID) { - return this.requestHandler.request("GET", Endpoints.GUILD_EMBED(guildID), true); + getChannelInvites(channelID) { + return this.requestHandler.request("GET", Endpoints.CHANNEL_INVITES(channelID), true).then((invites) => invites.map((invite) => new Invite(invite, this))); } /** - * Get a guild's widget object - * @arg {String} guildID The ID of the guild - * @returns {Promise} A guild widget object + * Get all the webhooks in a channel + * @arg {String} channelID The ID of the channel to get webhooks for + * @returns {Promise} Resolves with an array of webhook objects */ - getGuildWidget(guildID) { - return this.requestHandler.request("GET", Endpoints.GUILD_WIDGET(guildID), true); + getChannelWebhooks(channelID) { + return this.requestHandler.request("GET", Endpoints.CHANNEL_WEBHOOKS(channelID), true); } /** - * Modify a guild's widget - * @arg {String} guildID The ID of the guild - * @arg {Object} options The widget object to modify (https://discord.com/developers/docs/resources/guild#modify-guild-widget) - * @returns {Promise} A guild widget object + * Get a DM channel with a user, or create one if it does not exist + * @arg {String} userID The ID of the user + * @returns {Promise} */ - editGuildWidget(guildID, options) { - return this.requestHandler.request("PATCH", Endpoints.GUILD_WIDGET(guildID), true, options); + getDMChannel(userID) { + if(this.privateChannelMap[userID]) { + return Promise.resolve(this.privateChannels.get(this.privateChannelMap[userID])); + } + return this.requestHandler.request("POST", Endpoints.USER_CHANNELS("@me"), true, { + recipients: [userID], + type: 1 + }).then((privateChannel) => new PrivateChannel(privateChannel, this)); } /** - * Get a guild preview for a guild. Only available for public guilds. - * @arg {String} guildID The ID of the guild - * @returns {Promise} + * Get info on connecting to the Discord gateway + * @returns {Promise} Resolves with an object containing gateway connection info */ - getGuildPreview(guildID) { - return this.requestHandler.request("GET", Endpoints.GUILD_PREVIEW(guildID), true).then((data) => new GuildPreview(data, this)); + getGateway() { + return this.requestHandler.request("GET", Endpoints.GATEWAY); } /** - * Get a list of integrations for a guild + * Get the audit logs for a guild + * @arg {String} guildID The ID of the guild to get audit logs for + * @arg {Number} [limit=50] The maximum number of entries to return + * @arg {String} [before] Get entries before this entry ID + * @arg {Number} [actionType] Filter entries by action type + * @returns {Promise} Resolves with {users: Users[], entries: GuildAuditLogEntry[]} + */ + getGuildAuditLogs(guildID, limit, before, actionType) { + return this.requestHandler.request("GET", Endpoints.GUILD_AUDIT_LOGS(guildID), true, { + limit: limit || 50, + before: before, + action_type: actionType + }).then((data) => { + const guild = this.guilds.get(guildID); + return { + users: data.users.map((user) => this.users.add(user, this)), + entries: data.audit_log_entries.map((entry) => new GuildAuditLogEntry(entry, guild)) + }; + }); + } + + /** + * Get a ban from the ban list of a guild * @arg {String} guildID The ID of the guild - * @returns {Promise} + * @arg {String} userID The ID of the banned user + * @returns {Promise} Resolves with {reason: String, user: User} */ - getGuildIntegrations(guildID) { - const guild = this.guilds.get(guildID); - return this.requestHandler.request("GET", Endpoints.GUILD_INTEGRATIONS(guildID), true).then((integrations) => integrations.map((integration) => new GuildIntegration(integration, guild))); + getGuildBan(guildID, userID) { + return this.requestHandler.request("GET", Endpoints.GUILD_BAN(guildID, userID), true).then((ban) => { + ban.user = new User(ban.user, this); + return ban; + }); } /** - * Edit a guild integration + * Get the ban list of a guild * @arg {String} guildID The ID of the guild - * @arg {String} integrationID The ID of the integration - * @arg {Object} options The properties to edit - * @arg {String} [options.expireBehavior] What to do when a user's subscription runs out - * @arg {String} [options.expireGracePeriod] How long before the integration's role is removed from an unsubscribed user - * @arg {String} [options.enableEmoticons] Whether to enable integration emoticons or not - * @returns {Promise} + * @returns {Promise} Resolves with an array of {reason: String, user: User} */ - editGuildIntegration(guildID, integrationID, options) { - return this.requestHandler.request("PATCH", Endpoints.GUILD_INTEGRATION(guildID, integrationID), true, { - expire_behavior: options.expireBehavior, - expire_grace_period: options.expireGracePeriod, - enable_emoticons: options.enableEmoticons + getGuildBans(guildID) { + return this.requestHandler.request("GET", Endpoints.GUILD_BANS(guildID), true).then((bans) => { + bans.forEach((ban) => { + ban.user = new User(ban.user, this); + }); + return bans; }); } /** - * Delete a guild integration + * [DEPRECATED] Get a guild's embed object * @arg {String} guildID The ID of the guild - * @arg {String} integrationID The ID of the integration - * @returns {Promise} + * @returns {Promise} A guild embed object */ - deleteGuildIntegration(guildID, integrationID) { - return this.requestHandler.request("DELETE", Endpoints.GUILD_INTEGRATION(guildID, integrationID), true); + getGuildEmbed(guildID) { + return this.requestHandler.request("GET", Endpoints.GUILD_EMBED(guildID), true); } /** - * Force a guild integration to sync + * Get a list of integrations for a guild * @arg {String} guildID The ID of the guild - * @arg {String} integrationID The ID of the integration - * @returns {Promise} + * @returns {Promise} */ - syncGuildIntegration(guildID, integrationID) { - return this.requestHandler.request("POST", Endpoints.GUILD_INTEGRATION_SYNC(guildID, integrationID), true); + getGuildIntegrations(guildID) { + const guild = this.guilds.get(guildID); + return this.requestHandler.request("GET", Endpoints.GUILD_INTEGRATIONS(guildID), true).then((integrations) => integrations.map((integration) => new GuildIntegration(integration, guild))); } /** @@ -1449,361 +1447,324 @@ class Client extends EventEmitter { } /** - * Returns the vanity url of the guild + * Get a guild preview for a guild. Only available for public guilds. * @arg {String} guildID The ID of the guild - * @returns {Promise} + * @returns {Promise} */ - getGuildVanity(guildID) { - return this.requestHandler.request("GET", Endpoints.GUILD_VANITY_URL(guildID), true); + getGuildPreview(guildID) { + return this.requestHandler.request("GET", Endpoints.GUILD_PREVIEW(guildID), true).then((data) => new GuildPreview(data, this)); } /** - * Ban a user from a guild + * Returns the vanity url of the guild * @arg {String} guildID The ID of the guild - * @arg {String} userID The ID of the user - * @arg {Number} [deleteMessageDays=0] Number of days to delete messages for, between 0-7 inclusive - * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - banGuildMember(guildID, userID, deleteMessageDays, reason) { - if(!isNaN(deleteMessageDays) && (deleteMessageDays < 0 || deleteMessageDays > 7)) { - return Promise.reject(new Error(`Invalid deleteMessageDays value (${deleteMessageDays}), should be a number between 0-7 inclusive`)); - } - return this.requestHandler.request("PUT", Endpoints.GUILD_BAN(guildID, userID), true, { - delete_message_days: deleteMessageDays || 0, - reason: reason - }); + getGuildVanity(guildID) { + return this.requestHandler.request("GET", Endpoints.GUILD_VANITY_URL(guildID), true); } /** - * Unban a user from a guild - * @arg {String} guildID The ID of the guild - * @arg {String} userID The ID of the user - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get all the webhooks in a guild + * @arg {String} guildID The ID of the guild to get webhooks for + * @returns {Promise} Resolves with an array of webhook objects */ - unbanGuildMember(guildID, userID, reason) { - return this.requestHandler.request("DELETE", Endpoints.GUILD_BAN(guildID, userID), true, { - reason - }); + getGuildWebhooks(guildID) { + return this.requestHandler.request("GET", Endpoints.GUILD_WEBHOOKS(guildID), true); } /** - * Create a guild - * @arg {String} name The name of the guild - * @arg {Object} options The properties of the guild - * @arg {String} [options.region] The region of the guild - * @arg {String} [options.icon] The guild icon as a base64 data URI. Note: base64 strings alone are not base64 data URI strings - * @arg {Number} [options.verificationLevel] The guild verification level - * @arg {Number} [options.defaultNotifications] The default notification settings for the guild. 0 is "All Messages", 1 is "Only @mentions". - * @arg {Number} [options.explicitContentFilter] The level of the explicit content filter for messages/images in the guild. 0 disables message scanning, 1 enables scanning the messages of members without roles, 2 enables scanning for all messages. - * @arg {String} [options.systemChannelID] The ID of the system channel - * @arg {String} [options.afkChannelID] The ID of the AFK voice channel - * @arg {Number} [options.afkTimeout] The AFK timeout in seconds - * @arg {Array} [options.roles] The new roles of the guild, the first one is the @everyone role. IDs are placeholders which allow channel overwrites. - * @arg {Array} [options.channels] The new channels of the guild. IDs are placeholders which allow use of category channels. - * @returns {Promise} + * Get a guild's widget object + * @arg {String} guildID The ID of the guild + * @returns {Promise} A guild widget object */ - createGuild(name, options) { - if(this.guilds.size > 9) { - throw new Error("This method can't be used when in 10 or more guilds."); - } - - return this.requestHandler.request("POST", Endpoints.GUILDS, true, { - name: name, - region: options.region, - icon: options.icon, - verification_level: options.verificationLevel, - default_message_notifications: options.defaultNotifications, - explicit_content_filter: options.explicitContentFilter, - system_channel_id: options.systemChannelID, - afk_channel_id: options.afkChannelID, - afk_timeout: options.afkTimeout, - roles: options.roles, - channels: options.channels - }).then((guild) => new Guild(guild, this)); + getGuildWidget(guildID) { + return this.requestHandler.request("GET", Endpoints.GUILD_WIDGET(guildID), true); } /** - * Edit a guild - * @arg {String} guildID The ID of the guild - * @arg {Object} options The properties to edit - * @arg {String} [options.name] The ID of the guild - * @arg {String} [options.region] The region of the guild - * @arg {String} [options.icon] The guild icon as a base64 data URI. Note: base64 strings alone are not base64 data URI strings - * @arg {Number} [options.verificationLevel] The guild verification level - * @arg {Number} [options.defaultNotifications] The default notification settings for the guild. 0 is "All Messages", 1 is "Only @mentions". - * @arg {Number} [options.explicitContentFilter] The level of the explicit content filter for messages/images in the guild. 0 disables message scanning, 1 enables scanning the messages of members without roles, 2 enables scanning for all messages. - * @arg {String} [options.systemChannelID] The ID of the system channel - * @arg {String} [options.rulesChannelID] The id of the channel where "PUBLIC" guilds display rules and/or guidelines - * @arg {String} [options.publicUpdatesChannelID] The id of the channel where admins and moderators of "PUBLIC" guilds receive notices from Discord - * @arg {String} [options.preferredLocale] Preferred "PUBLIC" guild language used in server discovery and notices from Discord - * @arg {String} [options.afkChannelID] The ID of the AFK voice channel - * @arg {Number} [options.afkTimeout] The AFK timeout in seconds - * @arg {String} [options.ownerID] The ID of the user to transfer server ownership to (bot user must be owner) - * @arg {String} [options.splash] The guild splash image as a base64 data URI (VIP only). Note: base64 strings alone are not base64 data URI strings - * @arg {String} [options.banner] The guild banner image as a base64 data URI (VIP only). Note: base64 strings alone are not base64 data URI strings - * @arg {String} [options.description] The description for the guild (VIP only) - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get info on an invite + * @arg {String} inviteID The ID of the invite + * @arg {Boolean} [withCounts] Whether to fetch additional invite info or not (approximate member counts, approximate presences, channel counts, etc.) + * @returns {Promise} */ - editGuild(guildID, options, reason) { - return this.requestHandler.request("PATCH", Endpoints.GUILD(guildID), true, { - name: options.name, - region: options.region, - icon: options.icon, - verification_level: options.verificationLevel, - default_message_notifications: options.defaultNotifications, - explicit_content_filter: options.explicitContentFilter, - system_channel_id: options.systemChannelID, - rules_channel_id: options.rulesChannelID, - public_updates_channel_id: options.publicUpdatesChannelID, - preferred_locale: options.preferredLocale, - afk_channel_id: options.afkChannelID, - afk_timeout: options.afkTimeout, - owner_id: options.ownerID, - splash: options.splash, - banner: options.banner, - description: options.description, - reason: reason - }).then((guild) => new Guild(guild, this)); + getInvite(inviteID, withCounts) { + return this.requestHandler.request("GET", Endpoints.INVITE(inviteID), true, { + with_counts: withCounts + }).then((invite) => new Invite(invite, this)); } /** - * Get the ban list of a guild - * @arg {String} guildID The ID of the guild - * @returns {Promise} Resolves with an array of {reason: String, user: User} + * Get a previous message in a channel + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message + * @returns {Promise} */ - getGuildBans(guildID) { - return this.requestHandler.request("GET", Endpoints.GUILD_BANS(guildID), true).then((bans) => { - bans.forEach((ban) => { - ban.user = new User(ban.user, this); - }); - return bans; - }); + getMessage(channelID, messageID) { + return this.requestHandler.request("GET", Endpoints.CHANNEL_MESSAGE(channelID, messageID), true).then((message) => new Message(message, this)); } /** - * Get a ban from the ban list of a guild - * @arg {String} guildID The ID of the guild - * @arg {String} userID The ID of the banned user - * @returns {Promise} Resolves with {reason: String, user: User} + * Get a list of users who reacted with a specific reaction + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @arg {Number} [limit=100] The maximum number of users to get + * @arg {String} [before] Get users before this user ID + * @arg {String} [after] Get users after this user ID + * @returns {Promise} */ - getGuildBan(guildID, userID) { - return this.requestHandler.request("GET", Endpoints.GUILD_BAN(guildID, userID), true).then((ban) => { - ban.user = new User(ban.user, this); - return ban; - }); + getMessageReaction(channelID, messageID, reaction, limit, before, after) { + if(reaction === decodeURI(reaction)) { + reaction = encodeURIComponent(reaction); + } + return this.requestHandler.request("GET", Endpoints.CHANNEL_MESSAGE_REACTION(channelID, messageID, reaction), true, { + limit: limit || 100, + before: before, + after: after + }).then((users) => users.map((user) => new User(user, this))); } /** - * Edit a guild member - * @arg {String} guildID The ID of the guild - * @arg {String} memberID The ID of the member - * @arg {Object} options The properties to edit - * @arg {String[]} [options.roles] The array of role IDs the member should have - * @arg {String} [options.nick] Set the member's server nickname, "" to remove - * @arg {Boolean} [options.mute] Server mute the member - * @arg {Boolean} [options.deaf] Server deafen the member - * @arg {String} [options.channelID] The ID of the voice channel to move the member to (must be in voice) - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get previous messages in a channel + * @arg {String} channelID The ID of the channel + * @arg {Number} [limit=50] The max number of messages to get + * @arg {String} [before] Get messages before this message ID + * @arg {String} [after] Get messages after this message ID + * @arg {String} [around] Get messages around this message ID (does not work with limit > 100) + * @returns {Promise} */ - editGuildMember(guildID, memberID, options, reason) { - return this.requestHandler.request("PATCH", Endpoints.GUILD_MEMBER(guildID, memberID), true, { - roles: options.roles && options.roles.filter((roleID, index) => options.roles.indexOf(roleID) === index), - nick: options.nick, - mute: options.mute, - deaf: options.deaf, - channel_id: options.channelID, - reason: reason + async getMessages(channelID, limit = 50, before, after, around) { + if(limit && limit > 100) { + let logs = []; + const get = async (_before, _after) => { + const messages = await this.requestHandler.request("GET", Endpoints.CHANNEL_MESSAGES(channelID), true, { + limit: 100, + before: _before || undefined, + after: _after || undefined + }); + if(limit <= messages.length) { + return (_after ? messages.slice(messages.length - limit, messages.length).map((message) => new Message(message, this)).concat(logs) : logs.concat(messages.slice(0, limit).map((message) => new Message(message, this)))); + } + limit -= messages.length; + logs = (_after ? messages.map((message) => new Message(message, this)).concat(logs) : logs.concat(messages.map((message) => new Message(message, this)))); + if(messages.length < 100) { + return logs; + } + this.emit("debug", `Getting ${limit} more messages during getMessages for ${channelID}: ${_before} ${_after}`, -1); + return get((_before || !_after) && messages[messages.length - 1].id, _after && messages[0].id); + }; + return get(before, after); + } + const messages = await this.requestHandler.request("GET", Endpoints.CHANNEL_MESSAGES(channelID), true, { + limit, + before, + after, + around }); - } - - /** - * Add a role to a guild member - * @arg {String} guildID The ID of the guild - * @arg {String} memberID The ID of the member - * @arg {String} roleID The ID of the role - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - addGuildMemberRole(guildID, memberID, roleID, reason) { - return this.requestHandler.request("PUT", Endpoints.GUILD_MEMBER_ROLE(guildID, memberID, roleID), true, { - reason + return messages.map((message) => { + try { + return new Message(message, this); + } catch(err) { + this.emit("error", `Error creating message from channel messages\n${err.stack}\n${JSON.stringify(messages)}`); + return null; + } }); } /** - * Remove a role from a guild member - * @arg {String} guildID The ID of the guild - * @arg {String} memberID The ID of the member - * @arg {String} roleID The ID of the role - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get data on an OAuth2 application + * @arg {String} [appID="@me"] The client ID of the application to get data for (user accounts only). "@me" refers to the logged in user's own application + * @returns {Promise} The bot's application data. Refer to [the official Discord API documentation entry](https://discordapp.com/developers/docs/topics/oauth2#get-current-application-information) for object structure */ - removeGuildMemberRole(guildID, memberID, roleID, reason) { - return this.requestHandler.request("DELETE", Endpoints.GUILD_MEMBER_ROLE(guildID, memberID, roleID), true, { - reason - }); + getOAuthApplication(appID) { + return this.requestHandler.request("GET", Endpoints.OAUTH2_APPLICATION(appID || "@me"), true); } /** - * Edit the bot's nickname in a guild - * @arg {String} guildID The ID of the guild - * @arg {String} nick The nickname - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get all the pins in a channel + * @arg {String} channelID The ID of the channel + * @returns {Promise} */ - editNickname(guildID, nick, reason) { - return this.requestHandler.request("PATCH", Endpoints.GUILD_MEMBER_NICK(guildID, "@me"), true, { - nick, - reason - }); + getPins(channelID) { + return this.requestHandler.request("GET", Endpoints.CHANNEL_PINS(channelID), true).then((messages) => messages.map((message) => new Message(message, this))); } /** - * Kick a user from a guild + * Get the prune count for a guild * @arg {String} guildID The ID of the guild - * @arg {String} userID The ID of the user - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * @arg {Number} [options] The options to use to get number of prune members + * @arg {Number} [options.days=7] The number of days of inactivity to prune for + * @arg {Array} [options.includeRoles] An array of role IDs that members must have to be considered for pruning + * @returns {Promise} Resolves with the number of members that would be pruned */ - kickGuildMember(guildID, userID, reason) { - return this.requestHandler.request("DELETE", Endpoints.GUILD_MEMBER(guildID, userID), true, { - reason - }); + getPruneCount(guildID, options = {}) { + return this.requestHandler.request("GET", Endpoints.GUILD_PRUNE(guildID), true, { + days: options.days, + include_roles: options.includeRoles + }).then((data) => data.pruned); } /** - * Delete a guild (bot user must be owner) - * @arg {String} guildID The ID of the guild - * @returns {Promise} + * Get a channel's data via the REST API. REST mode is required to use this endpoint. + * @arg {String} channelID The ID of the channel + * @returns {Promise} */ - deleteGuild(guildID) { - return this.requestHandler.request("DELETE", Endpoints.GUILD(guildID), true); + getRESTChannel(channelID) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.CHANNEL(channelID), true) + .then((channel) => Channel.from(channel, this)); } /** - * Leave a guild + * Get a guild's data via the REST API. REST mode is required to use this endpoint. * @arg {String} guildID The ID of the guild - * @returns {Promise} + * @arg {Boolean} [withCounts=false] Whether the guild object will have approximateMemberCount and approximatePresenceCount + * @returns {Promise} */ - leaveGuild(guildID) { - return this.requestHandler.request("DELETE", Endpoints.USER_GUILD("@me", guildID), true); + getRESTGuild(guildID, withCounts = false) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.GUILD(guildID), true, { + with_counts: withCounts + }).then((guild) => new Guild(guild, this)); } /** - * Get data on an OAuth2 application - * @arg {String} [appID="@me"] The client ID of the application to get data for (user accounts only). "@me" refers to the logged in user's own application - * @returns {Promise} The bot's application data. Refer to [the official Discord API documentation entry](https://discordapp.com/developers/docs/topics/oauth2#get-current-application-information) for object structure + * Get a guild's channels via the REST API. REST mode is required to use this endpoint. + * @arg {String} guildID The ID of the guild + * @returns {Promise<(CategoryChannel[] | TextChannel[] | VoiceChannel[] | NewsChannel[])>} */ - getOAuthApplication(appID) { - return this.requestHandler.request("GET", Endpoints.OAUTH2_APPLICATION(appID || "@me"), true); + getRESTGuildChannels(guildID) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.GUILD_CHANNELS(guildID), true) + .then((channels) => channels.map((channel) => Channel.from(channel, this))); } /** - * [USER ACCOUNT] Create a relationship with a user - * @arg {String} userID The ID of the target user - * @arg {Boolean} [block=false] If true, block the user. Otherwise, add the user as a friend - * @returns {Promise} + * Get a guild emoji via the REST API. REST mode is required to use this endpoint. + * @arg {String} guildID The ID of the guild + * @arg {String} emojiID The ID of the emoji + * @returns {Promise} An emoji object */ - addRelationship(userID, block) { - return this.requestHandler.request("PUT", Endpoints.USER_RELATIONSHIP("@me", userID), true, { - type: block ? 2 : undefined - }); + getRESTGuildEmoji(guildID, emojiID) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.GUILD_EMOJI(guildID, emojiID), true); } /** - * [USER ACCOUNT] Remove a relationship with a user - * @arg {String} userID The ID of the target user - * @returns {Promise} + * Get a guild's emojis via the REST API. REST mode is required to use this endpoint. + * @arg {String} guildID The ID of the guild + * @returns {Promise} An array of guild emoji objects */ - removeRelationship(userID) { - return this.requestHandler.request("DELETE", Endpoints.USER_RELATIONSHIP("@me", userID), true); + getRESTGuildEmojis(guildID) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.GUILD_EMOJIS(guildID), true); } /** - * [USER ACCOUNT] Add a user to a group - * @arg {String} groupID The ID of the target group - * @arg {String} userID The ID of the target user - * @returns {Promise} + * Get a guild's members via the REST API. REST mode is required to use this endpoint. + * @arg {String} guildID The ID of the guild + * @arg {String} memberID The ID of the member + * @returns {Promise} */ - addGroupRecipient(groupID, userID) { - return this.requestHandler.request("PUT", Endpoints.CHANNEL_RECIPIENT(groupID, userID), true); + getRESTGuildMember(guildID, memberID) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.GUILD_MEMBER(guildID, memberID), true).then((member) => new Member(member, this.guilds.get(guildID), this)); } /** - * [USER ACCOUNT] Remove a user from a group - * @arg {String} groupID The ID of the target group - * @arg {String} userID The ID of the target user - * @returns {Promise} + * Get a guild's members via the REST API. REST mode is required to use this endpoint. + * @arg {String} guildID The ID of the guild + * @arg {Number} [limit=1] The max number of members to get (1 to 1000) + * @arg {String} [after] The highest user ID of the previous page + * @returns {Promise} */ - removeGroupRecipient(groupID, userID) { - return this.requestHandler.request("DELETE", Endpoints.CHANNEL_RECIPIENT(groupID, userID), true); + getRESTGuildMembers(guildID, limit, after) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.GUILD_MEMBERS(guildID), true, { + limit, + after + }).then((members) => members.map((member) => new Member(member, this.guilds.get(guildID), this))); } /** - * [USER ACCOUNT] Get profile data for a user - * @arg {String} userID The ID of the target user - * @returns {Promise} The user's profile data. + * Get a guild's roles via the REST API. REST mode is required to use this endpoint. + * @arg {String} guildID The ID of the guild + * @returns {Promise} */ - getUserProfile(userID) { - return this.requestHandler.request("GET", Endpoints.USER_PROFILE(userID), true); + getRESTGuildRoles(guildID) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.GUILD_ROLES(guildID), true).then((roles) => roles.map((role) => new Role(role, null))); } /** - * [USER ACCOUNT] Edit the current user's note for another user - * @arg {String} userID The ID of the target user - * @arg {String} note The note - * @returns {Promise} + * Get a list of the user's guilds via the REST API. REST mode is required to use this endpoint. + * @arg {Number} [limit=100] The max number of guilds to get (1 to 1000) + * @arg {String} [before] The lowest guild ID of the next page + * @arg {String} [after] The highest guild ID of the previous page + * @returns {Promise} */ - editUserNote(userID, note) { - return this.requestHandler.request("PUT", Endpoints.USER_NOTE("@me", userID), true, { - note - }); + getRESTGuilds(limit, before, after) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.USER_GUILDS("@me"), true, { + limit, + before, + after + }).then((guilds) => guilds.map((guild) => new Guild(guild, this))); } /** - * [USER ACCOUNT] Delete the current user's note for another user - * @returns {Promise} + * Get a user's data via the REST API. REST mode is required to use this endpoint. + * @arg {String} userID The ID of the user + * @returns {Promise} */ - deleteUserNote(userID) { - return this.requestHandler.request("DELETE", Endpoints.USER_NOTE("@me", userID), true); + getRESTUser(userID) { + if(!this.options.restMode) { + return Promise.reject(new Error("Eris REST mode is not enabled")); + } + return this.requestHandler.request("GET", Endpoints.USER(userID), true).then((user) => new User(user, this)); } /** - * [USER ACCOUNT] Get the connections for the current user - * @returns {Promise} The user's connections + * Get properties of the bot user + * @returns {Promise} */ - getSelfConnections() { - return this.requestHandler.request("GET", Endpoints.USER_CONNECTIONS("@me"), true); + getSelf() { + return this.requestHandler.request("GET", Endpoints.USER("@me"), true).then((data) => new ExtendedUser(data, this)); } /** - * [USER ACCOUNT] Edit a connection for the current user - * @arg {String} platform The connection platform (e.g. "twitch", "reddit") - * @arg {String} id The connection ID - * @arg {Object} data The connection data - * @arg {Boolean} [data.friendSync] Whether to sync friends from the connection or not - * @arg {Number} [data.visibility] The visibility level for the connection. 0 = hidden, 1 = shown on profile - * @returns {Promise} The updated connection data + * [USER ACCOUNT] Get the billing info for the current user + * @returns {Promise} The user's billing info */ - editSelfConnection(platform, id, data) { - return this.requestHandler.request("PATCH", Endpoints.USER_CONNECTION_PLATFORM("@me", platform, id), true, { - visibility: data.visibility, - friend_sync: data.friendSync - }); + getSelfBilling() { + return this.requestHandler.request("GET", Endpoints.USER_BILLING("@me"), true); } /** - * [USER ACCOUNT] Delete a connection for the current user - * @arg {String} platform The connection platform (e.g. "twitch", "reddit") - * @arg {String} id The connection ID - * @returns {Promise} + * [USER ACCOUNT] Get the connections for the current user + * @returns {Promise} The user's connections */ - deleteSelfConnection(platform, id) { - return this.requestHandler.request("DELETE", Endpoints.USER_CONNECTION_PLATFORM("@me", platform, id), true); + getSelfConnections() { + return this.requestHandler.request("GET", Endpoints.USER_CONNECTIONS("@me"), true); } /** @@ -1814,64 +1775,6 @@ class Client extends EventEmitter { return this.requestHandler.request("GET", Endpoints.USER_SETTINGS("@me"), true); } - /** - * [USER ACCOUNT] Edit settings for the current user - * @arg {Object} data The user settings data - * @arg {Boolean} [data.convertEmoticons] Whether to convert emoticons or not (e.g. :D => 😄) - * @arg {Boolean} [data.detectPlatformAccounts] Whether to automatically detect accounts from other platforms or not (Blizzard, Skype, etc.) - * @arg {Boolean} [data.developerMode] Whether to enable developer mode or not - * @arg {Boolean} [data.enableTTSCommand] Whether to respect usage of the TTS command or not - * @arg {Object} [data.friendSourceFlags] An object representing allowed friend request sources - * @arg {Boolean} [data.friendSourceFlags.all] Whether to allow friends requests from anywhere or not - * @arg {Boolean} [data.friendSourceFlags.mutualFriends] Whether to allow friend requests from people with mutual friends or not - * @arg {Boolean} [data.friendSourceFlags.mutualGuilds] Whether to allow friend requests from people in mutual guilds or not - * @arg {Array} [data.guildPositions] An ordered array of guild IDs representing the guild list order in the Discord client - * @arg {Boolean} [data.inlineAttachmentMedia] Whether to show attachment previews or not - * @arg {Boolean} [data.inlineEmbedMedia] Whether to show embed images or not - * @arg {String} [data.locale] The locale to use for the Discord UI - * @arg {Boolean} [data.messageDisplayCompact] Whether to use compact mode or not - * @arg {Boolean} [data.renderEmbeds] Whether to show embeds or not - * @arg {Boolean} [data.renderReactions] Whether to show reactions or not - * @arg {Array} [data.restrictedGuilds] An array of guild IDs where direct messages from guild members are disallowed - * @arg {Boolean} [data.showCurrentGame] Whether to set the user's status to the current game or not - * @arg {String} [data.status] The status of the user, either "invisible", "dnd", "away", or "online" - * @arg {String} [data.theme] The theme to use for the Discord UI, either "dark" or "light" - * @returns {Promise} The user's settings data. - */ - editSelfSettings(data) { - let friendSourceFlags = undefined; - if(data.friendSourceFlags) { - friendSourceFlags = {}; - if(data.friendSourceFlags.all) { - friendSourceFlags.all = true; - } - if(data.friendSourceFlags.mutualFriends) { - friendSourceFlags.mutual_friends = true; - } - if(data.friendSourceFlags.mutualGuilds) { - friendSourceFlags.mutual_guilds = true; - } - } - return this.requestHandler.request("PATCH", Endpoints.USER_SETTINGS("@me"), true, { - convert_emoticons: data.convertEmoticons, - detect_platform_accounts: data.detectPlatformAccounts, - developer_mode: data.developerMode, - enable_tts_command: data.enableTTSCommand, - friend_source_flags: friendSourceFlags, - guild_positions: data.guildPositions, - inline_attachment_media: data.inlineAttachmentMedia, - inline_embed_media: data.inlineEmbedMedia, - locale: data.locale, - message_display_compact: data.messageDisplayCompact, - render_embeds: data.renderEmbeds, - render_reactions: data.renderReactions, - restricted_guilds: data.restrictedGuilds, - show_current_game: data.showCurrentGame, - status: data.status, - theme: data.theme - }); - } - /** * [USER ACCOUNT] Get the MFA backup codes for the current user * @arg {String} password The password for the current user @@ -1886,250 +1789,290 @@ class Client extends EventEmitter { } /** - * [USER ACCOUNT] Enable TOTP authentication for the current user - * @arg {String} secret The TOTP secret used to generate the auth code - * @arg {String} code The timed auth code for the current user - * @returns {Promise} An object containing the user's new authorization token and backup codes + * [USER ACCOUNT] Get the payment history for the current user + * @returns {Promise} The user's payment history */ - enableSelfMFATOTP(secret, code) { - return this.requestHandler.request("POST", Endpoints.USER_MFA_TOTP_ENABLE("@me"), true, { - secret, - code - }).then((data) => { - if(data.token) { - this.token = data.token; - } - }); + getSelfPayments() { + return this.requestHandler.request("GET", Endpoints.USER_BILLING_PAYMENTS("@me"), true); } /** - * [USER ACCOUNT] Disable TOTP authentication for the current user - * @arg {String} code The timed auth code for the current user - * @returns {Promise} An object containing the user's new authorization token + * [USER ACCOUNT] Get profile data for a user + * @arg {String} userID The ID of the target user + * @returns {Promise} The user's profile data. */ - disableSelfMFATOTP(code) { - return this.requestHandler.request("POST", Endpoints.USER_MFA_TOTP_DISABLE("@me"), true, { - code - }).then((data) => { - if(data.token) { - this.token = data.token; - } - }); + getUserProfile(userID) { + return this.requestHandler.request("GET", Endpoints.USER_PROFILE(userID), true); } /** - * [USER ACCOUNT] Get the billing info for the current user - * @returns {Promise} The user's billing info + * Get a list of general/guild-specific voice regions + * @arg {String} [guildID] The ID of the guild + * @returns {Promise} Resolves with an array of voice region objects */ - getSelfBilling() { - return this.requestHandler.request("GET", Endpoints.USER_BILLING("@me"), true); + getVoiceRegions(guildID) { + return guildID ? this.requestHandler.request("GET", Endpoints.GUILD_VOICE_REGIONS(guildID), true) : this.requestHandler.request("GET", Endpoints.VOICE_REGIONS, true); } /** - * [USER ACCOUNT] Get the payment history for the current user - * @returns {Promise} The user's payment history + * Get a webhook + * @arg {String} webhookID The ID of the webhook + * @arg {String} [token] The token of the webhook, used instead of the Bot Authorization token + * @returns {Promise} Resolves with a webhook object */ - getSelfPayments() { - return this.requestHandler.request("GET", Endpoints.USER_BILLING_PAYMENTS("@me"), true); + getWebhook(webhookID, token) { + return this.requestHandler.request("GET", token ? Endpoints.WEBHOOK_TOKEN(webhookID, token) : Endpoints.WEBHOOK(webhookID), !token); } /** - * [USER ACCOUNT] Purchase a premium subscription (Nitro) for the current user - * You must get a Stripe card token from the Stripe API for this to work - * @arg {String} token The Stripe credit card token - * @arg {String} plan The plan to purchase, either "premium_month" or "premium_year" - * @returns {Promise} + * Join a voice channel. If joining a group call, the voice connection ID will be stored in voiceConnections as "call". Otherwise, it will be the guild ID + * @arg {String} channelID The ID of the voice channel + * @arg {Object} [options] VoiceConnection constructor options + * @arg {Object} [options.opusOnly] Skip opus encoder initialization. You should not enable this unless you know what you are doing + * @arg {Object} [options.shared] Whether the VoiceConnection will be part of a SharedStream or not + * @returns {Promise} Resolves with a VoiceConnection */ - addSelfPremiumSubscription(token, plan) { - return this.requestHandler.request("PUT", Endpoints.USER_BILLING_PREMIUM_SUBSCRIPTION("@me"), true, { - token: token, - payment_gateway: "stripe", - plan: plan + joinVoiceChannel(channelID, options = {}) { + const channel = this.getChannel(channelID); + if(!channel) { + return Promise.reject(new Error("Channel not found")); + } + if(channel.guild && !(channel.permissionsOf(this.user.id).allow & Constants.Permissions.voiceConnect)) { + return Promise.reject(new Error("Insufficient permission to connect to voice channel")); + } + this.shards.get(this.guildShardMap[this.channelGuildMap[channelID]] || 0).sendWS(Constants.GatewayOPCodes.VOICE_STATE_UPDATE, { + guild_id: this.channelGuildMap[channelID] || null, + channel_id: channelID || null, + self_mute: false, + self_deaf: false }); + if(options.opusOnly === undefined) { + options.opusOnly = this.options.opusOnly; + } + return this.voiceConnections.join(this.channelGuildMap[channelID] || "call", channelID, options); } /** - * [USER ACCOUNT] Cancel the premium subscription (Nitro) for the current user + * Kick a user from a guild + * @arg {String} guildID The ID of the guild + * @arg {String} userID The ID of the user + * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - deleteSelfPremiumSubscription() { - return this.requestHandler.request("DELETE", Endpoints.USER_BILLING_PREMIUM_SUBSCRIPTION("@me"), true); + kickGuildMember(guildID, userID, reason) { + return this.requestHandler.request("DELETE", Endpoints.GUILD_MEMBER(guildID, userID), true, { + reason + }); } /** - * Get a channel's data via the REST API. REST mode is required to use this endpoint. - * @arg {String} channelID The ID of the channel - * @returns {Promise} + * Leave a guild + * @arg {String} guildID The ID of the guild + * @returns {Promise} */ - getRESTChannel(channelID) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); - } - return this.requestHandler.request("GET", Endpoints.CHANNEL(channelID), true) - .then((channel) => Channel.from(channel, this)); + leaveGuild(guildID) { + return this.requestHandler.request("DELETE", Endpoints.USER_GUILD("@me", guildID), true); } /** - * Get a guild's data via the REST API. REST mode is required to use this endpoint. - * @arg {String} guildID The ID of the guild - * @arg {Boolean} [withCounts=false] Whether the guild object will have approximateMemberCount and approximatePresenceCount - * @returns {Promise} + * Leaves a voice channel + * @arg {String} channelID The ID of the voice channel */ - getRESTGuild(guildID, withCounts = false) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); + leaveVoiceChannel(channelID) { + if(!channelID || !this.channelGuildMap[channelID]) { + return; } - return this.requestHandler.request("GET", Endpoints.GUILD(guildID), true, { - with_counts: withCounts - }).then((guild) => new Guild(guild, this)); + this.closeVoiceConnection(this.channelGuildMap[channelID]); } /** - * Get a list of the user's guilds via the REST API. REST mode is required to use this endpoint. - * @arg {Number} [limit=100] The max number of guilds to get (1 to 1000) - * @arg {String} [before] The lowest guild ID of the next page - * @arg {String} [after] The highest guild ID of the previous page - * @returns {Promise} + * Pin a message + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message + * @returns {Promise} */ - getRESTGuilds(limit, before, after) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); - } - return this.requestHandler.request("GET", Endpoints.USER_GUILDS("@me"), true, { - limit, - before, - after - }).then((guilds) => guilds.map((guild) => new Guild(guild, this))); + pinMessage(channelID, messageID) { + return this.requestHandler.request("PUT", Endpoints.CHANNEL_PIN(channelID, messageID), true); } /** - * Get a guild's channels via the REST API. REST mode is required to use this endpoint. + * Begin pruning a guild * @arg {String} guildID The ID of the guild - * @returns {Promise<(CategoryChannel[] | TextChannel[] | VoiceChannel[] | NewsChannel[])>} + * @arg {Number} [options] The options to pass to prune members + * @arg {Boolean} [options.computePruneCount=true] Whether or not the number of pruned members should be returned. Discord discourages setting this to true for larger guilds + * @arg {Number} [options.days=7] The number of days of inactivity to prune for + * @arg {Array} [options.includeRoles] An array of role IDs that members must have to be considered for pruning + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @returns {Promise} If computePruneCount was true, resolves with the number of pruned members */ - getRESTGuildChannels(guildID) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); - } - return this.requestHandler.request("GET", Endpoints.GUILD_CHANNELS(guildID), true) - .then((channels) => channels.map((channel) => Channel.from(channel, this))); + pruneMembers(guildID, options = {}) { + return this.requestHandler.request("POST", Endpoints.GUILD_PRUNE(guildID), true, { + days: options.days, + compute_prune_count: options.computePruneCount, + include_roles: options.includeRoles, + reason: options.reason + }).then((data) => data.pruned); } /** - * Get a guild's emojis via the REST API. REST mode is required to use this endpoint. - * @arg {String} guildID The ID of the guild - * @returns {Promise} An array of guild emoji objects + * Purge previous messages in a channel with an optional filter (bot accounts only) + * @arg {String} channelID The ID of the channel + * @arg {Number} limit The max number of messages to search through, -1 for no limit + * @arg {function} [filter] Optional filter function that returns a boolean when passed a Message object + * @arg {String} [before] Get messages before this message ID + * @arg {String} [after] Get messages after this message ID + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} Resolves with the number of messages deleted */ - getRESTGuildEmojis(guildID) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); + async purgeChannel(channelID, limit, filter, before, after, reason) { + if(typeof filter === "string") { + filter = (msg) => msg.content.includes(filter); + } + if(limit !== -1 && limit <= 0) { + return 0; } - return this.requestHandler.request("GET", Endpoints.GUILD_EMOJIS(guildID), true); + const toDelete = []; + let deleted = 0; + let done = false; + const checkToDelete = async () => { + const messageIDs = (done && toDelete) || (toDelete.length >= 100 && toDelete.splice(0, 100)); + if(messageIDs) { + deleted += messageIDs.length; + await this.deleteMessages(channelID, messageIDs, reason); + if(done) { + return deleted; + } + await sleep(1000); + return checkToDelete(); + } else if(done) { + return deleted; + } else { + await sleep(250); + return checkToDelete(); + } + }; + const del = async (_before, _after) => { + const messages = await this.getMessages(channelID, 100, _before, _after); + if(limit !== -1 && limit <= 0) { + done = true; + return; + } + for(const message of messages) { + if(limit !== -1 && limit <= 0) { + break; + } + if(message.timestamp < Date.now() - 1209600000) { // 14d * 24h * 60m * 60s * 1000ms + done = true; + return; + } + if(!filter || filter(message)) { + toDelete.push(message.id); + } + if(limit !== -1) { + limit--; + } + } + if((limit !== -1 && limit <= 0) || messages.length < 100) { + done = true; + return; + } + await del((_before || !_after) && messages[messages.length - 1].id, _after && messages[0].id); + }; + await del(before, after); + return checkToDelete(); } /** - * Get a guild emoji via the REST API. REST mode is required to use this endpoint. - * @arg {String} guildID The ID of the guild - * @arg {String} emojiID The ID of the emoji - * @returns {Promise} An emoji object + * [USER ACCOUNT] Remove a user from a group + * @arg {String} groupID The ID of the target group + * @arg {String} userID The ID of the target user + * @returns {Promise} */ - getRESTGuildEmoji(guildID, emojiID) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); - } - return this.requestHandler.request("GET", Endpoints.GUILD_EMOJI(guildID, emojiID), true); + removeGroupRecipient(groupID, userID) { + return this.requestHandler.request("DELETE", Endpoints.CHANNEL_RECIPIENT(groupID, userID), true); } /** - * Get a guild's members via the REST API. REST mode is required to use this endpoint. + * Remove a role from a guild member * @arg {String} guildID The ID of the guild - * @arg {Number} [limit=1] The max number of members to get (1 to 1000) - * @arg {String} [after] The highest user ID of the previous page - * @returns {Promise} + * @arg {String} memberID The ID of the member + * @arg {String} roleID The ID of the role + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - getRESTGuildMembers(guildID, limit, after) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); - } - return this.requestHandler.request("GET", Endpoints.GUILD_MEMBERS(guildID), true, { - limit, - after - }).then((members) => members.map((member) => new Member(member, this.guilds.get(guildID), this))); + removeGuildMemberRole(guildID, memberID, roleID, reason) { + return this.requestHandler.request("DELETE", Endpoints.GUILD_MEMBER_ROLE(guildID, memberID, roleID), true, { + reason + }); } /** - * Get a guild's members via the REST API. REST mode is required to use this endpoint. - * @arg {String} guildID The ID of the guild - * @arg {String} memberID The ID of the member - * @returns {Promise} + * Remove a reaction from a message + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @arg {String} [userID="@me"] The ID of the user to remove the reaction for + * @returns {Promise} */ - getRESTGuildMember(guildID, memberID) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); + removeMessageReaction(channelID, messageID, reaction, userID) { + if(reaction === decodeURI(reaction)) { + reaction = encodeURIComponent(reaction); } - return this.requestHandler.request("GET", Endpoints.GUILD_MEMBER(guildID, memberID), true).then((member) => new Member(member, this.guilds.get(guildID), this)); + return this.requestHandler.request("DELETE", Endpoints.CHANNEL_MESSAGE_REACTION_USER(channelID, messageID, reaction, userID || "@me"), true); } /** - * Get a guild's roles via the REST API. REST mode is required to use this endpoint. - * @arg {String} guildID The ID of the guild - * @returns {Promise} + * Remove all reactions from a message for a single emoji. + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @returns {Promise} */ - getRESTGuildRoles(guildID) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); + removeMessageReactionEmoji(channelID, messageID, reaction) { + if(reaction === decodeURI(reaction)) { + reaction = encodeURIComponent(reaction); } - return this.requestHandler.request("GET", Endpoints.GUILD_ROLES(guildID), true).then((roles) => roles.map((role) => new Role(role, null))); + return this.requestHandler.request("DELETE", Endpoints.CHANNEL_MESSAGE_REACTION(channelID, messageID, reaction), true); } /** - * Get a user's data via the REST API. REST mode is required to use this endpoint. - * @arg {String} userID The ID of the user - * @returns {Promise} + * Remove all reactions from a message + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message + * @returns {Promise} */ - getRESTUser(userID) { - if(!this.options.restMode) { - return Promise.reject(new Error("Eris REST mode is not enabled")); - } - return this.requestHandler.request("GET", Endpoints.USER(userID), true).then((user) => new User(user, this)); + removeMessageReactions(channelID, messageID) { + return this.requestHandler.request("DELETE", Endpoints.CHANNEL_MESSAGE_REACTIONS(channelID, messageID), true); } /** - * Search for guild members by partial nickname/username - * @arg {String} guildID The ID of the guild - * @arg {String} query The query string to match username(s) and nickname(s) against - * @arg {Number} [limit=1] The maximum number of members you want returned, capped at 100 - * @returns {Promise} + * [USER ACCOUNT] Remove a relationship with a user + * @arg {String} userID The ID of the target user + * @returns {Promise} */ - searchGuildMembers(guildID, query, limit) { - return this.requestHandler.request("GET", Endpoints.GUILD_MEMBERS_SEARCH(guildID), true, { - query, - limit - }).then((members) => { - const guild = this.guilds.get(guildID); - return members.map((member) => new Member(member, guild, this)); - }); + removeRelationship(userID) { + return this.requestHandler.request("DELETE", Endpoints.USER_RELATIONSHIP("@me", userID), true); } /** * [USER ACCOUNT] Search a channel's messages * @arg {String} channelID The ID of the channel * @arg {Object} query Search parameters - * @arg {String} [query.sortBy="timestamp"] What to sort by, either "timestamp" or "relevance" - * @arg {String} [query.sortOrder="desc"] What order to sort by, either "asc" or "desc" - * @arg {String} [query.content] Filter results by a content string + * @arg {String} [query.attachmentExtensions] Filter results by attachment extension + * @arg {String} [query.attachmentFilename] Filter results by attachment filename * @arg {String} [query.authorID] Filter results by an author ID - * @arg {String} [query.minID] The minimum message ID to return results for - * @arg {String} [query.maxID] The maximum message ID to return results for - * @arg {Number} [query.limit=25] How many messages to return, 1 <= limit <= 25 - * @arg {Number} [query.offset=0] The query index of the first message to be returned, 0 <= offset <= 5000 + * @arg {String} [query.content] Filter results by a content string * @arg {Number} [query.contextSize=2] How many context messages around each result to return. - * For example, if you searched for `6` and contextSize was 2, `[4, 5, 6, 7, 8]` would be returned - * @arg {String} [query.has] Only return messages with an "attachment", "embed", or "link" * @arg {String} [query.embedProviders] Filter results by embed provider * @arg {String} [query.embedTypes] Filter results by embed type - * @arg {String} [query.attachmentExtensions] Filter results by attachment extension - * @arg {String} [query.attachmentFilename] Filter results by attachment filename + * @arg {String} [query.has] Only return messages with an "attachment", "embed", or "link" + * @arg {Number} [query.limit=25] How many messages to return, 1 <= limit <= 25 + * @arg {String} [query.maxID] The maximum message ID to return results for + * @arg {String} [query.minID] The minimum message ID to return results for + * @arg {Number} [query.offset=0] The query index of the first message to be returned, 0 <= offset <= 5000 + * @arg {String} [query.sortBy="timestamp"] What to sort by, either "timestamp" or "relevance" + * @arg {String} [query.sortOrder="desc"] What order to sort by, either "asc" or "desc" + * For example, if you searched for `6` and contextSize was 2, `[4, 5, 6, 7, 8]` would be returned * @returns {Promise} A search result object. The object will have a `totalResults` key and `results` key. * Each entry in the result array is an array of Message objects. * In each array, the message where `Message.hit === true` is the matched message, while the other messages are context messages. @@ -2165,26 +2108,43 @@ class Client extends EventEmitter { })); } + /** + * Search for guild members by partial nickname/username + * @arg {String} guildID The ID of the guild + * @arg {String} query The query string to match username(s) and nickname(s) against + * @arg {Number} [limit=1] The maximum number of members you want returned, capped at 100 + * @returns {Promise} + */ + searchGuildMembers(guildID, query, limit) { + return this.requestHandler.request("GET", Endpoints.GUILD_MEMBERS_SEARCH(guildID), true, { + query, + limit + }).then((members) => { + const guild = this.guilds.get(guildID); + return members.map((member) => new Member(member, guild, this)); + }); + } + /** * [USER ACCOUNT] Search a guild's messages * @arg {String} guildID The ID of the guild * @arg {Object} query Search parameters - * @arg {String} [query.sortBy="timestamp"] What to sort by, either "timestamp" or "relevance" - * @arg {String} [query.sortOrder="desc"] What order to sort by, either "asc" or "desc" - * @arg {String} [query.content] Filter results by a content string + * @arg {String} [query.attachmentExtensions] Filter results by attachment extension + * @arg {String} [query.attachmentFilename] Filter results by attachment filename * @arg {String} [query.authorID] Filter results by an author ID + * @arg {String[]} [query.channelIDs] Filter results by channel ID + * @arg {String} [query.content] Filter results by a content string + * @arg {Number} [query.contextSize=2] How many context messages around each result to return. + * @arg {String} [query.embedProviders] Filter results by embed provider + * @arg {String} [query.embedTypes] Filter results by embed type + * @arg {String} [query.has] Only return messages with an "attachment", "embed", or "link" + * @arg {Number} [query.limit=25] How many messages to return, 1 <= limit <= 25 * @arg {String} [query.minID] The minimum message ID to return results for * @arg {String} [query.maxID] The maximum message ID to return results for - * @arg {Number} [query.limit=25] How many messages to return, 1 <= limit <= 25 * @arg {Number} [query.offset=0] The query index of the first message to be returned, 0 <= offset <= 5000 - * @arg {Number} [query.contextSize=2] How many context messages around each result to return. + * @arg {String} [query.sortBy="timestamp"] What to sort by, either "timestamp" or "relevance" + * @arg {String} [query.sortOrder="desc"] What order to sort by, either "asc" or "desc" * For example, if you searched for `6` and contextSize was 2, `[4, 5, 6, 7, 8]` would be returned - * @arg {String} [query.has] Only return messages with an "attachment", "embed", or "link" - * @arg {String} [query.embedProviders] Filter results by embed provider - * @arg {String} [query.embedTypes] Filter results by embed type - * @arg {String} [query.attachmentExtensions] Filter results by attachment extension - * @arg {String} [query.attachmentFilename] Filter results by attachment filename - * @arg {String[]} [query.channelIDs] Filter results by channel ID * @returns {Promise} A search result object. The object will have a `totalResults` key and `results` key. * Each entry in the result array is an array of Message objects. * In each array, the message where `Message.hit === true` is the matched message, while the other messages are context messages. @@ -2221,6 +2181,48 @@ class Client extends EventEmitter { })); } + /** + * Send typing status in a channel + * @arg {String} channelID The ID of the channel + * @returns {Promise} + */ + sendChannelTyping(channelID) { + return this.requestHandler.request("POST", Endpoints.CHANNEL_TYPING(channelID), true); + } + + /** + * Force a guild integration to sync + * @arg {String} guildID The ID of the guild + * @arg {String} integrationID The ID of the integration + * @returns {Promise} + */ + syncGuildIntegration(guildID, integrationID) { + return this.requestHandler.request("POST", Endpoints.GUILD_INTEGRATION_SYNC(guildID, integrationID), true); + } + + /** + * Unban a user from a guild + * @arg {String} guildID The ID of the guild + * @arg {String} userID The ID of the user + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + unbanGuildMember(guildID, userID, reason) { + return this.requestHandler.request("DELETE", Endpoints.GUILD_BAN(guildID, userID), true, { + reason + }); + } + + /** + * Unpin a message + * @arg {String} channelID The ID of the channel + * @arg {String} messageID The ID of the message + * @returns {Promise} + */ + unpinMessage(channelID, messageID) { + return this.requestHandler.request("DELETE", Endpoints.CHANNEL_PIN(channelID, messageID), true); + } + _formatAllowedMentions(allowed) { if(!allowed) { return this.options.allowedMentions; diff --git a/lib/gateway/Shard.js b/lib/gateway/Shard.js index dce912761..aff7c9867 100644 --- a/lib/gateway/Shard.js +++ b/lib/gateway/Shard.js @@ -286,16 +286,16 @@ class Shard extends EventEmitter { * @event Client#presenceUpdate * @prop {Member | Relationship} other The updated member or relationship * @prop {Object?} oldPresence The old presence data. If the user was offline when the bot started and the client option getAllUsers is not true, this will be null - * @prop {String} oldPresence.status The other user's old status. Either "online", "idle", or "offline" - * @prop {Object?} oldPresence.game The old game the other user was playing - * @prop {String} oldPresence.game.name The name of the active game - * @prop {Number} oldPresence.game.type The type of the active game (0 is default, 1 is Twitch, 2 is YouTube) - * @prop {String} oldPresence.game.url The url of the active game + * @prop {Object[]?} oldPresence.activities The member's current activities * @prop {Object?} oldPresence.clientStatus The member's per-client status * @prop {String} oldPresence.clientStatus.web The member's status on web. Either "online", "idle", "dnd", or "offline". Will be "online" for bots * @prop {String} oldPresence.clientStatus.desktop The member's status on desktop. Either "online", "idle", "dnd", or "offline". Will be "offline" for bots * @prop {String} oldPresence.clientStatus.mobile The member's status on mobile. Either "online", "idle", "dnd", or "offline". Will be "offline" for bots - * @prop {Object[]?} oldPresence.activities The member's current activities + * @prop {Object?} oldPresence.game The old game the other user was playing + * @prop {String} oldPresence.game.name The name of the active game + * @prop {Number} oldPresence.game.type The type of the active game (0 is default, 1 is Twitch, 2 is YouTube) + * @prop {String} oldPresence.game.url The url of the active game + * @prop {String} oldPresence.status The other user's old status. Either "online", "idle", or "offline" */ this.emit("presenceUpdate", this.client.relationships.update(packet.d), oldPresence); break; @@ -309,10 +309,10 @@ class Shard extends EventEmitter { let oldPresence = null; if(member) { oldPresence = { - game: member.game, - status: member.status, + activities: member.activities, clientStatus: member.clientStatus, - activities: member.activities + game: member.game, + status: member.status }; } if((!member && packet.d.user.username) || oldPresence) { @@ -377,10 +377,10 @@ class Shard extends EventEmitter { } } const oldState = { - mute: member.voiceState.mute, deaf: member.voiceState.deaf, - selfMute: member.voiceState.selfMute, + mute: member.voiceState.mute, selfDeaf: member.voiceState.selfDeaf, + selfMute: member.voiceState.selfMute, selfStream: member.voiceState.selfStream }; const oldChannelID = member.voiceState.channelID; @@ -433,10 +433,10 @@ class Shard extends EventEmitter { * @event Client#voiceStateUpdate * @prop {Member} member The member * @prop {Object} oldState The old voice state - * @prop {Boolean} oldState.mute The previous server mute status * @prop {Boolean} oldState.deaf The previous server deaf status - * @prop {Boolean} oldState.selfMute The previous self mute status + * @prop {Boolean} oldState.mute The previous server mute status * @prop {Boolean} oldState.selfDeaf The previous self deaf status + * @prop {Boolean} oldState.selfMute The previous self mute status * @prop {Boolean} oldState.selfStream The previous self stream status */ this.emit("voiceStateUpdate", member, oldState); @@ -480,15 +480,15 @@ class Shard extends EventEmitter { if(message) { oldMessage = { attachments: message.attachments, + channelMentions: message.channelMentions, content: message.content, - embeds: message.embeds, editedTimestamp: message.editedTimestamp, + embeds: message.embeds, mentionedBy: message.mentionedBy, mentions: message.mentions, + pinned: message.pinned, roleMentions: message.roleMentions, - channelMentions: message.channelMentions, - tts: message.tts, - pinned: message.pinned + tts: message.tts }; } else if(!packet.d.timestamp) { packet.d.channel = channel; @@ -501,15 +501,15 @@ class Shard extends EventEmitter { * @prop {Message} message The updated message. If oldMessage is null, it is recommended to discard this event, since the message data will be very incomplete (only `id` and `channel` are guaranteed) * @prop {Object?} oldMessage The old message data. If the message was cached, this will return the full old message. Otherwise, it will be null * @prop {Object[]} oldMessage.attachments Array of attachments - * @prop {Object[]} oldMessage.embeds Array of embeds + * @prop {String[]} oldMessage.channelMentions Array of mentions channels' ids. * @prop {String} oldMessage.content Message content * @prop {Number} oldMessage.editedTimestamp Timestamp of latest message edit + * @prop {Object[]} oldMessage.embeds Array of embeds * @prop {Object} oldMessage.mentionedBy Object of if different things mention the bot user - * @prop {Boolean} oldMessage.pinned Whether the message was pinned or not - * @prop {Boolean} oldMessage.tts Whether to play the message using TTS or not * @prop {String[]} oldMessage.mentions Array of mentioned users' ids + * @prop {Boolean} oldMessage.pinned Whether the message was pinned or not * @prop {String[]} oldMessage.roleMentions Array of mentioned roles' ids. - * @prop {String[]} oldMessage.channelMentions Array of mentions channels' ids. + * @prop {Boolean} oldMessage.tts Whether to play the message using TTS or not */ this.emit("messageUpdate", channel.messages.update(packet.d, this.client), oldMessage); break; @@ -698,8 +698,10 @@ class Shard extends EventEmitter { /** * Fired when someone removes all reactions from a message for a single emoji * @event Client#messageReactionRemoveEmoji - * @prop {Message | Object} message The message object. If the message is not cached, this will be an object with `id`, `channel`, and if inside a guild, `guildID` keys. If the channel is not cached, channel key will be an object with only an id. No other property is guaranteed - * @prop {Object} emoji The emoji object with a `name` prop. If the emoji is a custom emoji it will also have an `id` prop. + * @prop {Message | Object} message The message object. If the message is not cached, this will be an object with `id` and `channel` keys. If the channel is not cached, channel key will be an object with only an id. No other property is guaranteed + * @prop {Object} emoji The reaction emoji object + * @prop {String?} emoji.id The ID of the emoji (null for non-custom emojis) + * @prop {String} emoji.name The emoji name */ this.emit("messageReactionRemoveEmoji", message, packet.d.emoji); break; @@ -840,54 +842,54 @@ class Shard extends EventEmitter { break; } const oldGuild = { - name: guild.name, - region: guild.region, - icon: guild.icon, - verificationLevel: guild.verificationLevel, - defaultNotifications: guild.defaultNotifications, - explicitContentFilter: guild.explicitContentFilter, - systemChannelID: guild.systemChannelID, - rulesChannelID: guild.rulesChannelID, - publicUpdatesChannelID: guild.publicUpdatesChannelID, - preferredLocale: guild.preferredLocale, afkChannelID: guild.afkChannelID, afkTimeout: guild.afkTimeout, - ownerID: guild.ownerID, - splash: guild.splash, banner: guild.banner, + defaultNotifications: guild.defaultNotifications, description: guild.description, - features: guild.features, emojis: guild.emojis, - mfaLevel: guild.mfaLevel, + explicitContentFilter: guild.explicitContentFilter, + features: guild.features, + icon: guild.icon, large: guild.large, - maxPresences: guild.maxPresences + maxPresences: guild.maxPresences, + mfaLevel: guild.mfaLevel, + name: guild.name, + ownerID: guild.ownerID, + preferredLocale: guild.preferredLocale, + publicUpdatesChannelID: guild.publicUpdatesChannelID, + region: guild.region, + rulesChannelID: guild.rulesChannelID, + splash: guild.splash, + systemChannelID: guild.systemChannelID, + verificationLevel: guild.verificationLevel }; /** * Fired when a guild is updated * @event Client#guildUpdate * @prop {Guild} guild The guild * @prop {Object} oldGuild The old guild data - * @prop {String} oldGuild.name The name of the guild - * @prop {String} oldGuild.region The region of the guild - * @prop {String?} oldGuild.icon The hash of the guild icon, or null if no icon - * @prop {Number} oldGuild.verificationLevel The guild verification level - * @prop {Number} oldGuild.defaultNotifications The default notification settings for the guild. 0 is "All Messages", 1 is "Only @mentions" - * @prop {Number} oldGuild.explicitContentFilter The explicit content filter level for the guild. 0 is off, 1 is on for people without roles, 2 is on for all - * @prop {String?} oldGuild.systemChannelID The ID of the default channel for system messages (built-in join messages and boost messages) - * @prop {String?} oldGuild.rulesChannelID The channel where "PUBLIC" guilds display rules and/or guidelines - * @prop {String?} oldGuild.publicUpdatesChannelID ID of the guild's updates channel if the guild has "PUBLIC" features - * @prop {String} oldGuild.preferredLocale Preferred "PUBLIC" guild language used in server discovery and notices from Discord * @prop {String} oldGuild.afkChannelID The ID of the AFK voice channel * @prop {Number} oldGuild.afkTimeout The AFK timeout in seconds - * @prop {String} oldGuild.ownerID The ID of the user that is the guild owner - * @prop {String?} oldGuild.splash The hash of the guild splash image, or null if no splash (VIP only) * @prop {String?} oldGuild.banner The hash of the guild banner image, or null if no splash (VIP only) + * @prop {Number} oldGuild.defaultNotifications The default notification settings for the guild. 0 is "All Messages", 1 is "Only @mentions" * @prop {String?} oldGuild.description The description for the guild (VIP only) - * @prop {Object[]} oldGuild.features An array of guild features * @prop {Object[]} oldGuild.emojis An array of guild emojis - * @prop {Number} oldGuild.mfaLevel The admin 2FA level for the guild. 0 is not required, 1 is required + * @prop {Number} oldGuild.explicitContentFilter The explicit content filter level for the guild. 0 is off, 1 is on for people without roles, 2 is on for all + * @prop {Object[]} oldGuild.features An array of guild features + * @prop {String?} oldGuild.icon The hash of the guild icon, or null if no icon * @prop {Boolean} oldGuild.large Whether the guild is "large" by "some Discord standard" * @prop {Number} oldGuild.maxPresences The maximum number of people that can be online in a guild at once (returned from REST API only) + * @prop {Number} oldGuild.mfaLevel The admin 2FA level for the guild. 0 is not required, 1 is required + * @prop {String} oldGuild.name The name of the guild + * @prop {String} oldGuild.ownerID The ID of the user that is the guild owner + * @prop {String} oldGuild.preferredLocale Preferred "PUBLIC" guild language used in server discovery and notices from Discord + * @prop {String?} oldGuild.publicUpdatesChannelID ID of the guild's updates channel if the guild has "PUBLIC" features + * @prop {String} oldGuild.region The region of the guild + * @prop {String?} oldGuild.rulesChannelID The channel where "PUBLIC" guilds display rules and/or guidelines + * @prop {String?} oldGuild.splash The hash of the guild splash image, or null if no splash (VIP only) + * @prop {String?} oldGuild.systemChannelID The ID of the default channel for system messages (built-in join messages and boost messages) + * @prop {Number} oldGuild.verificationLevel The guild verification level */ this.emit("guildUpdate", this.client.guilds.update(packet.d, this.client), oldGuild); break; @@ -981,10 +983,10 @@ class Shard extends EventEmitter { color: role.color, hoist: role.hoist, managed: role.managed, + mentionable: role.mentionable, name: role.name, permissions: role.permissions, - position: role.position, - mentionable: role.mentionable + position: role.position }; /** * Fired when a guild role is updated @@ -992,13 +994,13 @@ class Shard extends EventEmitter { * @prop {Guild} guild The guild * @prop {Role} role The updated role * @prop {Object} oldRole The old role data - * @prop {String} oldRole.name The name of the role - * @prop {Boolean} oldRole.mentionable Whether the role is mentionable or not - * @prop {Boolean} oldRole.managed Whether a guild integration manages this role or not - * @prop {Boolean} oldRole.hoist Whether users with this role are hoisted in the user list or not * @prop {Number} oldRole.color The hex color of the role in base 10 - * @prop {Number} oldRole.position The position of the role + * @prop {Boolean} oldRole.hoist Whether users with this role are hoisted in the user list or not + * @prop {Boolean} oldRole.managed Whether a guild integration manages this role or not + * @prop {Boolean} oldRole.mentionable Whether the role is mentionable or not + * @prop {String} oldRole.name The name of the role * @prop {Permission} oldRole.permissions The permissions number of the role + * @prop {Number} oldRole.position The position of the role */ this.emit("guildRoleUpdate", guild, guild.roles.update(packet.d.role, guild), oldRole); break; @@ -1118,15 +1120,15 @@ class Shard extends EventEmitter { }; } else if(channel instanceof GuildChannel) { oldChannel = { - name: channel.name, - topic: channel.topic, - type: channel.type, - position: channel.position, bitrate: channel.bitrate, + name: channel.name, nsfw: channel.nsfw, - permissionOverwrites: channel.permissionOverwrites, parentID: channel.parentID, + permissionOverwrites: channel.permissionOverwrites, + position: channel.position, rateLimitPerUser: channel.rateLimitPerUser, + topic: channel.topic, + type: channel.type, userLimit: channel.userLimit }; } else { @@ -1166,15 +1168,15 @@ class Shard extends EventEmitter { * @event Client#channelUpdate * @prop {TextChannel | VoiceChannel | CategoryChannel | StoreChannel | NewsChannel | GuildChannel | PrivateChannel} channel The updated channel * @prop {Object} oldChannel The old channel data + * @prop {Number?} oldChannel.bitrate The bitrate of the channel (voice channels only) * @prop {String} oldChannel.name The name of the channel - * @prop {Number} oldChannel.position The position of the channel * @prop {Boolean} oldChannel.nsfw Whether the channel is NSFW or not - * @prop {String?} oldChannel.topic The topic of the channel (text channels only) - * @prop {Number} oldChannel.type The type of the old channel - * @prop {Number?} oldChannel.bitrate The bitrate of the channel (voice channels only) - * @prop {Collection} oldChannel.permissionOverwrites Collection of PermissionOverwrites in this channel * @prop {String?} oldChannel.parentID The ID of the category this channel belongs to + * @prop {Collection} oldChannel.permissionOverwrites Collection of PermissionOverwrites in this channel + * @prop {Number} oldChannel.position The position of the channel * @prop {Number} oldChannel.rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled (text channels only) + * @prop {String?} oldChannel.topic The topic of the channel (text channels only) + * @prop {Number} oldChannel.type The type of the old channel * @prop {Number} oldChannel.userLimit The max number of users that can join the channel (voice channels only) */ this.emit("channelUpdate", channel, oldChannel); @@ -1262,10 +1264,10 @@ class Shard extends EventEmitter { throw new Error("CALL_UPDATE but channel has no call"); } const oldCall = { + endedTimestamp: channel.call.endedTimestamp, participants: channel.call.participants, - ringing: channel.call.ringing, region: channel.call.region, - endedTimestamp: channel.call.endedTimestamp, + ringing: channel.call.ringing, unavailable: channel.call.unavailable }; /** @@ -1273,10 +1275,10 @@ class Shard extends EventEmitter { * @event Client#callUpdate * @prop {Call} call The updated call * @prop {Object} oldCall The old call data - * @prop {String[]} oldCall.participants The IDs of the call participants * @prop {Number?} oldCall.endedTimestamp The timestamp of the call end - * @prop {String[]?} oldCall.ringing The IDs of people that were being rung + * @prop {String[]} oldCall.participants The IDs of the call participants * @prop {String?} oldCall.region The region of the call server + * @prop {String[]?} oldCall.ringing The IDs of people that were being rung * @prop {Boolean} oldCall.unavailable Whether the call was unavailable or not */ this.emit("callUpdate", channel.call.update(packet.d), oldCall); @@ -1325,9 +1327,9 @@ class Shard extends EventEmitter { * @event Client#friendSuggestionCreate * @prop {User} user The suggested user * @prop {String[]} reasons Array of reasons why this suggestion was made - * @prop {Number} reasons.type Type of reason? - * @prop {String} reasons.platform_type Platform you share with the user * @prop {String} reasons.name Username of suggested user on that platform + * @prop {String} reasons.platform_type Platform you share with the user + * @prop {Number} reasons.type Type of reason? */ this.emit("friendSuggestionCreate", new User(packet.d.suggested_user, this.client), packet.d.reasons); break; diff --git a/lib/structures/Call.js b/lib/structures/Call.js index 7700d4d77..47ecdfa28 100644 --- a/lib/structures/Call.js +++ b/lib/structures/Call.js @@ -6,10 +6,10 @@ const VoiceState = require("./VoiceState"); /** * Represents a call -* @prop {String} id The ID of the call -* @prop {Number} createdAt Timestamp of the call's creation * @prop {GroupChannel} channel The call channel +* @prop {Number} createdAt Timestamp of the call's creation * @prop {Number?} endedTimestamp The timestamp of the call end +* @prop {String} id The ID of the call * @prop {String[]} participants The IDs of the call participants * @prop {String?} region The region of the call server * @prop {String[]?} ringing The IDs of people that still have not responded to the call request diff --git a/lib/structures/CategoryChannel.js b/lib/structures/CategoryChannel.js index 7f819333b..4d99d0223 100644 --- a/lib/structures/CategoryChannel.js +++ b/lib/structures/CategoryChannel.js @@ -6,16 +6,18 @@ const GuildChannel = require("./GuildChannel"); /** * Represents a guild category channel * @extends GuildChannel +* @prop {Collection} channels A collection of guild channels that are part of this category +* @prop {Client} client The client that initialized the channel +* @prop {Number} createdAt Timestamp of the channel's creation +* @prop {Guild} guild The guild that owns the channel * @prop {String} id The ID of the channel * @prop {String} mention A string that mentions the channel -* @prop {Number} type The type of the channel -* @prop {Guild} guild The guild that owns the channel -* @prop {String?} parentID The ID of the category this channel belongs to * @prop {String} name The name of the channel -* @prop {Number} position The position of the channel * @prop {Boolean} nsfw Whether the channel is an NSFW channel or not +* @prop {String?} parentID The ID of the category this channel belongs to * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this channel -* @prop {Collection} channels A collection of guild channels that are part of this category +* @prop {Number} position The position of the channel +* @prop {Number} type The type of the channel */ class CategoryChannel extends GuildChannel { get channels() { diff --git a/lib/structures/Channel.js b/lib/structures/Channel.js index 61eba5925..d0a9e69c4 100644 --- a/lib/structures/Channel.js +++ b/lib/structures/Channel.js @@ -5,9 +5,9 @@ const {ChannelTypes} = require("../Constants"); /** * Represents a channel. You also probably want to look at CategoryChannel, GroupChannel, NewsChannel, PrivateChannel, TextChannel, and VoiceChannel. -* @prop {String} id The ID of the channel * @prop {Client} client The client that initialized the channel * @prop {Number} createdAt Timestamp of the channel's creation +* @prop {String} id The ID of the channel * @prop {String} mention A string that mentions the channel * @prop {Number} type The type of the channel */ diff --git a/lib/structures/GroupChannel.js b/lib/structures/GroupChannel.js index 49173cce2..6a9216c82 100644 --- a/lib/structures/GroupChannel.js +++ b/lib/structures/GroupChannel.js @@ -8,15 +8,15 @@ const User = require("./User"); /** * [USER ACCOUNT] Represents a group channel. See PrivateChannel docs for additional properties. * @extends PrivateChannel -* @prop {String} id The ID of the channel -* @prop {String} mention A string that mentions the channel * @prop {Call?} call The current group call, if any -* @prop {Call?} lastCall The previous group call, if any -* @prop {Collection} recipients The recipients in this private channel -* @prop {String} name The name of the group channel * @prop {String?} icon The hash of the group channel icon * @prop {String?} iconURL The URL of the group channel icon +* @prop {String} id The ID of the channel +* @prop {Call?} lastCall The previous group call, if any +* @prop {String} mention A string that mentions the channel +* @prop {String} name The name of the group channel * @prop {String} ownerID The ID of the user that is the group owner +* @prop {Collection} recipients The recipients in this private channel */ class GroupChannel extends PrivateChannel { // (╯°□°)╯︵ ┻━┻ constructor(data, client) { @@ -40,16 +40,8 @@ class GroupChannel extends PrivateChannel { // (╯°□°)╯︵ ┻━┻ } } - /** - * [USER ACCOUNT] Edit the channel's properties - * @arg {Object} options The properties to edit - * @arg {String} [options.name] The name of the channel - * @arg {String} [options.icon] The icon of the channel as a base64 data URI (group channels only). Note: base64 strings alone are not base64 data URI strings - * @arg {String} [options.ownerID] The ID of the channel owner (group channels only) - * @returns {Promise} - */ - edit(options) { - return this.client.editChannel.call(this.client, this.id, options); + get iconURL() { + return this.icon ? this.client._formatImage(Endpoints.CHANNEL_ICON(this.id, this.icon)) : null; } /** @@ -62,25 +54,33 @@ class GroupChannel extends PrivateChannel { // (╯°□°)╯︵ ┻━┻ } /** - * [USER ACCOUNT] Remove a user from the group - * @arg {String} userID The ID of the target user - * @returns {Promise} + * Get the group's icon with the given format and size + * @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp") + * @arg {Number} [size] The size of the icon (any power of two between 16 and 4096) */ - removeRecipient(userID) { - return this.client.removeGroupRecipient.call(this.client, this.id, userID); + dynamicIconURL(format, size) { + return this.icon ? this.client._formatImage(Endpoints.CHANNEL_ICON(this.id, this.icon), format, size) : null; } - get iconURL() { - return this.icon ? this.client._formatImage(Endpoints.CHANNEL_ICON(this.id, this.icon)) : null; + /** + * [USER ACCOUNT] Edit the channel's properties + * @arg {Object} options The properties to edit + * @arg {String} [options.name] The name of the channel + * @arg {String} [options.icon] The icon of the channel as a base64 data URI (group channels only). Note: base64 strings alone are not base64 data URI strings + * @arg {String} [options.ownerID] The ID of the channel owner (group channels only) + * @returns {Promise} + */ + edit(options) { + return this.client.editChannel.call(this.client, this.id, options); } /** - * Get the group's icon with the given format and size - * @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp") - * @arg {Number} [size] The size of the icon (any power of two between 16 and 4096) + * [USER ACCOUNT] Remove a user from the group + * @arg {String} userID The ID of the target user + * @returns {Promise} */ - dynamicIconURL(format, size) { - return this.icon ? this.client._formatImage(Endpoints.CHANNEL_ICON(this.id, this.icon), format, size) : null; + removeRecipient(userID) { + return this.client.removeGroupRecipient.call(this.client, this.id, userID); } toJSON(props = []) { diff --git a/lib/structures/Guild.js b/lib/structures/Guild.js index 32616ea46..4341f57cc 100644 --- a/lib/structures/Guild.js +++ b/lib/structures/Guild.js @@ -13,49 +13,49 @@ const {Permissions} = require("../Constants"); /** * Represents a guild -* @prop {String} id The ID of the guild -* @prop {Number} createdAt Timestamp of the guild's creation -* @prop {String} name The name of the guild -* @prop {Number} verificationLevel The guild verification level -* @prop {String} region The region of the guild -* @prop {String?} icon The hash of the guild icon, or null if no icon * @prop {String?} afkChannelID The ID of the AFK voice channel * @prop {Number} afkTimeout The AFK timeout in seconds +* @prop {Number?} approximateMemberCount The approximate number of members in the guild (REST only) +* @prop {Number?} approximatePresenceCount The approximate number of presences in the guild (REST only) +* @prop {String?} banner The hash of the guild banner image, or null if no banner (VIP only) +* @prop {String?} bannerURL The URL of the guild's banner image +* @prop {Collection} channels Collection of Channels in the guild +* @prop {Number} createdAt Timestamp of the guild's creation * @prop {Number} defaultNotifications The default notification settings for the guild. 0 is "All Messages", 1 is "Only @mentions" -* @prop {Number} mfaLevel The admin 2FA level for the guild. 0 is not required, 1 is required +* @prop {String?} description The description for the guild (VIP only) +* @prop {Object[]} emojis An array of guild emoji objects +* @prop {Number} explicitContentFilter The explicit content filter level for the guild. 0 is off, 1 is on for people without roles, 2 is on for all +* @prop {String[]} features An array of guild feature strings +* @prop {String?} icon The hash of the guild icon, or null if no icon +* @prop {String?} iconURL The URL of the guild's icon +* @prop {String} id The ID of the guild * @prop {Number} joinedAt Timestamp of when the bot account joined the guild -* @prop {String} ownerID The ID of the user that is the guild owner -* @prop {String?} systemChannelID The ID of the default channel for system messages (built-in join messages and boost messages) -* @prop {String?} splash The hash of the guild splash image, or null if no splash (VIP only) -* @prop {String?} banner The hash of the guild banner image, or null if no banner (VIP only) -* @prop {Boolean} unavailable Whether the guild is unavailable or not * @prop {Boolean} large Whether the guild is "large" by "some Discord standard" +* @prop {Number} mfaLevel The admin 2FA level for the guild. 0 is not required, 1 is required +* @prop {Number} maxMembers The maximum amount of members for the guild * @prop {Number} maxPresences The maximum number of people that can be online in a guild at once (returned from REST API only) -* @prop {Collection} voiceStates Collection of voice states in the guild -* @prop {Collection} channels Collection of Channels in the guild -* @prop {Collection} members Collection of Members in the guild +* @prop {Number?} maxVideoChannelUsers The max number of users allowed in a video channel * @prop {Number} memberCount Number of members in the guild +* @prop {Collection} members Collection of Members in the guild +* @prop {String} name The name of the guild +* @prop {String} ownerID The ID of the user that is the guild owner +* @prop {String} preferredLocale Preferred "PUBLIC" guild language used in server discovery and notices from Discord +* @prop {Number?} premiumSubscriptionCount The total number of users currently boosting this guild +* @prop {Number} premiumTier Nitro boost level of the guild +* @prop {String?} publicUpdatesChannelID ID of the guild's updates channel if the guild has "PUBLIC" features +* @prop {String} region The region of the guild * @prop {Collection} roles Collection of Roles in the guild +* @prop {String?} rulesChannelID The channel where "PUBLIC" guilds display rules and/or guidelines * @prop {Shard} shard The Shard that owns the guild -* @prop {String[]} features An array of guild feature strings -* @prop {Object[]} emojis An array of guild emoji objects -* @prop {String?} iconURL The URL of the guild's icon -* @prop {String?} bannerURL The URL of the guild's banner image +* @prop {String?} splash The hash of the guild splash image, or null if no splash (VIP only) * @prop {String?} splashURL The URL of the guild's splash image -* @prop {Number} explicitContentFilter The explicit content filter level for the guild. 0 is off, 1 is on for people without roles, 2 is on for all -* @prop {Number} premiumTier Nitro boost level of the guild -* @prop {Number?} premiumSubscriptionCount The total number of users currently boosting this guild +* @prop {String?} systemChannelID The ID of the default channel for system messages (built-in join messages and boost messages) +* @prop {Boolean} unavailable Whether the guild is unavailable or not * @prop {String?} vanityURL The vanity URL of the guild (VIP only) -* @prop {String} preferredLocale Preferred "PUBLIC" guild language used in server discovery and notices from Discord -* @prop {String?} description The description for the guild (VIP only) -* @prop {Number} maxMembers The maximum amount of members for the guild -* @prop {String?} publicUpdatesChannelID ID of the guild's updates channel if the guild has "PUBLIC" features -* @prop {String?} rulesChannelID The channel where "PUBLIC" guilds display rules and/or guidelines -* @prop {Number?} maxVideoChannelUsers The max number of users allowed in a video channel -* @prop {Boolean?} widgetEnabled Whether the guild widget is enabled. REST only. +* @prop {Number} verificationLevel The guild verification level +* @prop {Collection} voiceStates Collection of voice states in the guild * @prop {Number?} widgetChannelID The channel id that the widget will generate an invite to. REST only. -* @prop {Number?} approximateMemberCount The approximate number of members in the guild (REST only) -* @prop {Number?} approximatePresenceCount The approximate number of presences in the guild (REST only) +* @prop {Boolean?} widgetEnabled Whether the guild widget is enabled. REST only. */ class Guild extends Base { constructor(data, client) { @@ -227,68 +227,38 @@ class Guild extends Base { } } - /** - * Request all guild members from Discord - * @arg {Number} [timeout] The number of milliseconds to wait before resolving early. Defaults to the `requestTimeout` client option - * @returns {Promise} Resolves with the total number of fetched members. - */ - fetchAllMembers(timeout) { - return this.fetchMembers({ - timeout - }).then((m) => m.length); - } - - /** - * Request specific guild members through the gateway connection - * @arg {Object} [options] Options for fetching the members - * @arg {String} [options.query] The query used for looking up the members. When using intents, `GUILD_MEMBERS` is required to fetch all members. - * @arg {String[]} [options.userIDs] The IDs of members to fetch - * @arg {Number} [options.limit] The maximum number of members to fetch - * @arg {Number} [options.timeout] The number of milliseconds to wait before resolving early. Defaults to the `requestTimeout` client option - * @arg {Boolean} [options.presences] Whether to request member presences or not. When using intents, the `GUILD_PRESENCES` intent is required. - * @returns {Promise} Resolves with the fetched members. - */ - fetchMembers(options) { - return this.shard.requestGuildMembers(this.id, options); + get bannerURL() { + return this.banner ? this._client._formatImage(Endpoints.GUILD_BANNER(this.id, this.banner)) : null; } get iconURL() { return this.icon ? this._client._formatImage(Endpoints.GUILD_ICON(this.id, this.icon)) : null; } - /** - * Get the guild's icon with the given format and size - * @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp") - * @arg {Number} [size] The size of the icon (any power of two between 16 and 4096) - */ - dynamicIconURL(format, size) { - return this.icon ? this._client._formatImage(Endpoints.GUILD_ICON(this.id, this.icon), format, size) : null; - } - get splashURL() { return this.splash ? this._client._formatImage(Endpoints.GUILD_SPLASH(this.id, this.splash)) : null; } - get bannerURL() { - return this.banner ? this._client._formatImage(Endpoints.GUILD_BANNER(this.id, this.banner)) : null; - } - /** - * Get the guild's splash with the given format and size - * @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp") - * @arg {Number} [size] The size of the icon (any power of two between 16 and 4096) + * Add a role to a guild member + * @arg {String} memberID The ID of the member + * @arg {String} roleID The ID of the role + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - dynamicSplashURL(format, size) { - return this.splash ? this._client._formatImage(Endpoints.GUILD_SPLASH(this.id, this.splash), format, size) : null; + addMemberRole(memberID, roleID, reason) { + return this._client.addGuildMemberRole.call(this._client, this.id, memberID, roleID, reason); } /** - * Get the guild's banner with the given format and size - * @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp") - * @arg {Number} [size] The size of the icon (any power of two between 16 and 4096) + * Ban a user from the guild + * @arg {String} userID The ID of the member + * @arg {Number} [deleteMessageDays=0] Number of days to delete messages for + * @arg {String} [reason] Reason for the ban + * @returns {Promise} */ - dynamicBannerURL(format, size) { - return this.banner ? this._client._formatImage(Endpoints.GUILD_BANNER(this.id, this.banner), format, size) : null; + banMember(userID, deleteMessageDays, reason) { + return this._client.banGuildMember.call(this._client, this.id, userID, deleteMessageDays, reason); } /** @@ -296,13 +266,13 @@ class Guild extends Base { * @arg {String} name The name of the channel * @arg {Number} [type=0] The type of the channel, either 0 (text), 2 (voice), or 4 (category) * @arg {Object | String} [options] The properties the channel should have. If `options` is a string, it will be treated as `options.parentID` (see below). Passing a string is deprecated and will not be supported in future versions. - * @arg {String} [options.topic] The topic of the channel (text channels only) - * @arg {Boolean} [options.nsfw] The nsfw status of the channel * @arg {Number} [options.bitrate] The bitrate of the channel (voice channels only) + * @arg {Boolean} [options.nsfw] The nsfw status of the channel * @arg {String?} [options.parentID] The ID of the parent category channel for this channel * @arg {Array} [options.permissionOverwrites] An array containing permission overwrite objects * @arg {Number} [options.rateLimitPerUser] The time in seconds a user has to wait before sending another message (does not affect bots or users with manageMessages/manageChannel permissions) (text channels only) * @arg {String} [options.reason] The reason to be displayed in audit logs + * @arg {String} [options.topic] The topic of the channel (text channels only) * @arg {Number} [options.userLimit] The channel user limit (voice channels only) * @returns {Promise} */ @@ -313,8 +283,8 @@ class Guild extends Base { /** * Create a emoji in the guild * @arg {Object} options Emoji options - * @arg {String} options.name The name of emoji * @arg {String} options.image The base 64 encoded string + * @arg {String} options.name The name of emoji * @arg {Array} [options.roles] An array containing authorized role IDs * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} A guild emoji object @@ -323,37 +293,14 @@ class Guild extends Base { return this._client.createGuildEmoji.call(this._client, this.id, options, reason); } - /** - * Edit a emoji in the guild - * @arg {String} emojiID The ID of the emoji you want to modify - * @arg {Object} options Emoji options - * @arg {String} [options.name] The name of emoji - * @arg {Array} [options.roles] An array containing authorized role IDs - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} A guild emoji object - */ - editEmoji(emojiID, options, reason) { - return this._client.editGuildEmoji.call(this._client, this.id, emojiID, options, reason); - } - - /** - * Delete a emoji in the guild - * @arg {String} emojiID The ID of the emoji - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - deleteEmoji(emojiID, reason) { - return this._client.deleteGuildEmoji.call(this._client, this.id, emojiID, reason); - } - /** * Create a guild role * @arg {Object|Role} [options] An object or Role containing the properties to set - * @arg {String} [options.name] The name of the role - * @arg {Number} [options.permissions] The role permissions number * @arg {Number} [options.color] The hex color of the role, in number form (ex: 0x3d15b3 or 4040115) * @arg {Boolean} [options.hoist] Whether to hoist the role in the user list or not * @arg {Boolean} [options.mentionable] Whether the role is mentionable or not + * @arg {String} [options.name] The name of the role + * @arg {Number} [options.permissions] The role permissions number * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ @@ -362,129 +309,155 @@ class Guild extends Base { } /** - * Get the prune count for the guild - * @arg {Number} [options] The options to use to get number of prune members - * @arg {Number} [options.days=7] The number of days of inactivity to prune for - * @arg {Array} [options.includeRoles] An array of role IDs that members must have to be considered for pruning - * @returns {Promise} Resolves with the number of members that would be pruned + * Delete the guild (bot user must be owner) + * @returns {Promise} */ - getPruneCount(options) { - return this._client.getPruneCount.call(this._client, this.id, options); + delete() { + return this._client.deleteGuild.call(this._client, this.id); } /** - * Begin pruning the guild - * @arg {Number} [options] The options to pass to prune members - * @arg {Number} [options.days=7] The number of days of inactivity to prune for - * @arg {Array} [options.includeRoles] An array of role IDs that members must have to be considered for pruning - * @arg {String} [options.reason] The reason to be displayed in audit logs - * @returns {Promise} Resolves with the number of pruned members + * Delete a emoji in the guild + * @arg {String} emojiID The ID of the emoji + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - pruneMembers(options) { - return this._client.pruneMembers.call(this._client, this.id, options); + deleteEmoji(emojiID, reason) { + return this._client.deleteGuildEmoji.call(this._client, this.id, emojiID, reason); } /** - * Get a guild's channels via the REST API. REST mode is required to use this endpoint. - * @returns {Promise<(CategoryChannel[] | TextChannel[] | VoiceChannel[])>} + * Delete a role + * @arg {String} roleID The ID of the role + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - getRESTChannels() { - return this._client.getRESTGuildChannels.call(this._client, this.id); + deleteRole(roleID, reason) { + return this._client.deleteRole.call(this._client, this.id, roleID, reason); } /** - * Get a guild's emojis via the REST API. REST mode is required to use this endpoint. - * @returns {Promise} An array of guild emoji objects + * Delete a guild integration + * @arg {String} integrationID The ID of the integration + * @returns {Promise} */ - getRESTEmojis() { - return this._client.getRESTGuildEmojis.call(this._client, this.id); + deleteIntegration(integrationID) { + return this._client.deleteGuildIntegration.call(this._client, this.id, integrationID); } /** - * Get a guild emoji via the REST API. REST mode is required to use this endpoint. - * @arg {String} emojiID The ID of the emoji - * @returns {Promise} An emoji object + * Get the guild's banner with the given format and size + * @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp") + * @param {Number} [size] The size of the icon (any power of two between 16 and 4096) */ - getRESTEmoji(emojiID) { - return this._client.getRESTGuildEmoji.call(this._client, this.id, emojiID); + dynamicBannerURL(format, size) { + return this.banner ? this._client._formatImage(Endpoints.GUILD_BANNER(this.id, this.banner), format, size) : null; } /** - * Get a guild's members via the REST API. REST mode is required to use this endpoint. - * @arg {Number} [limit=1] The max number of members to get (1 to 1000) - * @arg {String} [after] The highest user ID of the previous page - * @returns {Promise} + * Get the guild's icon with the given format and size + * @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp") + * @arg {Number} [size] The size of the icon (any power of two between 16 and 4096) */ - getRESTMembers(limit, after) { - return this._client.getRESTGuildMembers.call(this._client, this.id, limit, after); + dynamicIconURL(format, size) { + return this.icon ? this._client._formatImage(Endpoints.GUILD_ICON(this.id, this.icon), format, size) : null; } /** - * Get a guild's members via the REST API. REST mode is required to use this endpoint. - * @arg {String} memberID The ID of the member - * @returns {Promise} + * Get the guild's splash with the given format and size + * @arg {String} [format] The filetype of the icon ("jpg", "jpeg", "png", "gif", or "webp") + * @param {Number} [size] The size of the icon (any power of two between 16 and 4096) */ - getRESTMember(memberID) { - return this._client.getRESTGuildMember.call(this._client, this.id, memberID); + dynamicSplashURL(format, size) { + return this.splash ? this._client._formatImage(Endpoints.GUILD_SPLASH(this.id, this.splash), format, size) : null; } /** - * Get a guild's roles via the REST API. REST mode is required to use this endpoint. - * @returns {Promise} + * Edit the guild + * @arg {Object} options The properties to edit + * @arg {String} [options.afkChannelID] The ID of the AFK voice channel + * @arg {Number} [options.afkTimeout] The AFK timeout in seconds + * @arg {String} [options.banner] The guild banner image as a base64 data URI (VIP only). Note: base64 strings alone are not base64 data URI strings + * @arg {Number} [options.defaultNotifications] The default notification settings for the guild. 0 is "All Messages", 1 is "Only @mentions". + * @arg {String} [options.description] The description for the guild (VIP only) + * @arg {Number} [options.explicitContentFilter] The level of the explicit content filter for messages/images in the guild. 0 disables message scanning, 1 enables scanning the messages of members without roles, 2 enables scanning for all messages. + * @arg {String} [options.icon] The guild icon as a base64 data URI. Note: base64 strings alone are not base64 data URI strings + * @arg {String} [options.name] The ID of the guild + * @arg {String} [options.ownerID] The ID of the member to transfer guild ownership to (bot user must be owner) + * @arg {String} [options.preferredLocale] Preferred "PUBLIC" guild language used in server discovery and notices from Discord + * @arg {String} [options.publicUpdatesChannelID] The id of the channel where admins and moderators of "PUBLIC" guilds receive notices from Discord + * @arg {String} [options.region] The region of the guild + * @arg {String} [options.rulesChannelID] The id of the channel where "PUBLIC" guilds display rules and/or guidelines + * @arg {String} [options.splash] The guild splash image as a base64 data URI (VIP only). Note: base64 strings alone are not base64 data URI strings + * @arg {String} [options.systemChannelID] The ID of the system channel + * @arg {Number} [options.verificationLevel] The guild verification level + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - getRESTRoles() { - return this._client.getRESTGuildRoles.call(this._client, this.id); + edit(options, reason) { + return this._client.editGuild.call(this._client, this.id, options, reason); } /** - * [DEPRECATED] Get a guild's embed object - * @returns {Promise} A guild embed object - */ - getEmbed() { - return this._client.getGuildEmbed.call(this._client, this.id); - } - - /** - * Get a guild's widget object - * @returns {Promise} A guild widget object + * Edit a emoji in the guild + * @arg {String} emojiID The ID of the emoji you want to modify + * @arg {Object} options Emoji options + * @arg {String} [options.name] The name of emoji + * @arg {Array} [options.roles] An array containing authorized role IDs + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} A guild emoji object */ - getWidget() { - return this._client.getGuildWidget.call(this._client, this.id); + editEmoji(emojiID, options, reason) { + return this._client.editGuildEmoji.call(this._client, this.id, emojiID, options, reason); } /** - * Modify a guild's widget - * @arg {Object} options The widget object to modify (https://discord.com/developers/docs/resources/guild#modify-guild-widget) - * @returns {Promise} A guild widget object + * Edit a guild integration + * @arg {String} integrationID The ID of the integration + * @arg {Object} options The properties to edit + * @arg {String} [options.enableEmoticons] Whether to enable integration emoticons or not + * @arg {String} [options.expireBehavior] What to do when a user's subscription runs out + * @arg {String} [options.expireGracePeriod] How long before the integration's role is removed from an unsubscribed user + * @returns {Promise} */ - editWidget(options) { - return this._client.getGuildWidget.call(this._client, this.id, options); + editIntegration(integrationID, options) { + return this._client.editGuildIntegration.call(this._client, this.id, integrationID, options); } /** - * Get possible voice regions for a guild - * @returns {Promise} Resolves with an array of voice region objects + * Edit a guild member + * @arg {String} memberID The ID of the member + * @arg {Object} options The properties to edit + * @arg {String} [options.channelID] The ID of the voice channel to move the member to (must be in voice) + * @arg {Boolean} [options.deaf] Server deafen the member + * @arg {Boolean} [options.mute] Server mute the member + * @arg {String} [options.nick] Set the member's guild nickname, "" to remove + * @arg {String[]} [options.roles] The array of role IDs the member should have + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - getVoiceRegions() { - return this._client.getVoiceRegions.call(this._client, this.id); + editMember(memberID, options, reason) { + return this._client.editGuildMember.call(this._client, this.id, memberID, options, reason); } /** - * Leaves the voice channel in this guild + * Edit the bot's nickname in the guild + * @arg {String} nick The nickname + * @returns {Promise} */ - leaveVoiceChannel() { - this._client.closeVoiceConnection.call(this._client, this.id); + editNickname(nick) { + return this._client.editNickname.call(this._client, this.id, nick); } /** * Edit the guild role * @arg {String} roleID The ID of the role * @arg {Object} options The properties to edit - * @arg {String} [options.name] The name of the role - * @arg {Number} [options.permissions] The role permissions number * @arg {Number} [options.color] The hex color of the role, in number form (ex: 0x3da5b3 or 4040115) * @arg {Boolean} [options.hoist] Whether to hoist the role in the user list or not * @arg {Boolean} [options.mentionable] Whether the role is mentionable or not + * @arg {String} [options.name] The name of the role + * @arg {Number} [options.permissions] The role permissions number * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ @@ -493,13 +466,37 @@ class Guild extends Base { } /** - * Delete a role - * @arg {String} roleID The ID of the role - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Modify a guild's widget + * @arg {Object} options The widget object to modify (https://discord.com/developers/docs/resources/guild#modify-guild-widget) + * @returns {Promise} A guild widget object */ - deleteRole(roleID, reason) { - return this._client.deleteRole.call(this._client, this.id, roleID, reason); + editWidget(options) { + return this._client.getGuildWidget.call(this._client, this.id, options); + } + + /** + * Request all guild members from Discord + * @arg {Number} [timeout] The number of milliseconds to wait before resolving early. Defaults to the `requestTimeout` client option + * @returns {Promise} Resolves with the total number of fetched members. + */ + fetchAllMembers(timeout) { + return this.fetchMembers({ + timeout + }).then((m) => m.length); + } + + /** + * Request specific guild members through the gateway connection + * @arg {Object} [options] Options for fetching the members + * @arg {Number} [options.limit] The maximum number of members to fetch + * @arg {Boolean} [options.presences] Whether to request member presences or not. When using intents, the `GUILD_PRESENCES` intent is required. + * @arg {String} [options.query] The query used for looking up the members. When using intents, `GUILD_MEMBERS` is required to fetch all members. + * @arg {Number} [options.timeout] The number of milliseconds to wait before resolving early. Defaults to the `requestTimeout` client option + * @arg {String[]} [options.userIDs] The IDs of members to fetch + * @returns {Promise} Resolves with the fetched members. + */ + fetchMembers(options) { + return this.shard.requestGuildMembers(this.id, options); } /** @@ -514,42 +511,28 @@ class Guild extends Base { } /** - * Get a list of integrations for the guild - * @returns {Promise} - */ - getIntegrations() { - return this._client.getGuildIntegrations.call(this._client, this.id); - } - - /** - * Edit a guild integration - * @arg {String} integrationID The ID of the integration - * @arg {Object} options The properties to edit - * @arg {String} [options.expireBehavior] What to do when a user's subscription runs out - * @arg {String} [options.expireGracePeriod] How long before the integration's role is removed from an unsubscribed user - * @arg {String} [options.enableEmoticons] Whether to enable integration emoticons or not - * @returns {Promise} + * Get a ban from the ban list of a guild + * @arg {String} userID The ID of the banned user + * @returns {Promise} Resolves with {reason: String, user: User} */ - editIntegration(integrationID, options) { - return this._client.editGuildIntegration.call(this._client, this.id, integrationID, options); + getBan(userID) { + return this._client.getGuildBan.call(this._client, this.id, userID); } /** - * Force a guild integration to sync - * @arg {String} integrationID The ID of the integration - * @returns {Promise} + * Get the ban list of the guild + * @returns {Promise} Resolves with an array of {reason: String, user: User} */ - syncIntegration(integrationID) { - return this._client.syncGuildIntegration.call(this._client, this.id, integrationID); + getBans() { + return this._client.getGuildBans.call(this._client, this.id); } /** - * Delete a guild integration - * @arg {String} integrationID The ID of the integration - * @returns {Promise} + * [DEPRECATED] Get a guild's embed object + * @returns {Promise} A guild embed object */ - deleteIntegration(integrationID) { - return this._client.deleteGuildIntegration.call(this._client, this.id, integrationID); + getEmbed() { + return this._client.getGuildEmbed.call(this._client, this.id); } /** @@ -561,166 +544,131 @@ class Guild extends Base { } /** - * Returns the vanity url of the guild - * @returns {Promise} + * Get a list of integrations for the guild + * @returns {Promise} */ - getVanity() { - return this._client.getGuildVanity.call(this._client, this.id); + getIntegrations() { + return this._client.getGuildIntegrations.call(this._client, this.id); } /** - * Edit a guild member - * @arg {String} memberID The ID of the member - * @arg {Object} options The properties to edit - * @arg {String[]} [options.roles] The array of role IDs the member should have - * @arg {String} [options.nick] Set the member's guild nickname, "" to remove - * @arg {Boolean} [options.mute] Server mute the member - * @arg {Boolean} [options.deaf] Server deafen the member - * @arg {String} [options.channelID] The ID of the voice channel to move the member to (must be in voice) - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get the prune count for the guild + * @arg {Number} [options] The options to use to get number of prune members + * @arg {Number} [options.days=7] The number of days of inactivity to prune for + * @arg {Array} [options.includeRoles] An array of role IDs that members must have to be considered for pruning + * @returns {Promise} Resolves with the number of members that would be pruned */ - editMember(memberID, options, reason) { - return this._client.editGuildMember.call(this._client, this.id, memberID, options, reason); + getPruneCount(options) { + return this._client.getPruneCount.call(this._client, this.id, options); } /** - * Add a role to a guild member - * @arg {String} memberID The ID of the member - * @arg {String} roleID The ID of the role - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get a guild's channels via the REST API. REST mode is required to use this endpoint. + * @returns {Promise<(CategoryChannel[] | TextChannel[] | VoiceChannel[])>} */ - addMemberRole(memberID, roleID, reason) { - return this._client.addGuildMemberRole.call(this._client, this.id, memberID, roleID, reason); + getRESTChannels() { + return this._client.getRESTGuildChannels.call(this._client, this.id); } /** - * Remove a role from a guild member - * @arg {String} memberID The ID of the member - * @arg {String} roleID The ID of the role - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get a guild emoji via the REST API. REST mode is required to use this endpoint. + * @arg {String} emojiID The ID of the emoji + * @returns {Promise} An emoji object */ - removeMemberRole(memberID, roleID, reason) { - return this._client.removeGuildMemberRole.call(this._client, this.id, memberID, roleID, reason); + getRESTEmoji(emojiID) { + return this._client.getRESTGuildEmoji.call(this._client, this.id, emojiID); } /** - * Kick a member from the guild - * @arg {String} userID The ID of the member - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get a guild's emojis via the REST API. REST mode is required to use this endpoint. + * @returns {Promise} An array of guild emoji objects */ - kickMember(userID, reason) { - return this._client.kickGuildMember.call(this._client, this.id, userID, reason); + getRESTEmojis() { + return this._client.getRESTGuildEmojis.call(this._client, this.id); } /** - * Ban a user from the guild - * @arg {String} userID The ID of the member - * @arg {Number} [deleteMessageDays=0] Number of days to delete messages for - * @arg {String} [reason] Reason for the ban - * @returns {Promise} + * Get a guild's members via the REST API. REST mode is required to use this endpoint. + * @arg {String} memberID The ID of the member + * @returns {Promise} */ - banMember(userID, deleteMessageDays, reason) { - return this._client.banGuildMember.call(this._client, this.id, userID, deleteMessageDays, reason); + getRESTMember(memberID) { + return this._client.getRESTGuildMember.call(this._client, this.id, memberID); } /** - * Unban a user from the guild - * @arg {String} userID The ID of the member - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get a guild's members via the REST API. REST mode is required to use this endpoint. + * @arg {Number} [limit=1] The max number of members to get (1 to 1000) + * @arg {String} [after] The highest user ID of the previous page + * @returns {Promise} */ - unbanMember(userID, reason) { - return this._client.unbanGuildMember.call(this._client, this.id, userID, reason); + getRESTMembers(limit, after) { + return this._client.getRESTGuildMembers.call(this._client, this.id, limit, after); } /** - * Edit the guild - * @arg {Object} options The properties to edit - * @arg {String} [options.name] The ID of the guild - * @arg {String} [options.region] The region of the guild - * @arg {String} [options.icon] The guild icon as a base64 data URI. Note: base64 strings alone are not base64 data URI strings - * @arg {Number} [options.verificationLevel] The guild verification level - * @arg {Number} [options.defaultNotifications] The default notification settings for the guild. 0 is "All Messages", 1 is "Only @mentions". - * @arg {Number} [options.explicitContentFilter] The level of the explicit content filter for messages/images in the guild. 0 disables message scanning, 1 enables scanning the messages of members without roles, 2 enables scanning for all messages. - * @arg {String} [options.systemChannelID] The ID of the system channel - * @arg {String} [options.rulesChannelID] The id of the channel where "PUBLIC" guilds display rules and/or guidelines - * @arg {String} [options.publicUpdatesChannelID] The id of the channel where admins and moderators of "PUBLIC" guilds receive notices from Discord - * @arg {String} [options.preferredLocale] Preferred "PUBLIC" guild language used in server discovery and notices from Discord - * @arg {String} [options.afkChannelID] The ID of the AFK voice channel - * @arg {Number} [options.afkTimeout] The AFK timeout in seconds - * @arg {String} [options.ownerID] The ID of the member to transfer guild ownership to (bot user must be owner) - * @arg {String} [options.splash] The guild splash image as a base64 data URI (VIP only). Note: base64 strings alone are not base64 data URI strings - * @arg {String} [options.banner] The guild banner image as a base64 data URI (VIP only). Note: base64 strings alone are not base64 data URI strings - * @arg {String} [options.description] The description for the guild (VIP only) - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get a guild's roles via the REST API. REST mode is required to use this endpoint. + * @returns {Promise} */ - edit(options, reason) { - return this._client.editGuild.call(this._client, this.id, options, reason); + getRESTRoles() { + return this._client.getRESTGuildRoles.call(this._client, this.id); } /** - * Delete the guild (bot user must be owner) + * Returns the vanity url of the guild * @returns {Promise} */ - delete() { - return this._client.deleteGuild.call(this._client, this.id); + getVanity() { + return this._client.getGuildVanity.call(this._client, this.id); } /** - * Leave the guild - * @returns {Promise} + * Get possible voice regions for a guild + * @returns {Promise} Resolves with an array of voice region objects */ - leave() { - return this._client.leaveGuild.call(this._client, this.id); + getVoiceRegions() { + return this._client.getVoiceRegions.call(this._client, this.id); } /** - * Get the ban list of the guild - * @returns {Promise} Resolves with an array of {reason: String, user: User} + * Get all the webhooks in the guild + * @returns {Promise} Resolves with an array of webhook objects */ - getBans() { - return this._client.getGuildBans.call(this._client, this.id); + getWebhooks() { + return this._client.getGuildWebhooks.call(this._client, this.id); } /** - * Get a ban from the ban list of a guild - * @arg {String} userID The ID of the banned user - * @returns {Promise} Resolves with {reason: String, user: User} + * Get a guild's widget object + * @returns {Promise} A guild widget object */ - getBan(userID) { - return this._client.getGuildBan.call(this._client, this.id, userID); + getWidget() { + return this._client.getGuildWidget.call(this._client, this.id); } /** - * Edit the bot's nickname in the guild - * @arg {String} nick The nickname + * Kick a member from the guild + * @arg {String} userID The ID of the member + * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - editNickname(nick) { - return this._client.editNickname.call(this._client, this.id, nick); + kickMember(userID, reason) { + return this._client.kickGuildMember.call(this._client, this.id, userID, reason); } /** - * Get all the webhooks in the guild - * @returns {Promise} Resolves with an array of webhook objects + * Leave the guild + * @returns {Promise} */ - getWebhooks() { - return this._client.getGuildWebhooks.call(this._client, this.id); + leave() { + return this._client.leaveGuild.call(this._client, this.id); } /** - * Search for guild members by partial nickname/username - * @arg {String} query The query string to match username(s) and nickname(s) against - * @arg {Number} [limit=1] The maximum number of members you want returned, capped at 100 - * @returns {Promise} + * Leaves the voice channel in this guild */ - searchMembers(query, limit) { - return this._client.searchGuildMembers.call(this._client, this.id, query, limit); + leaveVoiceChannel() { + this._client.closeVoiceConnection.call(this._client, this.id); } permissionsOf(memberID) { @@ -747,6 +695,59 @@ class Guild extends Base { } } + /** + * Begin pruning the guild + * @arg {Number} [options] The options to pass to prune members + * @arg {Boolean} [options.computePruneCount=true] Whether or not the number of pruned members should be returned. Discord discourages setting this to true for larger guilds + * @arg {Number} [options.days=7] The number of days of inactivity to prune for + * @arg {Array} [options.includeRoles] An array of role IDs that members must have to be considered for pruning + * @arg {String} [options.reason] The reason to be displayed in audit logs + * @returns {Promise} Resolves with the number of pruned members + */ + pruneMembers(options) { + return this._client.pruneMembers.call(this._client, this.id, options); + } + + /** + * Remove a role from a guild member + * @arg {String} memberID The ID of the member + * @arg {String} roleID The ID of the role + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + removeMemberRole(memberID, roleID, reason) { + return this._client.removeGuildMemberRole.call(this._client, this.id, memberID, roleID, reason); + } + + /** + * Search for guild members by partial nickname/username + * @arg {String} query The query string to match username(s) and nickname(s) against + * @arg {Number} [limit=1] The maximum number of members you want returned, capped at 100 + * @returns {Promise} + */ + searchMembers(query, limit) { + return this._client.searchGuildMembers.call(this._client, this.id, query, limit); + } + + /** + * Force a guild integration to sync + * @arg {String} integrationID The ID of the integration + * @returns {Promise} + */ + syncIntegration(integrationID) { + return this._client.syncGuildIntegration.call(this._client, this.id, integrationID); + } + + /** + * Unban a user from the guild + * @arg {String} userID The ID of the member + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + unbanMember(userID, reason) { + return this._client.unbanGuildMember.call(this._client, this.id, userID, reason); + } + toJSON(props = []) { return super.toJSON([ "afkChannelID", diff --git a/lib/structures/GuildAuditLogEntry.js b/lib/structures/GuildAuditLogEntry.js index 03c329c93..98096a4b5 100644 --- a/lib/structures/GuildAuditLogEntry.js +++ b/lib/structures/GuildAuditLogEntry.js @@ -6,12 +6,20 @@ const {AuditLogActions} = require("../Constants"); /** * Represents a guild audit log entry describing a moderation action -* @prop {String} id The ID of the entry -* @prop {Guild} guild The guild containing the entry * @prop {Number} actionType The action type of the entry. See Constants.AuditLogActions for more details +* @prop {Object?} after The properties of the targeted object after the action was taken +* For example, if a channel was renamed from #general to #potato, this would be `{name: "potato"}`` +* @prop {Object?} before The properties of the targeted object before the action was taken +* For example, if a channel was renamed from #general to #potato, this would be `{name: "general"}`` +* @prop {(CategoryChannel | TextChannel | VoiceChannel)?} channel The channel containing the deleted messages, action type 72 (MESSAGE_DELETE) only +* @prop {Number?} count The number of messages deleted, action type 72 (MESSAGE_DELETE) only +* @prop {Number?} deleteMemberDays The number of days of inactivity to prune for, action type 21 (MEMBER_PRUNE) only +* @prop {Guild} guild The guild containing the entry +* @prop {String} id The ID of the entry +* @prop {(Member | Object)?} member The member described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the member is not cached, this could be {id: String} +* @prop {Number?} membersRemoved The number of members pruned from the server, action type 21 (MEMBER_PRUNE) only * @prop {String?} reason The reason for the action -* @prop {User} user The user that performed the action -* @prop {String} targetID The ID of the action target +* @prop {(Role | Object)?} role The role described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the role is not cached, this could be {id: String, name: String} * @prop {(CategoryChannel | Guild | Member | Invite | Role | Object | TextChannel | VoiceChannel | NewsChannel)?} target The object of the action target * If the item is not cached, this property will be null * If the action targets a guild, this could be a Guild object @@ -22,16 +30,8 @@ const {AuditLogActions} = require("../Constants"); * If the action targets a webhook, this is null * If the action targets a emoji, this could be an emoji Object * If the action targets a message, this is a User object -* @prop {Object?} before The properties of the targeted object before the action was taken -* For example, if a channel was renamed from #general to #potato, this would be `{name: "general"}`` -* @prop {Object?} after The properties of the targeted object after the action was taken -* For example, if a channel was renamed from #general to #potato, this would be `{name: "potato"}`` -* @prop {Number?} count The number of messages deleted, action type 72 (MESSAGE_DELETE) only -* @prop {(CategoryChannel | TextChannel | VoiceChannel)?} channel The channel containing the deleted messages, action type 72 (MESSAGE_DELETE) only -* @prop {Number?} deleteMemberDays The number of days of inactivity to prune for, action type 21 (MEMBER_PRUNE) only -* @prop {Number?} membersRemoved The number of members pruned from the server, action type 21 (MEMBER_PRUNE) only -* @prop {(Member | Object)?} member The member described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the member is not cached, this could be {id: String} -* @prop {(Role | Object)?} role The role described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the role is not cached, this could be {id: String, name: String} +* @prop {String} targetID The ID of the action target +* @prop {User} user The user that performed the action */ class GuildAuditLogEntry extends Base { constructor(data, guild) { diff --git a/lib/structures/GuildChannel.js b/lib/structures/GuildChannel.js index 35791f575..be8a190ea 100644 --- a/lib/structures/GuildChannel.js +++ b/lib/structures/GuildChannel.js @@ -10,15 +10,15 @@ const PermissionOverwrite = require("./PermissionOverwrite"); /** * Represents a guild channel. You also probably want to look at CategoryChannel, NewsChannel, StoreChannel, TextChannel, and VoiceChannel. * @extends Channel -* @prop {String} id The ID of the channel -* @prop {String} mention A string that mentions the channel -* @prop {Number} type The type of the channel * @prop {Guild} guild The guild that owns the channel -* @prop {String?} parentID The ID of the category this channel belongs to * @prop {String} name The name of the channel -* @prop {Number} position The position of the channel * @prop {Boolean} nsfw Whether the channel is an NSFW channel or not +* @prop {String?} parentID The ID of the category this channel belongs to * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this channel +* @prop {Number} position The position of the channel +* @prop {Number} type The type of the channel +* @prop {String} id The ID of the channel +* @prop {String} mention A string that mentions the channel */ class GuildChannel extends Channel { constructor(data, client) { @@ -53,34 +53,22 @@ class GuildChannel extends Channel { } /** - * Get the channel-specific permissions of a member - * @arg {String | Member} memberID The ID of the member or a Member instance - * @returns {Permission} + * Delete the channel + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} */ - permissionsOf(memberID) { - const member = memberID instanceof Member ? memberID : this.guild.members.get(memberID); - let permission = this.guild.permissionsOf(member).allow; - if(permission & Permissions.administrator) { - return new Permission(Permissions.all); - } - let overwrite = this.permissionOverwrites.get(this.guild.id); - if(overwrite) { - permission = (permission & ~overwrite.deny) | overwrite.allow; - } - let deny = 0; - let allow = 0; - for(const roleID of member.roles) { - if((overwrite = this.permissionOverwrites.get(roleID))) { - deny |= overwrite.deny; - allow |= overwrite.allow; - } - } - permission = (permission & ~deny) | allow; - overwrite = this.permissionOverwrites.get(member.id); - if(overwrite) { - permission = (permission & ~overwrite.deny) | overwrite.allow; - } - return new Permission(permission); + delete(reason) { + return this.client.deleteChannel.call(this.client, this.id, reason); + } + + /** + * Delete a channel permission overwrite + * @arg {String} overwriteID The ID of the overwritten user or role + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + deletePermission(overwriteID, reason) { + return this.client.deleteChannelPermission.call(this.client, this.id, overwriteID, reason); } /** @@ -109,15 +97,6 @@ class GuildChannel extends Channel { return this.client.editChannelPosition.call(this.client, this.id, position); } - /** - * Delete the channel - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - delete(reason) { - return this.client.deleteChannel.call(this.client, this.id, reason); - } - /** * Create a channel permission overwrite * @arg {String} overwriteID The ID of the overwritten user or role @@ -132,13 +111,34 @@ class GuildChannel extends Channel { } /** - * Delete a channel permission overwrite - * @arg {String} overwriteID The ID of the overwritten user or role - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} + * Get the channel-specific permissions of a member + * @arg {String | Member} memberID The ID of the member or a Member instance + * @returns {Permission} */ - deletePermission(overwriteID, reason) { - return this.client.deleteChannelPermission.call(this.client, this.id, overwriteID, reason); + permissionsOf(memberID) { + const member = memberID instanceof Member ? memberID : this.guild.members.get(memberID); + let permission = this.guild.permissionsOf(member).allow; + if(permission & Permissions.administrator) { + return new Permission(Permissions.all); + } + let overwrite = this.permissionOverwrites.get(this.guild.id); + if(overwrite) { + permission = (permission & ~overwrite.deny) | overwrite.allow; + } + let deny = 0; + let allow = 0; + for(const roleID of member.roles) { + if((overwrite = this.permissionOverwrites.get(roleID))) { + deny |= overwrite.deny; + allow |= overwrite.allow; + } + } + permission = (permission & ~deny) | allow; + overwrite = this.permissionOverwrites.get(member.id); + if(overwrite) { + permission = (permission & ~overwrite.deny) | overwrite.allow; + } + return new Permission(permission); } toJSON(props = []) { diff --git a/lib/structures/GuildIntegration.js b/lib/structures/GuildIntegration.js index a8b0003fc..563b730ec 100644 --- a/lib/structures/GuildIntegration.js +++ b/lib/structures/GuildIntegration.js @@ -4,22 +4,22 @@ const Base = require("./Base"); /** * Represents a guild integration -* @prop {String} id The ID of the integration -* @prop {Number} createdAt Timestamp of the guild integration's creation -* @prop {String} name The name of the integration -* @prop {String} type The type of the integration -* @prop {String} roleID The ID of the role connected to the integration -* @prop {User} user The user connected to the integration * @prop {Object} account Info on the integration account * @prop {String} account.id The ID of the integration account * @prop {String} account.name The name of the integration account +* @prop {Number} createdAt Timestamp of the guild integration's creation * @prop {Boolean} enabled Whether the integration is enabled or not -* @prop {Boolean} syncing Whether the integration is syncing or not +* @prop {Boolean} enableEmoticons Whether integration emoticons are enabled or not * @prop {Number} expireBehavior behavior of expired subscriptions * @prop {Number} expireGracePeriod grace period for expired subscriptions -* @prop {Boolean} enableEmoticons Whether integration emoticons are enabled or not +* @prop {String} id The ID of the integration +* @prop {String} name The name of the integration +* @prop {String} roleID The ID of the role connected to the integration * @prop {Number} subscriberCount number of subscribers * @prop {Number} syncedAt Unix timestamp of last integration sync +* @prop {Boolean} syncing Whether the integration is syncing or not +* @prop {String} type The type of the integration +* @prop {User} user The user connected to the integration */ class GuildIntegration extends Base { constructor(data, guild) { @@ -43,6 +43,14 @@ class GuildIntegration extends Base { this.syncedAt = data.synced_at; } + /** + * Delete the guild integration + * @returns {Promise} + */ + delete() { + return this.guild.shard.client.deleteGuildIntegration.call(this.guild.shard.client, this.guild.id, this.id); + } + /** * Edit the guild integration * @arg {Object} options The properties to edit @@ -55,14 +63,6 @@ class GuildIntegration extends Base { return this.guild.shard.client.editGuildIntegration.call(this.guild.shard.client, this.guild.id, this.id, options); } - /** - * Delete the guild integration - * @returns {Promise} - */ - delete() { - return this.guild.shard.client.deleteGuildIntegration.call(this.guild.shard.client, this.guild.id, this.id); - } - /** * Force the guild integration to sync * @returns {Promise} diff --git a/lib/structures/GuildPreview.js b/lib/structures/GuildPreview.js index 2a0a941fc..120254742 100644 --- a/lib/structures/GuildPreview.js +++ b/lib/structures/GuildPreview.js @@ -5,17 +5,19 @@ const Endpoints = require("../rest/Endpoints.js"); /** * Represents a GuildPreview structure -* @prop {String} id The ID of the guild -* @prop {String} name The name of the guild -* @prop {String?} icon The hash of the guild icon, or null if no icon -* @prop {String?} description The description for the guild (VIP only) -* @prop {String?} splash The hash of the guild splash image, or null if no splash (VIP only) -* @prop {String?} discoverySplash The hash of the discovery splash, or null if no discovery splash (VIP only) -* @prop {String[]} features An array of guild feature strings +* @extends Base * @prop {Number} approximateMemberCount The **approximate** number of members in the guild * @prop {Number} approximatePresenceCount The **approximate** number of presences in the guild +* @prop {String?} description The description for the guild (VIP only) +* @prop {String?} discoverySplash The description for the guild (VIP only) * @prop {Object[]} emojis An array of guild emoji objects +* @prop {String[]} features An array of guild feature strings +* @prop {String?} icon The hash of the guild icon, or null if no icon * @prop {String?} iconURL The URL of the guild's icon +* @prop {String} id The ID of the guild +* @prop {String} name The name of the guild +* @prop {String?} splash The hash of the guild splash image, or null if no splash (VIP only) +* @prop {String?} splashURL The URL of the guild's splash image */ class GuildPreview extends Base { constructor(data, client) { diff --git a/lib/structures/Invite.js b/lib/structures/Invite.js index b4a6c0bd8..6f1552bc7 100644 --- a/lib/structures/Invite.js +++ b/lib/structures/Invite.js @@ -5,21 +5,21 @@ const Guild = require("./Guild"); /** * Represents an invite. Some properties are only available when fetching invites from channels, which requires the Manage Channel permission. -* @prop {String} code The invite code * @prop {TextChannel | NewsChannel | VoiceChannel | GroupChannel | Object} channel Info on the invite channel * @prop {String} channel.id The ID of the invite's channel * @prop {String?} channel.name The name of the invite's channel * @prop {Number} channel.type The type of the invite's channel * @prop {String?} channel.icon The icon of a channel (group dm) +* @prop {String} code The invite code +* @prop {Number?} createdAt Timestamp of invite creation * @prop {Guild?} guild Info on the invite guild * @prop {User?} inviter The invite creator -* @prop {Number?} uses The number of invite uses -* @prop {Number?} maxUses The max number of invite uses * @prop {Number?} maxAge How long the invite lasts in seconds -* @prop {Boolean?} temporary Whether the invite grants temporary membership or not -* @prop {Number?} createdAt Timestamp of invite creation -* @prop {Number?} presenceCount The **approximate** presence count for the guild +* @prop {Number?} maxUses The max number of invite uses * @prop {Number?} memberCount The **approximate** member count for the guild +* @prop {Number?} presenceCount The **approximate** presence count for the guild +* @prop {Boolean?} temporary Whether the invite grants temporary membership or not +* @prop {Number?} uses The number of invite uses */ class Invite extends Base { constructor(data, client) { diff --git a/lib/structures/Member.js b/lib/structures/Member.js index c77af4e18..1fe40fe4e 100644 --- a/lib/structures/Member.js +++ b/lib/structures/Member.js @@ -6,36 +6,36 @@ const VoiceState = require("./VoiceState"); /** * Represents a server member -* @prop {String} id The ID of the member -* @prop {String} mention A string that mentions the member -* @prop {Guild} guild The guild the member is in -* @prop {Number} joinedAt Timestamp of when the member joined the guild -* @prop {String} status The member's status. Either "online", "idle", "dnd", or "offline" +* @prop {Object[]?} activities The member's current activities +* @prop {String?} avatar The hash of the user's avatar, or null if no avatar +* @prop {String} avatarURL The URL of the user's avatar which can be either a JPG or GIF +* @prop {Boolean} bot Whether the user is an OAuth bot or not * @prop {Object?} clientStatus The member's per-client status * @prop {String} clientStatus.web The member's status on web. Either "online", "idle", "dnd", or "offline". Will be "online" for bots * @prop {String} clientStatus.desktop The member's status on desktop. Either "online", "idle", "dnd", or "offline". Will be "offline" for bots * @prop {String} clientStatus.mobile The member's status on mobile. Either "online", "idle", "dnd", or "offline". Will be "offline" for bots -* @prop {Object[]?} activities The member's current activities +* @prop {Number} createdAt Timestamp of user creation +* @prop {String} defaultAvatar The hash for the default avatar of a user if there is no avatar set +* @prop {String} defaultAvatarURL The URL of the user's default avatar +* @prop {String} discriminator The discriminator of the user * @prop {Object?} game The active game the member is playing * @prop {String} game.name The name of the active game * @prop {Number} game.type The type of the active game (0 is default, 1 is Twitch, 2 is YouTube) * @prop {String?} game.url The url of the active game -* @prop {VoiceState} voiceState The voice state of the member +* @prop {Guild} guild The guild the member is in +* @prop {String} id The ID of the member +* @prop {Number} joinedAt Timestamp of when the member joined the guild +* @prop {String} mention A string that mentions the member * @prop {String?} nick The server nickname of the member -* @prop {String[]} roles An array of role IDs this member is a part of -* @prop {User} user The user object of the member * @prop {Permission} permission [DEPRECATED] The guild-wide permissions of the member. Use Member#permissions instead * @prop {Permission} permissions The guild-wide permissions of the member -* @prop {String} defaultAvatar The hash for the default avatar of a user if there is no avatar set -* @prop {Number} createdAt Timestamp of user creation -* @prop {Boolean} bot Whether the user is an OAuth bot or not -* @prop {String} username The username of the user -* @prop {String} discriminator The discriminator of the user -* @prop {String?} avatar The hash of the user's avatar, or null if no avatar -* @prop {String} defaultAvatarURL The URL of the user's default avatar -* @prop {String} avatarURL The URL of the user's avatar which can be either a JPG or GIF -* @prop {String} staticAvatarURL The URL of the user's avatar (always a JPG) * @prop {Number} premiumSince Timestamp of when the member boosted the guild +* @prop {String[]} roles An array of role IDs this member is a part of +* @prop {String} staticAvatarURL The URL of the user's avatar (always a JPG) +* @prop {String} status The member's status. Either "online", "idle", "dnd", or "offline" +* @prop {User} user The user object of the member +* @prop {String} username The username of the user +* @prop {VoiceState} voiceState The voice state of the member */ class Member extends Base { constructor(data, guild, client) { @@ -100,37 +100,14 @@ class Member extends Base { } } - get voiceState() { - if(this.guild && this.guild.voiceStates.has(this.id)) { - return this.guild.voiceStates.get(this.id); - } else { - return new VoiceState({ - id: this.id - }); - } - } - - get permission() { - this.guild.shard.client.emit("warn", "[DEPRECATED] Member#permission is deprecated. Use Member#permissions instead"); - return this.permissions; - } - - get permissions() { - return this.guild.permissionsOf(this); - } - - get username() { - return this.user.username; - } - - get discriminator() { - return this.user.discriminator; - } - get avatar() { return this.user.avatar; } + get avatarURL() { + return this.user.avatarURL; + } + get bot() { return this.user.bot; } @@ -147,51 +124,74 @@ class Member extends Base { return this.user.defaultAvatarURL; } + get discriminator() { + return this.user.discriminator; + } + + get mention() { + return `<@!${this.id}>`; + } + + get permission() { + this.guild.shard.client.emit("warn", "[DEPRECATED] Member#permission is deprecated. Use Member#permissions instead"); + return this.permissions; + } + + get permissions() { + return this.guild.permissionsOf(this); + } + get staticAvatarURL(){ return this.user.staticAvatarURL; } - get avatarURL() { - return this.user.avatarURL; + get username() { + return this.user.username; } - get mention() { - return `<@!${this.id}>`; + get voiceState() { + if(this.guild && this.guild.voiceStates.has(this.id)) { + return this.guild.voiceStates.get(this.id); + } else { + return new VoiceState({ + id: this.id + }); + } } /** - * Edit the guild member - * @arg {Object} options The properties to edit - * @arg {String[]} [options.roles] The array of role IDs the user should have - * @arg {String} [options.nick] Set the user's server nickname, "" to remove - * @arg {Boolean} [options.mute] Server mute the user - * @arg {Boolean} [options.deaf] Server deafen the user - * @arg {String} [options.channelID] The ID of the voice channel to move the user to (must be in voice) + * Add a role to the guild member + * @arg {String} roleID The ID of the role * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - edit(options, reason) { - return this.guild.shard.client.editGuildMember.call(this.guild.shard.client, this.guild.id, this.id, options, reason); + addRole(roleID, reason) { + return this.guild.shard.client.addGuildMemberRole.call(this.guild.shard.client, this.guild.id, this.id, roleID, reason); } /** - * Add a role to the guild member - * @arg {String} roleID The ID of the role + * Edit the guild member + * @arg {Object} options The properties to edit + * @arg {String} [options.channelID] The ID of the voice channel to move the user to (must be in voice) + * @arg {Boolean} [options.deaf] Server deafen the user + * @arg {Boolean} [options.mute] Server mute the user + * @arg {String} [options.nick] Set the user's server nickname, "" to remove + * @arg {String[]} [options.roles] The array of role IDs the user should have * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - addRole(roleID, reason) { - return this.guild.shard.client.addGuildMemberRole.call(this.guild.shard.client, this.guild.id, this.id, roleID, reason); + edit(options, reason) { + return this.guild.shard.client.editGuildMember.call(this.guild.shard.client, this.guild.id, this.id, options, reason); } /** - * Remove a role from the guild member - * @arg {String} roleID The ID of the role + * Ban the user from the guild + * @arg {Number} [deleteMessageDays=0] Number of days to delete messages for * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - removeRole(roleID, reason) { - return this.guild.shard.client.removeGuildMemberRole.call(this.guild.shard.client, this.guild.id, this.id, roleID, reason); + ban(deleteMessageDays, reason) { + return this.guild.shard.client.banGuildMember.call(this.guild.shard.client, this.guild.id, this.id, deleteMessageDays, reason); } /** @@ -204,13 +204,13 @@ class Member extends Base { } /** - * Ban the user from the guild - * @arg {Number} [deleteMessageDays=0] Number of days to delete messages for + * Remove a role from the guild member + * @arg {String} roleID The ID of the role * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - ban(deleteMessageDays, reason) { - return this.guild.shard.client.banGuildMember.call(this.guild.shard.client, this.guild.id, this.id, deleteMessageDays, reason); + removeRole(roleID, reason) { + return this.guild.shard.client.removeGuildMemberRole.call(this.guild.shard.client, this.guild.id, this.id, roleID, reason); } /** diff --git a/lib/structures/Message.js b/lib/structures/Message.js index cbc727a20..fc5b272a3 100644 --- a/lib/structures/Message.js +++ b/lib/structures/Message.js @@ -8,37 +8,40 @@ const User = require("./User"); /** * Represents a message -* @prop {String} id The ID of the message -* @prop {PrivateChannel | TextChannel | NewsChannel} channel The channel the message is in -* @prop {String} [guildID] The ID of the guild this message is in (undefined if in DMs) -* @prop {Number} timestamp Timestamp of message creation -* @prop {Number} type The type of the message +* @prop {Object?} activity The activity specified in the message +* @prop {Object?} application The application of the activity in the message +* @prop {Object[]} attachments Array of attachments * @prop {User} author The message author -* @prop {Member?} member The message author with server-specific data -* @prop {User[]} mentions Array of mentioned users -* @prop {String} content Message content -* @prop {String?} cleanContent Message content with mentions replaced by names. Mentions are currently escaped, but this behavior is [DEPRECATED] and will be removed soon. Use allowed mentions, the official way of avoiding unintended mentions, when creating messages. -* @prop {String[]} roleMentions Array of mentioned roles' ids +* @prop {PrivateChannel | TextChannel | NewsChannel} channel The channel the message is in * @prop {String[]} channelMentions Array of mentions channels' ids -* @prop {String} jumpLink The url used by Discord clients to jump to this message +* @prop {String?} cleanContent Message content with mentions replaced by names. Mentions are currently escaped, but this behavior is [DEPRECATED] and will be removed soon. Use allowed mentions, the official way of avoiding unintended mentions, when creating messages. +* @prop {Command?} command The Command used in the Message, if any (CommandClient only) +* @prop {String} content Message content +* @prop {Number} createdAt Timestamp of message creation * @prop {Number?} editedTimestamp Timestamp of latest message edit -* @prop {Boolean} tts Whether to play the message using TTS or not +* @prop {Object[]} embeds Array of embeds +* @prop {Number} flags Message flags (see constants) +* @prop {String} [guildID] The ID of the guild this message is in (undefined if in DMs) +* @prop {String} id The ID of the message +* @prop {String} jumpLink The url used by Discord clients to jump to this message +* @prop {Member?} member The message author with server-specific data * @prop {Boolean} mentionEveryone Whether the message mentions everyone/here or not +* @prop {User[]} mentions Array of mentioned users * @prop {Object?} messageReference An object containing the reference to the original message if it is a crossposted message * @prop {String} messageReference.messageID The id of the original message this message was crossposted from * @prop {String} messageReference.channelID The id of the channel this message was crossposted from * @prop {String} messageReference.guildID The id of the guild this message was crossposted from -* @prop {Number} flags Message flags (see constants) -* @prop {Object[]} attachments Array of attachments -* @prop {Object[]} embeds Array of embeds -* @prop {Object?} activity The activity specified in the message -* @prop {Object?} application The application of the activity in the message +* @prop {Boolean} pinned Whether the message is pinned or not +* @prop {String?} prefix The prefix used in the Message, if any (CommandClient only) * @prop {Object} reactions An object containing the reactions on the message -* @prop {Number} reactions.count The number of times the reaction was used * @prop {Boolean} reactions.me Whether or not the bot user did the reaction +* @prop {Number} reactions.count The number of times the reaction was used +* @prop {String[]} roleMentions Array of mentioned roles' ids +* @prop {Number} timestamp Timestamp of message creation +* @prop {Boolean} tts Whether to play the message using TTS or not +* @prop {Number} type The type of the message * @prop {String?} webhookID ID of the webhook that sent the message -* @prop {Boolean} pinned Whether the message is pinned or not -* @prop {Command?} command The Command used in the Message, if any (CommandClient only) + */ class Message extends Base { constructor(data, client) { @@ -236,6 +239,14 @@ class Message extends Base { } } + get channelMentions() { + if(this._channelMentions) { + return this._channelMentions; + } + + return (this._channelMentions = (this.content.match(/<#[0-9]+>/g) || []).map((mention) => mention.substring(2, mention.length - 1))); + } + get cleanContent() { let cleanContent = this.content.replace(/<(:\w+:)[0-9]+>/g, "$1"); @@ -278,16 +289,35 @@ class Message extends Base { return cleanContent.replace(/@everyone/g, "@\u200beveryone").replace(/@here/g, "@\u200bhere"); } - get channelMentions() { - if(this._channelMentions) { - return this._channelMentions; - } + get jumpLink() { + return `${Endpoints.CLIENT_URL}${Endpoints.MESSAGE_LINK(this.channel.type === 1 ? "@me" : this.channel.guild.id, this.channel.id, this.id)}`; + } - return (this._channelMentions = (this.content.match(/<#[0-9]+>/g) || []).map((mention) => mention.substring(2, mention.length - 1))); + /** + * Add a reaction to a message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @arg {String} [userID="@me"] The ID of the user to react as + * @returns {Promise} + */ + addReaction(reaction, userID) { + return this._client.addMessageReaction.call(this._client, this.channel.id, this.id, reaction, userID); } - get jumpLink() { - return `${Endpoints.CLIENT_URL}${Endpoints.MESSAGE_LINK(this.channel.type === 1 ? "@me" : this.channel.guild.id, this.channel.id, this.id)}`; + /** + * Crosspost (publish) a message to subscribed channels (NewsChannel only) + * @returns {Promise} + */ + crosspost() { + return this._client.crosspostMessage.call(this._client, this.channel.id, this.id); + } + + /** + * Delete the message + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + delete(reason) { + return this._client.deleteMessage.call(this._client, this.channel.id, this.id, reason); } /** @@ -307,22 +337,6 @@ class Message extends Base { return this._client.editMessage.call(this._client, this.channel.id, this.id, content); } - /** - * Pin the message - * @returns {Promise} - */ - pin() { - return this._client.pinMessage.call(this._client, this.channel.id, this.id); - } - - /** - * Unpin the message - * @returns {Promise} - */ - unpin() { - return this._client.unpinMessage.call(this._client, this.channel.id, this.id); - } - /** * Get a list of users who reacted with a specific reaction * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) @@ -336,13 +350,11 @@ class Message extends Base { } /** - * Add a reaction to a message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {String} [userID="@me"] The ID of the user to react as + * Pin the message * @returns {Promise} */ - addReaction(reaction, userID) { - return this._client.addMessageReaction.call(this._client, this.channel.id, this.id, reaction, userID); + pin() { + return this._client.pinMessage.call(this._client, this.channel.id, this.id); } /** @@ -355,14 +367,6 @@ class Message extends Base { return this._client.removeMessageReaction.call(this._client, this.channel.id, this.id, reaction, userID); } - /** - * Remove all reactions from a message - * @returns {Promise} - */ - removeReactions() { - return this._client.removeMessageReactions.call(this._client, this.channel.id, this.id); - } - /** * Remove all reactions from a message for a single emoji * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) @@ -373,20 +377,19 @@ class Message extends Base { } /** - * Delete the message - * @arg {String} [reason] The reason to be displayed in audit logs + * Remove all reactions from a message * @returns {Promise} */ - delete(reason) { - return this._client.deleteMessage.call(this._client, this.channel.id, this.id, reason); + removeReactions() { + return this._client.removeMessageReactions.call(this._client, this.channel.id, this.id); } /** - * Crosspost (publish) a message to subscribed channels (NewsChannel only) - * @returns {Promise} - */ - crosspost() { - return this._client.crosspostMessage.call(this._client, this.channel.id, this.id); + * Unpin the message + * @returns {Promise} + */ + unpin() { + return this._client.unpinMessage.call(this._client, this.channel.id, this.id); } toJSON(props = []) { diff --git a/lib/structures/NewsChannel.js b/lib/structures/NewsChannel.js index b049c23ab..76ca82974 100644 --- a/lib/structures/NewsChannel.js +++ b/lib/structures/NewsChannel.js @@ -3,22 +3,22 @@ const TextChannel = require("./TextChannel"); /** -* Represents a guild news channel +* Represents a guild news channel. * @extends TextChannel +* @prop {Guild} guild The guild that owns the channel * @prop {String} id The ID of the channel +* @prop {String} lastMessageID The ID of the last message in this channel +* @prop {Number} lastPinTimestamp The timestamp of the last pinned message * @prop {String} mention A string that mentions the channel -* @prop {Number} type The type of the channel -* @prop {Guild} guild The guild that owns the channel -* @prop {String?} parentID The ID of the category this channel belongs to +* @prop {Collection} messages Collection of Messages in this channel * @prop {String} name The name of the channel -* @prop {Number} position The position of the channel * @prop {Boolean} nsfw Whether the channel is an NSFW channel or not +* @prop {String?} parentID The ID of the category this channel belongs to * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this channel -* @prop {Collection} messages Collection of Messages in this channel -* @prop {String} lastMessageID The ID of the last message in this channel -* @prop {Number} lastPinTimestamp The timestamp of the last pinned message -* @prop {String?} topic The topic of the channel +* @prop {Number} position The position of the channel * @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled. Always 0 in NewsChannel +* @prop {String?} topic The topic of the channel +* @prop {Number} type The type of the channel */ class NewsChannel extends TextChannel { constructor(data, guild, messageLimit) { diff --git a/lib/structures/PrivateChannel.js b/lib/structures/PrivateChannel.js index e7cb86e30..294e2ffe4 100644 --- a/lib/structures/PrivateChannel.js +++ b/lib/structures/PrivateChannel.js @@ -11,11 +11,11 @@ const User = require("./User"); * Represents a private channel * @extends Channel * @prop {String} id The ID of the channel -* @prop {String} mention A string that mentions the channel -* @prop {Number} type The type of the channel * @prop {String} lastMessageID The ID of the last message in this channel -* @prop {User} recipient The recipient in this private channel (private channels only) * @prop {Collection} messages Collection of Messages in this channel +* @prop {String} mention A string that mentions the channel +* @prop {User} recipient The recipient in this private channel (private channels only) +* @prop {Number} type The type of the channel */ class PrivateChannel extends Channel { constructor(data, client) { @@ -30,38 +30,62 @@ class PrivateChannel extends Channel { } /** - * [USER ACCOUNT] Ring fellow group channel recipient(s) - * @arg {String[]} recipients The IDs of the recipients to ring + * Add a reaction to a message + * @arg {String} messageID The ID of the message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @arg {String} [userID="@me"] The ID of the user to react as + * @returns {Promise} */ - ring(recipients) { - this.client.requestHandler.request("POST", Endpoints.CHANNEL_CALL_RING(this.id), true, { - recipients - }); + addMessageReaction(messageID, reaction, userID) { + return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction, userID); } /** - * Check if the channel has an existing call + * Create a message in a text channel + * Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel ID for a user + * @arg {String | Object} content A string or object. If an object is passed: + * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) + * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. + * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. + * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. + * @arg {String} content.content A content string + * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure + * @arg {Boolean} [content.tts] Set the message TTS flag + * @arg {Object} [file] A file object + * @arg {Buffer} file.file A buffer containing file data + * @arg {String} file.name What to name the file + * @returns {Promise} */ - syncCall() { - this.client.shards.values().next().value.sendWS(GatewayOPCodes.SYNC_CALL, { - channel_id: this.id - }); + createMessage(content, file) { + return this.client.createMessage.call(this.client, this.id, content, file); } /** - * Leave the channel + * Delete a message + * @arg {String} messageID The ID of the message + * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - leave() { - return this.client.deleteChannel.call(this.client, this.id); + deleteMessage(messageID, reason) { + return this.client.deleteMessage.call(this.client, this.id, messageID, reason); } /** - * Send typing status in a text channel - * @returns {Promise} + * Edit a message + * @arg {String} messageID The ID of the message + * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: + * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) + * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. + * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. + * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. + * @arg {String} content.content A content string + * @arg {Boolean} [content.disableEveryone] Whether to filter @everyone/@here or not (overrides default) + * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure + * @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#message-object-message-flags) for flags reference + * @returns {Promise} */ - sendTyping() { - return this.client.sendChannelTyping.call(this.client, this.id); + editMessage(messageID, content) { + return this.client.editMessage.call(this.client, this.id, messageID, content); } /** @@ -73,6 +97,19 @@ class PrivateChannel extends Channel { return this.client.getMessage.call(this.client, this.id, messageID); } + /** + * Get a list of users who reacted with a specific reaction + * @arg {String} messageID The ID of the message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @arg {Number} [limit=100] The maximum number of users to get + * @arg {String} [before] Get users before this user ID + * @arg {String} [after] Get users after this user ID + * @returns {Promise} + */ + getMessageReaction(messageID, reaction, limit, before, after) { + return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, limit, before, after); + } + /** * Get a previous message in a text channel * @arg {Number} [limit=50] The max number of messages to get @@ -94,41 +131,11 @@ class PrivateChannel extends Channel { } /** - * Create a message in a text channel - * Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel ID for a user - * @arg {String | Object} content A string or object. If an object is passed: - * @arg {String} content.content A content string - * @arg {Boolean} [content.tts] Set the message TTS flag - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Object} [file] A file object - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - createMessage(content, file) { - return this.client.createMessage.call(this.client, this.id, content, file); - } - - /** - * Edit a message - * @arg {String} messageID The ID of the message - * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: - * @arg {String} content.content A content string - * @arg {Boolean} [content.disableEveryone] Whether to filter @everyone/@here or not (overrides default) - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#message-object-message-flags) for flags reference - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @returns {Promise} + * Leave the channel + * @returns {Promise} */ - editMessage(messageID, content) { - return this.client.editMessage.call(this.client, this.id, messageID, content); + leave() { + return this.client.deleteChannel.call(this.client, this.id); } /** @@ -141,57 +148,51 @@ class PrivateChannel extends Channel { } /** - * Unpin a message + * Remove a reaction from a message * @arg {String} messageID The ID of the message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @arg {String} [userID="@me"] The ID of the user to remove the reaction for * @returns {Promise} */ - unpinMessage(messageID) { - return this.client.unpinMessage.call(this.client, this.id, messageID); + removeMessageReaction(messageID, reaction, userID) { + return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID); } + /** - * Get a list of users who reacted with a specific reaction - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {Number} [limit=100] The maximum number of users to get - * @arg {String} [before] Get users before this user ID - * @arg {String} [after] Get users after this user ID - * @returns {Promise} + * [USER ACCOUNT] Ring fellow group channel recipient(s) + * @arg {String[]} recipients The IDs of the recipients to ring */ - getMessageReaction(messageID, reaction, limit, before, after) { - return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, limit, before, after); + ring(recipients) { + this.client.requestHandler.request("POST", Endpoints.CHANNEL_CALL_RING(this.id), true, { + recipients + }); } /** - * Add a reaction to a message - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {String} [userID="@me"] The ID of the user to react as + * Send typing status in a text channel * @returns {Promise} */ - addMessageReaction(messageID, reaction, userID) { - return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction, userID); + sendTyping() { + return this.client.sendChannelTyping.call(this.client, this.id); } /** - * Remove a reaction from a message - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {String} [userID="@me"] The ID of the user to remove the reaction for - * @returns {Promise} + * Check if the channel has an existing call */ - removeMessageReaction(messageID, reaction, userID) { - return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID); + syncCall() { + this.client.shards.values().next().value.sendWS(GatewayOPCodes.SYNC_CALL, { + channel_id: this.id + }); } /** - * Delete a message + * Unpin a message * @arg {String} messageID The ID of the message - * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - deleteMessage(messageID, reason) { - return this.client.deleteMessage.call(this.client, this.id, messageID, reason); + unpinMessage(messageID) { + return this.client.unpinMessage.call(this.client, this.id, messageID); } /** diff --git a/lib/structures/Role.js b/lib/structures/Role.js index 3646ffe44..23934891a 100644 --- a/lib/structures/Role.js +++ b/lib/structures/Role.js @@ -5,19 +5,18 @@ const Permission = require("./Permission"); /** * Represents a role -* @prop {String} id The ID of the role +* @prop {Number} color The hex color of the role in base 10 * @prop {Number} createdAt Timestamp of the role's creation +* @prop {Boolean} hoist Whether users with this role are hoisted in the user list or not +* @prop {String} id The ID of the role +* @prop {Object} json Generates a JSON representation of the role permissions * @prop {Guild} guild The guild that owns the role +* @prop {Boolean} managed Whether a guild integration manages this role or not * @prop {String} mention A string that mentions the role -* @prop {Number} createdAt Timestamp of role creation -* @prop {String} name The name of the role * @prop {Boolean} mentionable Whether the role is mentionable or not -* @prop {Boolean} managed Whether a guild integration manages this role or not -* @prop {Boolean} hoist Whether users with this role are hoisted in the user list or not -* @prop {Number} color The hex color of the role in base 10 -* @prop {Number} position The position of the role +* @prop {String} name The name of the role * @prop {Permission} permissions The permissions representation of the role -* @prop {Object} json Generates a JSON representation of the role permissions +* @prop {Number} position The position of the role */ class Role extends Base { constructor(data, guild) { @@ -50,22 +49,31 @@ class Role extends Base { } } + get mention() { + return `<@&${this.id}>`; + } + get json() { return this.permissions.json; } - get mention() { - return `<@&${this.id}>`; + /** + * Delete the role + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + delete(reason) { + return this.guild.shard.client.deleteRole.call(this.guild.shard.client, this.guild.id, this.id, reason); } /** * Edit the guild role * @arg {Object} options The properties to edit - * @arg {String} [options.name] The name of the role - * @arg {Number} [options.permissions] The role permissions number * @arg {Number} [options.color] The hex color of the role, in number form (ex: 0x3da5b3 or 4040115) * @arg {Boolean} [options.hoist] Whether to hoist the role in the user list or not * @arg {Boolean} [options.mentionable] Whether the role is mentionable or not + * @arg {String} [options.name] The name of the role + * @arg {Number} [options.permissions] The role permissions number * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ @@ -82,15 +90,6 @@ class Role extends Base { return this.guild.shard.client.editRolePosition.call(this.guild.shard.client, this.guild.id, this.id, position); } - /** - * Delete the role - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} - */ - delete(reason) { - return this.guild.shard.client.deleteRole.call(this.guild.shard.client, this.guild.id, this.id, reason); - } - toJSON(props = []) { return super.toJSON([ "color", diff --git a/lib/structures/StoreChannel.js b/lib/structures/StoreChannel.js index bb719be2b..c5d4ce1b6 100644 --- a/lib/structures/StoreChannel.js +++ b/lib/structures/StoreChannel.js @@ -5,15 +5,15 @@ const GuildChannel = require("./GuildChannel"); /** * Represents a store channel * @extends GuildChannel +* @prop {Guild} guild The guild that owns the channel * @prop {String} id The ID of the channel * @prop {String} mention A string that mentions the channel -* @prop {Number} type The type of the channel -* @prop {Guild} guild The guild that owns the channel -* @prop {String?} parentID The ID of the category this channel belongs to * @prop {String} name The name of the channel -* @prop {Number} position The position of the channel * @prop {Boolean} nsfw Whether the channel is an NSFW channel or not +* @prop {String?} parentID The ID of the category this channel belongs to +* @prop {Number} position The position of the channel * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this channel +* @prop {Number} type The type of the channel */ class StoreChannel extends GuildChannel { } diff --git a/lib/structures/TextChannel.js b/lib/structures/TextChannel.js index d68253afa..b3425ac67 100644 --- a/lib/structures/TextChannel.js +++ b/lib/structures/TextChannel.js @@ -7,20 +7,20 @@ const Message = require("./Message"); /** * Represents a guild text channel * @extends GuildChannel +* @prop {Guild} guild The guild that owns the channel * @prop {String} id The ID of the channel +* @prop {String} lastMessageID The ID of the last message in this channel +* @prop {Number} lastPinTimestamp The timestamp of the last pinned message * @prop {String} mention A string that mentions the channel -* @prop {Number} type The type of the channel -* @prop {Guild} guild The guild that owns the channel -* @prop {String?} parentID The ID of the category this channel belongs to +* @prop {Collection} messages Collection of Messages in this channel * @prop {String} name The name of the channel -* @prop {Number} position The position of the channel * @prop {Boolean} nsfw Whether the channel is an NSFW channel or not +* @prop {Number} type The type of the channel +* @prop {String?} parentID The ID of the category this channel belongs to * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this channel -* @prop {Collection} messages Collection of Messages in this channel -* @prop {String} lastMessageID The ID of the last message in this channel -* @prop {Number} lastPinTimestamp The timestamp of the last pinned message -* @prop {String?} topic The topic of the channel +* @prop {Number} position The position of the channel * @prop {Number} rateLimitPerUser The ratelimit of the channel, in seconds. 0 means no ratelimit is enabled +* @prop {String?} topic The topic of the channel */ class TextChannel extends GuildChannel { constructor(data, client, messageLimit) { @@ -43,11 +43,14 @@ class TextChannel extends GuildChannel { } /** - * Get all invites in the channel - * @returns {Promise} + * Add a reaction to a message + * @arg {String} messageID The ID of the message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @arg {String} [userID="@me"] The ID of the user to react as + * @returns {Promise} */ - getInvites() { - return this.client.getChannelInvites.call(this.client, this.id); + addMessageReaction(messageID, reaction, userID) { + return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction, userID); } /** @@ -65,18 +68,30 @@ class TextChannel extends GuildChannel { } /** - * Get all the webhooks in the channel - * @returns {Promise} Resolves with an array of webhook objects + * Create a message in the channel + * Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel ID for a user + * @arg {String | Object} content A string or object. If an object is passed: + * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) + * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. + * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. + * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. + * @arg {String} content.content A content string + * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure + * @arg {Boolean} [content.tts] Set the message TTS flag + * @arg {Object} [file] A file object + * @arg {Buffer} file.file A buffer containing file data + * @arg {String} file.name What to name the file + * @returns {Promise} */ - getWebhooks() { - return this.client.getChannelWebhooks.call(this.client, this.id); + createMessage(content, file) { + return this.client.createMessage.call(this.client, this.id, content, file); } /** * Create a channel webhook * @arg {Object} options Webhook options - * @arg {String} options.name The default name * @arg {String} options.avatar The default avatar as a base64 data URI. Note: base64 strings alone are not base64 data URI strings + * @arg {String} options.name The default name * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} Resolves with a webhook object */ @@ -84,6 +99,16 @@ class TextChannel extends GuildChannel { return this.client.createChannelWebhook.call(this.client, this.id, options, reason); } + /** + * Delete a message + * @arg {String} messageID The ID of the message + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + deleteMessage(messageID, reason) { + return this.client.deleteMessage.call(this.client, this.id, messageID, reason); + } + /** * Bulk delete messages (bot accounts only) * @arg {String[]} messageIDs Array of message IDs to delete @@ -95,24 +120,29 @@ class TextChannel extends GuildChannel { } /** - * Purge previous messages in the channel with an optional filter (bot accounts only) - * @arg {Number} limit The max number of messages to search through, -1 for no limit - * @arg {function} [filter] Optional filter function that returns a boolean when passed a Message object - * @arg {String} [before] Get messages before this message ID - * @arg {String} [after] Get messages after this message ID - * @arg {String} [reason] The reason to be displayed in audit logs - * @returns {Promise} Resolves with the number of messages deleted + * Edit a message + * @arg {String} messageID The ID of the message + * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: + * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) + * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. + * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. + * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. + * @arg {String} content.content A content string + * @arg {Boolean} [content.disableEveryone] Whether to filter @everyone/@here or not (overrides default) + * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure + * @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#message-object-message-flags) for flags reference + * @returns {Promise} */ - purge(limit, filter, before, after, reason) { - return this.client.purgeChannel.call(this.client, this.id, limit, filter, before, after, reason); + editMessage(messageID, content) { + return this.client.editMessage.call(this.client, this.id, messageID, content); } /** - * Send typing status in the channel - * @returns {Promise} + * Get all invites in the channel + * @returns {Promise} */ - sendTyping() { - return this.client.sendChannelTyping.call(this.client, this.id); + getInvites() { + return this.client.getChannelInvites.call(this.client, this.id); } /** @@ -124,6 +154,19 @@ class TextChannel extends GuildChannel { return this.client.getMessage.call(this.client, this.id, messageID); } + /** + * Get a list of users who reacted with a specific reaction + * @arg {String} messageID The ID of the message + * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * @arg {Number} [limit=100] The maximum number of users to get + * @arg {String} [before] Get users before this user ID + * @arg {String} [after] Get users after this user ID + * @returns {Promise} + */ + getMessageReaction(messageID, reaction, limit, before, after) { + return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, limit, before, after); + } + /** * Get previous messages in the channel * @arg {Number} [limit=50] The max number of messages to get @@ -145,41 +188,11 @@ class TextChannel extends GuildChannel { } /** - * Create a message in the channel - * Note: If you want to DM someone, the user ID is **not** the DM channel ID. use Client.getDMChannel() to get the DM channel ID for a user - * @arg {String | Object} content A string or object. If an object is passed: - * @arg {String} content.content A content string - * @arg {Boolean} [content.tts] Set the message TTS flag - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Object} [file] A file object - * @arg {Buffer} file.file A buffer containing file data - * @arg {String} file.name What to name the file - * @returns {Promise} - */ - createMessage(content, file) { - return this.client.createMessage.call(this.client, this.id, content, file); - } - - /** - * Edit a message - * @arg {String} messageID The ID of the message - * @arg {String | Array | Object} content A string, array of strings, or object. If an object is passed: - * @arg {String} content.content A content string - * @arg {Boolean} [content.disableEveryone] Whether to filter @everyone/@here or not (overrides default) - * @arg {Object} [content.embed] An embed object. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#embed-object) for object structure - * @arg {Number} [content.flags] A number representing the flags to apply to the message. See [the official Discord API documentation entry](https://discordapp.com/developers/docs/resources/channel#message-object-message-flags) for flags reference - * @arg {Object} [content.allowedMentions] A list of mentions to allow (overrides default) - * @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here. - * @arg {Boolean | Array} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow. - * @arg {Boolean | Array} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow. - * @returns {Promise} + * Get all the webhooks in the channel + * @returns {Promise} Resolves with an array of webhook objects */ - editMessage(messageID, content) { - return this.client.editMessage.call(this.client, this.id, messageID, content); + getWebhooks() { + return this.client.getChannelWebhooks.call(this.client, this.id); } /** @@ -192,47 +205,37 @@ class TextChannel extends GuildChannel { } /** - * Unpin a message - * @arg {String} messageID The ID of the message - * @returns {Promise} - */ - unpinMessage(messageID) { - return this.client.unpinMessage.call(this.client, this.id, messageID); - } - - /** - * Get a list of users who reacted with a specific reaction - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {Number} [limit=100] The maximum number of users to get - * @arg {String} [before] Get users before this user ID - * @arg {String} [after] Get users after this user ID - * @returns {Promise} + * Purge previous messages in the channel with an optional filter (bot accounts only) + * @arg {Number} limit The max number of messages to search through, -1 for no limit + * @arg {function} [filter] Optional filter function that returns a boolean when passed a Message object + * @arg {String} [before] Get messages before this message ID + * @arg {String} [after] Get messages after this message ID + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} Resolves with the number of messages deleted */ - getMessageReaction(messageID, reaction, limit, before, after) { - return this.client.getMessageReaction.call(this.client, this.id, messageID, reaction, limit, before, after); + purge(limit, filter, before, after, reason) { + return this.client.purgeChannel.call(this.client, this.id, limit, filter, before, after, reason); } /** - * Add a reaction to a message + * Remove a reaction from a message * @arg {String} messageID The ID of the message * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {String} [userID="@me"] The ID of the user to react as + * @arg {String} [userID="@me"] The ID of the user to remove the reaction for * @returns {Promise} */ - addMessageReaction(messageID, reaction, userID) { - return this.client.addMessageReaction.call(this.client, this.id, messageID, reaction, userID); + removeMessageReaction(messageID, reaction, userID) { + return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID); } /** - * Remove a reaction from a message + * Remove all reactions from a message for a single emoji * @arg {String} messageID The ID of the message * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) - * @arg {String} [userID="@me"] The ID of the user to remove the reaction for * @returns {Promise} */ - removeMessageReaction(messageID, reaction, userID) { - return this.client.removeMessageReaction.call(this.client, this.id, messageID, reaction, userID); + removeMessageReactionEmoji(messageID, reaction) { + return this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction); } /** @@ -245,23 +248,20 @@ class TextChannel extends GuildChannel { } /** - * Remove all reactions from a message for a single emoji - * @arg {String} messageID The ID of the message - * @arg {String} reaction The reaction (Unicode string if Unicode emoji, `emojiName:emojiID` if custom emoji) + * Send typing status in the channel * @returns {Promise} */ - removeMessageReactionEmoji(messageID, reaction) { - return this.client.removeMessageReactionEmoji.call(this.client, this.id, messageID, reaction); + sendTyping() { + return this.client.sendChannelTyping.call(this.client, this.id); } /** - * Delete a message + * Unpin a message * @arg {String} messageID The ID of the message - * @arg {String} [reason] The reason to be displayed in audit logs * @returns {Promise} */ - deleteMessage(messageID, reason) { - return this.client.deleteMessage.call(this.client, this.id, messageID, reason); + unpinMessage(messageID) { + return this.client.unpinMessage.call(this.client, this.id, messageID); } /** diff --git a/lib/structures/User.js b/lib/structures/User.js index 72ee1f2d6..421053ced 100644 --- a/lib/structures/User.js +++ b/lib/structures/User.js @@ -5,20 +5,19 @@ const Endpoints = require("../rest/Endpoints"); /** * Represents a user -* @prop {String} id The ID of the user +* @prop {String?} avatar The hash of the user's avatar, or null if no avatar +* @prop {String} avatarURL The URL of the user's avatar which can be either a JPG or GIF +* @prop {Boolean} bot Whether the user is an OAuth bot or not * @prop {Number} createdAt Timestamp of the user's creation -* @prop {String} mention A string that mentions the user * @prop {String} defaultAvatar The hash for the default avatar of a user if there is no avatar set -* @prop {Number} createdAt Timestamp of user creation -* @prop {Boolean} bot Whether the user is an OAuth bot or not -* @prop {String} username The username of the user -* @prop {String} discriminator The discriminator of the user -* @prop {String?} avatar The hash of the user's avatar, or null if no avatar * @prop {String} defaultAvatarURL The URL of the user's default avatar -* @prop {String} avatarURL The URL of the user's avatar which can be either a JPG or GIF +* @prop {String} discriminator The discriminator of the user +* @prop {String} id The ID of the user +* @prop {String} mention A string that mentions the user +* @prop {Number?} publicFlags Publicly vicible flags for this user * @prop {String} staticAvatarURL The URL of the user's avatar (always a JPG) * @prop {Boolean} system Whether the user is an official Discord system user (e.g. urgent messages) -* @prop {Number?} publicFlags Publicly visible flags for this user +* @prop {String} username The username of the user */ class User extends Base { constructor(data, client) { @@ -47,8 +46,11 @@ class User extends Base { } } - get mention() { - return `<@${this.id}>`; + get avatarURL() { + if(this._missingClientError) { + throw this._missingClientError; + } + return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar)) : this.defaultAvatarURL; } get defaultAvatar() { @@ -59,52 +61,49 @@ class User extends Base { return `${Endpoints.CDN_URL}${Endpoints.DEFAULT_USER_AVATAR(this.defaultAvatar)}.png`; } - get staticAvatarURL() { - if(this._missingClientError) { - throw this._missingClientError; - } - return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar), "jpg") : this.defaultAvatarURL; + get mention() { + return `<@${this.id}>`; } - get avatarURL() { + get staticAvatarURL() { if(this._missingClientError) { throw this._missingClientError; } - return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar)) : this.defaultAvatarURL; + return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar), "jpg") : this.defaultAvatarURL; } /** - * Get the user's avatar with the given format and size - * @arg {String} [format] The filetype of the avatar ("jpg", "jpeg", "png", "gif", or "webp") - * @arg {Number} [size] The size of the avatar (any power of two between 16 and 4096) + * [USER ACCOUNT] Create a relationship with the user + * @arg {Boolean} [block=false] If true, block the user. Otherwise, add the user as a friend + * @returns {Promise} */ - dynamicAvatarURL(format, size) { - return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar), format, size) : this.defaultAvatarURL; + addRelationship(block) { + return this._client.addRelationship.call(this._client, this.id, block); } /** - * Get a DM channel with the user, or create one if it does not exist - * @returns {Promise} + * [USER ACCOUNT] Delete the current user's note for another user */ - getDMChannel() { - return this._client.getDMChannel.call(this._client, this.id); + deleteNote() { + return this._client.deleteUserNote.call(this._client, this.id); } /** - * [USER ACCOUNT] Create a relationship with the user - * @arg {Boolean} [block=false] If true, block the user. Otherwise, add the user as a friend - * @returns {Promise} + * Get the user's avatar with the given format and size + * @arg {String} [format] The filetype of the avatar ("jpg", "jpeg", "png", "gif", or "webp") + * @arg {Number} [size] The size of the avatar (any power of two between 16 and 4096) */ - addRelationship(block) { - return this._client.addRelationship.call(this._client, this.id, block); + dynamicAvatarURL(format, size) { + return this.avatar ? this._client._formatImage(Endpoints.USER_AVATAR(this.id, this.avatar), format, size) : this.defaultAvatarURL; } /** - * [USER ACCOUNT] Remove a relationship with the user + * [USER ACCOUNT] Edit the current user's note for the user + * @arg {String} note The note * @returns {Promise} */ - removeRelationship() { - return this._client.removeRelationship.call(this._client, this.id); + editNote(note) { + return this._client.editUserNote.call(this._client, this.id, note); } /** @@ -116,19 +115,19 @@ class User extends Base { } /** - * [USER ACCOUNT] Edit the current user's note for the user - * @arg {String} note The note - * @returns {Promise} + * Get a DM channel with the user, or create one if it does not exist + * @returns {Promise} */ - editNote(note) { - return this._client.editUserNote.call(this._client, this.id, note); + getDMChannel() { + return this._client.getDMChannel.call(this._client, this.id); } /** - * [USER ACCOUNT] Delete the current user's note for another user + * [USER ACCOUNT] Remove a relationship with the user + * @returns {Promise} */ - deleteNote() { - return this._client.deleteUserNote.call(this._client, this.id); + removeRelationship() { + return this._client.removeRelationship.call(this._client, this.id); } toJSON(props = []) { diff --git a/lib/structures/VoiceChannel.js b/lib/structures/VoiceChannel.js index 7555ed6c0..11f6ea456 100644 --- a/lib/structures/VoiceChannel.js +++ b/lib/structures/VoiceChannel.js @@ -7,16 +7,16 @@ const Member = require("./Member"); /** * Represents a guild voice channel * @extends GuildChannel +* @prop {Number?} bitrate The bitrate of the channel +* @prop {Guild} guild The guild that owns the channel * @prop {String} id The ID of the channel * @prop {String} mention A string that mentions the channel -* @prop {Number} type The type of the channel -* @prop {Guild} guild The guild that owns the channel -* @prop {String?} parentID The ID of the category this channel belongs to * @prop {String} name The name of the channel -* @prop {Number} position The position of the channel * @prop {Boolean} nsfw Whether the channel is an NSFW channel or not +* @prop {String?} parentID The ID of the category this channel belongs to * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this channel -* @prop {Number?} bitrate The bitrate of the channel +* @prop {Number} position The position of the channel +* @prop {Number} type The type of the channel * @prop {Number?} userLimit The max number of users that can join the channel * @prop {Collection} voiceMembers Collection of Members in this channel */ @@ -38,14 +38,6 @@ class VoiceChannel extends GuildChannel { } } - /** - * Get all invites in the channel - * @returns {Promise} - */ - getInvites() { - return this.client.getChannelInvites.call(this.client, this.id); - } - /** * Create an invite for the channel * @arg {Object} [options] Invite generation options @@ -60,11 +52,19 @@ class VoiceChannel extends GuildChannel { return this.client.createChannelInvite.call(this.client, this.id, options, reason); } + /** + * Get all invites in the channel + * @returns {Promise} + */ + getInvites() { + return this.client.getChannelInvites.call(this.client, this.id); + } + /** * Joins the channel. * @arg {Object} [options] VoiceConnection constructor options - * @arg {Object} [options.shared] Whether the VoiceConnection will be part of a SharedStream or not * @arg {Object} [options.opusOnly] Skip opus encoder initialization. You should not enable this unless you know what you are doing + * @arg {Object} [options.shared] Whether the VoiceConnection will be part of a SharedStream or not * @returns {Promise} Resolves with a VoiceConnection */ join(options) { diff --git a/lib/structures/VoiceState.js b/lib/structures/VoiceState.js index e862e1bf9..a63b84537 100644 --- a/lib/structures/VoiceState.js +++ b/lib/structures/VoiceState.js @@ -4,15 +4,15 @@ const Base = require("./Base"); /** * Represents a member's voice state in a call/guild -* @prop {String} id The ID of the member -* @prop {String?} sessionID The ID of the member's current voice session * @prop {String?} channelID The ID of the member's current voice channel -* @prop {Boolean} mute Whether the member is server muted or not * @prop {Boolean} deaf Whether the member is server deafened or not -* @prop {Boolean} suppress Whether the member is suppressed or not -* @prop {Boolean} selfMute Whether the member is self muted or not +* @prop {String} id The ID of the member +* @prop {Boolean} mute Whether the member is server muted or not * @prop {Boolean} selfDeaf Whether the member is self deafened or not +* @prop {Boolean} selfMute Whether the member is self muted or not * @prop {Boolean} selfStream Whether the member is streaming using "Go Live" +* @prop {Boolean} suppress Whether the member is suppressed or not +* @prop {String?} sessionID The ID of the member's current voice session */ class VoiceState extends Base { constructor(data) {