Add files via upload

This commit is contained in:
Tropicallism 2022-04-11 04:14:16 +02:00 committed by GitHub
parent f1fc882d35
commit 3bc4608535
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 4505 additions and 4528 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,103 +1,103 @@
'use strict'; 'use strict';
const { TypeError } = require('../errors'); const { TypeError } = require('../errors');
const { MessageComponentTypes, Events } = require('../util/Constants'); const { MessageComponentTypes, Events } = require('../util/Constants');
/** /**
* Represents an interactive component of a Message. It should not be necessary to construct this directly. * Represents an interactive component of a Message. It should not be necessary to construct this directly.
* See {@link MessageComponent} * See {@link MessageComponent}
*/ */
class BaseMessageComponent { class BaseMessageComponent {
/** /**
* Options for a BaseMessageComponent * Options for a BaseMessageComponent
* @typedef {Object} BaseMessageComponentOptions * @typedef {Object} BaseMessageComponentOptions
* @property {MessageComponentTypeResolvable} type The type of this component * @property {MessageComponentTypeResolvable} type The type of this component
*/ */
/** /**
* Data that can be resolved into options for a MessageComponent. This can be: * Data that can be resolved into options for a MessageComponent. This can be:
* * MessageActionRowOptions * * MessageActionRowOptions
* * MessageButtonOptions * * MessageButtonOptions
* * MessageSelectMenuOptions * * MessageSelectMenuOptions
* @typedef {MessageActionRowOptions|MessageButtonOptions|MessageSelectMenuOptions} MessageComponentOptions * @typedef {MessageActionRowOptions|MessageButtonOptions|MessageSelectMenuOptions} MessageComponentOptions
*/ */
/** /**
* Components that can be sent in a message. These can be: * Components that can be sent in a message. These can be:
* * MessageActionRow * * MessageActionRow
* * MessageButton * * MessageButton
* * MessageSelectMenu * * MessageSelectMenu
* @typedef {MessageActionRow|MessageButton|MessageSelectMenu} MessageComponent * @typedef {MessageActionRow|MessageButton|MessageSelectMenu} MessageComponent
* @see {@link https://discord.com/developers/docs/interactions/message-components#component-object-component-types} * @see {@link https://discord.com/developers/docs/interactions/message-components#component-object-component-types}
*/ */
/** /**
* Data that can be resolved to a MessageComponentType. This can be: * Data that can be resolved to a MessageComponentType. This can be:
* * MessageComponentType * * MessageComponentType
* * string * * string
* * number * * number
* @typedef {string|number|MessageComponentType} MessageComponentTypeResolvable * @typedef {string|number|MessageComponentType} MessageComponentTypeResolvable
*/ */
/** /**
* @param {BaseMessageComponent|BaseMessageComponentOptions} [data={}] The options for this component * @param {BaseMessageComponent|BaseMessageComponentOptions} [data={}] The options for this component
*/ */
constructor(data) { constructor(data) {
/** /**
* The type of this component * The type of this component
* @type {?MessageComponentType} * @type {?MessageComponentType}
*/ */
this.type = 'type' in data ? BaseMessageComponent.resolveType(data.type) : null; this.type = 'type' in data ? BaseMessageComponent.resolveType(data.type) : null;
} }
/** /**
* Constructs a MessageComponent based on the type of the incoming data * Constructs a MessageComponent based on the type of the incoming data
* @param {MessageComponentOptions} data Data for a MessageComponent * @param {MessageComponentOptions} data Data for a MessageComponent
* @param {Client|WebhookClient} [client] Client constructing this component * @param {Client|WebhookClient} [client] Client constructing this component
* @returns {?MessageComponent} * @returns {?MessageComponent}
* @private * @private
*/ */
static create(data, client) { static create(data, client) {
let component; let component;
let type = data.type; let type = data.type;
if (typeof type === 'string') type = MessageComponentTypes[type]; if (typeof type === 'string') type = MessageComponentTypes[type];
switch (type) { switch (type) {
case MessageComponentTypes.ACTION_ROW: { case MessageComponentTypes.ACTION_ROW: {
const MessageActionRow = require('./MessageActionRow'); const MessageActionRow = require('./MessageActionRow');
component = data instanceof MessageActionRow ? data : new MessageActionRow(data, client); component = data instanceof MessageActionRow ? data : new MessageActionRow(data, client);
break; break;
} }
case MessageComponentTypes.BUTTON: { case MessageComponentTypes.BUTTON: {
const MessageButton = require('./MessageButton'); const MessageButton = require('./MessageButton');
component = data instanceof MessageButton ? data : new MessageButton(data); component = data instanceof MessageButton ? data : new MessageButton(data);
break; break;
} }
case MessageComponentTypes.SELECT_MENU: { case MessageComponentTypes.SELECT_MENU: {
const MessageSelectMenu = require('./MessageSelectMenu'); const MessageSelectMenu = require('./MessageSelectMenu');
component = data instanceof MessageSelectMenu ? data : new MessageSelectMenu(data); component = data instanceof MessageSelectMenu ? data : new MessageSelectMenu(data);
break; break;
} }
default: default:
if (client) { if (client) {
client.emit(Events.DEBUG, `[BaseMessageComponent] Received component with unknown type: ${data.type}`); client.emit(Events.DEBUG, `[BaseMessageComponent] Received component with unknown type: ${data.type}`);
} else { } else {
throw new TypeError('INVALID_TYPE', 'data.type', 'valid MessageComponentType'); throw new TypeError('INVALID_TYPE', 'data.type', 'valid MessageComponentType');
} }
} }
return component; return component;
} }
/** /**
* Resolves the type of a MessageComponent * Resolves the type of a MessageComponent
* @param {MessageComponentTypeResolvable} type The type to resolve * @param {MessageComponentTypeResolvable} type The type to resolve
* @returns {MessageComponentType} * @returns {MessageComponentType}
* @private * @private
*/ */
static resolveType(type) { static resolveType(type) {
return typeof type === 'string' ? type : MessageComponentTypes[type]; return typeof type === 'string' ? type : MessageComponentTypes[type];
} }
} }
module.exports = BaseMessageComponent; module.exports = BaseMessageComponent;

View File

@ -1,275 +1,275 @@
'use strict'; 'use strict';
const process = require('node:process'); const process = require('node:process');
const Base = require('./Base'); const Base = require('./Base');
let CategoryChannel; let CategoryChannel;
let DMChannel; let DMChannel;
let NewsChannel; let NewsChannel;
let StageChannel; let StageChannel;
let StoreChannel; let StoreChannel;
let TextChannel; let TextChannel;
let ThreadChannel; let ThreadChannel;
let VoiceChannel; let VoiceChannel;
const { ChannelTypes, ThreadChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants'); const { ChannelTypes, ThreadChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants');
const SnowflakeUtil = require('../util/SnowflakeUtil'); const SnowflakeUtil = require('../util/SnowflakeUtil');
const { Message } = require('discord.js'); const { Message } = require('discord.js');
const { ApplicationCommand } = require('discord.js-selfbot-v13'); //const { ApplicationCommand } = require('discord.js-selfbot-v13'); - Not being used in this file, not necessary.
/** /**
* @type {WeakSet<Channel>} * @type {WeakSet<Channel>}
* @private * @private
* @internal * @internal
*/ */
const deletedChannels = new WeakSet(); const deletedChannels = new WeakSet();
let deprecationEmittedForDeleted = false; let deprecationEmittedForDeleted = false;
/** /**
* Represents any channel on Discord. * Represents any channel on Discord.
* @extends {Base} * @extends {Base}
* @abstract * @abstract
*/ */
class Channel extends Base { class Channel extends Base {
constructor(client, data, immediatePatch = true) { constructor(client, data, immediatePatch = true) {
super(client); super(client);
const type = ChannelTypes[data?.type]; const type = ChannelTypes[data?.type];
/** /**
* The type of the channel * The type of the channel
* @type {ChannelType} * @type {ChannelType}
*/ */
this.type = type ?? 'UNKNOWN'; this.type = type ?? 'UNKNOWN';
if (data && immediatePatch) this._patch(data); if (data && immediatePatch) this._patch(data);
} }
_patch(data) { _patch(data) {
/** /**
* The channel's id * The channel's id
* @type {Snowflake} * @type {Snowflake}
*/ */
this.id = data.id; this.id = data.id;
} }
/** /**
* The timestamp the channel was created at * The timestamp the channel was created at
* @type {number} * @type {number}
* @readonly * @readonly
*/ */
get createdTimestamp() { get createdTimestamp() {
return SnowflakeUtil.timestampFrom(this.id); return SnowflakeUtil.timestampFrom(this.id);
} }
/** /**
* The time the channel was created at * The time the channel was created at
* @type {Date} * @type {Date}
* @readonly * @readonly
*/ */
get createdAt() { get createdAt() {
return new Date(this.createdTimestamp); return new Date(this.createdTimestamp);
} }
/** /**
* Whether or not the structure has been deleted * Whether or not the structure has been deleted
* @type {boolean} * @type {boolean}
* @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091 * @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
*/ */
get deleted() { get deleted() {
if (!deprecationEmittedForDeleted) { if (!deprecationEmittedForDeleted) {
deprecationEmittedForDeleted = true; deprecationEmittedForDeleted = true;
process.emitWarning( process.emitWarning(
'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.', 'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
'DeprecationWarning', 'DeprecationWarning',
); );
} }
return deletedChannels.has(this); return deletedChannels.has(this);
} }
set deleted(value) { set deleted(value) {
if (!deprecationEmittedForDeleted) { if (!deprecationEmittedForDeleted) {
deprecationEmittedForDeleted = true; deprecationEmittedForDeleted = true;
process.emitWarning( process.emitWarning(
'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.', 'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
'DeprecationWarning', 'DeprecationWarning',
); );
} }
if (value) deletedChannels.add(this); if (value) deletedChannels.add(this);
else deletedChannels.delete(this); else deletedChannels.delete(this);
} }
/** /**
* Whether this Channel is a partial * Whether this Channel is a partial
* <info>This is always false outside of DM channels.</info> * <info>This is always false outside of DM channels.</info>
* @type {boolean} * @type {boolean}
* @readonly * @readonly
*/ */
get partial() { get partial() {
return false; return false;
} }
/** /**
* When concatenated with a string, this automatically returns the channel's mention instead of the Channel object. * When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
* @returns {string} * @returns {string}
* @example * @example
* // Logs: Hello from <#123456789012345678>! * // Logs: Hello from <#123456789012345678>!
* console.log(`Hello from ${channel}!`); * console.log(`Hello from ${channel}!`);
*/ */
toString() { toString() {
return `<#${this.id}>`; return `<#${this.id}>`;
} }
/** /**
* Deletes this channel. * Deletes this channel.
* @returns {Promise<Channel>} * @returns {Promise<Channel>}
* @example * @example
* // Delete the channel * // Delete the channel
* channel.delete() * channel.delete()
* .then(console.log) * .then(console.log)
* .catch(console.error); * .catch(console.error);
*/ */
async delete() { async delete() {
await this.client.api.channels(this.id).delete(); await this.client.api.channels(this.id).delete();
return this; return this;
} }
/** /**
* Fetches this channel. * Fetches this channel.
* @param {boolean} [force=true] Whether to skip the cache check and request the API * @param {boolean} [force=true] Whether to skip the cache check and request the API
* @returns {Promise<Channel>} * @returns {Promise<Channel>}
*/ */
fetch(force = true) { fetch(force = true) {
return this.client.channels.fetch(this.id, { force }); return this.client.channels.fetch(this.id, { force });
} }
/** /**
* Indicates whether this channel is {@link TextBasedChannels text-based}. * Indicates whether this channel is {@link TextBasedChannels text-based}.
* @returns {boolean} * @returns {boolean}
*/ */
isText() { isText() {
return 'messages' in this; return 'messages' in this;
} }
/** /**
* Indicates whether this channel is {@link BaseGuildVoiceChannel voice-based}. * Indicates whether this channel is {@link BaseGuildVoiceChannel voice-based}.
* @returns {boolean} * @returns {boolean}
*/ */
isVoice() { isVoice() {
return VoiceBasedChannelTypes.includes(this.type); return VoiceBasedChannelTypes.includes(this.type);
} }
/** /**
* Indicates whether this channel is a {@link ThreadChannel}. * Indicates whether this channel is a {@link ThreadChannel}.
* @returns {boolean} * @returns {boolean}
*/ */
isThread() { isThread() {
return ThreadChannelTypes.includes(this.type); return ThreadChannelTypes.includes(this.type);
} }
static create(client, data, guild, { allowUnknownGuild, fromInteraction } = {}) { static create(client, data, guild, { allowUnknownGuild, fromInteraction } = {}) {
CategoryChannel ??= require('./CategoryChannel'); CategoryChannel ??= require('./CategoryChannel');
DMChannel ??= require('./DMChannel'); DMChannel ??= require('./DMChannel');
NewsChannel ??= require('./NewsChannel'); NewsChannel ??= require('./NewsChannel');
StageChannel ??= require('./StageChannel'); StageChannel ??= require('./StageChannel');
StoreChannel ??= require('./StoreChannel'); StoreChannel ??= require('./StoreChannel');
TextChannel ??= require('./TextChannel'); TextChannel ??= require('./TextChannel');
ThreadChannel ??= require('./ThreadChannel'); ThreadChannel ??= require('./ThreadChannel');
VoiceChannel ??= require('./VoiceChannel'); VoiceChannel ??= require('./VoiceChannel');
let channel; let channel;
if (!data.guild_id && !guild) { if (!data.guild_id && !guild) {
if ((data.recipients && data.type !== ChannelTypes.GROUP_DM) || data.type === ChannelTypes.DM) { if ((data.recipients && data.type !== ChannelTypes.GROUP_DM) || data.type === ChannelTypes.DM) {
channel = new DMChannel(client, data); channel = new DMChannel(client, data);
} else if (data.type === ChannelTypes.GROUP_DM) { } else if (data.type === ChannelTypes.GROUP_DM) {
const PartialGroupDMChannel = require('./PartialGroupDMChannel'); const PartialGroupDMChannel = require('./PartialGroupDMChannel');
channel = new PartialGroupDMChannel(client, data); channel = new PartialGroupDMChannel(client, data);
} }
} else { } else {
guild ??= client.guilds.cache.get(data.guild_id); guild ??= client.guilds.cache.get(data.guild_id);
if (guild || allowUnknownGuild) { if (guild || allowUnknownGuild) {
switch (data.type) { switch (data.type) {
case ChannelTypes.GUILD_TEXT: { case ChannelTypes.GUILD_TEXT: {
channel = new TextChannel(guild, data, client); channel = new TextChannel(guild, data, client);
break; break;
} }
case ChannelTypes.GUILD_VOICE: { case ChannelTypes.GUILD_VOICE: {
channel = new VoiceChannel(guild, data, client); channel = new VoiceChannel(guild, data, client);
break; break;
} }
case ChannelTypes.GUILD_CATEGORY: { case ChannelTypes.GUILD_CATEGORY: {
channel = new CategoryChannel(guild, data, client); channel = new CategoryChannel(guild, data, client);
break; break;
} }
case ChannelTypes.GUILD_NEWS: { case ChannelTypes.GUILD_NEWS: {
channel = new NewsChannel(guild, data, client); channel = new NewsChannel(guild, data, client);
break; break;
} }
case ChannelTypes.GUILD_STORE: { case ChannelTypes.GUILD_STORE: {
channel = new StoreChannel(guild, data, client); channel = new StoreChannel(guild, data, client);
break; break;
} }
case ChannelTypes.GUILD_STAGE_VOICE: { case ChannelTypes.GUILD_STAGE_VOICE: {
channel = new StageChannel(guild, data, client); channel = new StageChannel(guild, data, client);
break; break;
} }
case ChannelTypes.GUILD_NEWS_THREAD: case ChannelTypes.GUILD_NEWS_THREAD:
case ChannelTypes.GUILD_PUBLIC_THREAD: case ChannelTypes.GUILD_PUBLIC_THREAD:
case ChannelTypes.GUILD_PRIVATE_THREAD: { case ChannelTypes.GUILD_PRIVATE_THREAD: {
channel = new ThreadChannel(guild, data, client, fromInteraction); channel = new ThreadChannel(guild, data, client, fromInteraction);
if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel); if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel);
break; break;
} }
} }
if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel); if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel);
} }
} }
return channel; return channel;
} }
toJSON(...props) { toJSON(...props) {
return super.toJSON({ createdTimestamp: true }, ...props); return super.toJSON({ createdTimestamp: true }, ...props);
} }
// Send Slash // Send Slash
/** /**
* Send Slash to this channel * Send Slash to this channel
* @param {DiscordBot} botID Bot ID * @param {DiscordBot} botID Bot ID
* @param {String<ApplicationCommand.name>} commandName Command name * @param {String<ApplicationCommand.name>} commandName Command name
* @param {Array<ApplicationCommand.options>} args Command arguments * @param {Array<ApplicationCommand.options>} args Command arguments
* @returns {Promise<pending>} * @returns {Promise<pending>}
*/ */
async sendSlash(botID, commandName, args = []) { async sendSlash(botID, commandName, args = []) {
if (!this.isText()) throw new Error('This channel is not text-based.'); if (!this.isText()) throw new Error('This channel is not text-based.');
if(!botID) throw new Error('Bot ID is required'); if(!botID) throw new Error('Bot ID is required');
const user = await this.client.users.fetch(botID).catch(() => {}); const user = await this.client.users.fetch(botID).catch(() => {});
if (!user || !user.bot || !user.applications) throw new Error('BotID is not a bot or does not have an application slash command'); if (!user || !user.bot || !user.applications) throw new Error('BotID is not a bot or does not have an application slash command');
if (!commandName || typeof commandName !== 'string') throw new Error('Command name is required'); if (!commandName || typeof commandName !== 'string') throw new Error('Command name is required');
const listApplication = user.applications.cache.size == 0 ? await user.applications.fetch() : user.applications.cache; const listApplication = user.applications.cache.size == 0 ? await user.applications.fetch() : user.applications.cache;
let slashCommand; let slashCommand;
await Promise.all(listApplication.map(async application => { await Promise.all(listApplication.map(async application => {
if (commandName == application.name && application.type == 'CHAT_INPUT') slashCommand = application; if (commandName == application.name && application.type == 'CHAT_INPUT') slashCommand = application;
})); }));
if (!slashCommand) throw new Error( if (!slashCommand) throw new Error(
`Command ${commandName} is not found\nList command avalible: ${listApplication.filter(a => a.type == 'CHAT_INPUT').map(a => a.name).join(', ')}`, `Command ${commandName} is not found\nList command avalible: ${listApplication.filter(a => a.type == 'CHAT_INPUT').map(a => a.name).join(', ')}`,
); );
return slashCommand.sendSlashCommand( return slashCommand.sendSlashCommand(
new Message(this.client, { new Message(this.client, {
channel_id: this.id, channel_id: this.id,
guild_id: this.guild?.id || null, guild_id: this.guild?.id || null,
author: this.client.user, author: this.client.user,
content: '', content: '',
id: this.client.user.id id: this.client.user.id
}), }),
args args
); );
} }
} }
exports.Channel = Channel; exports.Channel = Channel;
exports.deletedChannels = deletedChannels; exports.deletedChannels = deletedChannels;
/** /**
* @external APIChannel * @external APIChannel
* @see {@link https://discord.com/developers/docs/resources/channel#channel-object} * @see {@link https://discord.com/developers/docs/resources/channel#channel-object}
*/ */

View File

@ -1,375 +1,357 @@
'use strict'; 'use strict';
const User = require('./User'); const User = require('./User');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const { HypeSquadOptions } = require('../util/Constants'); const { HypeSquadOptions } = require('../util/Constants');
const { Util } = require('..'); const { Util } = require('..');
/** /**
* Represents the logged in client's Discord user. * Represents the logged in client's Discord user.
* @extends {User} * @extends {User}
*/ */
class ClientUser extends User { class ClientUser extends User {
_patch(data) { _patch(data) {
super._patch(data); super._patch(data);
if ('verified' in data) { if ('verified' in data) {
/** /**
* Whether or not this account has been verified * Whether or not this account has been verified
* @type {boolean} * @type {boolean}
*/ */
this.verified = data.verified; this.verified = data.verified;
} }
if ('mfa_enabled' in data) { if ('mfa_enabled' in data) {
/** /**
* If the bot's {@link ClientApplication#owner Owner} has MFA enabled on their account * If the bot's {@link ClientApplication#owner Owner} has MFA enabled on their account
* @type {?boolean} * @type {?boolean}
*/ */
this.mfaEnabled = this.mfaEnabled =
typeof data.mfa_enabled === 'boolean' ? data.mfa_enabled : null; typeof data.mfa_enabled === 'boolean' ? data.mfa_enabled : null;
} else { } else {
this.mfaEnabled ??= null; this.mfaEnabled ??= null;
} }
if ('token' in data) this.client.token = data.token; if ('token' in data) this.client.token = data.token;
// Add (Selfbot) // Add (Selfbot)
if ('premium' in data) this.nitro = data.premium; if ('premium' in data) this.nitro = data.premium;
/** /**
* Nitro Status * Nitro Status
* `0`: None * `0`: None
* `1`: Classic * `1`: Classic
* `2`: Boost * `2`: Boost
* @external * @external
* https://discord.com/developers/docs/resources/user#user-object-premium-types * https://discord.com/developers/docs/resources/user#user-object-premium-types
* @type {Number} * @type {Number}
*/ */
if ('purchased_flags' in data) this.nitroType = data.purchased_flags; if ('purchased_flags' in data) this.nitroType = data.purchased_flags;
if ('phone' in data) this.phoneNumber = data.phone; if ('phone' in data) this.phoneNumber = data.phone;
if ('nsfw_allowed' in data) this.nsfwAllowed = data.nsfw_allowed; if ('nsfw_allowed' in data) this.nsfwAllowed = data.nsfw_allowed;
if ('email' in data) this.emailAddress = data.email; if ('email' in data) this.emailAddress = data.email;
} }
/** /**
* Represents the client user's presence * Represents the client user's presence
* @type {ClientPresence} * @type {ClientPresence}
* @readonly * @readonly
*/ */
get presence() { get presence() {
return this.client.presence; return this.client.presence;
} }
/** /**
* Data used to edit the logged in client * Data used to edit the logged in client
* @typedef {Object} ClientUserEditData * @typedef {Object} ClientUserEditData
* @property {string} [username] The new username * @property {string} [username] The new username
* @property {?(BufferResolvable|Base64Resolvable)} [avatar] The new avatar * @property {?(BufferResolvable|Base64Resolvable)} [avatar] The new avatar
*/ */
/** /**
* Edits the logged in client. * Edits the logged in client.
* @param {ClientUserEditData} data The new data * @param {ClientUserEditData} data The new data
* @returns {Promise<ClientUser>} * @returns {Promise<ClientUser>}
*/ */
async edit(data) { async edit(data) {
if (typeof data.avatar !== 'undefined') if (typeof data.avatar !== 'undefined')
data.avatar = await DataResolver.resolveImage(data.avatar); data.avatar = await DataResolver.resolveImage(data.avatar);
if (typeof data.banner !== 'undefined') if (typeof data.banner !== 'undefined')
data.banner = await DataResolver.resolveImage(data.banner); data.banner = await DataResolver.resolveImage(data.banner);
const newData = await this.client.api.users('@me').patch({ data }); const newData = await this.client.api.users('@me').patch({ data });
this.client.token = newData.token; this.client.token = newData.token;
this.client.password = data?.password this.client.password = data?.password
? data?.password ? data?.password
: this.client.password; : this.client.password;
const { updated } = this.client.actions.UserUpdate.handle(newData); const { updated } = this.client.actions.UserUpdate.handle(newData);
return updated ?? this; return updated ?? this;
} }
/** /**
* Sets the username of the logged in client. * Sets the username of the logged in client.
* <info>Changing usernames in Discord is heavily rate limited, with only 2 requests * <info>Changing usernames in Discord is heavily rate limited, with only 2 requests
* every hour. Use this sparingly!</info> * every hour. Use this sparingly!</info>
* @param {string} username The new username * @param {string} username The new username
* @param {string} password The password of the account * @param {string} password The password of the account
* @returns {Promise<ClientUser>} * @returns {Promise<ClientUser>}
* @example * @example
* // Set username * // Set username
* client.user.setUsername('discordjs') * client.user.setUsername('discordjs')
* .then(user => console.log(`My new username is ${user.username}`)) * .then(user => console.log(`My new username is ${user.username}`))
* .catch(console.error); * .catch(console.error);
*/ */
setUsername(username, password) { setUsername(username, password) {
if (!password && !this.client.password) if (!password && !this.client.password)
throw new Error('A password is required to change a username.'); throw new Error('A password is required to change a username.');
return this.edit({ return this.edit({
username, username,
password: this.client.password ? this.client.password : password, password: this.client.password ? this.client.password : password,
}); });
} }
/** /**
* Sets the avatar of the logged in client. * Sets the avatar of the logged in client.
* @param {?(BufferResolvable|Base64Resolvable)} avatar The new avatar * @param {?(BufferResolvable|Base64Resolvable)} avatar The new avatar
* @returns {Promise<ClientUser>} * @returns {Promise<ClientUser>}
* @example * @example
* // Set avatar * // Set avatar
* client.user.setAvatar('./avatar.png') * client.user.setAvatar('./avatar.png')
* .then(user => console.log(`New avatar set!`)) * .then(user => console.log(`New avatar set!`))
* .catch(console.error); * .catch(console.error);
*/ */
setAvatar(avatar) { setAvatar(avatar) {
return this.edit({ avatar }); return this.edit({ avatar });
} }
/** /**
* Sets the banner of the logged in client. * Sets the banner of the logged in client.
* @param {?(BufferResolvable|Base64Resolvable)} banner The new banner * @param {?(BufferResolvable|Base64Resolvable)} banner The new banner
* @returns {Promise<ClientUser>} * @returns {Promise<ClientUser>}
* @example * @example
* // Set banner * // Set banner
* client.user.setBanner('./banner.png') * client.user.setBanner('./banner.png')
* .then(user => console.log(`New banner set!`)) * .then(user => console.log(`New banner set!`))
* .catch(console.error); * .catch(console.error);
*/ */
setBanner(banner) { setBanner(banner) {
if (this.nitroType !== 2) if (this.nitroType !== 2)
throw new Error( throw new Error(
'You must be a Nitro Boosted User to change your banner.', 'You must be a Nitro Boosted User to change your banner.',
); );
return this.edit({ banner }); return this.edit({ banner });
} }
/** /**
* Set HyperSquad House * Set HyperSquad House
* @param {HypeSquadOptions<Number|String>} type * @param {HypeSquadOptions<Number|String>} type
* `LEAVE`: 0 * `LEAVE`: 0
* `HOUSE_BRAVERY`: 1 * `HOUSE_BRAVERY`: 1
* `HOUSE_BRILLIANCE`: 2 * `HOUSE_BRILLIANCE`: 2
* `HOUSE_BALANCE`: 3 * `HOUSE_BALANCE`: 3
* @returns {Promise<void>} * @returns {Promise<void>}
* @example * @example
* // Set HyperSquad HOUSE_BRAVERY * // Set HyperSquad HOUSE_BRAVERY
* client.user.setHypeSquad(1); || client.user.setHypeSquad('HOUSE_BRAVERY'); * client.user.setHypeSquad(1); || client.user.setHypeSquad('HOUSE_BRAVERY');
* // Leave * // Leave
* client.user.setHypeSquad(0); * client.user.setHypeSquad(0);
*/ */
async setHypeSquad(type) { async setHypeSquad(type) {
const id = typeof type === 'string' ? HypeSquadOptions[type] : type; const id = typeof type === 'string' ? HypeSquadOptions[type] : type;
if (!id && id !== 0) throw new Error('Invalid HypeSquad type.'); if (!id && id !== 0) throw new Error('Invalid HypeSquad type.');
if (id !== 0) if (id !== 0) return await this.client.api.hypesquad.online.post({
return await this.client.api.hypesquad.online.post({ data: { house_id: id },
data: { house_id: id }, });
}); else return await this.client.api.hypesquad.online.delete();
else return await this.client.api.hypesquad.online.delete(); }
}
/**
/** * Set Accent color
* Set Accent color * @param {ColorResolvable} color Color to set
* @param {ColorResolvable} color Color to set * @returns {Promise}
* @returns {Promise} */
*/ setAccentColor(color = null) {
setAccentColor(color = null) { return this.edit({ accent_color: color ? Util.resolveColor(color) : null });
return this.edit({ accent_color: color ? Util.resolveColor(color) : null }); }
}
/**
/** * Set discriminator
* Set discriminator * @param {User.discriminator} discriminator It is #1234
* @param {User.discriminator} discriminator It is #1234 * @param {string} password The password of the account
* @param {string} password The password of the account * @returns {Promise}
* @returns {Promise} */
*/ setDiscriminator(discriminator, password) {
setDiscriminator(discriminator, password) { if (!this.nitro) throw new Error('You must be a Nitro User to change your discriminator.');
if (!this.nitro) if (!password && !this.client.password)
throw new Error('You must be a Nitro User to change your discriminator.'); throw new Error('A password is required to change a discriminator.');
if (!password && !this.client.password) return this.edit({
throw new Error('A password is required to change a discriminator.'); discriminator,
return this.edit({ password: this.client.password ? this.client.password : password,
discriminator, });
password: this.client.password ? this.client.password : password, }
});
} /**
* Set About me
/** * @param {String} bio Bio to set
* Set About me * @returns {Promise}
* @param {String} bio Bio to set */
* @returns {Promise} setAboutMe(bio = null) {
*/ return this.edit({
setAboutMe(bio = null) { bio,
return this.edit({ });
bio, }
});
} /**
* Change the email
/** * @param {Email<string>} email Email to change
* Change the email * @param {string} password Password of the account
* @param {Email<string>} email Email to change * @returns {Promise}
* @param {string} password Password of the account */
* @returns {Promise} setEmail(email, password) {
*/ if (!password && !this.client.password)
setEmail(email, password) { throw new Error('A password is required to change a email.');
if (!password && !this.client.password) return this.edit({
throw new Error('A password is required to change a email.'); email,
return this.edit({ password: this.client.password ? this.client.password : password,
email, });
password: this.client.password ? this.client.password : password, }
});
} /**
* Set new password
/** * @param {string} oldPassword Old password
* Set new password * @param {string} newPassword New password to set
* @param {string} oldPassword Old password * @returns {Promise}
* @param {string} newPassword New password to set */
* @returns {Promise} setPassword(oldPassword, newPassword) {
*/ if (!oldPassword && !this.client.password)
setPassword(oldPassword, newPassword) { throw new Error('A password is required to change a password.');
if (!oldPassword && !this.client.password) if (!newPassword) throw new Error('New password is required.');
throw new Error('A password is required to change a password.'); return this.edit({
if (!newPassword) throw new Error('New password is required.'); password: this.client.password ? this.client.password : oldPassword,
return this.edit({ new_password: newPassword,
password: this.client.password ? this.client.password : oldPassword, });
new_password: newPassword, }
});
} /**
* Disable account
/** * @param {string} password Password of the account
* Disable account * @returns {Promise}
* @param {string} password Password of the account */
* @returns {Promise} async disableAccount(password) {
*/ if (!password && !this.client.password)
async disableAccount(password) { throw new Error('A password is required to disable an account.');
if (!password && !this.client.password) return await this.client.api.users['@me'].disable.post({
throw new Error('A password is required to disable an account.'); data: {
return await this.client.api.users['@me'].disable.post({ password: this.client.password ? this.client.password : password,
data: { },
password: this.client.password ? this.client.password : password, });
}, }
});
} /**
* Delete account. Warning: Cannot be changed once used!
/** * @param {string} password Password of the account
* Delete account. Warning: Cannot be changed once used! * @returns {Promise}
* @param {string} password Password of the account */
* @returns {Promise} async deleteAccount(password) {
*/ if (!password && !this.client.password)
async deleteAccount(password) { throw new Error('A password is required to delete an account.');
if (!password && !this.client.password) return await this.client.api.users['@me'].delete.post({
throw new Error('A password is required to delete an account.'); data: {
return await this.client.api.users['@me'].delete.post({ password: this.client.password ? this.client.password : password,
data: { },
password: this.client.password ? this.client.password : password, });
}, }
});
} /**
* Options for setting activities
/** * @typedef {Object} ActivitiesOptions
* Options for setting activities * @property {string} [name] Name of the activity
* @typedef {Object} ActivitiesOptions * @property {ActivityType|number} [type] Type of the activity
* @property {string} [name] Name of the activity * @property {string} [url] Twitch / YouTube stream URL
* @property {ActivityType|number} [type] Type of the activity */
* @property {string} [url] Twitch / YouTube stream URL
*/ /**
* Data resembling a raw Discord presence.
/** * @typedef {Object} PresenceData
* Data resembling a raw Discord presence. * @property {PresenceStatusData} [status] Status of the user
* @typedef {Object} PresenceData * @property {boolean} [afk] Whether the user is AFK
* @property {PresenceStatusData} [status] Status of the user * @property {ActivitiesOptions[]} [activities] Activity the user is playing
* @property {boolean} [afk] Whether the user is AFK * @property {number|number[]} [shardId] Shard id(s) to have the activity set on
* @property {ActivitiesOptions[]} [activities] Activity the user is playing */
* @property {number|number[]} [shardId] Shard id(s) to have the activity set on
*/ /**
* Sets the full presence of the client user.
/** * @param {PresenceData} data Data for the presence
* Sets the full presence of the client user. * @returns {ClientPresence}
* @param {PresenceData} data Data for the presence * @example
* @returns {ClientPresence} * // Set the client user's presence
* @example * client.user.setPresence({ activities: [{ name: 'with discord.js' }], status: 'idle' });
* // Set the client user's presence */
* client.user.setPresence({ activities: [{ name: 'with discord.js' }], status: 'idle' }); setPresence(data) {
*/ return this.client.presence.set(data);
setPresence(data) { }
return this.client.presence.set(data);
} /**
* A user's status. Must be one of:
/** * * `online`
* A user's status. Must be one of: * * `idle`
* * `online` * * `invisible`
* * `idle` * * `dnd` (do not disturb)
* * `invisible` * @typedef {string} PresenceStatusData
* * `dnd` (do not disturb) */
* @typedef {string} PresenceStatusData
*/ /**
* Sets the status of the client user.
/** * @param {PresenceStatusData} status Status to change to
* Sets the status of the client user. * @param {number|number[]} [shardId] Shard id(s) to have the activity set on
* @param {PresenceStatusData} status Status to change to * @returns {ClientPresence}
* @param {number|number[]} [shardId] Shard id(s) to have the activity set on * @example
* @returns {ClientPresence} * // Set the client user's status
* @example * client.user.setStatus('idle');
* // Set the client user's status */
* client.user.setStatus('idle'); setStatus(status, shardId) {
*/ return this.setPresence({ status, shardId });
setStatus(status, shardId) { }
return this.setPresence({ status, shardId });
} /**
* Options for setting an activity.
/** * @typedef {Object} ActivityOptions
* Options for setting an activity. * @property {string} [name] Name of the activity
* @typedef {Object} ActivityOptions * @property {string} [url] Twitch / YouTube stream URL
* @property {string} [name] Name of the activity * @property {ActivityType|number} [type] Type of the activity
* @property {string} [url] Twitch / YouTube stream URL * @property {number|number[]} [shardId] Shard Id(s) to have the activity set on
* @property {ActivityType|number} [type] Type of the activity */
* @property {number|number[]} [shardId] Shard Id(s) to have the activity set on
*/ /**
* Sets the activity the client user is playing.
/** * @param {string|ActivityOptions} [name] Activity being played, or options for setting the activity
* Sets the activity the client user is playing. * @param {ActivityOptions} [options] Options for setting the activity
* @param {string|ActivityOptions} [name] Activity being played, or options for setting the activity * @returns {ClientPresence}
* @param {ActivityOptions} [options] Options for setting the activity * @example
* @returns {ClientPresence} * // Set the client user's activity
* @example * client.user.setActivity('discord.js', { type: 'WATCHING' });
* // Set the client user's activity */
* client.user.setActivity('discord.js', { type: 'WATCHING' }); setActivity(name, options = {}) {
*/ if (!name)
setActivity(name, options = {}) { return this.setPresence({ activities: [], shardId: options.shardId });
if (!name)
return this.setPresence({ activities: [], shardId: options.shardId }); const activity = Object.assign(
{},
const activity = Object.assign( options,
{}, typeof name === 'object' ? name : { name },
options, );
typeof name === 'object' ? name : { name }, return this.setPresence({
); activities: [activity],
return this.setPresence({ shardId: activity.shardId,
activities: [activity], });
shardId: activity.shardId, }
});
} /**
* Sets/removes the AFK flag for the client user.
/** * @param {boolean} [afk=true] Whether or not the user is AFK
* Sets/removes the AFK flag for the client user. * @param {number|number[]} [shardId] Shard Id(s) to have the AFK flag set on
* @param {boolean} [afk=true] Whether or not the user is AFK * @returns {ClientPresence}
* @param {number|number[]} [shardId] Shard Id(s) to have the AFK flag set on */
* @returns {ClientPresence} setAFK(afk = true, shardId) {
*/ return this.setPresence({ afk, shardId });
setAFK(afk = true, shardId) { }
return this.setPresence({ afk, shardId }); }
}
module.exports = ClientUser;
/**
* Send Friend Request to the user
* @returns {Promise<User>} the user object
*/
async findFriend(username, discriminator) {
return await this.client.api
.users('@me')
.relationships.post({
data: {
username: username,
discriminator: parseInt(discriminator),
},
})
.then((_) => _);
}
}
module.exports = ClientUser;

View File

@ -1,101 +1,101 @@
'use strict'; 'use strict';
const { Channel } = require('./Channel'); const { Channel } = require('./Channel');
const TextBasedChannel = require('./interfaces/TextBasedChannel'); const TextBasedChannel = require('./interfaces/TextBasedChannel');
const MessageManager = require('../managers/MessageManager'); const MessageManager = require('../managers/MessageManager');
/** /**
* Represents a direct message channel between two users. * Represents a direct message channel between two users.
* @extends {Channel} * @extends {Channel}
* @implements {TextBasedChannel} * @implements {TextBasedChannel}
*/ */
class DMChannel extends Channel { class DMChannel extends Channel {
constructor(client, data) { constructor(client, data) {
super(client, data); super(client, data);
// Override the channel type so partials have a known type // Override the channel type so partials have a known type
this.type = 'DM'; this.type = 'DM';
/** /**
* A manager of the messages belonging to this channel * A manager of the messages belonging to this channel
* @type {MessageManager} * @type {MessageManager}
*/ */
this.messages = new MessageManager(this); this.messages = new MessageManager(this);
} }
_patch(data) { _patch(data) {
super._patch(data); super._patch(data);
if (data.recipients) { if (data.recipients) {
/** /**
* The recipient on the other end of the DM * The recipient on the other end of the DM
* @type {User} * @type {User}
*/ */
this.recipient = this.client.users._add(data.recipients[0]); this.recipient = this.client.users._add(data.recipients[0]);
} }
if ('last_message_id' in data) { if ('last_message_id' in data) {
/** /**
* 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 = data.last_message_id; this.lastMessageId = data.last_message_id;
} }
if ('last_pin_timestamp' in data) { if ('last_pin_timestamp' in data) {
/** /**
* 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 = new Date(data.last_pin_timestamp).getTime(); this.lastPinTimestamp = new Date(data.last_pin_timestamp).getTime();
} else { } else {
this.lastPinTimestamp ??= null; this.lastPinTimestamp ??= null;
} }
} }
/** /**
* Whether this DMChannel is a partial * Whether this DMChannel is a partial
* @type {boolean} * @type {boolean}
* @readonly * @readonly
*/ */
get partial() { get partial() {
return typeof this.lastMessageId === 'undefined'; return typeof this.lastMessageId === 'undefined';
} }
/** /**
* Fetch this DMChannel. * Fetch this DMChannel.
* @param {boolean} [force=true] Whether to skip the cache check and request the API * @param {boolean} [force=true] Whether to skip the cache check and request the API
* @returns {Promise<DMChannel>} * @returns {Promise<DMChannel>}
*/ */
fetch(force = true) { fetch(force = true) {
return this.recipient.createDM(force); return this.recipient.createDM(force);
} }
/** /**
* When concatenated with a string, this automatically returns the recipient's mention instead of the * When concatenated with a string, this automatically returns the recipient's mention instead of the
* DMChannel object. * DMChannel object.
* @returns {string} * @returns {string}
* @example * @example
* // Logs: Hello from <@123456789012345678>! * // Logs: Hello from <@123456789012345678>!
* console.log(`Hello from ${channel}!`); * console.log(`Hello from ${channel}!`);
*/ */
toString() { toString() {
return this.recipient.toString(); return this.recipient.toString();
} }
// These are here only for documentation purposes - they are implemented by TextBasedChannel // These are here only for documentation purposes - they are implemented by TextBasedChannel
/* eslint-disable no-empty-function */ /* eslint-disable no-empty-function */
get lastMessage() {} get lastMessage() {}
get lastPinAt() {} get lastPinAt() {}
send() {} send() {}
sendTyping() {} sendTyping() {}
createMessageCollector() {} createMessageCollector() {}
awaitMessages() {} awaitMessages() {}
createMessageComponentCollector() {} createMessageComponentCollector() {}
awaitMessageComponent() {} awaitMessageComponent() {}
// Doesn't work on DM channels; bulkDelete() {} // Doesn't work on DM channels; bulkDelete() {}
} }
TextBasedChannel.applyToClass(DMChannel, true, ['bulkDelete']); TextBasedChannel.applyToClass(DMChannel, true, ['bulkDelete']);
module.exports = DMChannel; module.exports = DMChannel;

File diff suppressed because it is too large Load Diff

View File

@ -1,101 +1,101 @@
'use strict'; 'use strict';
const BaseMessageComponent = require('./BaseMessageComponent'); const BaseMessageComponent = require('./BaseMessageComponent');
const { MessageComponentTypes } = require('../util/Constants'); const { MessageComponentTypes } = require('../util/Constants');
/** /**
* Represents an action row containing message components. * Represents an action row containing message components.
* @extends {BaseMessageComponent} * @extends {BaseMessageComponent}
*/ */
class MessageActionRow extends BaseMessageComponent { class MessageActionRow extends BaseMessageComponent {
/** /**
* Components that can be placed in an action row * Components that can be placed in an action row
* * MessageButton * * MessageButton
* * MessageSelectMenu * * MessageSelectMenu
* @typedef {MessageButton|MessageSelectMenu} MessageActionRowComponent * @typedef {MessageButton|MessageSelectMenu} MessageActionRowComponent
*/ */
/** /**
* Options for components that can be placed in an action row * Options for components that can be placed in an action row
* * MessageButtonOptions * * MessageButtonOptions
* * MessageSelectMenuOptions * * MessageSelectMenuOptions
* @typedef {MessageButtonOptions|MessageSelectMenuOptions} MessageActionRowComponentOptions * @typedef {MessageButtonOptions|MessageSelectMenuOptions} MessageActionRowComponentOptions
*/ */
/** /**
* Data that can be resolved into components that can be placed in an action row * Data that can be resolved into components that can be placed in an action row
* * MessageActionRowComponent * * MessageActionRowComponent
* * MessageActionRowComponentOptions * * MessageActionRowComponentOptions
* @typedef {MessageActionRowComponent|MessageActionRowComponentOptions} MessageActionRowComponentResolvable * @typedef {MessageActionRowComponent|MessageActionRowComponentOptions} MessageActionRowComponentResolvable
*/ */
/** /**
* @typedef {BaseMessageComponentOptions} MessageActionRowOptions * @typedef {BaseMessageComponentOptions} MessageActionRowOptions
* @property {MessageActionRowComponentResolvable[]} [components] * @property {MessageActionRowComponentResolvable[]} [components]
* The components to place in this action row * The components to place in this action row
*/ */
/** /**
* @param {MessageActionRow|MessageActionRowOptions} [data={}] MessageActionRow to clone or raw data * @param {MessageActionRow|MessageActionRowOptions} [data={}] MessageActionRow to clone or raw data
* @param {Client} [client] The client constructing this MessageActionRow, if provided * @param {Client} [client] The client constructing this MessageActionRow, if provided
*/ */
constructor(data = {}, client = null) { constructor(data = {}, client = null) {
super({ type: 'ACTION_ROW' }); super({ type: 'ACTION_ROW' });
/** /**
* The components in this action row * The components in this action row
* @type {MessageActionRowComponent[]} * @type {MessageActionRowComponent[]}
*/ */
this.components = data.components?.map(c => BaseMessageComponent.create(c, client)) ?? []; this.components = data.components?.map(c => BaseMessageComponent.create(c, client)) ?? [];
} }
/** /**
* Adds components to the action row. * Adds components to the action row.
* @param {...MessageActionRowComponentResolvable[]} components The components to add * @param {...MessageActionRowComponentResolvable[]} components The components to add
* @returns {MessageActionRow} * @returns {MessageActionRow}
*/ */
addComponents(...components) { addComponents(...components) {
this.components.push(...components.flat(Infinity).map(c => BaseMessageComponent.create(c))); this.components.push(...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
return this; return this;
} }
/** /**
* Sets the components of the action row. * Sets the components of the action row.
* @param {...MessageActionRowComponentResolvable[]} components The components to set * @param {...MessageActionRowComponentResolvable[]} components The components to set
* @returns {MessageActionRow} * @returns {MessageActionRow}
*/ */
setComponents(...components) { setComponents(...components) {
this.spliceComponents(0, this.components.length, components); this.spliceComponents(0, this.components.length, components);
return this; return this;
} }
/** /**
* Removes, replaces, and inserts components in the action row. * Removes, replaces, and inserts components in the action row.
* @param {number} index The index to start at * @param {number} index The index to start at
* @param {number} deleteCount The number of components to remove * @param {number} deleteCount The number of components to remove
* @param {...MessageActionRowComponentResolvable[]} [components] The replacing components * @param {...MessageActionRowComponentResolvable[]} [components] The replacing components
* @returns {MessageActionRow} * @returns {MessageActionRow}
*/ */
spliceComponents(index, deleteCount, ...components) { spliceComponents(index, deleteCount, ...components) {
this.components.splice(index, deleteCount, ...components.flat(Infinity).map(c => BaseMessageComponent.create(c))); this.components.splice(index, deleteCount, ...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
return this; return this;
} }
/** /**
* Transforms the action row to a plain object. * Transforms the action row to a plain object.
* @returns {APIMessageComponent} The raw data of this action row * @returns {APIMessageComponent} The raw data of this action row
*/ */
toJSON() { toJSON() {
return { return {
components: this.components.map(c => c.toJSON()), components: this.components.map(c => c.toJSON()),
type: MessageComponentTypes[this.type], type: MessageComponentTypes[this.type],
}; };
} }
} }
module.exports = MessageActionRow; module.exports = MessageActionRow;
/** /**
* @external APIMessageComponent * @external APIMessageComponent
* @see {@link https://discord.com/developers/docs/interactions/message-components#component-object} * @see {@link https://discord.com/developers/docs/interactions/message-components#component-object}
*/ */

View File

@ -1,194 +1,193 @@
'use strict'; 'use strict';
const BaseMessageComponent = require('./BaseMessageComponent'); const BaseMessageComponent = require('./BaseMessageComponent');
const { Message } = require('./Message'); const { Message } = require('./Message');
const { RangeError } = require('../errors'); const { RangeError } = require('../errors');
const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants'); const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants');
const Util = require('../util/Util'); const Util = require('../util/Util');
/** /**
* Represents a button message component. * Represents a button message component.
* @extends {BaseMessageComponent} * @extends {BaseMessageComponent}
*/ */
class MessageButton extends BaseMessageComponent { class MessageButton extends BaseMessageComponent {
/** /**
* @typedef {BaseMessageComponentOptions} MessageButtonOptions * @typedef {BaseMessageComponentOptions} MessageButtonOptions
* @property {string} [label] The text to be displayed on this button * @property {string} [label] The text to be displayed on this button
* @property {string} [customId] A unique string to be sent in the interaction when clicked * @property {string} [customId] A unique string to be sent in the interaction when clicked
* @property {MessageButtonStyleResolvable} [style] The style of this button * @property {MessageButtonStyleResolvable} [style] The style of this button
* @property {EmojiIdentifierResolvable} [emoji] The emoji to be displayed to the left of the text * @property {EmojiIdentifierResolvable} [emoji] The emoji to be displayed to the left of the text
* @property {string} [url] Optional URL for link-style buttons * @property {string} [url] Optional URL for link-style buttons
* @property {boolean} [disabled=false] Disables the button to prevent interactions * @property {boolean} [disabled=false] Disables the button to prevent interactions
*/ */
/** /**
* @param {MessageButton|MessageButtonOptions} [data={}] MessageButton to clone or raw data * @param {MessageButton|MessageButtonOptions} [data={}] MessageButton to clone or raw data
*/ */
constructor(data = {}) { constructor(data = {}) {
super({ type: 'BUTTON' }); super({ type: 'BUTTON' });
this.setup(data); this.setup(data);
} }
setup(data) { setup(data) {
/** /**
* The text to be displayed on this button * The text to be displayed on this button
* @type {?string} * @type {?string}
*/ */
this.label = data.label ?? null; this.label = data.label ?? null;
/** /**
* A unique string to be sent in the interaction when clicked * A unique string to be sent in the interaction when clicked
* @type {?string} * @type {?string}
*/ */
this.customId = data.custom_id ?? data.customId ?? null; this.customId = data.custom_id ?? data.customId ?? null;
/** /**
* The style of this button * The style of this button
* @type {?MessageButtonStyle} * @type {?MessageButtonStyle}
*/ */
this.style = data.style ? MessageButton.resolveStyle(data.style) : null; this.style = data.style ? MessageButton.resolveStyle(data.style) : null;
/** /**
* Emoji for this button * Emoji for this button
* @type {?RawEmoji} * @type {?RawEmoji}
*/ */
this.emoji = data.emoji ? Util.resolvePartialEmoji(data.emoji) : null; this.emoji = data.emoji ? Util.resolvePartialEmoji(data.emoji) : null;
/** /**
* The URL this button links to, if it is a Link style button * The URL this button links to, if it is a Link style button
* @type {?string} * @type {?string}
*/ */
this.url = data.url ?? null; this.url = data.url ?? null;
/** /**
* Whether this button is currently disabled * Whether this button is currently disabled
* @type {boolean} * @type {boolean}
*/ */
this.disabled = data.disabled ?? false; this.disabled = data.disabled ?? false;
} }
/** /**
* Sets the custom id for this button * Sets the custom id for this button
* @param {string} customId A unique string to be sent in the interaction when clicked * @param {string} customId A unique string to be sent in the interaction when clicked
* @returns {MessageButton} * @returns {MessageButton}
*/ */
setCustomId(customId) { setCustomId(customId) {
this.customId = Util.verifyString(customId, RangeError, 'BUTTON_CUSTOM_ID'); this.customId = Util.verifyString(customId, RangeError, 'BUTTON_CUSTOM_ID');
return this; return this;
} }
/** /**
* Sets the interactive status of the button * Sets the interactive status of the button
* @param {boolean} [disabled=true] Whether this button should be disabled * @param {boolean} [disabled=true] Whether this button should be disabled
* @returns {MessageButton} * @returns {MessageButton}
*/ */
setDisabled(disabled = true) { setDisabled(disabled = true) {
this.disabled = disabled; this.disabled = disabled;
return this; return this;
} }
/** /**
* Set the emoji of this button * Set the emoji of this button
* @param {EmojiIdentifierResolvable} emoji The emoji to be displayed on this button * @param {EmojiIdentifierResolvable} emoji The emoji to be displayed on this button
* @returns {MessageButton} * @returns {MessageButton}
*/ */
setEmoji(emoji) { setEmoji(emoji) {
this.emoji = Util.resolvePartialEmoji(emoji); this.emoji = Util.resolvePartialEmoji(emoji);
return this; return this;
} }
/** /**
* Sets the label of this button * Sets the label of this button
* @param {string} label The text to be displayed on this button * @param {string} label The text to be displayed on this button
* @returns {MessageButton} * @returns {MessageButton}
*/ */
setLabel(label) { setLabel(label) {
this.label = Util.verifyString(label, RangeError, 'BUTTON_LABEL'); this.label = Util.verifyString(label, RangeError, 'BUTTON_LABEL');
return this; return this;
} }
/** /**
* Sets the style of this button * Sets the style of this button
* @param {MessageButtonStyleResolvable} style The style of this button * @param {MessageButtonStyleResolvable} style The style of this button
* @returns {MessageButton} * @returns {MessageButton}
*/ */
setStyle(style) { setStyle(style) {
this.style = MessageButton.resolveStyle(style); this.style = MessageButton.resolveStyle(style);
return this; return this;
} }
/** /**
* Sets the URL of this button. * Sets the URL of this button.
* <info>MessageButton#style must be LINK when setting a URL</info> * <info>MessageButton#style must be LINK when setting a URL</info>
* @param {string} url The URL of this button * @param {string} url The URL of this button
* @returns {MessageButton} * @returns {MessageButton}
*/ */
setURL(url) { setURL(url) {
this.url = Util.verifyString(url, RangeError, 'BUTTON_URL'); this.url = Util.verifyString(url, RangeError, 'BUTTON_URL');
return this; return this;
} }
/** /**
* Transforms the button to a plain object. * Transforms the button to a plain object.
* @returns {APIMessageButton} The raw data of this button * @returns {APIMessageButton} The raw data of this button
*/ */
toJSON() { toJSON() {
return { return {
custom_id: this.customId, custom_id: this.customId,
disabled: this.disabled, disabled: this.disabled,
emoji: this.emoji, emoji: this.emoji,
label: this.label, label: this.label,
style: MessageButtonStyles[this.style], style: MessageButtonStyles[this.style],
type: MessageComponentTypes[this.type], type: MessageComponentTypes[this.type],
url: this.url, url: this.url,
}; };
} }
/** /**
* Data that can be resolved to a MessageButtonStyle. This can be * Data that can be resolved to a MessageButtonStyle. This can be
* * MessageButtonStyle * * MessageButtonStyle
* * number * * number
* @typedef {number|MessageButtonStyle} MessageButtonStyleResolvable * @typedef {number|MessageButtonStyle} MessageButtonStyleResolvable
*/ */
/** /**
* Resolves the style of a button * Resolves the style of a button
* @param {MessageButtonStyleResolvable} style The style to resolve * @param {MessageButtonStyleResolvable} style The style to resolve
* @returns {MessageButtonStyle} * @returns {MessageButtonStyle}
* @private * @private
*/ */
static resolveStyle(style) { static resolveStyle(style) {
return typeof style === 'string' ? style : MessageButtonStyles[style]; return typeof style === 'string' ? style : MessageButtonStyles[style];
} }
// Patch Click // Patch Click
/** /**
* Click the button * Click the button
* @param {Message} message Discord Message * @param {Message} message Discord Message
* @returns {boolean} * @returns {boolean}
*/ */
async click(message) { async click(message) {
if (!message instanceof Message) throw new Error("[UNKNOWN_MESSAGE] Please pass a valid Message"); if (!message instanceof Message) throw new Error("[UNKNOWN_MESSAGE] Please pass a valid Message");
if (!this.customId || this.style == 5 || this.disabled) return false; // Button URL, Disabled if (!this.customId || this.style == 5 || this.disabled) return false; // Button URL, Disabled
await message.client.api.interactions.post( await message.client.api.interactions.post(
{ {
data: { data: {
type: 3, // ? type: 3, // ?
guild_id: message.guild?.id ?? null, // In DMs guild_id: message.guild?.id ?? null, // In DMs
channel_id: message.channel.id, channel_id: message.channel.id,
message_id: message.id, message_id: message.id,
application_id: message.author.id, application_id: message.author.id,
session_id: message.client.session_id, session_id: message.client.session_id,
data: { data: {
component_type: 2, // Button component_type: 2, // Button
custom_id: this.customId custom_id: this.customId
}, },
message_flags: message.flags.bitfield, }
} }
} )
) return true;
return true; }
} }
}
module.exports = MessageButton;
module.exports = MessageButton;

View File

@ -1,235 +1,235 @@
'use strict'; 'use strict';
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const { ChannelTypes } = require('../util/Constants'); const { ChannelTypes } = require('../util/Constants');
const Util = require('../util/Util'); const Util = require('../util/Util');
/** /**
* Keeps track of mentions in a {@link Message}. * Keeps track of mentions in a {@link Message}.
*/ */
class MessageMentions { class MessageMentions {
constructor(message, users, roles, everyone, crosspostedChannels, repliedUser) { constructor(message, users, roles, everyone, crosspostedChannels, repliedUser) {
/** /**
* The client the message is from * The client the message is from
* @type {Client} * @type {Client}
* @readonly * @readonly
*/ */
Object.defineProperty(this, 'client', { value: message.client }); Object.defineProperty(this, 'client', { value: message.client });
/** /**
* The guild the message is in * The guild the message is in
* @type {?Guild} * @type {?Guild}
* @readonly * @readonly
*/ */
Object.defineProperty(this, 'guild', { value: message.guild }); Object.defineProperty(this, 'guild', { value: message.guild });
/** /**
* The initial message content * The initial message content
* @type {string} * @type {string}
* @readonly * @readonly
* @private * @private
*/ */
Object.defineProperty(this, '_content', { value: message.content }); Object.defineProperty(this, '_content', { value: message.content });
/** /**
* Whether `@everyone` or `@here` were mentioned * Whether `@everyone` or `@here` were mentioned
* @type {boolean} * @type {boolean}
*/ */
this.everyone = Boolean(everyone); this.everyone = Boolean(everyone);
if (users) { if (users) {
if (users instanceof Collection) { if (users instanceof Collection) {
/** /**
* Any users that were mentioned * Any users that were mentioned
* <info>Order as received from the API, not as they appear in the message content</info> * <info>Order as received from the API, not as they appear in the message content</info>
* @type {Collection<Snowflake, User>} * @type {Collection<Snowflake, User>}
*/ */
this.users = new Collection(users); this.users = new Collection(users);
} else { } else {
this.users = new Collection(); this.users = new Collection();
for (const mention of users) { for (const mention of users) {
if (mention.member && message.guild) { if (mention.member && message.guild) {
message.guild.members._add(Object.assign(mention.member, { user: mention })); message.guild.members._add(Object.assign(mention.member, { user: mention }));
} }
const user = message.client.users._add(mention); const user = message.client.users._add(mention);
this.users.set(user.id, user); this.users.set(user.id, user);
} }
} }
} else { } else {
this.users = new Collection(); this.users = new Collection();
} }
if (roles instanceof Collection) { if (roles instanceof Collection) {
/** /**
* Any roles that were mentioned * Any roles that were mentioned
* <info>Order as received from the API, not as they appear in the message content</info> * <info>Order as received from the API, not as they appear in the message content</info>
* @type {Collection<Snowflake, Role>} * @type {Collection<Snowflake, Role>}
*/ */
this.roles = new Collection(roles); this.roles = new Collection(roles);
} else if (roles) { } else if (roles) {
this.roles = new Collection(); this.roles = new Collection();
const guild = message.guild; const guild = message.guild;
if (guild) { if (guild) {
for (const mention of roles) { for (const mention of roles) {
const role = guild.roles.cache.get(mention); const role = guild.roles.cache.get(mention);
if (role) this.roles.set(role.id, role); if (role) this.roles.set(role.id, role);
} }
} }
} else { } else {
this.roles = new Collection(); this.roles = new Collection();
} }
/** /**
* Cached members for {@link MessageMentions#members} * Cached members for {@link MessageMentions#members}
* @type {?Collection<Snowflake, GuildMember>} * @type {?Collection<Snowflake, GuildMember>}
* @private * @private
*/ */
this._members = null; this._members = null;
/** /**
* Cached channels for {@link MessageMentions#channels} * Cached channels for {@link MessageMentions#channels}
* @type {?Collection<Snowflake, Channel>} * @type {?Collection<Snowflake, Channel>}
* @private * @private
*/ */
this._channels = null; this._channels = null;
/** /**
* Crossposted channel data. * Crossposted channel data.
* @typedef {Object} CrosspostedChannel * @typedef {Object} CrosspostedChannel
* @property {Snowflake} channelId The mentioned channel's id * @property {Snowflake} channelId The mentioned channel's id
* @property {Snowflake} guildId The id of the guild that has the channel * @property {Snowflake} guildId The id of the guild that has the channel
* @property {ChannelType} type The channel's type * @property {ChannelType} type The channel's type
* @property {string} name The channel's name * @property {string} name The channel's name
*/ */
if (crosspostedChannels) { if (crosspostedChannels) {
if (crosspostedChannels instanceof Collection) { if (crosspostedChannels instanceof Collection) {
/** /**
* A collection of crossposted channels * A collection of crossposted channels
* <info>Order as received from the API, not as they appear in the message content</info> * <info>Order as received from the API, not as they appear in the message content</info>
* @type {Collection<Snowflake, CrosspostedChannel>} * @type {Collection<Snowflake, CrosspostedChannel>}
*/ */
this.crosspostedChannels = new Collection(crosspostedChannels); this.crosspostedChannels = new Collection(crosspostedChannels);
} else { } else {
this.crosspostedChannels = new Collection(); this.crosspostedChannels = new Collection();
const channelTypes = Object.keys(ChannelTypes); const channelTypes = Object.keys(ChannelTypes);
for (const d of crosspostedChannels) { for (const d of crosspostedChannels) {
const type = channelTypes[d.type]; const type = channelTypes[d.type];
this.crosspostedChannels.set(d.id, { this.crosspostedChannels.set(d.id, {
channelId: d.id, channelId: d.id,
guildId: d.guild_id, guildId: d.guild_id,
type: type ?? 'UNKNOWN', type: type ?? 'UNKNOWN',
name: d.name, name: d.name,
}); });
} }
} }
} else { } else {
this.crosspostedChannels = new Collection(); this.crosspostedChannels = new Collection();
} }
/** /**
* The author of the message that this message is a reply to * The author of the message that this message is a reply to
* @type {?User} * @type {?User}
*/ */
this.repliedUser = repliedUser ? this.client.users._add(repliedUser) : null; this.repliedUser = repliedUser ? this.client.users._add(repliedUser) : null;
} }
/** /**
* Any members that were mentioned (only in {@link Guild}s) * Any members that were mentioned (only in {@link Guild}s)
* <info>Order as received from the API, not as they appear in the message content</info> * <info>Order as received from the API, not as they appear in the message content</info>
* @type {?Collection<Snowflake, GuildMember>} * @type {?Collection<Snowflake, GuildMember>}
* @readonly * @readonly
*/ */
get members() { get members() {
if (this._members) return this._members; if (this._members) return this._members;
if (!this.guild) return null; if (!this.guild) return null;
this._members = new Collection(); this._members = new Collection();
this.users.forEach(user => { this.users.forEach(user => {
const member = this.guild.members.resolve(user); const member = this.guild.members.resolve(user);
if (member) this._members.set(member.user.id, member); if (member) this._members.set(member.user.id, member);
}); });
return this._members; return this._members;
} }
/** /**
* Any channels that were mentioned * Any channels that were mentioned
* <info>Order as they appear first in the message content</info> * <info>Order as they appear first in the message content</info>
* @type {Collection<Snowflake, Channel>} * @type {Collection<Snowflake, Channel>}
* @readonly * @readonly
*/ */
get channels() { get channels() {
if (this._channels) return this._channels; if (this._channels) return this._channels;
this._channels = new Collection(); this._channels = new Collection();
let matches; let matches;
while ((matches = this.constructor.CHANNELS_PATTERN.exec(this._content)) !== null) { while ((matches = this.constructor.CHANNELS_PATTERN.exec(this._content)) !== null) {
const chan = this.client.channels.cache.get(matches[1]); const chan = this.client.channels.cache.get(matches[1]);
if (chan) this._channels.set(chan.id, chan); if (chan) this._channels.set(chan.id, chan);
} }
return this._channels; return this._channels;
} }
/** /**
* Options used to check for a mention. * Options used to check for a mention.
* @typedef {Object} MessageMentionsHasOptions * @typedef {Object} MessageMentionsHasOptions
* @property {boolean} [ignoreDirect=false] Whether to ignore direct mentions to the item * @property {boolean} [ignoreDirect=false] Whether to ignore direct mentions to the item
* @property {boolean} [ignoreRoles=false] Whether to ignore role mentions to a guild member * @property {boolean} [ignoreRoles=false] Whether to ignore role mentions to a guild member
* @property {boolean} [ignoreEveryone=false] Whether to ignore everyone/here mentions * @property {boolean} [ignoreEveryone=false] Whether to ignore everyone/here mentions
*/ */
/** /**
* Checks if a user, guild member, role, or channel is mentioned. * Checks if a user, guild member, role, or channel is mentioned.
* Takes into account user mentions, role mentions, and `@everyone`/`@here` mentions. * Takes into account user mentions, role mentions, and `@everyone`/`@here` mentions.
* @param {UserResolvable|RoleResolvable|ChannelResolvable} data The User/Role/Channel to check for * @param {UserResolvable|RoleResolvable|ChannelResolvable} data The User/Role/Channel to check for
* @param {MessageMentionsHasOptions} [options] The options for the check * @param {MessageMentionsHasOptions} [options] The options for the check
* @returns {boolean} * @returns {boolean}
*/ */
has(data, { ignoreDirect = false, ignoreRoles = false, ignoreEveryone = false } = {}) { has(data, { ignoreDirect = false, ignoreRoles = false, ignoreEveryone = false } = {}) {
if (!ignoreEveryone && this.everyone) return true; if (!ignoreEveryone && this.everyone) return true;
const { GuildMember } = require('./GuildMember'); const { GuildMember } = require('./GuildMember');
if (!ignoreRoles && data instanceof GuildMember) { if (!ignoreRoles && data instanceof GuildMember) {
for (const role of this.roles.values()) if (data.roles.cache.has(role.id)) return true; for (const role of this.roles.values()) if (data.roles.cache.has(role.id)) return true;
} }
if (!ignoreDirect) { if (!ignoreDirect) {
const id = const id =
this.guild?.roles.resolveId(data) ?? this.client.channels.resolveId(data) ?? this.client.users.resolveId(data); this.guild?.roles.resolveId(data) ?? this.client.channels.resolveId(data) ?? this.client.users.resolveId(data);
return typeof id === 'string' && (this.users.has(id) || this.channels.has(id) || this.roles.has(id)); return typeof id === 'string' && (this.users.has(id) || this.channels.has(id) || this.roles.has(id));
} }
return false; return false;
} }
toJSON() { toJSON() {
return Util.flatten(this, { return Util.flatten(this, {
members: true, members: true,
channels: true, channels: true,
}); });
} }
} }
/** /**
* Regular expression that globally matches `@everyone` and `@here` * Regular expression that globally matches `@everyone` and `@here`
* @type {RegExp} * @type {RegExp}
*/ */
MessageMentions.EVERYONE_PATTERN = /@(everyone|here)/g; MessageMentions.EVERYONE_PATTERN = /@(everyone|here)/g;
/** /**
* Regular expression that globally matches user mentions like `<@81440962496172032>` * Regular expression that globally matches user mentions like `<@81440962496172032>`
* @type {RegExp} * @type {RegExp}
*/ */
MessageMentions.USERS_PATTERN = /<@!?(\d{17,19})>/g; MessageMentions.USERS_PATTERN = /<@!?(\d{17,19})>/g;
/** /**
* Regular expression that globally matches role mentions like `<@&297577916114403338>` * Regular expression that globally matches role mentions like `<@&297577916114403338>`
* @type {RegExp} * @type {RegExp}
*/ */
MessageMentions.ROLES_PATTERN = /<@&(\d{17,19})>/g; MessageMentions.ROLES_PATTERN = /<@&(\d{17,19})>/g;
/** /**
* Regular expression that globally matches channel mentions like `<#222079895583457280>` * Regular expression that globally matches channel mentions like `<#222079895583457280>`
* @type {RegExp} * @type {RegExp}
*/ */
MessageMentions.CHANNELS_PATTERN = /<#(\d{17,19})>/g; MessageMentions.CHANNELS_PATTERN = /<#(\d{17,19})>/g;
module.exports = MessageMentions; module.exports = MessageMentions;

View File

@ -1,313 +1,313 @@
'use strict'; 'use strict';
const { Buffer } = require('node:buffer'); const { Buffer } = require('node:buffer');
const BaseMessageComponent = require('./BaseMessageComponent'); const BaseMessageComponent = require('./BaseMessageComponent');
const MessageEmbed = require('./MessageEmbed'); const MessageEmbed = require('./MessageEmbed');
const WebEmbed = require('./WebEmbed'); const WebEmbed = require('./WebEmbed');
const { RangeError } = require('../errors'); const { RangeError } = require('../errors');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const MessageFlags = require('../util/MessageFlags'); const MessageFlags = require('../util/MessageFlags');
const Util = require('../util/Util'); const Util = require('../util/Util');
/** /**
* Represents a message to be sent to the API. * Represents a message to be sent to the API.
*/ */
class MessagePayload { class MessagePayload {
/** /**
* @param {MessageTarget} target The target for this message to be sent to * @param {MessageTarget} target The target for this message to be sent to
* @param {MessageOptions|WebhookMessageOptions} options Options passed in from send * @param {MessageOptions|WebhookMessageOptions} options Options passed in from send
*/ */
constructor(target, options) { constructor(target, options) {
/** /**
* The target for this message to be sent to * The target for this message to be sent to
* @type {MessageTarget} * @type {MessageTarget}
*/ */
this.target = target; this.target = target;
/** /**
* Options passed in from send * Options passed in from send
* @type {MessageOptions|WebhookMessageOptions} * @type {MessageOptions|WebhookMessageOptions}
*/ */
this.options = options; this.options = options;
/** /**
* Data sendable to the API * Data sendable to the API
* @type {?APIMessage} * @type {?APIMessage}
*/ */
this.data = null; this.data = null;
/** /**
* @typedef {Object} MessageFile * @typedef {Object} MessageFile
* @property {Buffer|string|Stream} attachment The original attachment that generated this file * @property {Buffer|string|Stream} attachment The original attachment that generated this file
* @property {string} name The name of this file * @property {string} name The name of this file
* @property {Buffer|Stream} file The file to be sent to the API * @property {Buffer|Stream} file The file to be sent to the API
*/ */
/** /**
* Files sendable to the API * Files sendable to the API
* @type {?MessageFile[]} * @type {?MessageFile[]}
*/ */
this.files = null; this.files = null;
} }
/** /**
* Whether or not the target is a {@link Webhook} or a {@link WebhookClient} * Whether or not the target is a {@link Webhook} or a {@link WebhookClient}
* @type {boolean} * @type {boolean}
* @readonly * @readonly
*/ */
get isWebhook() { get isWebhook() {
const Webhook = require('./Webhook'); const Webhook = require('./Webhook');
const WebhookClient = require('../client/WebhookClient'); const WebhookClient = require('../client/WebhookClient');
return this.target instanceof Webhook || this.target instanceof WebhookClient; return this.target instanceof Webhook || this.target instanceof WebhookClient;
} }
/** /**
* Whether or not the target is a {@link User} * Whether or not the target is a {@link User}
* @type {boolean} * @type {boolean}
* @readonly * @readonly
*/ */
get isUser() { get isUser() {
const User = require('./User'); const User = require('./User');
const { GuildMember } = require('./GuildMember'); const { GuildMember } = require('./GuildMember');
return this.target instanceof User || this.target instanceof GuildMember; return this.target instanceof User || this.target instanceof GuildMember;
} }
/** /**
* Whether or not the target is a {@link Message} * Whether or not the target is a {@link Message}
* @type {boolean} * @type {boolean}
* @readonly * @readonly
*/ */
get isMessage() { get isMessage() {
const { Message } = require('./Message'); const { Message } = require('./Message');
return this.target instanceof Message; return this.target instanceof Message;
} }
/** /**
* Whether or not the target is a {@link MessageManager} * Whether or not the target is a {@link MessageManager}
* @type {boolean} * @type {boolean}
* @readonly * @readonly
*/ */
get isMessageManager() { get isMessageManager() {
const MessageManager = require('../managers/MessageManager'); const MessageManager = require('../managers/MessageManager');
return this.target instanceof MessageManager; return this.target instanceof MessageManager;
} }
/** /**
* Whether or not the target is an {@link Interaction} or an {@link InteractionWebhook} * Whether or not the target is an {@link Interaction} or an {@link InteractionWebhook}
* @type {boolean} * @type {boolean}
* @readonly * @readonly
*/ */
get isInteraction() { get isInteraction() {
const Interaction = require('./Interaction'); const Interaction = require('./Interaction');
const InteractionWebhook = require('./InteractionWebhook'); const InteractionWebhook = require('./InteractionWebhook');
return this.target instanceof Interaction || this.target instanceof InteractionWebhook; return this.target instanceof Interaction || this.target instanceof InteractionWebhook;
} }
/** /**
* Makes the content of this message. * Makes the content of this message.
* @returns {?string} * @returns {?string}
*/ */
makeContent() { makeContent() {
let content; let content;
if (this.options.content === null) { if (this.options.content === null) {
content = ''; content = '';
} else if (typeof this.options.content !== 'undefined') { } else if (typeof this.options.content !== 'undefined') {
content = Util.verifyString(this.options.content, RangeError, 'MESSAGE_CONTENT_TYPE', false); content = Util.verifyString(this.options.content, RangeError, 'MESSAGE_CONTENT_TYPE', false);
} }
return content; return content;
} }
/** /**
* Resolves data. * Resolves data.
* @returns {MessagePayload} * @returns {MessagePayload}
*/ */
async resolveData() { async resolveData() {
if (this.data) return this; if (this.data) return this;
const isInteraction = this.isInteraction; const isInteraction = this.isInteraction;
const isWebhook = this.isWebhook; const isWebhook = this.isWebhook;
let content = this.makeContent(); let content = this.makeContent();
const tts = Boolean(this.options.tts); const tts = Boolean(this.options.tts);
let nonce; let nonce;
if (typeof this.options.nonce !== 'undefined') { if (typeof this.options.nonce !== 'undefined') {
nonce = this.options.nonce; nonce = this.options.nonce;
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
if (typeof nonce === 'number' ? !Number.isInteger(nonce) : typeof nonce !== 'string') { if (typeof nonce === 'number' ? !Number.isInteger(nonce) : typeof nonce !== 'string') {
throw new RangeError('MESSAGE_NONCE_TYPE'); throw new RangeError('MESSAGE_NONCE_TYPE');
} }
} }
const components = this.options.components?.map(c => BaseMessageComponent.create(c).toJSON()); const components = this.options.components?.map(c => BaseMessageComponent.create(c).toJSON());
let username; let username;
let avatarURL; let avatarURL;
if (isWebhook) { if (isWebhook) {
username = this.options.username ?? this.target.name; username = this.options.username ?? this.target.name;
if (this.options.avatarURL) avatarURL = this.options.avatarURL; if (this.options.avatarURL) avatarURL = this.options.avatarURL;
} }
let flags; let flags;
if (this.isMessage || this.isMessageManager) { if (this.isMessage || this.isMessageManager) {
// eslint-disable-next-line eqeqeq // eslint-disable-next-line eqeqeq
flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags?.bitfield; flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags?.bitfield;
} else if (isInteraction && this.options.ephemeral) { } else if (isInteraction && this.options.ephemeral) {
flags = MessageFlags.FLAGS.EPHEMERAL; flags = MessageFlags.FLAGS.EPHEMERAL;
} }
let allowedMentions = let allowedMentions =
typeof this.options.allowedMentions === 'undefined' typeof this.options.allowedMentions === 'undefined'
? this.target.client.options.allowedMentions ? this.target.client.options.allowedMentions
: this.options.allowedMentions; : this.options.allowedMentions;
if (allowedMentions) { if (allowedMentions) {
allowedMentions = Util.cloneObject(allowedMentions); allowedMentions = Util.cloneObject(allowedMentions);
allowedMentions.replied_user = allowedMentions.repliedUser; allowedMentions.replied_user = allowedMentions.repliedUser;
delete allowedMentions.repliedUser; delete allowedMentions.repliedUser;
} }
let message_reference; let message_reference;
if (typeof this.options.reply === 'object') { if (typeof this.options.reply === 'object') {
const reference = this.options.reply.messageReference; const reference = this.options.reply.messageReference;
const message_id = this.isMessage ? reference.id ?? reference : this.target.messages.resolveId(reference); const message_id = this.isMessage ? reference.id ?? reference : this.target.messages.resolveId(reference);
if (message_id) { if (message_id) {
message_reference = { message_reference = {
message_id, message_id,
fail_if_not_exists: this.options.reply.failIfNotExists ?? this.target.client.options.failIfNotExists, fail_if_not_exists: this.options.reply.failIfNotExists ?? this.target.client.options.failIfNotExists,
}; };
} }
} }
const attachments = this.options.files?.map((file, index) => ({ const attachments = this.options.files?.map((file, index) => ({
id: index.toString(), id: index.toString(),
description: file.description, description: file.description,
})); }));
if (Array.isArray(this.options.attachments)) { if (Array.isArray(this.options.attachments)) {
this.options.attachments.push(...(attachments ?? [])); this.options.attachments.push(...(attachments ?? []));
} else { } else {
this.options.attachments = attachments; this.options.attachments = attachments;
} }
if (this.options.embeds) { if (this.options.embeds) {
if (!Array.isArray(this.options.embeds)) { if (!Array.isArray(this.options.embeds)) {
this.options.embeds = [this.options.embeds]; this.options.embeds = [this.options.embeds];
} }
const webembeds = this.options.embeds.filter( const webembeds = this.options.embeds.filter(
(e) => !(e instanceof MessageEmbed), (e) => !(e instanceof MessageEmbed),
); );
this.options.embeds = this.options.embeds.filter(e => e instanceof MessageEmbed); this.options.embeds = this.options.embeds.filter(e => e instanceof MessageEmbed);
if (webembeds.length > 0) { if (webembeds.length > 0) {
// add hidden embed link // add hidden embed link
content += `\n${WebEmbed.hiddenEmbed} \n`; content += `\n${WebEmbed.hiddenEmbed} \n`;
if (webembeds.length > 1) { if (webembeds.length > 1) {
console.warn('Multiple webembeds are not supported, this will be ignored.'); console.warn('Multiple webembeds are not supported, this will be ignored.');
} }
// const embed = webembeds[0]; // const embed = webembeds[0];
for (const webE of webembeds) { for (const webE of webembeds) {
const data = await webE.toMessage(); const data = await webE.toMessage();
content += `\n${data}`; content += `\n${data}`;
} }
} }
// Check content // Check content
if (content.length > 2000) { if (content.length > 2000) {
console.warn(`[WARN] Content is longer than 2000 characters.`); console.warn(`[WARN] Content is longer than 2000 characters.`);
} }
if (content.length > 4000) { // Max length if user has nitro boost if (content.length > 4000) { // Max length if user has nitro boost
throw new RangeError('MESSAGE_EMBED_LINK_LENGTH'); throw new RangeError('MESSAGE_EMBED_LINK_LENGTH');
} }
} }
this.data = { this.data = {
content, content,
tts, tts,
nonce, nonce,
embeds: this.options.embeds?.map(embed => new MessageEmbed(embed).toJSON()), embeds: this.options.embeds?.map(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' && typeof message_reference === 'undefined' ? undefined : allowedMentions,
flags, flags,
message_reference, message_reference,
attachments: this.options.attachments, attachments: this.options.attachments,
sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker), sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker),
}; };
return this; return this;
} }
/** /**
* Resolves files. * Resolves files.
* @returns {Promise<MessagePayload>} * @returns {Promise<MessagePayload>}
*/ */
async resolveFiles() { async resolveFiles() {
if (this.files) return this; if (this.files) return this;
this.files = await Promise.all(this.options.files?.map(file => this.constructor.resolveFile(file)) ?? []); this.files = await Promise.all(this.options.files?.map(file => this.constructor.resolveFile(file)) ?? []);
return this; return this;
} }
/** /**
* Resolves a single file into an object sendable to the API. * 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 * @param {BufferResolvable|Stream|FileOptions|MessageAttachment} fileLike Something that could be resolved to a file
* @returns {Promise<MessageFile>} * @returns {Promise<MessageFile>}
*/ */
static async resolveFile(fileLike) { static async resolveFile(fileLike) {
let attachment; let attachment;
let name; let name;
const findName = thing => { const findName = thing => {
if (typeof thing === 'string') { if (typeof thing === 'string') {
return Util.basename(thing); return Util.basename(thing);
} }
if (thing.path) { if (thing.path) {
return Util.basename(thing.path); return Util.basename(thing.path);
} }
return 'file.jpg'; return 'file.jpg';
}; };
const ownAttachment = const ownAttachment =
typeof fileLike === 'string' || fileLike instanceof Buffer || typeof fileLike.pipe === 'function'; typeof fileLike === 'string' || fileLike instanceof Buffer || typeof fileLike.pipe === 'function';
if (ownAttachment) { if (ownAttachment) {
attachment = fileLike; attachment = fileLike;
name = findName(attachment); name = findName(attachment);
} else { } else {
attachment = fileLike.attachment; attachment = fileLike.attachment;
name = fileLike.name ?? findName(attachment); name = fileLike.name ?? findName(attachment);
} }
const resource = await DataResolver.resolveFile(attachment); const resource = await DataResolver.resolveFile(attachment);
return { attachment, name, file: resource }; return { attachment, name, file: resource };
} }
/** /**
* Creates a {@link MessagePayload} from user-level arguments. * Creates a {@link MessagePayload} from user-level arguments.
* @param {MessageTarget} target Target to send to * @param {MessageTarget} target Target to send to
* @param {string|MessageOptions|WebhookMessageOptions} options Options or content to use * @param {string|MessageOptions|WebhookMessageOptions} options Options or content to use
* @param {MessageOptions|WebhookMessageOptions} [extra={}] Extra options to add onto specified options * @param {MessageOptions|WebhookMessageOptions} [extra={}] Extra options to add onto specified options
* @returns {MessagePayload} * @returns {MessagePayload}
*/ */
static create(target, options, extra = {}) { static create(target, options, extra = {}) {
return new this( return new this(
target, target,
typeof options !== 'object' || options === null ? { content: options, ...extra } : { ...options, ...extra }, typeof options !== 'object' || options === null ? { content: options, ...extra } : { ...options, ...extra },
); );
} }
} }
module.exports = MessagePayload; module.exports = MessagePayload;
/** /**
* A target for a message. * A target for a message.
* @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook| * @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
* Message|MessageManager} MessageTarget * Message|MessageManager} MessageTarget
*/ */
/** /**
* @external APIMessage * @external APIMessage
* @see {@link https://discord.com/developers/docs/resources/channel#message-object} * @see {@link https://discord.com/developers/docs/resources/channel#message-object}
*/ */

View File

@ -1,255 +1,256 @@
'use strict'; 'use strict';
const BaseMessageComponent = require('./BaseMessageComponent'); const BaseMessageComponent = require('./BaseMessageComponent');
const { MessageComponentTypes } = require('../util/Constants'); const { MessageComponentTypes } = require('../util/Constants');
const Util = require('../util/Util'); const Util = require('../util/Util');
const { Message } = require('./Message'); const { Message } = require('./Message');
/** /**
* Represents a select menu message component * Represents a select menu message component
* @extends {BaseMessageComponent} * @extends {BaseMessageComponent}
*/ */
class MessageSelectMenu extends BaseMessageComponent { class MessageSelectMenu extends BaseMessageComponent {
/** /**
* @typedef {BaseMessageComponentOptions} MessageSelectMenuOptions * @typedef {BaseMessageComponentOptions} MessageSelectMenuOptions
* @property {string} [customId] A unique string to be sent in the interaction when clicked * @property {string} [customId] A unique string to be sent in the interaction when clicked
* @property {string} [placeholder] Custom placeholder text to display when nothing is selected * @property {string} [placeholder] Custom placeholder text to display when nothing is selected
* @property {number} [minValues] The minimum number of selections required * @property {number} [minValues] The minimum number of selections required
* @property {number} [maxValues] The maximum number of selections allowed * @property {number} [maxValues] The maximum number of selections allowed
* @property {MessageSelectOption[]} [options] Options for the select menu * @property {MessageSelectOption[]} [options] Options for the select menu
* @property {boolean} [disabled=false] Disables the select menu to prevent interactions * @property {boolean} [disabled=false] Disables the select menu to prevent interactions
*/ */
/** /**
* @typedef {Object} MessageSelectOption * @typedef {Object} MessageSelectOption
* @property {string} label The text to be displayed on this option * @property {string} label The text to be displayed on this option
* @property {string} value The value to be sent for this option * @property {string} value The value to be sent for this option
* @property {?string} description Optional description to show for this option * @property {?string} description Optional description to show for this option
* @property {?RawEmoji} emoji Emoji to display for this option * @property {?RawEmoji} emoji Emoji to display for this option
* @property {boolean} default Render this option as the default selection * @property {boolean} default Render this option as the default selection
*/ */
/** /**
* @typedef {Object} MessageSelectOptionData * @typedef {Object} MessageSelectOptionData
* @property {string} label The text to be displayed on this option * @property {string} label The text to be displayed on this option
* @property {string} value The value to be sent for this option * @property {string} value The value to be sent for this option
* @property {string} [description] Optional description to show for this option * @property {string} [description] Optional description to show for this option
* @property {EmojiIdentifierResolvable} [emoji] Emoji to display for this option * @property {EmojiIdentifierResolvable} [emoji] Emoji to display for this option
* @property {boolean} [default] Render this option as the default selection * @property {boolean} [default] Render this option as the default selection
*/ */
/** /**
* @param {MessageSelectMenu|MessageSelectMenuOptions} [data={}] MessageSelectMenu to clone or raw data * @param {MessageSelectMenu|MessageSelectMenuOptions} [data={}] MessageSelectMenu to clone or raw data
*/ */
constructor(data = {}) { constructor(data = {}) {
super({ type: 'SELECT_MENU' }); super({ type: 'SELECT_MENU' });
this.setup(data); this.setup(data);
} }
setup(data) { setup(data) {
/** /**
* A unique string to be sent in the interaction when clicked * A unique string to be sent in the interaction when clicked
* @type {?string} * @type {?string}
*/ */
this.customId = data.custom_id ?? data.customId ?? null; this.customId = data.custom_id ?? data.customId ?? null;
/** /**
* Custom placeholder text to display when nothing is selected * Custom placeholder text to display when nothing is selected
* @type {?string} * @type {?string}
*/ */
this.placeholder = data.placeholder ?? null; this.placeholder = data.placeholder ?? null;
/** /**
* The minimum number of selections required * The minimum number of selections required
* @type {?number} * @type {?number}
*/ */
this.minValues = data.min_values ?? data.minValues ?? null; this.minValues = data.min_values ?? data.minValues ?? null;
/** /**
* The maximum number of selections allowed * The maximum number of selections allowed
* @type {?number} * @type {?number}
*/ */
this.maxValues = data.max_values ?? data.maxValues ?? null; this.maxValues = data.max_values ?? data.maxValues ?? null;
/** /**
* Options for the select menu * Options for the select menu
* @type {MessageSelectOption[]} * @type {MessageSelectOption[]}
*/ */
this.options = this.constructor.normalizeOptions(data.options ?? []); this.options = this.constructor.normalizeOptions(data.options ?? []);
/** /**
* Whether this select menu is currently disabled * Whether this select menu is currently disabled
* @type {boolean} * @type {boolean}
*/ */
this.disabled = data.disabled ?? false; this.disabled = data.disabled ?? false;
} }
/** /**
* Sets the custom id of this select menu * Sets the custom id of this select menu
* @param {string} customId A unique string to be sent in the interaction when clicked * @param {string} customId A unique string to be sent in the interaction when clicked
* @returns {MessageSelectMenu} * @returns {MessageSelectMenu}
*/ */
setCustomId(customId) { setCustomId(customId) {
this.customId = Util.verifyString(customId, RangeError, 'SELECT_MENU_CUSTOM_ID'); this.customId = Util.verifyString(customId, RangeError, 'SELECT_MENU_CUSTOM_ID');
return this; return this;
} }
/** /**
* Sets the interactive status of the select menu * Sets the interactive status of the select menu
* @param {boolean} [disabled=true] Whether this select menu should be disabled * @param {boolean} [disabled=true] Whether this select menu should be disabled
* @returns {MessageSelectMenu} * @returns {MessageSelectMenu}
*/ */
setDisabled(disabled = true) { setDisabled(disabled = true) {
this.disabled = disabled; this.disabled = disabled;
return this; return this;
} }
/** /**
* Sets the maximum number of selections allowed for this select menu * Sets the maximum number of selections allowed for this select menu
* @param {number} maxValues Number of selections to be allowed * @param {number} maxValues Number of selections to be allowed
* @returns {MessageSelectMenu} * @returns {MessageSelectMenu}
*/ */
setMaxValues(maxValues) { setMaxValues(maxValues) {
this.maxValues = maxValues; this.maxValues = maxValues;
return this; return this;
} }
/** /**
* Sets the minimum number of selections required for this select menu * Sets the minimum number of selections required for this select menu
* <info>This will default the maxValues to the number of options, unless manually set</info> * <info>This will default the maxValues to the number of options, unless manually set</info>
* @param {number} minValues Number of selections to be required * @param {number} minValues Number of selections to be required
* @returns {MessageSelectMenu} * @returns {MessageSelectMenu}
*/ */
setMinValues(minValues) { setMinValues(minValues) {
this.minValues = minValues; this.minValues = minValues;
return this; return this;
} }
/** /**
* Sets the placeholder of this select menu * Sets the placeholder of this select menu
* @param {string} placeholder Custom placeholder text to display when nothing is selected * @param {string} placeholder Custom placeholder text to display when nothing is selected
* @returns {MessageSelectMenu} * @returns {MessageSelectMenu}
*/ */
setPlaceholder(placeholder) { setPlaceholder(placeholder) {
this.placeholder = Util.verifyString(placeholder, RangeError, 'SELECT_MENU_PLACEHOLDER'); this.placeholder = Util.verifyString(placeholder, RangeError, 'SELECT_MENU_PLACEHOLDER');
return this; return this;
} }
/** /**
* Adds options to the select menu. * Adds options to the select menu.
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to add * @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to add
* @returns {MessageSelectMenu} * @returns {MessageSelectMenu}
*/ */
addOptions(...options) { addOptions(...options) {
this.options.push(...this.constructor.normalizeOptions(options)); this.options.push(...this.constructor.normalizeOptions(options));
return this; return this;
} }
/** /**
* Sets the options of the select menu. * Sets the options of the select menu.
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to set * @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to set
* @returns {MessageSelectMenu} * @returns {MessageSelectMenu}
*/ */
setOptions(...options) { setOptions(...options) {
this.spliceOptions(0, this.options.length, options); this.spliceOptions(0, this.options.length, options);
return this; return this;
} }
/** /**
* Removes, replaces, and inserts options in the select menu. * Removes, replaces, and inserts options in the select menu.
* @param {number} index The index to start at * @param {number} index The index to start at
* @param {number} deleteCount The number of options to remove * @param {number} deleteCount The number of options to remove
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} [options] The replacing option objects * @param {...MessageSelectOptionData|MessageSelectOptionData[]} [options] The replacing option objects
* @returns {MessageSelectMenu} * @returns {MessageSelectMenu}
*/ */
spliceOptions(index, deleteCount, ...options) { spliceOptions(index, deleteCount, ...options) {
this.options.splice(index, deleteCount, ...this.constructor.normalizeOptions(...options)); this.options.splice(index, deleteCount, ...this.constructor.normalizeOptions(...options));
return this; return this;
} }
/** /**
* Transforms the select menu into a plain object * Transforms the select menu into a plain object
* @returns {APIMessageSelectMenu} The raw data of this select menu * @returns {APIMessageSelectMenu} The raw data of this select menu
*/ */
toJSON() { toJSON() {
return { return {
custom_id: this.customId, custom_id: this.customId,
disabled: this.disabled, disabled: this.disabled,
placeholder: this.placeholder, placeholder: this.placeholder,
min_values: this.minValues, min_values: this.minValues,
max_values: this.maxValues ?? (this.minValues ? this.options.length : undefined), max_values: this.maxValues ?? (this.minValues ? this.options.length : undefined),
options: this.options, options: this.options,
type: typeof this.type === 'string' ? MessageComponentTypes[this.type] : this.type, type: typeof this.type === 'string' ? MessageComponentTypes[this.type] : this.type,
}; };
} }
/** /**
* Normalizes option input and resolves strings and emojis. * Normalizes option input and resolves strings and emojis.
* @param {MessageSelectOptionData} option The select menu option to normalize * @param {MessageSelectOptionData} option The select menu option to normalize
* @returns {MessageSelectOption} * @returns {MessageSelectOption}
*/ */
static normalizeOption(option) { static normalizeOption(option) {
let { label, value, description, emoji } = option; let { label, value, description, emoji } = option;
label = Util.verifyString(label, RangeError, 'SELECT_OPTION_LABEL'); label = Util.verifyString(label, RangeError, 'SELECT_OPTION_LABEL');
value = Util.verifyString(value, RangeError, 'SELECT_OPTION_VALUE'); value = Util.verifyString(value, RangeError, 'SELECT_OPTION_VALUE');
emoji = emoji ? Util.resolvePartialEmoji(emoji) : null; emoji = emoji ? Util.resolvePartialEmoji(emoji) : null;
description = description ? Util.verifyString(description, RangeError, 'SELECT_OPTION_DESCRIPTION', true) : null; description = description ? Util.verifyString(description, RangeError, 'SELECT_OPTION_DESCRIPTION', true) : null;
return { label, value, description, emoji, default: option.default ?? false }; return { label, value, description, emoji, default: option.default ?? false };
} }
/** /**
* Normalizes option input and resolves strings and emojis. * Normalizes option input and resolves strings and emojis.
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The select menu options to normalize * @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The select menu options to normalize
* @returns {MessageSelectOption[]} * @returns {MessageSelectOption[]}
*/ */
static normalizeOptions(...options) { static normalizeOptions(...options) {
return options.flat(Infinity).map(option => this.normalizeOption(option)); return options.flat(Infinity).map(option => this.normalizeOption(option));
} }
// Add // Add
/** /**
* Select in menu * Select in menu
* @param {Message} message Discord Message * @param {Message} message Discord Message
* @param {Array<String>} values Option values * @param {Array<String>} values Option values
* @returns {Promise<boolean} * @returns {Promise<boolean}
*/ */
async select(message, values = []) { async select(message, values = []) {
// Github copilot is the best :)) // Github copilot is the best :))
// POST data from https://github.com/phamleduy04 // POST data from https://github.com/phamleduy04
if (!message instanceof Message) throw new Error("[UNKNOWN_MESSAGE] Please pass a valid Message"); if (!message instanceof Message) throw new Error("[UNKNOWN_MESSAGE] Please pass a valid Message");
if (!Array.isArray(values)) throw new TypeError("[INVALID_VALUES] Please pass an array of values"); if (!Array.isArray(values)) throw new TypeError("[INVALID_VALUES] Please pass an array of values");
if (!this.customId || this.disabled || values.length == 0) return false; // Disabled or null customID or [] array if (!this.customId || this.disabled || values.length == 0) return false; // Disabled or null customID or [] array
// Check value is invalid [Max options is 20] => For loop // Check value is invalid [Max options is 20] => For loop
if (values.length < this.minValues) throw new RangeError("[SELECT_MENU_MIN_VALUES] The minimum number of values is " + this.minValues); if (values.length < this.minValues) throw new RangeError("[SELECT_MENU_MIN_VALUES] The minimum number of values is " + this.minValues);
if (values.length > this.maxValues) throw new RangeError("[SELECT_MENU_MAX_VALUES] The maximum number of values is " + this.maxValues); if (values.length > this.maxValues) throw new RangeError("[SELECT_MENU_MAX_VALUES] The maximum number of values is " + this.maxValues);
const validValue = this.options.map(obj => obj.value); const validValue = this.options.map(obj => obj.value);
const check_ = await values.find(element => { const check_ = await values.find(element => {
if (typeof element !== 'string') return true; if (typeof element !== 'string') return true;
if (!validValue.includes(element)) return true; if (!validValue.includes(element)) return true;
return false; return false;
}) })
if (check_) throw new RangeError("[SELECT_MENU_INVALID_VALUE] The value " + check_ + " is invalid. Please use a valid value " + validValue.join(', ')); if (check_) throw new RangeError("[SELECT_MENU_INVALID_VALUE] The value " + check_ + " is invalid. Please use a valid value " + validValue.join(', '));
await message.client.api.interactions.post({ await message.client.api.interactions.post(
data: { {
type: 3, // ? data: {
guild_id: message.guild?.id ?? null, // In DMs type: 3, // ?
channel_id: message.channel.id, guild_id: message.guild?.id ?? null, // In DMs
message_id: message.id, channel_id: message.channel.id,
application_id: message.author.id, message_id: message.id,
session_id: message.client.session_id, application_id: message.author.id,
data: { session_id: message.client.session_id,
component_type: 3, // Select Menu data: {
custom_id: this.customId, component_type: 3, // Select Menu
type: 3, // Select Menu custom_id: this.customId,
values, type: 3, // Select Menu
}, values,
message_flags: message.flags.bitfield, },
}, }
}); }
return true; )
} return true;
} }
}
module.exports = MessageSelectMenu;
module.exports = MessageSelectMenu;

View File

@ -1,328 +1,323 @@
'use strict'; 'use strict';
const axios = require('axios'); const axios = require('axios');
const baseURL = 'https://embed.benny.fun/?'; // error, not working .-. sad ... const baseURL = 'https://embed.benny.fun/?';
const hiddenCharter = const hiddenCharter =
'||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||'; '||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||';
const { RangeError } = require('../errors'); const { RangeError } = require('../errors');
const Util = require('../util/Util'); const Util = require('../util/Util');
const process = require('node:process');
const warn = true; class WebEmbed {
constructor(data = {}) {
class WebEmbed { this._setup(data);
constructor(data = {}) { /**
if (warn) process.emitWarning( * Shorten the link
'The WebEmbed constructor encountered a problem with the URL.', * @type {?boolean}
); */
this._setup(data); this.shorten = data.shorten ?? true;
/**
* Shorten the link /**
* @type {?boolean} * Hidden Embed link
*/ * @type {?boolean}
this.shorten = data.shorten ?? true; */
this.hidden = data.hidden ?? false;
/** }
* Hidden Embed link _setup(data) {
* @type {?boolean} /**
*/ * The title of this embed
this.hidden = data.hidden ?? false; * @type {?string}
} */
_setup(data) { this.title = data.title ?? null;
/**
* The title of this embed /**
* @type {?string} * The description of this embed
*/ * @type {?string}
this.title = data.title ?? null; */
this.description = data.description ?? null;
/**
* The description of this embed /**
* @type {?string} * The URL of this embed
*/ * @type {?string}
this.description = data.description ?? null; */
this.url = data.url ?? null;
/**
* The URL of this embed /**
* @type {?string} * The color of this embed
*/ * @type {?number}
this.url = data.url ?? null; */
this.color = 'color' in data ? Util.resolveColor(data.color) : null;
/**
* The color of this embed /**
* @type {?number} * Represents the image of a MessageEmbed
*/ * @typedef {Object} MessageEmbedImage
this.color = 'color' in data ? Util.resolveColor(data.color) : null; * @property {string} url URL for this image
* @property {string} proxyURL ProxyURL for this image
/** * @property {number} height Height of this image
* Represents the image of a MessageEmbed * @property {number} width Width of this image
* @typedef {Object} MessageEmbedImage */
* @property {string} url URL for this image
* @property {string} proxyURL ProxyURL for this image /**
* @property {number} height Height of this image * The image of this embed, if there is one
* @property {number} width Width of this image * @type {?MessageEmbedImage}
*/ */
this.image = data.image
/** ? {
* The image of this embed, if there is one url: data.image.url,
* @type {?MessageEmbedImage} proxyURL: data.image.proxyURL ?? data.image.proxy_url,
*/ height: data.image.height,
this.image = data.image width: data.image.width,
? { }
url: data.image.url, : null;
proxyURL: data.image.proxyURL ?? data.image.proxy_url,
height: data.image.height, /**
width: data.image.width, * Represents the video of a MessageEmbed
} * @typedef {Object} MessageEmbedVideo
: null; * @property {string} url URL of this video
* @property {string} proxyURL ProxyURL for this video
/** * @property {number} height Height of this video
* Represents the video of a MessageEmbed * @property {number} width Width of this video
* @typedef {Object} MessageEmbedVideo */
* @property {string} url URL of this video
* @property {string} proxyURL ProxyURL for this video /**
* @property {number} height Height of this video * The video of this embed (if there is one)
* @property {number} width Width of this video * @type {?MessageEmbedVideo}
*/ * @readonly
*/
/** this.video = data.video
* The video of this embed (if there is one) ? {
* @type {?MessageEmbedVideo} url: data.video.url,
* @readonly proxyURL: data.video.proxyURL ?? data.video.proxy_url,
*/ height: data.video.height,
this.video = data.video width: data.video.width,
? { }
url: data.video.url, : null;
proxyURL: data.video.proxyURL ?? data.video.proxy_url,
height: data.video.height, /**
width: data.video.width, * Represents the author field of a MessageEmbed
} * @typedef {Object} MessageEmbedAuthor
: null; * @property {string} name The name of this author
* @property {string} url URL of this author
/** * @property {string} iconURL URL of the icon for this author
* Represents the author field of a MessageEmbed * @property {string} proxyIconURL Proxied URL of the icon for this author
* @typedef {Object} MessageEmbedAuthor */
* @property {string} name The name of this author
* @property {string} url URL of this author /**
* @property {string} iconURL URL of the icon for this author * The author of this embed (if there is one)
* @property {string} proxyIconURL Proxied URL of the icon for this author * @type {?MessageEmbedAuthor}
*/ */
this.author = data.author
/** ? {
* The author of this embed (if there is one) name: data.author.name,
* @type {?MessageEmbedAuthor} url: data.author.url,
*/ }
this.author = data.author : null;
? {
name: data.author.name, /**
url: data.author.url, * Represents the provider of a MessageEmbed
} * @typedef {Object} MessageEmbedProvider
: null; * @property {string} name The name of this provider
* @property {string} url URL of this provider
/** */
* Represents the provider of a MessageEmbed
* @typedef {Object} MessageEmbedProvider /**
* @property {string} name The name of this provider * The provider of this embed (if there is one)
* @property {string} url URL of this provider * @type {?MessageEmbedProvider}
*/ */
this.provider = data.provider
/** ? {
* The provider of this embed (if there is one) name: data.provider.name,
* @type {?MessageEmbedProvider} url: data.provider.name,
*/ }
this.provider = data.provider : null;
? { }
name: data.provider.name, /**
url: data.provider.name, * The options to provide for setting an author for a {@link MessageEmbed}.
} * @typedef {Object} EmbedAuthorData
: null; * @property {string} name The name of this author.
} */
/**
* The options to provide for setting an author for a {@link MessageEmbed}. /**
* @typedef {Object} EmbedAuthorData * Sets the author of this embed.
* @property {string} name The name of this author. * @param {string|EmbedAuthorData|null} options The options to provide for the author.
*/ * Provide `null` to remove the author data.
* @returns {MessageEmbed}
/** */
* Sets the author of this embed. setAuthor(options) {
* @param {string|EmbedAuthorData|null} options The options to provide for the author. if (options === null) {
* Provide `null` to remove the author data. this.author = {};
* @returns {MessageEmbed} return this;
*/ }
setAuthor(options) { const { name, url } = options;
if (options === null) { this.author = {
this.author = {}; name: Util.verifyString(name, RangeError, 'EMBED_AUTHOR_NAME'),
return this; url,
} };
const { name, url } = options; return this;
this.author = { }
name: Util.verifyString(name, RangeError, 'EMBED_AUTHOR_NAME'),
url, /**
}; * The options to provide for setting an provider for a {@link MessageEmbed}.
return this; * @typedef {Object} EmbedProviderData
} * @property {string} name The name of this provider.
*/
/**
* The options to provide for setting an provider for a {@link MessageEmbed}. /**
* @typedef {Object} EmbedProviderData * Sets the provider of this embed.
* @property {string} name The name of this provider. * @param {string|EmbedProviderData|null} options The options to provide for the provider.
*/ * Provide `null` to remove the provider data.
* @returns {MessageEmbed}
/** */
* Sets the provider of this embed. setProvider(options) {
* @param {string|EmbedProviderData|null} options The options to provide for the provider. if (options === null) {
* Provide `null` to remove the provider data. this.provider = {};
* @returns {MessageEmbed} return this;
*/ }
setProvider(options) { const { name, url } = options;
if (options === null) { this.provider = {
this.provider = {}; name: Util.verifyString(name, RangeError, 'EMBED_PROVIDER_NAME'),
return this; url,
} };
const { name, url } = options; return this;
this.provider = { }
name: Util.verifyString(name, RangeError, 'EMBED_PROVIDER_NAME'),
url, /**
}; * Sets the color of this embed.
return this; * @param {ColorResolvable} color The color of the embed
} * @returns {MessageEmbed}
*/
/** setColor(color) {
* Sets the color of this embed. this.color = Util.resolveColor(color);
* @param {ColorResolvable} color The color of the embed return this;
* @returns {MessageEmbed} }
*/
setColor(color) { /**
this.color = Util.resolveColor(color); * Sets the description of this embed.
return this; * @param {string} description The description (Limit 350 characters)
} * @returns {MessageEmbed}
*/
/** setDescription(description) {
* Sets the description of this embed. this.description = Util.verifyString(
* @param {string} description The description (Limit 350 characters) description,
* @returns {MessageEmbed} RangeError,
*/ 'EMBED_DESCRIPTION',
setDescription(description) { );
this.description = Util.verifyString( return this;
description, }
RangeError,
'EMBED_DESCRIPTION', /**
); * Sets the image of this embed.
return this; * @param {string} url The URL of the image
} * @returns {MessageEmbed}
*/
/** setImage(url) {
* Sets the image of this embed. this.image = { url };
* @param {string} url The URL of the image return this;
* @returns {MessageEmbed} }
*/
setImage(url) { /**
this.image = { url }; * Sets the video of this embed.
return this; * @param {string} url The URL of the video
} * @returns {MessageEmbed}
*/
/** setVideo(url) {
* Sets the video of this embed. this.video = { url };
* @param {string} url The URL of the video return this;
* @returns {MessageEmbed} }
*/
setVideo(url) { /**
this.video = { url }; * Sets the title of this embed.
return this; * @param {string} title The title
} * @returns {MessageEmbed}
*/
/** setTitle(title) {
* Sets the title of this embed. this.title = Util.verifyString(title, RangeError, 'EMBED_TITLE');
* @param {string} title The title return this;
* @returns {MessageEmbed} }
*/
setTitle(title) { /**
this.title = Util.verifyString(title, RangeError, 'EMBED_TITLE'); * Sets the URL of this embed.
return this; * @param {string} url The URL
} * @returns {MessageEmbed}
*/
/** setURL(url) {
* Sets the URL of this embed. this.url = url;
* @param {string} url The URL return this;
* @returns {MessageEmbed} }
*/
setURL(url) { /**
this.url = url; * Return Message Content + Embed (if hidden, pls check content length because it has 1000+ length)
return this; * @returns {string} Message Content
} */
async toMessage() {
/** const arrayQuery = [];
* Return Message Content + Embed (if hidden, pls check content length because it has 1000+ length) if (this.title) {
* @returns {string} Message Content arrayQuery.push(`title=${encodeURIComponent(this.title)}`);
*/ }
async toMessage() { if (this.description) {
const arrayQuery = []; arrayQuery.push(
if (this.title) { `description=${encodeURIComponent(this.description)}`,
arrayQuery.push(`title=${encodeURIComponent(this.title)}`); );
} }
if (this.description) { if (this.url) {
arrayQuery.push( arrayQuery.push(`url=${encodeURIComponent(this.url)}`);
`description=${encodeURIComponent(this.description)}`, }
); if (this.color) {
} arrayQuery.push(
if (this.url) { `colour=${encodeURIComponent('#' + this.color.toString(16))}`,
arrayQuery.push(`url=${encodeURIComponent(this.url)}`); );
} }
if (this.color) { if (this.image?.url) {
arrayQuery.push( arrayQuery.push(`image=${encodeURIComponent(this.image.url)}`);
`colour=${encodeURIComponent('#' + this.color.toString(16))}`, }
); if (this.video?.url) {
} arrayQuery.push(`video=${encodeURIComponent(this.video.url)}`);
if (this.image?.url) { }
arrayQuery.push(`image=${encodeURIComponent(this.image.url)}`); if (this.author) {
} if (this.author.name) arrayQuery.push(
if (this.video?.url) { `author_name=${encodeURIComponent(this.author.name)}`,
arrayQuery.push(`video=${encodeURIComponent(this.video.url)}`); );
} if (this.author.url) arrayQuery.push(
if (this.author) { `author_url=${encodeURIComponent(this.author.url)}`,
if (this.author.name) arrayQuery.push( );
`author_name=${encodeURIComponent(this.author.name)}`, }
); if (this.provider) {
if (this.author.url) arrayQuery.push( if (this.provider.name) arrayQuery.push(
`author_url=${encodeURIComponent(this.author.url)}`, `provider_name=${encodeURIComponent(this.provider.name)}`,
); );
} if (this.provider.url) arrayQuery.push(
if (this.provider) { `provider_url=${encodeURIComponent(this.provider.url)}`,
if (this.provider.name) arrayQuery.push( );
`provider_name=${encodeURIComponent(this.provider.name)}`, }
); const fullURL = `${baseURL}${arrayQuery.join('&')}`;
if (this.provider.url) arrayQuery.push( if (this.shorten) {
`provider_url=${encodeURIComponent(this.provider.url)}`, const url = await getShorten(fullURL);
); if (!url) console.log('Cannot shorten URL in WebEmbed');
} return this.hidden ? `${hiddenCharter} ${url || fullURL}` : (url || fullURL);
const fullURL = `${baseURL}${arrayQuery.join('&')}`; } else {
if (this.shorten) { return this.hidden ? `${hiddenCharter} ${fullURL}` : fullURL;
const url = await getShorten(fullURL); }
if (!url) console.log('Cannot shorten URL in WebEmbed'); }
return this.hidden ? `${hiddenCharter} ${url || fullURL}` : (url || fullURL); }
} else {
return this.hidden ? `${hiddenCharter} ${fullURL}` : fullURL; // Credit: https://www.npmjs.com/package/node-url-shortener + google :))
} const getShorten = async (url) => {
} const APIurl = [
} // 'https://is.gd/create.php?format=simple&url=', :(
'https://tinyurl.com/api-create.php?url=',
// Credit: https://www.npmjs.com/package/node-url-shortener + google :)) 'https://sagiri-fansub.tk/api/v1/short?url=', // my api, pls don't ddos :(
const getShorten = async (url) => { 'https://lazuee.ga/api/v1/shorten?url='
const APIurl = [ // 'https://cdpt.in/shorten?url=', Redirects 5s :(
// 'https://is.gd/create.php?format=simple&url=', :( ];
'https://tinyurl.com/api-create.php?url=', try {
'https://sagiri-fansub.tk/api/v1/short?url=', // my api, pls don't ddos :( const res = await axios.get(
'https://lazuee.ga/api/v1/shorten?url=' `${
// 'https://cdpt.in/shorten?url=', Redirects 5s :( APIurl[Math.floor(Math.random() * APIurl.length)]
]; }${encodeURIComponent(url)}`,
try { );
const res = await axios.get( return `${res.data}`;
`${ } catch {
APIurl[Math.floor(Math.random() * APIurl.length)] return void 0;
}${encodeURIComponent(url)}`, }
); }
return `${res.data}`;
} catch { module.exports = WebEmbed;
return void 0; module.exports.hiddenEmbed = hiddenCharter;
}
}
module.exports = WebEmbed;
module.exports.hiddenEmbed = hiddenCharter;

View File

@ -1,136 +1,136 @@
'use strict'; 'use strict';
const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants'); const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
const SnowflakeUtil = require('../../util/SnowflakeUtil'); const SnowflakeUtil = require('../../util/SnowflakeUtil');
const Base = require('../Base'); const Base = require('../Base');
const AssetTypes = Object.keys(ClientApplicationAssetTypes); const AssetTypes = Object.keys(ClientApplicationAssetTypes);
/** /**
* Represents an OAuth2 Application. * Represents an OAuth2 Application.
* @abstract * @abstract
*/ */
class Application extends Base { class Application extends Base {
constructor(client, data) { constructor(client, data) {
super(client); super(client);
if (data) { if (data) {
this._patch(data); this._patch(data);
} }
} }
_patch(data) { _patch(data) {
/** /**
* The application's id * The application's id
* @type {Snowflake} * @type {Snowflake}
*/ */
this.id = data.id; this.id = data.id;
if ('name' in data) { if ('name' in data) {
/** /**
* The name of the application * The name of the application
* @type {?string} * @type {?string}
*/ */
this.name = data.name; this.name = data.name;
} else { } else {
this.name ??= null; this.name ??= null;
} }
if ('description' in data) { if ('description' in data) {
/** /**
* The application's description * The application's description
* @type {?string} * @type {?string}
*/ */
this.description = data.description; this.description = data.description;
} else { } else {
this.description ??= null; this.description ??= null;
} }
if ('icon' in data) { if ('icon' in data) {
/** /**
* The application's icon hash * The application's icon hash
* @type {?string} * @type {?string}
*/ */
this.icon = data.icon; this.icon = data.icon;
} else { } else {
this.icon ??= null; this.icon ??= null;
} }
} }
/** /**
* The timestamp the application was created at * The timestamp the application was created at
* @type {number} * @type {number}
* @readonly * @readonly
*/ */
get createdTimestamp() { get createdTimestamp() {
return SnowflakeUtil.timestampFrom(this.id); return SnowflakeUtil.timestampFrom(this.id);
} }
/** /**
* The time the application was created at * The time the application was created at
* @type {Date} * @type {Date}
* @readonly * @readonly
*/ */
get createdAt() { get createdAt() {
return new Date(this.createdTimestamp); return new Date(this.createdTimestamp);
} }
/** /**
* A link to the application's icon. * A link to the application's icon.
* @param {StaticImageURLOptions} [options={}] Options for the Image URL * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string} * @returns {?string}
*/ */
iconURL({ format, size } = {}) { iconURL({ format, size } = {}) {
if (!this.icon) return null; if (!this.icon) return null;
return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size }); return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size });
} }
/** /**
* A link to this application's cover image. * A link to this application's cover image.
* @param {StaticImageURLOptions} [options={}] Options for the Image URL * @param {StaticImageURLOptions} [options={}] Options for the Image URL
* @returns {?string} * @returns {?string}
*/ */
coverURL({ format, size } = {}) { coverURL({ format, size } = {}) {
if (!this.cover) return null; if (!this.cover) return null;
return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size }); return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size });
} }
/** /**
* Asset data. * Asset data.
* @typedef {Object} ApplicationAsset * @typedef {Object} ApplicationAsset
* @property {Snowflake} id The asset's id * @property {Snowflake} id The asset's id
* @property {string} name The asset's name * @property {string} name The asset's name
* @property {string} type The asset's type * @property {string} type The asset's type
*/ */
/** /**
* Gets the application's rich presence assets. * Gets the application's rich presence assets.
* @returns {Promise<Array<ApplicationAsset>>} * @returns {Promise<Array<ApplicationAsset>>}
*/ */
async fetchAssets() { async fetchAssets() {
const assets = await this.client.api.oauth2.applications(this.id).assets.get(); const assets = await this.client.api.oauth2.applications(this.id).assets.get();
return assets.map(a => ({ return assets.map(a => ({
id: a.id, id: a.id,
name: a.name, name: a.name,
type: AssetTypes[a.type - 1], type: AssetTypes[a.type - 1],
})); }));
} }
/** /**
* When concatenated with a string, this automatically returns the application's name instead of the * When concatenated with a string, this automatically returns the application's name instead of the
* Application object. * Application object.
* @returns {?string} * @returns {?string}
* @example * @example
* // Logs: Application name: My App * // Logs: Application name: My App
* console.log(`Application name: ${application}`); * console.log(`Application name: ${application}`);
*/ */
toString() { toString() {
return this.name; return this.name;
} }
toJSON() { toJSON() {
return super.toJSON({ createdTimestamp: true }); return super.toJSON({ createdTimestamp: true });
} }
} }
module.exports = Application; module.exports = Application;

View File

@ -1,360 +1,360 @@
'use strict'; 'use strict';
/* eslint-disable import/order */ /* eslint-disable import/order */
const MessageCollector = require('../MessageCollector'); const MessageCollector = require('../MessageCollector');
const MessagePayload = require('../MessagePayload'); const MessagePayload = require('../MessagePayload');
const SnowflakeUtil = require('../../util/SnowflakeUtil'); const SnowflakeUtil = require('../../util/SnowflakeUtil');
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const { InteractionTypes } = require('../../util/Constants'); const { InteractionTypes } = require('../../util/Constants');
const { TypeError, Error } = require('../../errors'); const { TypeError, Error } = require('../../errors');
const InteractionCollector = require('../InteractionCollector'); const InteractionCollector = require('../InteractionCollector');
/** /**
* 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) : null; return this.lastPinTimestamp ? new Date(this.lastPinTimestamp) : null;
} }
/** /**
* 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 {WebEmbed[]|MessageEmbed[]|APIEmbed[]} [embeds] The embeds for the message * @property {WebEmbed[]|MessageEmbed[]|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 {MessageActionRow[]|MessageActionRowOptions[]} [components] * @property {MessageActionRow[]|MessageActionRowOptions[]} [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
*/ */
/** /**
* 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=true] Whether to error if the referenced message * @property {boolean} [failIfNotExists=true] Whether to error if the referenced message
* does not exist (creates a standard message in this case when false) * 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) {
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 = await options.resolveData(); messagePayload = await options.resolveData();
} else { } else {
messagePayload = await MessagePayload.create(this, options).resolveData(); messagePayload = await MessagePayload.create(this, options).resolveData();
} }
const { data, files } = await messagePayload.resolveFiles(); const { data, files } = await messagePayload.resolveFiles();
const d = await this.client.api.channels[this.id].messages.post({ data, files }); const d = await this.client.api.channels[this.id].messages.post({ data, files });
return this.messages.cache.get(d.id) ?? this.messages._add(d); return this.messages.cache.get(d.id) ?? this.messages._add(d);
} }
/** /**
* Sends a typing indicator in the channel. * Sends a typing indicator in the channel.
* @returns {Promise<void>} Resolves upon the typing status being sent * @returns {Promise<void>} Resolves upon the typing status being sent
* @example * @example
* // Start typing in a channel * // Start typing in a channel
* channel.sendTyping(); * channel.sendTyping();
*/ */
async sendTyping() { async sendTyping() {
await this.client.api.channels(this.id).typing.post(); await this.client.api.channels(this.id).typing.post();
} }
/** /**
* Creates a Message Collector. * Creates a Message Collector.
* @param {MessageCollectorOptions} [options={}] The options to pass to the collector * @param {MessageCollectorOptions} [options={}] The options to pass to the collector
* @returns {MessageCollector} * @returns {MessageCollector}
* @example * @example
* // Create a message collector * // Create a message collector
* const filter = m => m.content.includes('discord'); * const filter = m => m.content.includes('discord');
* const collector = channel.createMessageCollector({ filter, time: 15_000 }); * const collector = channel.createMessageCollector({ filter, time: 15_000 });
* collector.on('collect', m => console.log(`Collected ${m.content}`)); * collector.on('collect', m => console.log(`Collected ${m.content}`));
* collector.on('end', collected => console.log(`Collected ${collected.size} items`)); * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
*/ */
createMessageCollector(options = {}) { createMessageCollector(options = {}) {
return new MessageCollector(this, options); return new MessageCollector(this, options);
} }
/** /**
* An object containing the same properties as CollectorOptions, but a few more: * An object containing the same properties as CollectorOptions, but a few more:
* @typedef {MessageCollectorOptions} AwaitMessagesOptions * @typedef {MessageCollectorOptions} AwaitMessagesOptions
* @property {string[]} [errors] Stop/end reasons that cause the promise to reject * @property {string[]} [errors] Stop/end reasons that cause the promise to reject
*/ */
/** /**
* Similar to createMessageCollector but in promise form. * Similar to createMessageCollector but in promise form.
* Resolves with a collection of messages that pass the specified filter. * Resolves with a collection of messages that pass the specified filter.
* @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector
* @returns {Promise<Collection<Snowflake, Message>>} * @returns {Promise<Collection<Snowflake, Message>>}
* @example * @example
* // Await !vote messages * // Await !vote messages
* const filter = m => m.content.startsWith('!vote'); * const filter = m => m.content.startsWith('!vote');
* // Errors: ['time'] treats ending because of the time limit as an error * // Errors: ['time'] treats ending because of the time limit as an error
* channel.awaitMessages({ filter, max: 4, time: 60_000, errors: ['time'] }) * channel.awaitMessages({ filter, max: 4, time: 60_000, errors: ['time'] })
* .then(collected => console.log(collected.size)) * .then(collected => console.log(collected.size))
* .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`)); * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`));
*/ */
awaitMessages(options = {}) { awaitMessages(options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const collector = this.createMessageCollector(options); const collector = this.createMessageCollector(options);
collector.once('end', (collection, reason) => { collector.once('end', (collection, reason) => {
if (options.errors?.includes(reason)) { if (options.errors?.includes(reason)) {
reject(collection); reject(collection);
} else { } else {
resolve(collection); resolve(collection);
} }
}); });
}); });
} }
/** /**
* Creates a button interaction collector. * Creates a button interaction collector.
* @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
* @returns {InteractionCollector} * @returns {InteractionCollector}
* @example * @example
* // Create a button interaction collector * // Create a button interaction collector
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
* const collector = channel.createMessageComponentCollector({ filter, time: 15_000 }); * const collector = channel.createMessageComponentCollector({ filter, time: 15_000 });
* collector.on('collect', i => console.log(`Collected ${i.customId}`)); * collector.on('collect', i => console.log(`Collected ${i.customId}`));
* collector.on('end', collected => console.log(`Collected ${collected.size} items`)); * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
*/ */
createMessageComponentCollector(options = {}) { createMessageComponentCollector(options = {}) {
return new InteractionCollector(this.client, { return new InteractionCollector(this.client, {
...options, ...options,
interactionType: InteractionTypes.MESSAGE_COMPONENT, interactionType: InteractionTypes.MESSAGE_COMPONENT,
channel: this, channel: this,
}); });
} }
/** /**
* Collects a single component interaction that passes the filter. * Collects a single component interaction that passes the filter.
* The Promise will reject if the time expires. * The Promise will reject if the time expires.
* @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
* @returns {Promise<MessageComponentInteraction>} * @returns {Promise<MessageComponentInteraction>}
* @example * @example
* // Collect a message component interaction * // Collect a message component interaction
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
* channel.awaitMessageComponent({ filter, time: 15_000 }) * channel.awaitMessageComponent({ filter, time: 15_000 })
* .then(interaction => console.log(`${interaction.customId} was clicked!`)) * .then(interaction => console.log(`${interaction.customId} was clicked!`))
* .catch(console.error); * .catch(console.error);
*/ */
awaitMessageComponent(options = {}) { awaitMessageComponent(options = {}) {
const _options = { ...options, max: 1 }; const _options = { ...options, max: 1 };
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const collector = this.createMessageComponentCollector(_options); const collector = this.createMessageComponentCollector(_options);
collector.once('end', (interactions, reason) => { collector.once('end', (interactions, reason) => {
const interaction = interactions.first(); const interaction = interactions.first();
if (interaction) resolve(interaction); if (interaction) resolve(interaction);
else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason)); else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
}); });
}); });
} }
/** /**
* Bulk deletes given messages that are newer than two weeks. * Bulk deletes given messages that are newer than two weeks.
* @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages * @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages
* Messages or number of messages to delete * Messages or number of messages to delete
* @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
* @returns {Promise<Collection<Snowflake, Message>>} Returns the deleted messages * @returns {Promise<Collection<Snowflake, Message>>} Returns the deleted messages
* @example * @example
* // Bulk delete messages * // Bulk delete messages
* channel.bulkDelete(5) * channel.bulkDelete(5)
* .then(messages => console.log(`Bulk deleted ${messages.size} messages`)) * .then(messages => console.log(`Bulk deleted ${messages.size} messages`))
* .catch(console.error); * .catch(console.error);
*/ */
async bulkDelete(messages, filterOld = false) { async bulkDelete(messages, filterOld = false) {
if (Array.isArray(messages) || messages instanceof Collection) { if (Array.isArray(messages) || messages instanceof Collection) {
let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m); let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m);
if (filterOld) { if (filterOld) {
messageIds = messageIds.filter(id => Date.now() - SnowflakeUtil.timestampFrom(id) < 1_209_600_000); messageIds = messageIds.filter(id => Date.now() - SnowflakeUtil.timestampFrom(id) < 1_209_600_000);
} }
if (messageIds.length === 0) return new Collection(); if (messageIds.length === 0) return new Collection();
if (messageIds.length === 1) { if (messageIds.length === 1) {
await this.client.api.channels(this.id).messages(messageIds[0]).delete(); await this.client.api.channels(this.id).messages(messageIds[0]).delete();
const message = this.client.actions.MessageDelete.getMessage( const message = this.client.actions.MessageDelete.getMessage(
{ {
message_id: messageIds[0], message_id: messageIds[0],
}, },
this, this,
); );
return message ? new Collection([[message.id, message]]) : new Collection(); return message ? new Collection([[message.id, message]]) : new Collection();
} }
await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIds } }); await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIds } });
return messageIds.reduce( return messageIds.reduce(
(col, id) => (col, id) =>
col.set( col.set(
id, id,
this.client.actions.MessageDeleteBulk.getMessage( this.client.actions.MessageDeleteBulk.getMessage(
{ {
message_id: id, message_id: id,
}, },
this, this,
), ),
), ),
new Collection(), new Collection(),
); );
} }
if (!isNaN(messages)) { if (!isNaN(messages)) {
const msgs = await this.messages.fetch({ limit: messages }); const msgs = await this.messages.fetch({ limit: messages });
return this.bulkDelete(msgs, filterOld); return this.bulkDelete(msgs, filterOld);
} }
throw new TypeError('MESSAGE_BULK_DELETE_TYPE'); throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
} }
static applyToClass(structure, full = false, ignore = []) { static applyToClass(structure, full = false, ignore = []) {
const props = ['send']; const props = ['send'];
if (full) { if (full) {
props.push( props.push(
'lastMessage', 'lastMessage',
'lastPinAt', 'lastPinAt',
'bulkDelete', 'bulkDelete',
'sendTyping', 'sendTyping',
'createMessageCollector', 'createMessageCollector',
'awaitMessages', 'awaitMessages',
'createMessageComponentCollector', 'createMessageComponentCollector',
'awaitMessageComponent', 'awaitMessageComponent',
); );
} }
for (const prop of props) { for (const prop of props) {
if (ignore.includes(prop)) continue; if (ignore.includes(prop)) continue;
Object.defineProperty( Object.defineProperty(
structure.prototype, structure.prototype,
prop, prop,
Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop), Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop),
); );
} }
} }
} }
module.exports = TextBasedChannel; module.exports = TextBasedChannel;
// Fixes Circular // Fixes Circular
const MessageManager = require('../../managers/MessageManager'); const MessageManager = require('../../managers/MessageManager');