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