From 19c298f5cc131f152befa80eb4e956e52feaa778 Mon Sep 17 00:00:00 2001 From: 1Computer1 Date: Tue, 21 Aug 2018 12:22:29 -0400 Subject: [PATCH] refactor: rewrite message creation (#2774) * Rework createMessage - MessageAttachment is now structurally similar to FileOptions - No longer mutates the object passed as options - Supports more permutations of arguments * Ignore complexity warning * Refactor name finding * Fix typo * Update typings * Default name to null for MessageAttachment * Make Message#reply use transformOptions * Move transformOptions * Fix Message#reply * Fix mutation * Update tests * Fix options passing * Refactor into APIMessage * Fix webhook send * Expose APIMessage * Add documentation * Add types * Fix type doc * Fix another type doc * Fix another another type doc (is this one even right!?) * Remove trailing comma * Properly clone split options * Add support for sending file as stream * Missed a doc * Resolve files only once when splitting messages * This looks nicer * Assign directly * Don't cache data and files * Missing return type * Use object spread instead Object.assign * Document constructors * Crawl is a little dot * comp pls * tests: sanitize local file path, disable no-await-in-loop --- src/index.js | 1 + src/structures/APIMessage.js | 292 ++++++++++++++++++ src/structures/Message.js | 29 +- src/structures/MessageAttachment.js | 78 ++--- src/structures/MessageEmbed.js | 10 +- src/structures/Webhook.js | 29 +- src/structures/interfaces/TextBasedChannel.js | 41 ++- src/structures/shared/CreateMessage.js | 126 -------- src/structures/shared/SendMessage.js | 23 -- src/structures/shared/index.js | 4 - src/util/DataResolver.js | 2 +- test/blobReach.png | Bin 0 -> 10423 bytes test/sendtest.js | 140 +++++++++ typings/index.d.ts | 60 +++- 14 files changed, 551 insertions(+), 284 deletions(-) create mode 100644 src/structures/APIMessage.js delete mode 100644 src/structures/shared/CreateMessage.js delete mode 100644 src/structures/shared/SendMessage.js delete mode 100644 src/structures/shared/index.js create mode 100644 test/blobReach.png create mode 100644 test/sendtest.js diff --git a/src/index.js b/src/index.js index fd2239ade..1c9f068ce 100644 --- a/src/index.js +++ b/src/index.js @@ -51,6 +51,7 @@ module.exports = { // Structures Base: require('./structures/Base'), Activity: require('./structures/Presence').Activity, + APIMessage: require('./structures/APIMessage'), CategoryChannel: require('./structures/CategoryChannel'), Channel: require('./structures/Channel'), ClientApplication: require('./structures/ClientApplication'), diff --git a/src/structures/APIMessage.js b/src/structures/APIMessage.js new file mode 100644 index 000000000..77eb4234b --- /dev/null +++ b/src/structures/APIMessage.js @@ -0,0 +1,292 @@ +const DataResolver = require('../util/DataResolver'); +const MessageEmbed = require('./MessageEmbed'); +const MessageAttachment = require('./MessageAttachment'); +const { browser } = require('../util/Constants'); +const Util = require('../util/Util'); +const { RangeError } = require('../errors'); + +/** + * Represents a message to be sent to the API. + */ +class APIMessage { + /** + * @param {MessageTarget} target - The target for this message to be sent to + * @param {MessageOptions|WebhookMessageOptions} options - Options passed in from send + */ + constructor(target, options) { + /** + * The target for this message to be sent to + * @type {MessageTarget} + */ + this.target = target; + + /** + * Options passed in from send + * @type {MessageOptions|WebhookMessageOptions} + */ + this.options = options; + } + + /** + * Whether or not the target is a webhook + * @type {boolean} + * @readonly + */ + get isWebhook() { + const Webhook = require('./Webhook'); + const WebhookClient = require('../client/WebhookClient'); + return this.target instanceof Webhook || this.target instanceof WebhookClient; + } + + /** + * Whether or not the target is a user + * @type {boolean} + * @readonly + */ + get isUser() { + const User = require('./User'); + const GuildMember = require('./GuildMember'); + return this.target instanceof User || this.target instanceof GuildMember; + } + + /** + * Makes the content of this message. + * @returns {string|string[]} + */ + makeContent() { // eslint-disable-line complexity + const GuildMember = require('./GuildMember'); + + // eslint-disable-next-line eqeqeq + let content = Util.resolveString(this.options.content == null ? '' : this.options.content); + const isSplit = typeof this.options.split !== 'undefined' && this.options.split !== false; + const isCode = typeof this.options.code !== 'undefined' && this.options.code !== false; + const splitOptions = isSplit ? { ...this.options.split } : undefined; + + let mentionPart = ''; + if (this.options.reply && !this.isUser && this.target.type !== 'dm') { + const id = this.target.client.users.resolveID(this.options.reply); + mentionPart = `<@${this.options.reply instanceof GuildMember && this.options.reply.nickname ? '!' : ''}${id}>, `; + if (isSplit) { + splitOptions.prepend = `${mentionPart}${splitOptions.prepend || ''}`; + } + } + + if (content || mentionPart) { + if (isCode) { + const codeName = typeof this.options.code === 'string' ? this.options.code : ''; + content = `${mentionPart}\`\`\`${codeName}\n${Util.escapeMarkdown(content, true)}\n\`\`\``; + if (isSplit) { + splitOptions.prepend = `${splitOptions.prepend || ''}\`\`\`${codeName}\n`; + splitOptions.append = `\n\`\`\`${splitOptions.append || ''}`; + } + } else if (mentionPart) { + content = `${mentionPart}${content}`; + } + + const disableEveryone = typeof this.options.disableEveryone === 'undefined' ? + this.target.client.options.disableEveryone : + this.options.disableEveryone; + if (disableEveryone) { + content = content.replace(/@(everyone|here)/g, '@\u200b$1'); + } + + if (isSplit) { + content = Util.splitMessage(content, splitOptions); + } + } + + return content; + } + + /** + * Resolves data. + * @returns {Object} + */ + resolveData() { + const content = this.makeContent(); + const tts = Boolean(this.options.tts); + let nonce; + if (typeof this.options.nonce !== 'undefined') { + nonce = parseInt(this.options.nonce); + if (isNaN(nonce) || nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE'); + } + + const embedLikes = []; + if (this.isWebhook) { + if (this.options.embeds) { + embedLikes.push(...this.options.embeds); + } + } else if (this.options.embed) { + embedLikes.push(this.options.embed); + } + const embeds = embedLikes.map(e => new MessageEmbed(e)._apiTransform()); + + let username; + let avatarURL; + if (this.isWebhook) { + username = this.options.username || this.target.name; + if (this.options.avatarURL) avatarURL = this.options.avatarURL; + } + + return { + content, + tts, + nonce, + embed: this.options.embed === null ? null : embeds[0], + embeds, + username, + avatar_url: avatarURL, + }; + } + + /** + * Resolves files. + * @returns {Promise} + */ + resolveFiles() { + const embedLikes = []; + if (this.isWebhook) { + if (this.options.embeds) { + embedLikes.push(...this.options.embeds); + } + } else if (this.options.embed) { + embedLikes.push(this.options.embed); + } + + const fileLikes = []; + if (this.options.files) { + fileLikes.push(...this.options.files); + } + for (const embed of embedLikes) { + if (embed.files) { + fileLikes.push(...embed.files); + } + } + + return Promise.all(fileLikes.map(f => this.constructor.resolveFile(f))); + } + + /** + * Resolves a single file into an object sendable to the API. + * @param {BufferResolvable|Stream|FileOptions|MessageAttachment} fileLike Something that could be resolved to a file + * @returns {Object} + */ + static async resolveFile(fileLike) { + let attachment; + let name; + + const findName = thing => { + if (typeof thing === 'string') { + return Util.basename(thing); + } + + if (thing.path) { + return Util.basename(thing.path); + } + + return 'file.jpg'; + }; + + const ownAttachment = typeof fileLike === 'string' || + fileLike instanceof (browser ? ArrayBuffer : Buffer) || + typeof fileLike.pipe === 'function'; + if (ownAttachment) { + attachment = fileLike; + name = findName(attachment); + } else { + attachment = fileLike.attachment; + name = fileLike.name || findName(attachment); + } + + const resource = await DataResolver.resolveFile(attachment); + return { attachment, name, file: resource }; + } + + /** + * Partitions embeds and attachments. + * @param {Array} items Items to partition + * @returns {Array} + */ + static partitionMessageAdditions(items) { + const embeds = []; + const files = []; + for (const item of items) { + if (item instanceof MessageEmbed) { + embeds.push(item); + } else if (item instanceof MessageAttachment) { + files.push(item); + } + } + + return [embeds, files]; + } + + /** + * Transforms the user-level arguments into a final options object. Passing a transformed options object alone into + * this method will keep it the same, allowing for the reuse of the final options object. + * @param {StringResolvable} [content=''] Content to send + * @param {MessageOptions|WebhookMessageOptions|MessageAdditions} [options={}] Options to use + * @param {MessageOptions|WebhookMessageOptions} [extra={}] Extra options to add onto transformed options + * @param {boolean} [isWebhook=false] Whether or not to use WebhookMessageOptions as the result + * @returns {MessageOptions|WebhookMessageOptions} + */ + static transformOptions(content, options, extra = {}, isWebhook = false) { + if (!options && typeof content === 'object' && !(content instanceof Array)) { + options = content; + content = ''; + } + + if (!options) { + options = {}; + } + + if (options instanceof MessageEmbed) { + return isWebhook ? { content, embeds: [options], ...extra } : { content, embed: options, ...extra }; + } + + if (options instanceof MessageAttachment) { + return { content, files: [options], ...extra }; + } + + if (options instanceof Array) { + const [embeds, files] = this.partitionMessageAdditions(options); + return isWebhook ? { content, embeds, files, ...extra } : { content, embed: embeds[0], files, ...extra }; + } else if (content instanceof Array) { + const [embeds, files] = this.partitionMessageAdditions(content); + if (embeds.length || files.length) { + return isWebhook ? { embeds, files, ...extra } : { embed: embeds[0], files, ...extra }; + } + } + + return { content, ...options, ...extra }; + } + + /** + * Creates an `APIMessage` from user-level arguments. + * @param {MessageTarget} target Target to send to + * @param {StringResolvable} [content=''] Content to send + * @param {MessageOptions|WebhookMessageOptions|MessageAdditions} [options={}] Options to use + * @param {MessageOptions|WebhookMessageOptions} [extra={}] - Extra options to add onto transformed options + * @returns {MessageOptions|WebhookMessageOptions} + */ + static create(target, content, options, extra = {}) { + const Webhook = require('./Webhook'); + const WebhookClient = require('../client/WebhookClient'); + + const isWebhook = target instanceof Webhook || target instanceof WebhookClient; + const transformed = this.transformOptions(content, options, extra, isWebhook); + return new this(target, transformed); + } +} + +module.exports = APIMessage; + +/** + * A target for a message. + * @typedef {TextChannel|DMChannel|GroupDMChannel|User|GuildMember|Webhook|WebhookClient} MessageTarget + */ + +/** + * Additional items that can be sent with a message. + * @typedef {MessageEmbed|MessageAttachment|Array} MessageAdditions + */ diff --git a/src/structures/Message.js b/src/structures/Message.js index 96ffacbb7..e229e20cf 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -10,7 +10,7 @@ const { MessageTypes } = require('../util/Constants'); const Permissions = require('../util/Permissions'); const Base = require('./Base'); const { Error, TypeError } = require('../errors'); -const { createMessage } = require('./shared'); +const APIMessage = require('./APIMessage'); /** * Represents a message on Discord. @@ -359,7 +359,7 @@ class Message extends Base { /** * Edits the content of the message. - * @param {StringResolvable} [content] The new content for the message + * @param {StringResolvable} [content=''] The new content for the message * @param {MessageEditOptions|MessageEmbed} [options] The options to provide * @returns {Promise} * @example @@ -368,17 +368,8 @@ class Message extends Base { * .then(msg => console.log(`Updated the content of a message to ${msg.content}`)) * .catch(console.error); */ - async edit(content, options) { - if (!options && typeof content === 'object' && !(content instanceof Array)) { - options = content; - content = null; - } else if (!options) { - options = {}; - } - if (!options.content) options.content = content; - - const { data } = await createMessage(this, options); - + edit(content, options) { + const data = APIMessage.create(this, content, options).resolveData(); return this.client.api.channels[this.channel.id].messages[this.id] .patch({ data }) .then(d => { @@ -467,8 +458,8 @@ class Message extends Base { /** * Replies to the message. - * @param {StringResolvable} [content] The content for the message - * @param {MessageOptions} [options] The options to provide + * @param {StringResolvable} [content=''] The content for the message + * @param {MessageOptions|MessageAdditions} [options={}] The options to provide * @returns {Promise} * @example * // Reply to a message @@ -477,13 +468,7 @@ class Message extends Base { * .catch(console.error); */ reply(content, options) { - if (!options && typeof content === 'object' && !(content instanceof Array)) { - options = content; - content = ''; - } else if (!options) { - options = {}; - } - return this.channel.send(content, Object.assign(options, { reply: this.member || this.author })); + return this.channel.send(APIMessage.transformOptions(content, options, { reply: this.member || this.author })); } /** diff --git a/src/structures/MessageAttachment.js b/src/structures/MessageAttachment.js index 29f730764..a8d74ee67 100644 --- a/src/structures/MessageAttachment.js +++ b/src/structures/MessageAttachment.js @@ -2,77 +2,41 @@ const Util = require('../util/Util'); /** * Represents an attachment in a message. - * @param {BufferResolvable|Stream} file The file - * @param {string} [name] The name of the file, if any */ class MessageAttachment { - constructor(file, name, data) { - this.file = null; + /** + * @param {BufferResolvable|Stream} attachment The file + * @param {string} [name=null] The name of the file, if any + * @param {Object} [data] Extra data + */ + constructor(attachment, name = null, data) { + this.attachment = attachment; + this.name = name; if (data) this._patch(data); - if (name) this.setAttachment(file, name); - else this._attach(file); } /** - * The name of the file - * @type {?string} - * @readonly - */ - get name() { - return this.file.name; - } - - /** - * The file - * @type {?BufferResolvable|Stream} - * @readonly - */ - get attachment() { - return this.file.attachment; - } - - /** - * Sets the file of this attachment. - * @param {BufferResolvable|Stream} file The file - * @param {string} name The name of the file - * @returns {MessageAttachment} This attachment - */ - setAttachment(file, name) { - this.file = { attachment: file, name }; + * Sets the file of this attachment. + * @param {BufferResolvable|Stream} attachment The file + * @param {string} [name=null] The name of the file, if any + * @returns {MessageAttachment} This attachment + */ + setFile(attachment, name = null) { + this.attachment = attachment; + this.name = name; return this; } /** - * Sets the file of this attachment. - * @param {BufferResolvable|Stream} attachment The file - * @returns {MessageAttachment} This attachment - */ - setFile(attachment) { - this.file = { attachment }; - return this; - } - - /** - * Sets the name of this attachment. - * @param {string} name The name of the image - * @returns {MessageAttachment} This attachment - */ + * Sets the name of this attachment. + * @param {string} name The name of the file + * @returns {MessageAttachment} This attachment + */ setName(name) { - this.file.name = name; + this.name = name; return this; } - /** - * Sets the file of this attachment. - * @param {BufferResolvable|Stream} file The file - * @param {string} name The name of the file - * @private - */ - _attach(file, name) { - if (typeof file === 'string') this.file = file; - else this.setAttachment(file, name); - } - _patch(data) { /** * The ID of this attachment diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js index 72e22464e..2d0208b24 100644 --- a/src/structures/MessageEmbed.js +++ b/src/structures/MessageEmbed.js @@ -1,4 +1,3 @@ -const MessageAttachment = require('./MessageAttachment'); const Util = require('../util/Util'); const { RangeError } = require('../errors'); @@ -141,14 +140,8 @@ class MessageEmbed { * @type {Array} */ this.files = []; - if (data.files) { - this.files = data.files.map(file => { - if (file instanceof MessageAttachment) { - return typeof file.file === 'string' ? file.file : Util.cloneObject(file.file); - } - return file; - }); + this.files = data.files; } } @@ -203,7 +196,6 @@ class MessageEmbed { * @returns {MessageEmbed} */ attachFiles(files) { - files = files.map(file => file instanceof MessageAttachment ? file.file : file); this.files = this.files.concat(files); return this; } diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index 088806ca2..711b3db79 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -1,6 +1,6 @@ const DataResolver = require('../util/DataResolver'); const Channel = require('./Channel'); -const { createMessage } = require('./shared'); +const APIMessage = require('./APIMessage'); /** * Represents a webhook. @@ -82,11 +82,10 @@ class Webhook { * it exceeds the character limit. If an object is provided, these are the options for splitting the message. */ - /* eslint-disable max-len */ /** * Sends a message with this webhook. - * @param {StringResolvable} [content] The content to send - * @param {WebhookMessageOptions|MessageEmbed|MessageAttachment|MessageAttachment[]} [options={}] The options to provide + * @param {StringResolvable} [content=''] The content to send + * @param {WebhookMessageOptions|MessageAdditions} [options={}] The options to provide * @returns {Promise} * @example * // Send a basic message @@ -127,20 +126,18 @@ class Webhook { * .catch(console.error); */ async send(content, options) { - if (!options && typeof content === 'object' && !(content instanceof Array)) { - options = content; - content = null; - } else if (!options) { - options = {}; - } - if (!options.content) options.content = content; - - const { data, files } = await createMessage(this, options); - + const apiMessage = APIMessage.create(this, content, options); + const data = apiMessage.resolveData(); if (data.content instanceof Array) { const messages = []; for (let i = 0; i < data.content.length; i++) { - const opt = i === data.content.length - 1 ? { embeds: data.embeds, files } : {}; + let opt; + if (i === data.content.length - 1) { + opt = { embeds: data.embeds, files: apiMessage.options.files }; + } else { + opt = {}; + } + Object.assign(opt, { avatarURL: data.avatar_url, content: data.content[i], username: data.username }); // eslint-disable-next-line no-await-in-loop const message = await this.send(data.content[i], opt); @@ -149,7 +146,7 @@ class Webhook { return messages; } - + const files = await apiMessage.resolveFiles(); return this.client.api.webhooks(this.id, this.token).post({ data, files, query: { wait: true }, diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index b7b487dfa..207f74248 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -1,8 +1,8 @@ const MessageCollector = require('../MessageCollector'); -const Shared = require('../shared'); const Snowflake = require('../../util/Snowflake'); const Collection = require('../../util/Collection'); const { RangeError, TypeError } = require('../../errors'); +const APIMessage = require('../APIMessage'); /** * Interface for classes that have text-channel-like features. @@ -66,8 +66,8 @@ class TextBasedChannel { /** * Sends a message to this channel. - * @param {StringResolvable} [content] Text for the message - * @param {MessageOptions|MessageEmbed|MessageAttachment|MessageAttachment[]} [options={}] Options for the message + * @param {StringResolvable} [content=''] The content to send + * @param {MessageOptions|MessageAdditions} [options={}] The options to provide * @returns {Promise} * @example * // Send a basic message @@ -107,16 +107,35 @@ class TextBasedChannel { * .then(console.log) * .catch(console.error); */ - send(content, options) { // eslint-disable-line complexity - if (!options && typeof content === 'object' && !(content instanceof Array)) { - options = content; - content = null; - } else if (!options) { - options = {}; + async send(content, options) { + const User = require('../User'); + const GuildMember = require('../GuildMember'); + if (this instanceof User || this instanceof GuildMember) { + return this.createDM().then(dm => dm.send(content, options)); } - if (!options.content) options.content = content; - return Shared.sendMessage(this, options); + const apiMessage = APIMessage.create(this, content, options); + const data = apiMessage.resolveData(); + if (data.content instanceof Array) { + const messages = []; + for (let i = 0; i < data.content.length; i++) { + let opt; + if (i === data.content.length - 1) { + opt = { tts: data.tts, embed: data.embed, files: apiMessage.options.files }; + } else { + opt = { tts: data.tts }; + } + + // eslint-disable-next-line no-await-in-loop + const message = await this.send(data.content[i], opt); + messages.push(message); + } + return messages; + } + + const files = await apiMessage.resolveFiles(); + return this.client.api.channels[this.id].messages.post({ data, files }) + .then(d => this.client.actions.MessageCreate.handle(d).message); } /** diff --git a/src/structures/shared/CreateMessage.js b/src/structures/shared/CreateMessage.js deleted file mode 100644 index 8ba66c342..000000000 --- a/src/structures/shared/CreateMessage.js +++ /dev/null @@ -1,126 +0,0 @@ -const Embed = require('../MessageEmbed'); -const DataResolver = require('../../util/DataResolver'); -const MessageEmbed = require('../MessageEmbed'); -const MessageAttachment = require('../MessageAttachment'); -const { browser } = require('../../util/Constants'); -const Util = require('../../util/Util'); -const { RangeError } = require('../../errors'); - -// eslint-disable-next-line complexity -module.exports = async function createMessage(channel, options) { - const User = require('../User'); - const GuildMember = require('../GuildMember'); - const Webhook = require('../Webhook'); - const WebhookClient = require('../../client/WebhookClient'); - - const webhook = channel instanceof Webhook || channel instanceof WebhookClient; - - if (typeof options.nonce !== 'undefined') { - options.nonce = parseInt(options.nonce); - if (isNaN(options.nonce) || options.nonce < 0) throw new RangeError('MESSAGE_NONCE_TYPE'); - } - - let { content, reply } = 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.split && typeof options.split !== 'object') options.split = {}; - let mentionPart = ''; - if (reply && !(channel instanceof User || channel instanceof GuildMember) && channel.type !== 'dm') { - const id = channel.client.users.resolveID(reply); - mentionPart = `<@${reply instanceof GuildMember && reply.nickname ? '!' : ''}${id}>, `; - if (options.split) options.split.prepend = `${mentionPart}${options.split.prepend || ''}`; - } - - if (content || mentionPart) { - options.content = Util.resolveString(content || ''); - // Wrap everything in a code block - if (typeof options.code !== 'undefined' && (typeof options.code !== 'boolean' || options.code === true)) { - options.content = Util.escapeMarkdown(options.content, true); - options.content = `${mentionPart}\`\`\`${typeof options.code !== 'boolean' ? - options.code || '' : ''}\n${options.content}\n\`\`\``; - if (options.split) { - options.split.prepend = - `${options.split.prepend || ''}\`\`\`${typeof options.code !== 'boolean' ? options.code || '' : ''}\n`; - - options.split.append = `\n\`\`\`${options.split.append || ''}`; - } - } else if (mentionPart) { - options.content = mentionPart + (options.content || ''); - } - - // Add zero-width spaces to @everyone/@here - if (options.disableEveryone || - (typeof options.disableEveryone === 'undefined' && channel.client.options.disableEveryone)) { - options.content = options.content.replace(/@(everyone|here)/g, '@\u200b$1'); - } - - if (options.split) options.content = Util.splitMessage(options.content, options.split); - } - - if (options.embed && options.embed.files) { - if (options.files) options.files = options.files.concat(options.embed.files); - else options.files = options.embed.files; - } - - if (options.embed && webhook) options.embeds = [new Embed(options.embed)._apiTransform()]; - else if (options.embed) options.embed = new Embed(options.embed)._apiTransform(); - else if (options.embeds) options.embeds = options.embeds.map(e => new Embed(e)._apiTransform()); - - let files; - - if (options.files) { - for (let i = 0; i < options.files.length; i++) { - let file = options.files[i]; - if (typeof file === 'string' || (!browser && Buffer.isBuffer(file))) file = { attachment: file }; - if (!file.name) { - if (typeof file.attachment === 'string') { - file.name = Util.basename(file.attachment); - } else if (file.attachment && file.attachment.path) { - file.name = Util.basename(file.attachment.path); - } else if (file instanceof MessageAttachment) { - file = { attachment: file.file, name: Util.basename(file.file) || 'file.jpg' }; - } else { - file.name = 'file.jpg'; - } - } else if (file instanceof MessageAttachment) { - file = file.file; - } - options.files[i] = file; - } - - files = await Promise.all(options.files.map(file => - DataResolver.resolveFile(file.attachment).then(resource => { - file.file = resource; - return file; - }) - )); - } - - if (webhook) { - if (!options.username) options.username = this.name; - if (options.avatarURL) options.avatar_url = options.avatarURL; - } - - return { data: { - content: options.content, - tts: options.tts, - nonce: options.nonce, - embed: options.embed, - embeds: options.embeds, - username: options.username, - avatar_url: options.avatar_url, - }, files }; -}; diff --git a/src/structures/shared/SendMessage.js b/src/structures/shared/SendMessage.js deleted file mode 100644 index 95ea49ca3..000000000 --- a/src/structures/shared/SendMessage.js +++ /dev/null @@ -1,23 +0,0 @@ -const createMessage = require('./CreateMessage'); - -module.exports = async function sendMessage(channel, options) { // eslint-disable-line complexity - const User = require('../User'); - const GuildMember = require('../GuildMember'); - if (channel instanceof User || channel instanceof GuildMember) return channel.createDM().then(dm => dm.send(options)); - - const { data, files } = await createMessage(channel, options); - - if (data.content instanceof Array) { - const messages = []; - for (let i = 0; i < data.content.length; i++) { - const opt = i === data.content.length - 1 ? { tts: data.tts, embed: data.embed, files } : { tts: data.tts }; - // eslint-disable-next-line no-await-in-loop - const message = await channel.send(data.content[i], opt); - messages.push(message); - } - return messages; - } - - return channel.client.api.channels[channel.id].messages.post({ data, files }) - .then(d => channel.client.actions.MessageCreate.handle(d).message); -}; diff --git a/src/structures/shared/index.js b/src/structures/shared/index.js deleted file mode 100644 index 5e81ec3c0..000000000 --- a/src/structures/shared/index.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - sendMessage: require('./SendMessage'), - createMessage: require('./CreateMessage'), -}; diff --git a/src/util/DataResolver.js b/src/util/DataResolver.js index 95e0aa5ad..5fcfd0d43 100644 --- a/src/util/DataResolver.js +++ b/src/util/DataResolver.js @@ -103,7 +103,7 @@ class DataResolver { }); }); } - } else if (resource.pipe && typeof resource.pipe === 'function') { + } else if (typeof resource.pipe === 'function') { return new Promise((resolve, reject) => { const buffers = []; resource.once('error', reject); diff --git a/test/blobReach.png b/test/blobReach.png new file mode 100644 index 0000000000000000000000000000000000000000..358bf47397bd25facfbe2c3f7f03071159f672a5 GIT binary patch literal 10423 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 literal 0 HcmV?d00001 diff --git a/test/sendtest.js b/test/sendtest.js new file mode 100644 index 000000000..14d48260a --- /dev/null +++ b/test/sendtest.js @@ -0,0 +1,140 @@ +const Discord = require('../src'); +const { owner, token } = require('./auth.js'); + +const fetch = require('node-fetch'); +const fs = require('fs'); +const path = require('path'); +const util = require('util'); + +const client = new Discord.Client(); + +const fill = c => Array(4).fill(c.repeat(1000)); +const buffer = l => fetch(l).then(res => res.buffer()); +const read = util.promisify(fs.readFile); +const readStream = fs.createReadStream; +const wait = util.promisify(setTimeout); + +const linkA = 'https://lolisafe.moe/iiDMtAXA.png'; +const linkB = 'https://lolisafe.moe/9hSpedPh.png'; +const fileA = path.join(__dirname, 'blobReach.png'); + +const embed = () => new Discord.MessageEmbed(); +const attach = (attachment, name) => new Discord.MessageAttachment(attachment, name); + +const tests = [ + m => m.channel.send('x'), + m => m.channel.send(['x', 'y']), + + m => m.channel.send('x', { code: true }), + m => m.channel.send('1', { code: 'js' }), + m => m.channel.send('x', { code: '' }), + + m => m.channel.send(fill('x'), { split: true }), + m => m.channel.send(fill('1'), { code: 'js', split: true }), + m => m.channel.send(fill('x'), { reply: m.author, code: 'js', split: true }), + m => m.channel.send(fill('xyz '), { split: { char: ' ' } }), + + m => m.channel.send('x', { embed: { description: 'a' } }), + m => m.channel.send({ embed: { description: 'a' } }), + m => m.channel.send({ files: [{ attachment: linkA }] }), + m => m.channel.send({ + embed: { description: 'a' }, + files: [{ attachment: linkA, name: 'xyz.png' }], + }), + + m => m.channel.send('x', embed().setDescription('a')), + m => m.channel.send(embed().setDescription('a')), + m => m.channel.send({ embed: embed().setDescription('a') }), + m => m.channel.send([embed().setDescription('a'), embed().setDescription('b')]), + + m => m.channel.send('x', attach(linkA)), + m => m.channel.send(attach(linkA)), + m => m.channel.send({ files: [linkA] }), + m => m.channel.send({ files: [attach(linkA)] }), + async m => m.channel.send(attach(await buffer(linkA))), + async m => m.channel.send({ files: [await buffer(linkA)] }), + async m => m.channel.send({ files: [{ attachment: await buffer(linkA) }] }), + m => m.channel.send([attach(linkA), attach(linkB)]), + + m => m.channel.send({ embed: { description: 'a' } }).then(m2 => m2.edit('x')), + m => m.channel.send(embed().setDescription('a')).then(m2 => m2.edit('x')), + m => m.channel.send({ embed: embed().setDescription('a') }).then(m2 => m2.edit('x')), + + m => m.channel.send('x').then(m2 => m2.edit({ embed: { description: 'a' } })), + m => m.channel.send('x').then(m2 => m2.edit(embed().setDescription('a'))), + m => m.channel.send('x').then(m2 => m2.edit({ embed: embed().setDescription('a') })), + + m => m.channel.send({ embed: { description: 'a' } }).then(m2 => m2.edit({ embed: null })), + m => m.channel.send(embed().setDescription('a')).then(m2 => m2.edit({ embed: null })), + + m => m.channel.send(['x', 'y'], [embed().setDescription('a'), attach(linkB)]), + m => m.channel.send(['x', 'y'], [attach(linkA), attach(linkB)]), + + m => m.channel.send([embed().setDescription('a'), attach(linkB)]), + m => m.channel.send({ + embed: embed().setImage('attachment://two.png'), + files: [attach(linkB, 'two.png')], + }), + m => m.channel.send({ + embed: embed() + .setImage('attachment://two.png') + .attachFiles([attach(linkB, 'two.png')]), + }), + async m => m.channel.send(['x', 'y', 'z'], { + code: 'js', + embed: embed() + .setImage('attachment://two.png') + .attachFiles([attach(linkB, 'two.png')]), + files: [{ attachment: await buffer(linkA) }], + }), + + m => m.channel.send('x', attach(fileA)), + m => m.channel.send({ files: [fileA] }), + m => m.channel.send(attach(fileA)), + async m => m.channel.send({ files: [await read(fileA)] }), + async m => m.channel.send(fill('x'), { + reply: m.author, + code: 'js', + split: true, + embed: embed().setImage('attachment://zero.png'), + files: [attach(await buffer(linkA), 'zero.png')], + }), + + m => m.channel.send('x', attach(readStream(fileA))), + m => m.channel.send({ files: [readStream(fileA)] }), + m => m.channel.send({ files: [{ attachment: readStream(fileA) }] }), + async m => m.channel.send(fill('xyz '), { + reply: m.author, + code: 'js', + split: { char: ' ', prepend: 'hello! ', append: '!!!' }, + embed: embed().setImage('attachment://zero.png'), + files: [linkB, attach(await buffer(linkA), 'zero.png'), readStream(fileA)], + }), + + m => m.channel.send('Done!'), +]; + +client.on('message', async message => { + if (message.author.id !== owner) return; + const match = message.content.match(/^do (.+)$/); + if (match && match[1] === 'it') { + /* eslint-disable no-await-in-loop */ + for (const [i, test] of tests.entries()) { + await message.channel.send(`**#${i}**\n\`\`\`js\n${test.toString()}\`\`\``); + await test(message).catch(e => message.channel.send(`Error!\n\`\`\`\n${e}\`\`\``)); + await wait(1000); + } + /* eslint-enable no-await-in-loop */ + } else if (match) { + const n = parseInt(match[1]) || 0; + const test = tests.slice(n)[0]; + const i = tests.indexOf(test); + await message.channel.send(`**#${i}**\n\`\`\`js\n${test.toString()}\`\`\``); + await test(message).catch(e => message.channel.send(`Error!\n\`\`\`\n${e}\`\`\``)); + } +}); + +client.login(token); + +// eslint-disable-next-line no-console +process.on('unhandledRejection', console.error); diff --git a/typings/index.d.ts b/typings/index.d.ts index 3501f7e9d..a8cf2cf1e 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -40,6 +40,33 @@ declare module 'discord.js' { public static FLAGS: Record; } + export class APIMessage { + constructor(target: MessageTarget, options: MessageOptions | WebhookMessageOptions); + public readonly isUser: boolean; + public readonly isWebhook: boolean; + public options: MessageOptions | WebhookMessageOptions; + public target: MessageTarget; + + public static create( + target: MessageTarget, + content?: StringResolvable, + options?: MessageOptions | WebhookMessageOptions | MessageAdditions, + extra?: MessageOptions | WebhookMessageOptions + ): APIMessage; + public static partitionMessageAdditions(items: (MessageEmbed | MessageAttachment)[]): [MessageEmbed[], MessageAttachment[]]; + public static resolveFile(fileLike: BufferResolvable | Stream | FileOptions | MessageAttachment): Promise; + public static transformOptions( + content: StringResolvable, + options: MessageOptions | WebhookMessageOptions | MessageAdditions, + extra?: MessageOptions | WebhookMessageOptions, + isWebhook?: boolean + ): MessageOptions | WebhookMessageOptions; + + public makeContent(): string | string[]; + public resolveData(): object; + public resolveFiles(): Promise; + } + export class Base { constructor (client: Client); public readonly client: Client; @@ -618,30 +645,29 @@ declare module 'discord.js' { public createReactionCollector(filter: CollectorFilter, options?: ReactionCollectorOptions): ReactionCollector; public delete(options?: { timeout?: number, reason?: string }): Promise; public edit(content: StringResolvable, options?: MessageEditOptions | MessageEmbed): Promise; + public edit(options: MessageEditOptions | MessageEmbed): Promise; public equals(message: Message, rawData: object): boolean; public fetchWebhook(): Promise; public pin(): Promise; public react(emoji: EmojiIdentifierResolvable): Promise; - public reply(content?: StringResolvable, options?: MessageOptions): Promise; - public reply(options?: MessageOptions): Promise; + public reply(content?: StringResolvable, options?: MessageOptions | MessageAdditions): Promise; + public reply(options?: MessageOptions | MessageAdditions): Promise; public toJSON(): object; public toString(): string; public unpin(): Promise; } export class MessageAttachment { - constructor(file: BufferResolvable | Stream, name?: string); - private _attach(file: BufferResolvable | Stream, name: string): void; + constructor(attachment: BufferResolvable | Stream, name?: string); - public readonly attachment: BufferResolvable | Stream; + public attachment: BufferResolvable | Stream; public height: number; public id: Snowflake; - public readonly name: string; + public name?: string; public proxyURL: string; public url: string; public width: number; - public setAttachment(file: BufferResolvable | Stream, name: string): this; - public setFile(attachment: BufferResolvable | Stream): this; + public setFile(attachment: BufferResolvable | Stream, name?: string): this; public setName(name: string): this; public toJSON(): object; } @@ -1345,8 +1371,8 @@ declare module 'discord.js' { lastMessageID: Snowflake; lastMessageChannelID: Snowflake; readonly lastMessage: Message; - send(content?: StringResolvable, options?: MessageOptions | MessageEmbed | MessageAttachment): Promise; - send(options?: MessageOptions | MessageEmbed | MessageAttachment): Promise; + send(content?: StringResolvable, options?: MessageOptions | MessageAdditions): Promise; + send(options?: MessageOptions | MessageAdditions): Promise; }; type TextBasedChannelFields = { @@ -1367,8 +1393,8 @@ declare module 'discord.js' { token: string; delete(reason?: string): Promise; edit(options: WebhookEditData): Promise; - send(content?: StringResolvable, options?: WebhookMessageOptions | MessageEmbed | MessageAttachment | MessageAttachment[]): Promise; - send(options?: WebhookMessageOptions | MessageEmbed | MessageAttachment | MessageAttachment[]): Promise; + send(content?: StringResolvable, options?: WebhookMessageOptions | MessageAdditions): Promise; + send(options?: WebhookMessageOptions | MessageAdditions): Promise; sendSlackMessage(body: object): Promise; }; @@ -1604,7 +1630,7 @@ declare module 'discord.js' { }; type FileOptions = { - attachment: BufferResolvable; + attachment: BufferResolvable | Stream; name?: string; }; @@ -1783,6 +1809,8 @@ declare module 'discord.js' { maxProcessed?: number; }; + type MessageAdditions = MessageEmbed | MessageAttachment | (MessageEmbed | MessageAttachment)[]; + type MessageEditOptions = { content?: string; embed?: MessageEmbedOptions | null; @@ -1810,7 +1838,7 @@ declare module 'discord.js' { content?: string; embed?: MessageEmbed | MessageEmbedOptions, disableEveryone?: boolean; - files?: (FileOptions | BufferResolvable | MessageAttachment)[]; + files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; code?: string | boolean; split?: boolean | SplitOptions; reply?: UserResolvable; @@ -1820,6 +1848,8 @@ declare module 'discord.js' { type MessageResolvable = Message | Snowflake; + type MessageTarget = TextChannel | DMChannel | GroupDMChannel | User | GuildMember | Webhook | WebhookClient; + type MessageType = 'DEFAULT' | 'RECIPIENT_ADD' | 'RECIPIENT_REMOVE' @@ -1968,7 +1998,7 @@ declare module 'discord.js' { nonce?: string; embeds?: (MessageEmbed | object)[]; disableEveryone?: boolean; - files?: (FileOptions | BufferResolvable | MessageAttachment)[]; + files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; code?: string | boolean; split?: boolean | SplitOptions; };