diff --git a/src/structures/CategoryChannel.js b/src/structures/CategoryChannel.js index d7121a32..b9d3ceff 100644 --- a/src/structures/CategoryChannel.js +++ b/src/structures/CategoryChannel.js @@ -13,6 +13,19 @@ class CategoryChannel extends GuildChannel { get children() { return this.guild.channels.filter(c => c.parentID === this.id); } + + /** + * Sets the category parent of this channel. + * It is not currently possible to set the parent of a CategoryChannel. + * @method setParent + * @memberof CategoryChannel + * @instance + * @param {?GuildChannel|Snowflake} channel Parent channel + * @param {Object} [options={}] Options to pass + * @param {boolean} [options.lockPermissions=true] Lock the permissions to what the parent's permissions are + * @param {string} [options.reason] Reason for modifying the parent of this channel + * @returns {Promise} + */ } module.exports = CategoryChannel; diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js index d1cca284..302ebfcc 100644 --- a/src/structures/Emoji.js +++ b/src/structures/Emoji.js @@ -45,6 +45,12 @@ class Emoji extends Base { */ this.managed = data.managed; + /** + * Whether this emoji is animated + * @type {boolean} + */ + this.animated = data.animated; + this._roles = data.roles; } @@ -85,7 +91,7 @@ class Emoji extends Base { * @readonly */ get url() { - return this.client.rest.cdn.Emoji(this.id); + return this.client.rest.cdn.Emoji(this.id, this.animated ? 'gif' : 'png'); } /** @@ -198,7 +204,11 @@ class Emoji extends Base { * msg.reply(`Hello! ${emoji}`); */ toString() { - return this.requiresColons ? `<:${this.name}:${this.id}>` : this.name; + if (!this.id || !this.requiresColons) { + return this.name; + } + + return `<${this.animated ? 'a' : ''}:${this.name}:${this.id}>`; } /** diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 307e5eda..5f967458 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -910,8 +910,8 @@ class Guild extends Base { /** * Creates a new channel in the guild. * @param {string} name The name of the new channel - * @param {string} type The type of the new channel, either `text`, `voice`, or `category` * @param {Object} [options] Options + * @param {string} [options.type='text'] The type of the new channel, either `text`, `voice`, or `category` * @param {boolean} [options.nsfw] Whether the new channel is nsfw * @param {number} [options.bitrate] Bitrate of the new channel in bits (only voice) * @param {number} [options.userLimit] Maximum amount of users allowed in the new channel (only voice) @@ -925,7 +925,7 @@ class Guild extends Base { * .then(channel => console.log(`Created new channel ${channel}`)) * .catch(console.error); */ - createChannel(name, type, { nsfw, bitrate, userLimit, parent, overwrites, reason } = {}) { + createChannel(name, { type, nsfw, bitrate, userLimit, parent, overwrites, reason } = {}) { if (overwrites instanceof Collection || overwrites instanceof Array) { overwrites = overwrites.map(overwrite => { let allow = overwrite.allow || (overwrite.allowed ? overwrite.allowed.bitfield : 0); @@ -955,7 +955,7 @@ class Guild extends Base { return this.client.api.guilds(this.id).channels.post({ data: { name, - type: ChannelTypes[type.toUpperCase()], + type: type ? ChannelTypes[type.toUpperCase()] : 'text', nsfw, bitrate, user_limit: userLimit, diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js index fa0f8f84..a8b5d132 100644 --- a/src/structures/GuildChannel.js +++ b/src/structures/GuildChannel.js @@ -9,7 +9,7 @@ const { MessageNotificationTypes } = require('../util/Constants'); const { Error, TypeError } = require('../errors'); /** - * Represents a guild channel (e.g. text channels and voice channels). + * Represents a guild channel (i.g. a {@link TextChannel}, {@link VoiceChannel} or {@link CategoryChannel}). * @extends {Channel} */ class GuildChannel extends Channel { @@ -323,14 +323,15 @@ class GuildChannel extends Channel { /** * Sets the category parent of this channel. - * @param {GuildChannel|Snowflake} channel Parent channel - * @param {boolean} [options.lockPermissions] Lock the permissions to what the parent's permissions are + * @param {?GuildChannel|Snowflake} channel Parent channel + * @param {Object} [options={}] Options to pass + * @param {boolean} [options.lockPermissions=true] Lock the permissions to what the parent's permissions are * @param {string} [options.reason] Reason for modifying the parent of this channel * @returns {Promise} */ setParent(channel, { lockPermissions = true, reason } = {}) { return this.edit({ - parentID: channel.id ? channel.id : channel, + parentID: channel !== null ? channel.id ? channel.id : channel : null, lockPermissions, }, reason); } diff --git a/src/structures/Message.js b/src/structures/Message.js index a409e411..9b86279e 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -377,10 +377,10 @@ class Message extends Base { } if (!options.content) options.content = content; - const { data, files } = await createMessage(this, options); + const { data } = await createMessage(this, options); return this.client.api.channels[this.channel.id].messages[this.id] - .patch({ data, files }) + .patch({ data }) .then(d => { const clone = this._clone(); clone._patch(d); diff --git a/src/structures/interfaces/Collector.js b/src/structures/interfaces/Collector.js index 2c4fd158..0e7f1f95 100644 --- a/src/structures/interfaces/Collector.js +++ b/src/structures/interfaces/Collector.js @@ -76,17 +76,18 @@ class Collector extends EventEmitter { */ handleCollect(...args) { const collect = this.collect(...args); - if (!collect || !this.filter(...args, this.collected)) return; - this.collected.set(collect.key, collect.value); + if (collect && this.filter(...args, this.collected)) { + this.collected.set(collect.key, collect.value); - /** - * Emitted whenever an element is collected. - * @event Collector#collect - * @param {*} element The element that got collected - * @param {...*} args The arguments emitted by the listener - */ - this.emit('collect', collect.value, ...args); + /** + * Emitted whenever an element is collected. + * @event Collector#collect + * @param {*} element The element that got collected + * @param {...*} args The arguments emitted by the listener + */ + this.emit('collect', collect.value, ...args); + } this.checkEnd(); } diff --git a/src/structures/shared/CreateMessage.js b/src/structures/shared/CreateMessage.js index 5abf0799..d0fa3572 100644 --- a/src/structures/shared/CreateMessage.js +++ b/src/structures/shared/CreateMessage.js @@ -19,18 +19,31 @@ module.exports = async function createMessage(channel, options) { if (isNaN(options.nonce) || options.nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE'); } + let { content } = options; if (options instanceof MessageEmbed) options = webhook ? { embeds: [options] } : { embed: options }; if (options instanceof MessageAttachment) options = { files: [options.file] }; + if (content instanceof Array || options instanceof Array) { + const which = content instanceof Array ? content : options; + const attachments = which.filter(item => item instanceof MessageAttachment); + const embeds = which.filter(item => item instanceof MessageEmbed); + if (attachments.length) options = { files: attachments }; + if (embeds.length) options = { embeds }; + if ((embeds.length || attachments.length) && content instanceof Array) { + content = null; + options.content = ''; + } + } + if (options.reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') { const id = channel.client.users.resolveID(options.reply); const mention = `<@${options.reply instanceof GuildMember && options.reply.nickname ? '!' : ''}${id}>`; if (options.split) options.split.prepend = `${mention}, ${options.split.prepend || ''}`; - options.content = `${mention}${typeof options.content !== 'undefined' ? `, ${options.content}` : ''}`; + content = `${mention}${typeof options.content !== 'undefined' ? `, ${options.content}` : ''}`; } - if (options.content) { - options.content = Util.resolveString(options.content); + if (content) { + options.content = Util.resolveString(content); if (options.split && typeof options.split !== 'object') options.split = {}; // Wrap everything in a code block if (typeof options.code !== 'undefined' && (typeof options.code !== 'boolean' || options.code === true)) { diff --git a/src/util/Constants.js b/src/util/Constants.js index efeeb29f..bc67df35 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -110,7 +110,7 @@ function makeImageUrl(root, { format = 'webp', size } = {}) { exports.Endpoints = { CDN(root) { return { - Emoji: emojiID => `${root}/emojis/${emojiID}.png`, + Emoji: (emojiID, format = 'png') => `${root}/emojis/${emojiID}.${format}`, Asset: name => `${root}/assets/${name}`, DefaultAvatar: number => `${root}/embed/avatars/${number}.png`, Avatar: (userID, hash, format = 'default', size) => { diff --git a/src/util/Util.js b/src/util/Util.js index d92dc94b..f1758000 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -70,18 +70,21 @@ class Util { * Parses emoji info out of a string. The string must be one of: * * A UTF-8 emoji (no ID) * * A URL-encoded UTF-8 emoji (no ID) - * * A Discord custom emoji (`<:name:id>`) + * * A Discord custom emoji (`<:name:id>` or ``) * @param {string} text Emoji string to parse - * @returns {Object} Object with `name` and `id` properties + * @returns {Object} Object with `animated`, `name`, and `id` properties * @private */ static parseEmoji(text) { if (text.includes('%')) text = decodeURIComponent(text); if (text.includes(':')) { - const [name, id] = text.split(':'); - return { name, id }; + const m = text.match(/?/); + if (!m) { + return null; + } + return { animated: Boolean(m[1]), name: m[2], id: m[3] }; } else { - return { name: text, id: null }; + return { animated: false, name: text, id: null }; } }