From 721d1216e0841dc2bb345073ee6e6c32569cf590 Mon Sep 17 00:00:00 2001 From: March 7th <71698422+aiko-chan-ai@users.noreply.github.com> Date: Sun, 20 Mar 2022 19:08:18 +0700 Subject: [PATCH] Fix Message cannot Send - Known Issue: Unable to send Embed --- package.json | 5 +- src/structures/MessagePayload.js | 48 +- src/structures/interfaces/TextBasedChannel.js | 694 ++++++++++-------- src/util/Options.js | 113 +-- 4 files changed, 464 insertions(+), 396 deletions(-) diff --git a/package.json b/package.json index 33d60a7..ed367cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord.js-selfbot-v13", - "version": "0.0.3", + "version": "0.1.0", "description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]", "main": "./src/index.js", "types": "./typings/index.d.ts", @@ -44,8 +44,11 @@ "@sapphire/async-queue": "^1.3.0", "@sapphire/snowflake": "^3.2.0", "@types/ws": "^8.5.2", + "axios": "^0.26.1", "discord-api-types": "^0.27.3", + "discord.js": "^13.6.0", "form-data": "^4.0.0", + "json-bigint": "^1.0.0", "lodash": "^4.17.21", "lodash.snakecase": "^4.1.1", "node-fetch": "^3.2.2", diff --git a/src/structures/MessagePayload.js b/src/structures/MessagePayload.js index b79feb6..5380277 100644 --- a/src/structures/MessagePayload.js +++ b/src/structures/MessagePayload.js @@ -1,7 +1,7 @@ 'use strict'; const { Buffer } = require('node:buffer'); -const { isJSONEncodable } = require('@discordjs/builders'); +const { BaseMessageComponent, MessageEmbed } = require('discord.js'); const { MessageFlags } = require('discord-api-types/v9'); const { RangeError } = require('../errors'); const DataResolver = require('../util/DataResolver'); @@ -109,7 +109,6 @@ class MessagePayload { return content; } - /** * Resolves the body. * @returns {MessagePayload} @@ -131,9 +130,9 @@ class MessagePayload { } } - const components = this.options.components?.map(c => - isJSONEncodable(c) ? c.toJSON() : this.target.client.options.jsonTransformer(c), - ); + const components = this.options.components?.map((c) => + BaseMessageComponent.create(c).toJSON(), + ); let username; let avatarURL; @@ -193,22 +192,27 @@ class MessagePayload { } this.body = { - content, - tts, - nonce, - embeds: this.options.embeds?.map(embed => - isJSONEncodable(embed) ? embed.toJSON() : this.target.client.options.jsonTransformer(embed), - ), - components, - username, - avatar_url: avatarURL, - allowed_mentions: - typeof content === 'undefined' && typeof message_reference === 'undefined' ? undefined : allowedMentions, - flags, - message_reference, - attachments: this.options.attachments, - sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker), - }; + content, + tts, + nonce, + embeds: this.options.embeds?.map((embed) => + new MessageEmbed(embed).toJSON(), + ), + components, + username, + avatar_url: avatarURL, + allowed_mentions: + typeof content === 'undefined' && + typeof message_reference === 'undefined' + ? undefined + : allowedMentions, + flags, + message_reference, + attachments: this.options.attachments, + sticker_ids: this.options.stickers?.map( + (sticker) => sticker.id ?? sticker, + ), + }; return this; } @@ -289,4 +293,4 @@ module.exports = MessagePayload; /** * @external RawFile * @see {@link https://discord.js.org/#/docs/rest/main/typedef/RawFile} - */ + */ \ No newline at end of file diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index ebca69d..739545e 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -7,353 +7,411 @@ const { TypeError, Error } = require('../../errors'); const InteractionCollector = require('../InteractionCollector'); const MessageCollector = require('../MessageCollector'); const MessagePayload = require('../MessagePayload'); +const DiscordAPIError = require('../../rest/DiscordAPIError'); +const _send = (client, channelID, data, files) => { + return new Promise((resolve, reject) => { + require('axios')({ + method: 'post', + url: `${client.options.http.api}/v${client.options.http.version}/channels/${channelID}/messages`, + headers: { + authorization: client.token, + Accept: '*/*', + 'Accept-Language': 'en-US,en;q=0.9', + 'Cache-Control': 'no-cache', + Pragma: 'no-cache', + Referer: 'https://discord.com/channels/@me', + 'Sec-Ch-Ua': '" Not A;Brand";v="99" "', + 'Sec-Ch-Ua-Mobile': '?0', + 'Sec-Ch-Ua-Platform': '"iOS"', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'X-Debug-Options': 'bugReporterEnabled', + 'X-Discord-Locale': 'en-US', + Origin: 'https://discord.com', + }, + data, + files, + }) + .then((res) => resolve(res.data)) + .catch((err) => { + err.request.options = { + data, + files, + }; + return reject( + new DiscordAPIError( + err.response.data, + err.response.status, + err.request, + ), + ); + }); + }); +} /** * Interface for classes that have text-channel-like features. * @interface */ class TextBasedChannel { - constructor() { - /** - * A manager of the messages sent to this channel - * @type {MessageManager} - */ - this.messages = new MessageManager(this); + constructor() { + /** + * A manager of the messages sent to this channel + * @type {MessageManager} + */ + this.messages = new MessageManager(this); - /** - * The channel's last message id, if one was sent - * @type {?Snowflake} - */ - this.lastMessageId = null; + /** + * The channel's last message id, if one was sent + * @type {?Snowflake} + */ + this.lastMessageId = null; - /** - * The timestamp when the last pinned message was pinned, if there was one - * @type {?number} - */ - this.lastPinTimestamp = null; - } + /** + * The timestamp when the last pinned message was pinned, if there was one + * @type {?number} + */ + this.lastPinTimestamp = null; + } - /** - * The Message object of the last message in the channel, if one was sent - * @type {?Message} - * @readonly - */ - get lastMessage() { - return this.messages.resolve(this.lastMessageId); - } + /** + * The Message object of the last message in the channel, if one was sent + * @type {?Message} + * @readonly + */ + get lastMessage() { + return this.messages.resolve(this.lastMessageId); + } - /** - * The date when the last pinned message was pinned, if there was one - * @type {?Date} - * @readonly - */ - get lastPinAt() { - return this.lastPinTimestamp && new Date(this.lastPinTimestamp); - } + /** + * The date when the last pinned message was pinned, if there was one + * @type {?Date} + * @readonly + */ + get lastPinAt() { + return this.lastPinTimestamp && new Date(this.lastPinTimestamp); + } - /** - * Base options provided when sending. - * @typedef {Object} BaseMessageOptions - * @property {boolean} [tts=false] Whether or not the message should be spoken aloud - * @property {string} [nonce=''] The nonce for the message - * @property {string} [content=''] The content for the message - * @property {Embed[]|APIEmbed[]} [embeds] The embeds for the message - * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details) - * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content - * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details) - * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to send with the message - * @property {ActionRow[]|ActionRowOptions[]} [components] - * Action rows containing interactive components for the message (buttons, select menus) - * @property {MessageAttachment[]} [attachments] Attachments to send in the message - */ + /** + * Base options provided when sending. + * @typedef {Object} BaseMessageOptions + * @property {boolean} [tts=false] Whether or not the message should be spoken aloud + * @property {string} [nonce=''] The nonce for the message + * @property {string} [content=''] The content for the message + * @property {Embed[]|APIEmbed[]} [embeds] The embeds for the message + * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details) + * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content + * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details) + * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to send with the message + * @property {ActionRow[]|ActionRowOptions[]} [components] + * Action rows containing interactive components for the message (buttons, select menus) + * @property {MessageAttachment[]} [attachments] Attachments to send in the message + */ - /** - * Options provided when sending or editing a message. - * @typedef {BaseMessageOptions} MessageOptions - * @property {ReplyOptions} [reply] The options for replying to a message - * @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message - * @property {MessageFlags} [flags] Which flags to set for the message. Only `MessageFlags.SuppressEmbeds` can be set. - */ + /** + * Options provided when sending or editing a message. + * @typedef {BaseMessageOptions} MessageOptions + * @property {ReplyOptions} [reply] The options for replying to a message + * @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message + * @property {MessageFlags} [flags] Which flags to set for the message. Only `MessageFlags.SuppressEmbeds` can be set. + */ - /** - * Options provided to control parsing of mentions by Discord - * @typedef {Object} MessageMentionOptions - * @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed - * @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions - * @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions - * @property {boolean} [repliedUser=true] Whether the author of the Message being replied to should be pinged - */ + /** + * Options provided to control parsing of mentions by Discord + * @typedef {Object} MessageMentionOptions + * @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed + * @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions + * @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions + * @property {boolean} [repliedUser=true] Whether the author of the Message being replied to should be pinged + */ - /** - * Types of mentions to enable in MessageMentionOptions. - * - `roles` - * - `users` - * - `everyone` - * @typedef {string} MessageMentionTypes - */ + /** + * Types of mentions to enable in MessageMentionOptions. + * - `roles` + * - `users` + * - `everyone` + * @typedef {string} MessageMentionTypes + */ - /** - * @typedef {Object} FileOptions - * @property {BufferResolvable} attachment File to attach - * @property {string} [name='file.jpg'] Filename of the attachment - * @property {string} description The description of the file - */ + /** + * @typedef {Object} FileOptions + * @property {BufferResolvable} attachment File to attach + * @property {string} [name='file.jpg'] Filename of the attachment + * @property {string} description The description of the file + */ - /** - * Options for sending a message with a reply. - * @typedef {Object} ReplyOptions - * @property {MessageResolvable} messageReference The message to reply to (must be in the same channel and not system) - * @property {boolean} [failIfNotExists=this.client.options.failIfNotExists] Whether to error if the referenced - * message does not exist (creates a standard message in this case when false) - */ + /** + * Options for sending a message with a reply. + * @typedef {Object} ReplyOptions + * @property {MessageResolvable} messageReference The message to reply to (must be in the same channel and not system) + * @property {boolean} [failIfNotExists=this.client.options.failIfNotExists] Whether to error if the referenced + * message does not exist (creates a standard message in this case when false) + */ - /** - * Sends a message to this channel. - * @param {string|MessagePayload|MessageOptions} options The options to provide - * @returns {Promise} - * @example - * // Send a basic message - * channel.send('hello!') - * .then(message => console.log(`Sent message: ${message.content}`)) - * .catch(console.error); - * @example - * // Send a remote file - * channel.send({ - * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] - * }) - * .then(console.log) - * .catch(console.error); - * @example - * // Send a local file - * channel.send({ - * files: [{ - * attachment: 'entire/path/to/file.jpg', - * name: 'file.jpg', - * description: 'A description of the file' - * }] - * }) - * .then(console.log) - * .catch(console.error); - * @example - * // Send an embed with a local image inside - * channel.send({ - * content: 'This is an embed', - * embeds: [ - * { - * thumbnail: { - * url: 'attachment://file.jpg' - * } - * } - * ], - * files: [{ - * attachment: 'entire/path/to/file.jpg', - * name: 'file.jpg', - * description: 'A description of the file' - * }] - * }) - * .then(console.log) - * .catch(console.error); - */ - async send(options) { - await this.client.api.channels(this.id).typing.post(); - const User = require('../User'); - const { GuildMember } = require('../GuildMember'); + /** + * Sends a message to this channel. + * @param {string|MessagePayload|MessageOptions} options The options to provide + * @returns {Promise} + * @example + * // Send a basic message + * channel.send('hello!') + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); + * @example + * // Send a remote file + * channel.send({ + * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send a local file + * channel.send({ + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg', + * description: 'A description of the file' + * }] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send an embed with a local image inside + * channel.send({ + * content: 'This is an embed', + * embeds: [ + * { + * thumbnail: { + * url: 'attachment://file.jpg' + * } + * } + * ], + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg', + * description: 'A description of the file' + * }] + * }) + * .then(console.log) + * .catch(console.error); + */ + async send(options) { + await this.client.api.channels(this.id).typing.post(); + const User = require('../User'); + const { GuildMember } = require('../GuildMember'); - if (this instanceof User || this instanceof GuildMember) { - const dm = await this.createDM(); - return dm.send(options); - } + if (this instanceof User || this instanceof GuildMember) { + const dm = await this.createDM(); + return dm.send(options); + } - let messagePayload; + let messagePayload; - if (options instanceof MessagePayload) { - messagePayload = options.resolveBody(); - } else { - messagePayload = MessagePayload.create(this, options).resolveBody(); - } + if (options instanceof MessagePayload) { + messagePayload = options.resolveBody(); + } else { + messagePayload = MessagePayload.create(this, options).resolveBody(); + } - const { body, files } = await messagePayload.resolveFiles(); - const d = await this.client.api.channels[this.id].messages.post({ body, files }); + const { body, files } = await messagePayload.resolveFiles(); + console.log(body); + // const d = await this.client.api.channels[this.id].messages.post({ body, files }); + const d = await _send(this.client, this.id, body, files); + console.log(d); + await this.client.api.channels(this.id).typing.delete(); + return this.messages.cache.get(d.id) ?? this.messages._add(d); + } - await this.client.api.channels(this.id).typing.delete(); - return this.messages.cache.get(d.id) ?? this.messages._add(d); - } + // Patch send message [fck :(] + /** + * Sends a typing indicator in the channel. + * @returns {Promise} Resolves upon the typing status being sent + * @example + * // Start typing in a channel + * channel.sendTyping(); + */ + async sendTyping() { + await this.client.api.channels(this.id).typing.post(); + } - /** - * Sends a typing indicator in the channel. - * @returns {Promise} Resolves upon the typing status being sent - * @example - * // Start typing in a channel - * channel.sendTyping(); - */ - async sendTyping() { - await this.client.api.channels(this.id).typing.post(); - } + /** + * Creates a Message Collector. + * @param {MessageCollectorOptions} [options={}] The options to pass to the collector + * @returns {MessageCollector} + * @example + * // Create a message collector + * const filter = m => m.content.includes('discord'); + * const collector = channel.createMessageCollector({ filter, time: 15_000 }); + * collector.on('collect', m => console.log(`Collected ${m.content}`)); + * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); + */ + createMessageCollector(options = {}) { + return new MessageCollector(this, options); + } - /** - * Creates a Message Collector. - * @param {MessageCollectorOptions} [options={}] The options to pass to the collector - * @returns {MessageCollector} - * @example - * // Create a message collector - * const filter = m => m.content.includes('discord'); - * const collector = channel.createMessageCollector({ filter, time: 15_000 }); - * collector.on('collect', m => console.log(`Collected ${m.content}`)); - * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); - */ - createMessageCollector(options = {}) { - return new MessageCollector(this, options); - } + /** + * An object containing the same properties as CollectorOptions, but a few more: + * @typedef {MessageCollectorOptions} AwaitMessagesOptions + * @property {string[]} [errors] Stop/end reasons that cause the promise to reject + */ - /** - * An object containing the same properties as CollectorOptions, but a few more: - * @typedef {MessageCollectorOptions} AwaitMessagesOptions - * @property {string[]} [errors] Stop/end reasons that cause the promise to reject - */ + /** + * Similar to createMessageCollector but in promise form. + * Resolves with a collection of messages that pass the specified filter. + * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector + * @returns {Promise>} + * @example + * // Await !vote messages + * const filter = m => m.content.startsWith('!vote'); + * // Errors: ['time'] treats ending because of the time limit as an error + * channel.awaitMessages({ filter, max: 4, time: 60_000, errors: ['time'] }) + * .then(collected => console.log(collected.size)) + * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`)); + */ + awaitMessages(options = {}) { + return new Promise((resolve, reject) => { + const collector = this.createMessageCollector(options); + collector.once('end', (collection, reason) => { + if (options.errors?.includes(reason)) { + reject(collection); + } else { + resolve(collection); + } + }); + }); + } - /** - * Similar to createMessageCollector but in promise form. - * Resolves with a collection of messages that pass the specified filter. - * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector - * @returns {Promise>} - * @example - * // Await !vote messages - * const filter = m => m.content.startsWith('!vote'); - * // Errors: ['time'] treats ending because of the time limit as an error - * channel.awaitMessages({ filter, max: 4, time: 60_000, errors: ['time'] }) - * .then(collected => console.log(collected.size)) - * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`)); - */ - awaitMessages(options = {}) { - return new Promise((resolve, reject) => { - const collector = this.createMessageCollector(options); - collector.once('end', (collection, reason) => { - if (options.errors?.includes(reason)) { - reject(collection); - } else { - resolve(collection); - } - }); - }); - } + /** + * Creates a component interaction collector. + * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector + * @returns {InteractionCollector} + * @example + * // Create a button interaction collector + * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; + * const collector = channel.createMessageComponentCollector({ filter, time: 15_000 }); + * collector.on('collect', i => console.log(`Collected ${i.customId}`)); + * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); + */ + createMessageComponentCollector(options = {}) { + return new InteractionCollector(this.client, { + ...options, + interactionType: InteractionType.MessageComponent, + channel: this, + }); + } - /** - * Creates a component interaction collector. - * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector - * @returns {InteractionCollector} - * @example - * // Create a button interaction collector - * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; - * const collector = channel.createMessageComponentCollector({ filter, time: 15_000 }); - * collector.on('collect', i => console.log(`Collected ${i.customId}`)); - * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); - */ - createMessageComponentCollector(options = {}) { - return new InteractionCollector(this.client, { - ...options, - interactionType: InteractionType.MessageComponent, - channel: this, - }); - } + /** + * Collects a single component interaction that passes the filter. + * The Promise will reject if the time expires. + * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector + * @returns {Promise} + * @example + * // Collect a message component interaction + * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; + * channel.awaitMessageComponent({ filter, time: 15_000 }) + * .then(interaction => console.log(`${interaction.customId} was clicked!`)) + * .catch(console.error); + */ + awaitMessageComponent(options = {}) { + const _options = { ...options, max: 1 }; + return new Promise((resolve, reject) => { + const collector = this.createMessageComponentCollector(_options); + collector.once('end', (interactions, reason) => { + const interaction = interactions.first(); + if (interaction) resolve(interaction); + else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason)); + }); + }); + } - /** - * Collects a single component interaction that passes the filter. - * The Promise will reject if the time expires. - * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector - * @returns {Promise} - * @example - * // Collect a message component interaction - * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; - * channel.awaitMessageComponent({ filter, time: 15_000 }) - * .then(interaction => console.log(`${interaction.customId} was clicked!`)) - * .catch(console.error); - */ - awaitMessageComponent(options = {}) { - const _options = { ...options, max: 1 }; - return new Promise((resolve, reject) => { - const collector = this.createMessageComponentCollector(_options); - collector.once('end', (interactions, reason) => { - const interaction = interactions.first(); - if (interaction) resolve(interaction); - else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason)); - }); - }); - } + /** + * Bulk deletes given messages that are newer than two weeks. + * @param {Collection|MessageResolvable[]|number} messages + * Messages or number of messages to delete + * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically + * @returns {Promise>} Returns the deleted messages + * @example + * // Bulk delete messages + * channel.bulkDelete(5) + * .then(messages => console.log(`Bulk deleted ${messages.size} messages`)) + * .catch(console.error); + */ + async bulkDelete(messages, filterOld = false) { + if (Array.isArray(messages) || messages instanceof Collection) { + let messageIds = + messages instanceof Collection + ? [...messages.keys()] + : messages.map((m) => m.id ?? m); + if (filterOld) { + messageIds = messageIds.filter( + (id) => + Date.now() - DiscordSnowflake.timestampFrom(id) < 1_209_600_000, + ); + } + if (messageIds.length === 0) return new Collection(); + if (messageIds.length === 1) { + await this.client.api + .channels(this.id) + .messages(messageIds[0]) + .delete(); + const message = this.client.actions.MessageDelete.getMessage( + { + message_id: messageIds[0], + }, + this, + ); + return message + ? new Collection([[message.id, message]]) + : new Collection(); + } + await this.client.api + .channels(this.id) + .messages['bulk-delete'].post({ body: { messages: messageIds } }); + return messageIds.reduce( + (col, id) => + col.set( + id, + this.client.actions.MessageDeleteBulk.getMessage( + { + message_id: id, + }, + this, + ), + ), + new Collection(), + ); + } + if (!isNaN(messages)) { + const msgs = await this.messages.fetch({ limit: messages }); + return this.bulkDelete(msgs, filterOld); + } + throw new TypeError('MESSAGE_BULK_DELETE_TYPE'); + } - /** - * Bulk deletes given messages that are newer than two weeks. - * @param {Collection|MessageResolvable[]|number} messages - * Messages or number of messages to delete - * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically - * @returns {Promise>} Returns the deleted messages - * @example - * // Bulk delete messages - * channel.bulkDelete(5) - * .then(messages => console.log(`Bulk deleted ${messages.size} messages`)) - * .catch(console.error); - */ - async bulkDelete(messages, filterOld = false) { - if (Array.isArray(messages) || messages instanceof Collection) { - let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m); - if (filterOld) { - messageIds = messageIds.filter(id => Date.now() - DiscordSnowflake.timestampFrom(id) < 1_209_600_000); - } - if (messageIds.length === 0) return new Collection(); - if (messageIds.length === 1) { - await this.client.api.channels(this.id).messages(messageIds[0]).delete(); - const message = this.client.actions.MessageDelete.getMessage( - { - message_id: messageIds[0], - }, - this, - ); - return message ? new Collection([[message.id, message]]) : new Collection(); - } - await this.client.api.channels(this.id).messages['bulk-delete'].post({ body: { messages: messageIds } }); - return messageIds.reduce( - (col, id) => - col.set( - id, - this.client.actions.MessageDeleteBulk.getMessage( - { - message_id: id, - }, - this, - ), - ), - new Collection(), - ); - } - if (!isNaN(messages)) { - const msgs = await this.messages.fetch({ limit: messages }); - return this.bulkDelete(msgs, filterOld); - } - throw new TypeError('MESSAGE_BULK_DELETE_TYPE'); - } - - static applyToClass(structure, full = false, ignore = []) { - const props = ['send']; - if (full) { - props.push( - 'lastMessage', - 'lastPinAt', - 'bulkDelete', - 'sendTyping', - 'createMessageCollector', - 'awaitMessages', - 'createMessageComponentCollector', - 'awaitMessageComponent', - ); - } - for (const prop of props) { - if (ignore.includes(prop)) continue; - Object.defineProperty( - structure.prototype, - prop, - Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop), - ); - } - } + static applyToClass(structure, full = false, ignore = []) { + const props = ['send']; + if (full) { + props.push( + 'lastMessage', + 'lastPinAt', + 'bulkDelete', + 'sendTyping', + 'createMessageCollector', + 'awaitMessages', + 'createMessageComponentCollector', + 'awaitMessageComponent', + ); + } + for (const prop of props) { + if (ignore.includes(prop)) continue; + Object.defineProperty( + structure.prototype, + prop, + Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop), + ); + } + } } module.exports = TextBasedChannel; diff --git a/src/util/Options.js b/src/util/Options.js index 31a3101..3fd2b28 100644 --- a/src/util/Options.js +++ b/src/util/Options.js @@ -2,6 +2,7 @@ const process = require('node:process'); const Transformers = require('./Transformers'); +const JSONBig = require('json-bigint'); /** * @typedef {Function} CacheFactory @@ -71,61 +72,63 @@ class Options extends null { */ static createDefault() { return { - waitGuildTimeout: 15_000, - shardCount: 1, - makeCache: this.cacheWithLimits(this.defaultMakeCacheSettings), - messageCacheLifetime: 0, - messageSweepInterval: 0, - invalidRequestWarningInterval: 0, - intents: 32767, - partials: [], - restWsBridgeTimeout: 5_000, - restRequestTimeout: 15_000, - restGlobalRateLimit: 0, - retryLimit: 1, - restTimeOffset: 500, - restSweepInterval: 60, - failIfNotExists: true, - userAgentSuffix: [], - presence: {}, - sweepers: {}, - ws: { - large_threshold: 50, - compress: false, - properties: { - $os: 'iPhone14,5', - $browser: 'Discord iOS', - $device: 'iPhone14,5 OS 15.2', - }, - version: 9, - }, - http: { - headers: { - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": 'en-US,en;q=0.9', - "Cache-Control": "no-cache", - "Pragma": "no-cache", - "Referer": "https://discord.com/channels/@me", - "Sec-Ch-Ua": '" Not A;Brand";v="99" "', - "Sec-Ch-Ua-Mobile": '?0', - "Sec-Ch-Ua-Platform": '"iOS"', - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "cors", - "Sec-Fetch-Site": "same-origin", - "X-Debug-Options": "bugReporterEnabled", - "X-Discord-Locale": 'en-US', - "Origin": "https://discord.com" - }, - agent: {}, - version: 9, - api: 'https://discord.com/api', - cdn: 'https://cdn.discordapp.com', - invite: 'https://discord.gg', - template: 'https://discord.new', - scheduledEvent: 'https://discord.com/events', - }, - }; + waitGuildTimeout: 15_000, + shardCount: 1, + makeCache: this.cacheWithLimits(this.defaultMakeCacheSettings), + messageCacheLifetime: 0, + messageSweepInterval: 0, + invalidRequestWarningInterval: 0, + intents: 32767, + partials: [], + restWsBridgeTimeout: 5_000, + restRequestTimeout: 15_000, + restGlobalRateLimit: 0, + retryLimit: 1, + restTimeOffset: 500, + restSweepInterval: 60, + failIfNotExists: true, + userAgentSuffix: [], + presence: {}, + sweepers: {}, + ws: { + large_threshold: 50, + compress: false, + properties: { + $os: 'iPhone14,5', + $browser: 'Discord iOS', + $device: 'iPhone14,5 OS 15.2', + }, + version: 9, + }, + http: { + headers: { + Accept: '*/*', + // 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'en-US,en;q=0.9', + 'Cache-Control': 'no-cache', + 'Content-Type': 'application/json', + Pragma: 'no-cache', + Referer: 'https://discord.com/channels/@me', + 'Sec-Ch-Ua': '" Not A;Brand";v="99" "', + 'Sec-Ch-Ua-Mobile': '?0', + 'Sec-Ch-Ua-Platform': '"iOS"', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'X-Debug-Options': 'bugReporterEnabled', + 'X-Discord-Locale': 'en-US', + Origin: 'https://discord.com', + }, + agent: {}, + version: 9, + api: 'https://discord.com/api', + cdn: 'https://cdn.discordapp.com', + invite: 'https://discord.gg', + template: 'https://discord.new', + scheduledEvent: 'https://discord.com/events', + }, + jsonTransformer: (object) => JSONBig.stringify(object), + }; } /**