diff options
author | sowgro <tpoke.ferrari@gmail.com> | 2023-09-02 19:12:47 -0400 |
---|---|---|
committer | sowgro <tpoke.ferrari@gmail.com> | 2023-09-02 19:12:47 -0400 |
commit | e4450c8417624b71d779cb4f41692538f9165e10 (patch) | |
tree | b70826542223ecdf8a7a259f61b0a1abb8a217d8 /node_modules/discord.js/src | |
download | sowbot3-e4450c8417624b71d779cb4f41692538f9165e10.tar.gz sowbot3-e4450c8417624b71d779cb4f41692538f9165e10.tar.bz2 sowbot3-e4450c8417624b71d779cb4f41692538f9165e10.zip |
first commit
Diffstat (limited to 'node_modules/discord.js/src')
322 files changed, 35034 insertions, 0 deletions
diff --git a/node_modules/discord.js/src/client/BaseClient.js b/node_modules/discord.js/src/client/BaseClient.js new file mode 100644 index 0000000..631748c --- /dev/null +++ b/node_modules/discord.js/src/client/BaseClient.js @@ -0,0 +1,83 @@ +'use strict'; + +const EventEmitter = require('node:events'); +const { REST } = require('@discordjs/rest'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const Options = require('../util/Options'); +const { mergeDefault, flatten } = require('../util/Util'); + +/** + * The base class for all clients. + * @extends {EventEmitter} + */ +class BaseClient extends EventEmitter { + constructor(options = {}) { + super({ captureRejections: true }); + + if (typeof options !== 'object' || options === null) { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); + } + + /** + * The options the client was instantiated with + * @type {ClientOptions} + */ + this.options = mergeDefault(Options.createDefault(), { + ...options, + rest: { + ...options.rest, + userAgentAppendix: options.rest?.userAgentAppendix + ? `${Options.userAgentAppendix} ${options.rest.userAgentAppendix}` + : undefined, + }, + }); + + /** + * The REST manager of the client + * @type {REST} + */ + this.rest = new REST(this.options.rest); + } + + /** + * Destroys all assets used by the base client. + * @returns {void} + */ + destroy() { + this.rest.clearHashSweeper(); + this.rest.clearHandlerSweeper(); + } + + /** + * Increments max listeners by one, if they are not zero. + * @private + */ + incrementMaxListeners() { + const maxListeners = this.getMaxListeners(); + if (maxListeners !== 0) { + this.setMaxListeners(maxListeners + 1); + } + } + + /** + * Decrements max listeners by one, if they are not zero. + * @private + */ + decrementMaxListeners() { + const maxListeners = this.getMaxListeners(); + if (maxListeners !== 0) { + this.setMaxListeners(maxListeners - 1); + } + } + + toJSON(...props) { + return flatten(this, ...props); + } +} + +module.exports = BaseClient; + +/** + * @external REST + * @see {@link https://discord.js.org/docs/packages/rest/stable/REST:Class} + */ diff --git a/node_modules/discord.js/src/client/Client.js b/node_modules/discord.js/src/client/Client.js new file mode 100644 index 0000000..80719e8 --- /dev/null +++ b/node_modules/discord.js/src/client/Client.js @@ -0,0 +1,608 @@ +'use strict'; + +const process = require('node:process'); +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { OAuth2Scopes, Routes } = require('discord-api-types/v10'); +const BaseClient = require('./BaseClient'); +const ActionsManager = require('./actions/ActionsManager'); +const ClientVoiceManager = require('./voice/ClientVoiceManager'); +const WebSocketManager = require('./websocket/WebSocketManager'); +const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors'); +const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager'); +const ChannelManager = require('../managers/ChannelManager'); +const GuildManager = require('../managers/GuildManager'); +const UserManager = require('../managers/UserManager'); +const ShardClientUtil = require('../sharding/ShardClientUtil'); +const ClientPresence = require('../structures/ClientPresence'); +const GuildPreview = require('../structures/GuildPreview'); +const GuildTemplate = require('../structures/GuildTemplate'); +const Invite = require('../structures/Invite'); +const { Sticker } = require('../structures/Sticker'); +const StickerPack = require('../structures/StickerPack'); +const VoiceRegion = require('../structures/VoiceRegion'); +const Webhook = require('../structures/Webhook'); +const Widget = require('../structures/Widget'); +const DataResolver = require('../util/DataResolver'); +const Events = require('../util/Events'); +const IntentsBitField = require('../util/IntentsBitField'); +const Options = require('../util/Options'); +const PermissionsBitField = require('../util/PermissionsBitField'); +const Status = require('../util/Status'); +const Sweepers = require('../util/Sweepers'); + +/** + * The main hub for interacting with the Discord API, and the starting point for any bot. + * @extends {BaseClient} + */ +class Client extends BaseClient { + /** + * @param {ClientOptions} options Options for the client + */ + constructor(options) { + super(options); + + const data = require('node:worker_threads').workerData ?? process.env; + const defaults = Options.createDefault(); + + if (this.options.shards === defaults.shards) { + if ('SHARDS' in data) { + this.options.shards = JSON.parse(data.SHARDS); + } + } + + if (this.options.shardCount === defaults.shardCount) { + if ('SHARD_COUNT' in data) { + this.options.shardCount = Number(data.SHARD_COUNT); + } else if (Array.isArray(this.options.shards)) { + this.options.shardCount = this.options.shards.length; + } + } + + const typeofShards = typeof this.options.shards; + + if (typeofShards === 'undefined' && typeof this.options.shardCount === 'number') { + this.options.shards = Array.from({ length: this.options.shardCount }, (_, i) => i); + } + + if (typeofShards === 'number') this.options.shards = [this.options.shards]; + + if (Array.isArray(this.options.shards)) { + this.options.shards = [ + ...new Set( + this.options.shards.filter(item => !isNaN(item) && item >= 0 && item < Infinity && item === (item | 0)), + ), + ]; + } + + this._validateOptions(); + + /** + * The WebSocket manager of the client + * @type {WebSocketManager} + */ + this.ws = new WebSocketManager(this); + + /** + * The action manager of the client + * @type {ActionsManager} + * @private + */ + this.actions = new ActionsManager(this); + + /** + * The voice manager of the client + * @type {ClientVoiceManager} + */ + this.voice = new ClientVoiceManager(this); + + /** + * Shard helpers for the client (only if the process was spawned from a {@link ShardingManager}) + * @type {?ShardClientUtil} + */ + this.shard = process.env.SHARDING_MANAGER + ? ShardClientUtil.singleton(this, process.env.SHARDING_MANAGER_MODE) + : null; + + /** + * All of the {@link User} objects that have been cached at any point, mapped by their ids + * @type {UserManager} + */ + this.users = new UserManager(this); + + /** + * All of the guilds the client is currently handling, mapped by their ids - + * as long as sharding isn't being used, this will be *every* guild the bot is a member of + * @type {GuildManager} + */ + this.guilds = new GuildManager(this); + + /** + * All of the {@link BaseChannel}s that the client is currently handling, mapped by their ids - + * as long as sharding isn't being used, this will be *every* channel in *every* guild the bot + * is a member of. Note that DM channels will not be initially cached, and thus not be present + * in the Manager without their explicit fetching or use. + * @type {ChannelManager} + */ + this.channels = new ChannelManager(this); + + /** + * The sweeping functions and their intervals used to periodically sweep caches + * @type {Sweepers} + */ + this.sweepers = new Sweepers(this, this.options.sweepers); + + /** + * The presence of the Client + * @private + * @type {ClientPresence} + */ + this.presence = new ClientPresence(this, this.options.presence); + + Object.defineProperty(this, 'token', { writable: true }); + if (!this.token && 'DISCORD_TOKEN' in process.env) { + /** + * Authorization token for the logged in bot. + * If present, this defaults to `process.env.DISCORD_TOKEN` when instantiating the client + * <warn>This should be kept private at all times.</warn> + * @type {?string} + */ + this.token = process.env.DISCORD_TOKEN; + } else { + this.token = null; + } + + /** + * User that the client is logged in as + * @type {?ClientUser} + */ + this.user = null; + + /** + * The application of this bot + * @type {?ClientApplication} + */ + this.application = null; + + /** + * Timestamp of the time the client was last {@link Status.Ready} at + * @type {?number} + */ + this.readyTimestamp = null; + } + + /** + * All custom emojis that the client has access to, mapped by their ids + * @type {BaseGuildEmojiManager} + * @readonly + */ + get emojis() { + const emojis = new BaseGuildEmojiManager(this); + for (const guild of this.guilds.cache.values()) { + if (guild.available) for (const emoji of guild.emojis.cache.values()) emojis.cache.set(emoji.id, emoji); + } + return emojis; + } + + /** + * Time at which the client was last regarded as being in the {@link Status.Ready} state + * (each time the client disconnects and successfully reconnects, this will be overwritten) + * @type {?Date} + * @readonly + */ + get readyAt() { + return this.readyTimestamp && new Date(this.readyTimestamp); + } + + /** + * How long it has been since the client last entered the {@link Status.Ready} state in milliseconds + * @type {?number} + * @readonly + */ + get uptime() { + return this.readyTimestamp && Date.now() - this.readyTimestamp; + } + + /** + * Logs the client in, establishing a WebSocket connection to Discord. + * @param {string} [token=this.token] Token of the account to log in with + * @returns {Promise<string>} Token of the account used + * @example + * client.login('my token'); + */ + async login(token = this.token) { + if (!token || typeof token !== 'string') throw new DiscordjsError(ErrorCodes.TokenInvalid); + this.token = token = token.replace(/^(Bot|Bearer)\s*/i, ''); + this.rest.setToken(token); + this.emit(Events.Debug, `Provided token: ${this._censoredToken}`); + + if (this.options.presence) { + this.options.ws.presence = this.presence._parse(this.options.presence); + } + + this.emit(Events.Debug, 'Preparing to connect to the gateway...'); + + try { + await this.ws.connect(); + return this.token; + } catch (error) { + await this.destroy(); + throw error; + } + } + + /** + * Returns whether the client has logged in, indicative of being able to access + * properties such as `user` and `application`. + * @returns {boolean} + */ + isReady() { + return this.ws.status === Status.Ready; + } + + /** + * Logs out, terminates the connection to Discord, and destroys the client. + * @returns {Promise<void>} + */ + async destroy() { + super.destroy(); + + this.sweepers.destroy(); + await this.ws.destroy(); + this.token = null; + this.rest.setToken(null); + } + + /** + * Options used for deleting a webhook. + * @typedef {Object} WebhookDeleteOptions + * @property {string} [token] Token of the webhook + * @property {string} [reason] The reason for deleting the webhook + */ + + /** + * Deletes a webhook. + * @param {Snowflake} id The webhook's id + * @param {WebhookDeleteOptions} [options] Options for deleting the webhook + * @returns {Promise<void>} + */ + async deleteWebhook(id, { token, reason } = {}) { + await this.rest.delete(Routes.webhook(id, token), { auth: !token, reason }); + } + + /** + * Options used when fetching an invite from Discord. + * @typedef {Object} ClientFetchInviteOptions + * @property {Snowflake} [guildScheduledEventId] The id of the guild scheduled event to include with + * the invite + */ + + /** + * Obtains an invite from Discord. + * @param {InviteResolvable} invite Invite code or URL + * @param {ClientFetchInviteOptions} [options] Options for fetching the invite + * @returns {Promise<Invite>} + * @example + * client.fetchInvite('https://discord.gg/djs') + * .then(invite => console.log(`Obtained invite with code: ${invite.code}`)) + * .catch(console.error); + */ + async fetchInvite(invite, options) { + const code = DataResolver.resolveInviteCode(invite); + const query = makeURLSearchParams({ + with_counts: true, + with_expiration: true, + guild_scheduled_event_id: options?.guildScheduledEventId, + }); + const data = await this.rest.get(Routes.invite(code), { query }); + return new Invite(this, data); + } + + /** + * Obtains a template from Discord. + * @param {GuildTemplateResolvable} template Template code or URL + * @returns {Promise<GuildTemplate>} + * @example + * client.fetchGuildTemplate('https://discord.new/FKvmczH2HyUf') + * .then(template => console.log(`Obtained template with code: ${template.code}`)) + * .catch(console.error); + */ + async fetchGuildTemplate(template) { + const code = DataResolver.resolveGuildTemplateCode(template); + const data = await this.rest.get(Routes.template(code)); + return new GuildTemplate(this, data); + } + + /** + * Obtains a webhook from Discord. + * @param {Snowflake} id The webhook's id + * @param {string} [token] Token for the webhook + * @returns {Promise<Webhook>} + * @example + * client.fetchWebhook('id', 'token') + * .then(webhook => console.log(`Obtained webhook with name: ${webhook.name}`)) + * .catch(console.error); + */ + async fetchWebhook(id, token) { + const data = await this.rest.get(Routes.webhook(id, token), { auth: token === undefined }); + return new Webhook(this, { token, ...data }); + } + + /** + * Obtains the available voice regions from Discord. + * @returns {Promise<Collection<string, VoiceRegion>>} + * @example + * client.fetchVoiceRegions() + * .then(regions => console.log(`Available regions are: ${regions.map(region => region.name).join(', ')}`)) + * .catch(console.error); + */ + async fetchVoiceRegions() { + const apiRegions = await this.rest.get(Routes.voiceRegions()); + const regions = new Collection(); + for (const region of apiRegions) regions.set(region.id, new VoiceRegion(region)); + return regions; + } + + /** + * Obtains a sticker from Discord. + * @param {Snowflake} id The sticker's id + * @returns {Promise<Sticker>} + * @example + * client.fetchSticker('id') + * .then(sticker => console.log(`Obtained sticker with name: ${sticker.name}`)) + * .catch(console.error); + */ + async fetchSticker(id) { + const data = await this.rest.get(Routes.sticker(id)); + return new Sticker(this, data); + } + + /** + * Obtains the list of sticker packs available to Nitro subscribers from Discord. + * @returns {Promise<Collection<Snowflake, StickerPack>>} + * @example + * client.fetchPremiumStickerPacks() + * .then(packs => console.log(`Available sticker packs are: ${packs.map(pack => pack.name).join(', ')}`)) + * .catch(console.error); + */ + async fetchPremiumStickerPacks() { + const data = await this.rest.get(Routes.nitroStickerPacks()); + return new Collection(data.sticker_packs.map(p => [p.id, new StickerPack(this, p)])); + } + + /** + * Obtains a guild preview from Discord, available for all guilds the bot is in and all Discoverable guilds. + * @param {GuildResolvable} guild The guild to fetch the preview for + * @returns {Promise<GuildPreview>} + */ + async fetchGuildPreview(guild) { + const id = this.guilds.resolveId(guild); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'guild', 'GuildResolvable'); + const data = await this.rest.get(Routes.guildPreview(id)); + return new GuildPreview(this, data); + } + + /** + * Obtains the widget data of a guild from Discord, available for guilds with the widget enabled. + * @param {GuildResolvable} guild The guild to fetch the widget data for + * @returns {Promise<Widget>} + */ + async fetchGuildWidget(guild) { + const id = this.guilds.resolveId(guild); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'guild', 'GuildResolvable'); + const data = await this.rest.get(Routes.guildWidgetJSON(id)); + return new Widget(this, data); + } + + /** + * Options for {@link Client#generateInvite}. + * @typedef {Object} InviteGenerationOptions + * @property {OAuth2Scopes[]} scopes Scopes that should be requested + * @property {PermissionResolvable} [permissions] Permissions to request + * @property {GuildResolvable} [guild] Guild to preselect + * @property {boolean} [disableGuildSelect] Whether to disable the guild selection + */ + + /** + * Generates a link that can be used to invite the bot to a guild. + * @param {InviteGenerationOptions} [options={}] Options for the invite + * @returns {string} + * @example + * const link = client.generateInvite({ + * scopes: [OAuth2Scopes.ApplicationsCommands], + * }); + * console.log(`Generated application invite link: ${link}`); + * @example + * const link = client.generateInvite({ + * permissions: [ + * PermissionFlagsBits.SendMessages, + * PermissionFlagsBits.ManageGuild, + * PermissionFlagsBits.MentionEveryone, + * ], + * scopes: [OAuth2Scopes.Bot], + * }); + * console.log(`Generated bot invite link: ${link}`); + */ + generateInvite(options = {}) { + if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); + if (!this.application) throw new DiscordjsError(ErrorCodes.ClientNotReady, 'generate an invite link'); + + const { scopes } = options; + if (scopes === undefined) { + throw new DiscordjsTypeError(ErrorCodes.InvalidMissingScopes); + } + if (!Array.isArray(scopes)) { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'scopes', 'Array of Invite Scopes', true); + } + if (!scopes.some(scope => [OAuth2Scopes.Bot, OAuth2Scopes.ApplicationsCommands].includes(scope))) { + throw new DiscordjsTypeError(ErrorCodes.InvalidMissingScopes); + } + if (!scopes.includes(OAuth2Scopes.Bot) && options.permissions) { + throw new DiscordjsTypeError(ErrorCodes.InvalidScopesWithPermissions); + } + const validScopes = Object.values(OAuth2Scopes); + const invalidScope = scopes.find(scope => !validScopes.includes(scope)); + if (invalidScope) { + throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array', 'scopes', invalidScope); + } + + const query = makeURLSearchParams({ + client_id: this.application.id, + scope: scopes.join(' '), + disable_guild_select: options.disableGuildSelect, + }); + + if (options.permissions) { + const permissions = PermissionsBitField.resolve(options.permissions); + if (permissions) query.set('permissions', permissions.toString()); + } + + if (options.guild) { + const guildId = this.guilds.resolveId(options.guild); + if (!guildId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options.guild', 'GuildResolvable'); + query.set('guild_id', guildId); + } + + return `${this.options.rest.api}${Routes.oauth2Authorization()}?${query}`; + } + + toJSON() { + return super.toJSON({ + actions: false, + presence: false, + }); + } + + /** + * Partially censored client token for debug logging purposes. + * @type {?string} + * @readonly + * @private + */ + get _censoredToken() { + if (!this.token) return null; + + return this.token + .split('.') + .map((val, i) => (i > 1 ? val.replace(/./g, '*') : val)) + .join('.'); + } + + /** + * Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script + * with the client as `this`. + * @param {string} script Script to eval + * @returns {*} + * @private + */ + _eval(script) { + return eval(script); + } + + /** + * Validates the client options. + * @param {ClientOptions} [options=this.options] Options to validate + * @private + */ + _validateOptions(options = this.options) { + if (options.intents === undefined) { + throw new DiscordjsTypeError(ErrorCodes.ClientMissingIntents); + } else { + options.intents = new IntentsBitField(options.intents).freeze(); + } + if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount < 1) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'shardCount', 'a number greater than or equal to 1'); + } + if (options.shards && !(options.shards === 'auto' || Array.isArray(options.shards))) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'shards', "'auto', a number or array of numbers"); + } + if (options.shards && !options.shards.length) throw new DiscordjsRangeError(ErrorCodes.ClientInvalidProvidedShards); + if (typeof options.makeCache !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'makeCache', 'a function'); + } + if (typeof options.sweepers !== 'object' || options.sweepers === null) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'sweepers', 'an object'); + } + if (!Array.isArray(options.partials)) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'partials', 'an Array'); + } + if (typeof options.waitGuildTimeout !== 'number' || isNaN(options.waitGuildTimeout)) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'waitGuildTimeout', 'a number'); + } + if (typeof options.failIfNotExists !== 'boolean') { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'failIfNotExists', 'a boolean'); + } + if ( + (typeof options.allowedMentions !== 'object' && options.allowedMentions !== undefined) || + options.allowedMentions === null + ) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'allowedMentions', 'an object'); + } + if (typeof options.presence !== 'object' || options.presence === null) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'presence', 'an object'); + } + if (typeof options.ws !== 'object' || options.ws === null) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'ws', 'an object'); + } + if (typeof options.rest !== 'object' || options.rest === null) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'rest', 'an object'); + } + if (typeof options.jsonTransformer !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'jsonTransformer', 'a function'); + } + } +} + +module.exports = Client; + +/** + * @class SnowflakeUtil + * @classdesc This class is an alias for {@link https://www.npmjs.com/package/@sapphire/snowflake @sapphire/snowflake}'s + * `DiscordSnowflake` class. + * + * Check their documentation + * {@link https://www.sapphirejs.dev/docs/Documentation/api-utilities/classes/sapphire_snowflake.Snowflake here} + * ({@link https://www.sapphirejs.dev/docs/Guide/utilities/snowflake guide}) + * to see what you can do. + * @hideconstructor + */ + +/** + * A {@link https://developer.twitter.com/en/docs/twitter-ids Twitter snowflake}, + * except the epoch is 2015-01-01T00:00:00.000Z. + * + * If we have a snowflake '266241948824764416' we can represent it as binary: + * ``` + * 64 22 17 12 0 + * 000000111011000111100001101001000101000000 00001 00000 000000000000 + * number of milliseconds since Discord epoch worker pid increment + * ``` + * @typedef {string} Snowflake + */ + +/** + * Emitted for general debugging information. + * @event Client#debug + * @param {string} info The debug information + */ + +/** + * Emitted for general warnings. + * @event Client#warn + * @param {string} info The warning + */ + +/** + * @external Collection + * @see {@link https://discord.js.org/docs/packages/collection/stable/Collection:Class} + */ + +/** + * @external ImageURLOptions + * @see {@link https://discord.js.org/docs/packages/rest/stable/ImageURLOptions:Interface} + */ + +/** + * @external BaseImageURLOptions + * @see {@link https://discord.js.org/docs/packages/rest/stable/BaseImageURLOptions:Interface} + */ diff --git a/node_modules/discord.js/src/client/WebhookClient.js b/node_modules/discord.js/src/client/WebhookClient.js new file mode 100644 index 0000000..5bb6412 --- /dev/null +++ b/node_modules/discord.js/src/client/WebhookClient.js @@ -0,0 +1,103 @@ +'use strict'; + +const BaseClient = require('./BaseClient'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const Webhook = require('../structures/Webhook'); +const { parseWebhookURL } = require('../util/Util'); + +/** + * The webhook client. + * @implements {Webhook} + * @extends {BaseClient} + */ +class WebhookClient extends BaseClient { + /** + * Represents the credentials used for a webhook in the form of its id and token. + * @typedef {Object} WebhookClientDataIdWithToken + * @property {Snowflake} id The webhook's id + * @property {string} token The webhook's token + */ + + /** + * Represents the credentials used for a webhook in the form of a URL. + * @typedef {Object} WebhookClientDataURL + * @property {string} url The full URL for the webhook + */ + + /** + * Represents the credentials used for a webhook. + * @typedef {WebhookClientDataIdWithToken|WebhookClientDataURL} WebhookClientData + */ + + /** + * Options for a webhook client. + * @typedef {Object} WebhookClientOptions + * @property {MessageMentionOptions} [allowedMentions] Default value for {@link BaseMessageOptions#allowedMentions} + * @property {RESTOptions} [rest] Options for the REST manager + */ + + /** + * @param {WebhookClientData} data The data of the webhook + * @param {WebhookClientOptions} [options] Options for the webhook client + */ + constructor(data, options) { + super(options); + Object.defineProperty(this, 'client', { value: this }); + let { id, token } = data; + + if ('url' in data) { + const parsed = parseWebhookURL(data.url); + if (!parsed) { + throw new DiscordjsError(ErrorCodes.WebhookURLInvalid); + } + + ({ id, token } = parsed); + } + + this.id = id; + Object.defineProperty(this, 'token', { value: token, writable: true, configurable: true }); + } + + /** + * The options the webhook client was instantiated with. + * @type {WebhookClientOptions} + * @name WebhookClient#options + */ + + // These are here only for documentation purposes - they are implemented by Webhook + /* eslint-disable no-empty-function, valid-jsdoc */ + /** + * Sends a message with this webhook. + * @param {string|MessagePayload|WebhookMessageCreateOptions} options The content for the reply + * @returns {Promise<APIMessage>} + */ + send() {} + + /** + * Gets a message that was sent by this webhook. + * @param {Snowflake} message The id of the message to fetch + * @param {WebhookFetchMessageOptions} [options={}] The options to provide to fetch the message. + * @returns {Promise<APIMessage>} Returns the message sent by this webhook + */ + fetchMessage() {} + + /** + * Edits a message that was sent by this webhook. + * @param {MessageResolvable} message The message to edit + * @param {string|MessagePayload|WebhookMessageEditOptions} options The options to provide + * @returns {Promise<APIMessage>} Returns the message edited by this webhook + */ + editMessage() {} + + sendSlackMessage() {} + edit() {} + delete() {} + deleteMessage() {} + get createdTimestamp() {} + get createdAt() {} + get url() {} +} + +Webhook.applyToClass(WebhookClient); + +module.exports = WebhookClient; diff --git a/node_modules/discord.js/src/client/actions/Action.js b/node_modules/discord.js/src/client/actions/Action.js new file mode 100644 index 0000000..6c04a64 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/Action.js @@ -0,0 +1,120 @@ +'use strict'; + +const Partials = require('../../util/Partials'); + +/* + +ABOUT ACTIONS + +Actions are similar to WebSocket Packet Handlers, but since introducing +the REST API methods, in order to prevent rewriting code to handle data, +"actions" have been introduced. They're basically what Packet Handlers +used to be but they're strictly for manipulating data and making sure +that WebSocket events don't clash with REST methods. + +*/ + +class GenericAction { + constructor(client) { + this.client = client; + } + + handle(data) { + return data; + } + + getPayload(data, manager, id, partialType, cache) { + return this.client.options.partials.includes(partialType) ? manager._add(data, cache) : manager.cache.get(id); + } + + getChannel(data) { + const payloadData = {}; + const id = data.channel_id ?? data.id; + + if ('recipients' in data) { + payloadData.recipients = data.recipients; + } else { + // Try to resolve the recipient, but do not add the client user. + const recipient = data.author ?? data.user ?? { id: data.user_id }; + if (recipient.id !== this.client.user.id) payloadData.recipients = [recipient]; + } + + if (id !== undefined) payloadData.id = id; + if ('guild_id' in data) payloadData.guild_id = data.guild_id; + if ('last_message_id' in data) payloadData.last_message_id = data.last_message_id; + + return ( + data[this.client.actions.injectedChannel] ?? + this.getPayload(payloadData, this.client.channels, id, Partials.Channel) + ); + } + + getMessage(data, channel, cache) { + const id = data.message_id ?? data.id; + return ( + data[this.client.actions.injectedMessage] ?? + this.getPayload( + { + id, + channel_id: channel.id, + guild_id: data.guild_id ?? channel.guild?.id, + }, + channel.messages, + id, + Partials.Message, + cache, + ) + ); + } + + getReaction(data, message, user) { + const id = data.emoji.id ?? decodeURIComponent(data.emoji.name); + return this.getPayload( + { + emoji: data.emoji, + count: message.partial ? null : 0, + me: user?.id === this.client.user.id, + }, + message.reactions, + id, + Partials.Reaction, + ); + } + + getMember(data, guild) { + return this.getPayload(data, guild.members, data.user.id, Partials.GuildMember); + } + + getUser(data) { + const id = data.user_id; + return data[this.client.actions.injectedUser] ?? this.getPayload({ id }, this.client.users, id, Partials.User); + } + + getUserFromMember(data) { + if (data.guild_id && data.member?.user) { + const guild = this.client.guilds.cache.get(data.guild_id); + if (guild) { + return guild.members._add(data.member).user; + } else { + return this.client.users._add(data.member.user); + } + } + return this.getUser(data); + } + + getScheduledEvent(data, guild) { + const id = data.guild_scheduled_event_id ?? data.id; + return this.getPayload( + { id, guild_id: data.guild_id ?? guild.id }, + guild.scheduledEvents, + id, + Partials.GuildScheduledEvent, + ); + } + + getThreadMember(id, manager) { + return this.getPayload({ user_id: id }, manager, id, Partials.ThreadMember, false); + } +} + +module.exports = GenericAction; diff --git a/node_modules/discord.js/src/client/actions/ActionsManager.js b/node_modules/discord.js/src/client/actions/ActionsManager.js new file mode 100644 index 0000000..301a6a9 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ActionsManager.js @@ -0,0 +1,79 @@ +'use strict'; + +class ActionsManager { + // These symbols represent fully built data that we inject at times when calling actions manually. + // Action#getUser, for example, will return the injected data (which is assumed to be a built structure) + // instead of trying to make it from provided data + injectedUser = Symbol('djs.actions.injectedUser'); + injectedChannel = Symbol('djs.actions.injectedChannel'); + injectedMessage = Symbol('djs.actions.injectedMessage'); + + constructor(client) { + this.client = client; + + this.register(require('./ApplicationCommandPermissionsUpdate')); + this.register(require('./AutoModerationActionExecution')); + this.register(require('./AutoModerationRuleCreate')); + this.register(require('./AutoModerationRuleDelete')); + this.register(require('./AutoModerationRuleUpdate')); + this.register(require('./ChannelCreate')); + this.register(require('./ChannelDelete')); + this.register(require('./ChannelUpdate')); + this.register(require('./GuildAuditLogEntryCreate')); + this.register(require('./GuildBanAdd')); + this.register(require('./GuildBanRemove')); + this.register(require('./GuildChannelsPositionUpdate')); + this.register(require('./GuildDelete')); + this.register(require('./GuildEmojiCreate')); + this.register(require('./GuildEmojiDelete')); + this.register(require('./GuildEmojiUpdate')); + this.register(require('./GuildEmojisUpdate')); + this.register(require('./GuildIntegrationsUpdate')); + this.register(require('./GuildMemberRemove')); + this.register(require('./GuildMemberUpdate')); + this.register(require('./GuildRoleCreate')); + this.register(require('./GuildRoleDelete')); + this.register(require('./GuildRoleUpdate')); + this.register(require('./GuildRolesPositionUpdate')); + this.register(require('./GuildScheduledEventCreate')); + this.register(require('./GuildScheduledEventDelete')); + this.register(require('./GuildScheduledEventUpdate')); + this.register(require('./GuildScheduledEventUserAdd')); + this.register(require('./GuildScheduledEventUserRemove')); + this.register(require('./GuildStickerCreate')); + this.register(require('./GuildStickerDelete')); + this.register(require('./GuildStickerUpdate')); + this.register(require('./GuildStickersUpdate')); + this.register(require('./GuildUpdate')); + this.register(require('./InteractionCreate')); + this.register(require('./InviteCreate')); + this.register(require('./InviteDelete')); + this.register(require('./MessageCreate')); + this.register(require('./MessageDelete')); + this.register(require('./MessageDeleteBulk')); + this.register(require('./MessageReactionAdd')); + this.register(require('./MessageReactionRemove')); + this.register(require('./MessageReactionRemoveAll')); + this.register(require('./MessageReactionRemoveEmoji')); + this.register(require('./MessageUpdate')); + this.register(require('./PresenceUpdate')); + this.register(require('./StageInstanceCreate')); + this.register(require('./StageInstanceDelete')); + this.register(require('./StageInstanceUpdate')); + this.register(require('./ThreadCreate')); + this.register(require('./ThreadDelete')); + this.register(require('./ThreadListSync')); + this.register(require('./ThreadMemberUpdate')); + this.register(require('./ThreadMembersUpdate')); + this.register(require('./TypingStart')); + this.register(require('./UserUpdate')); + this.register(require('./VoiceStateUpdate')); + this.register(require('./WebhooksUpdate')); + } + + register(Action) { + this[Action.name.replace(/Action$/, '')] = new Action(this.client); + } +} + +module.exports = ActionsManager; diff --git a/node_modules/discord.js/src/client/actions/ApplicationCommandPermissionsUpdate.js b/node_modules/discord.js/src/client/actions/ApplicationCommandPermissionsUpdate.js new file mode 100644 index 0000000..f2bc214 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ApplicationCommandPermissionsUpdate.js @@ -0,0 +1,34 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +/** + * The data received in the {@link Client#event:applicationCommandPermissionsUpdate} event + * @typedef {Object} ApplicationCommandPermissionsUpdateData + * @property {Snowflake} id The id of the command or global entity that was updated + * @property {Snowflake} guildId The id of the guild in which permissions were updated + * @property {Snowflake} applicationId The id of the application that owns the command or entity being updated + * @property {ApplicationCommandPermissions[]} permissions The updated permissions + */ + +class ApplicationCommandPermissionsUpdateAction extends Action { + handle(data) { + const client = this.client; + /** + * Emitted whenever permissions for an application command in a guild were updated. + * <warn>This includes permission updates for other applications in addition to the logged in client, + * check `data.applicationId` to verify which application the update is for</warn> + * @event Client#applicationCommandPermissionsUpdate + * @param {ApplicationCommandPermissionsUpdateData} data The updated permissions + */ + client.emit(Events.ApplicationCommandPermissionsUpdate, { + permissions: data.permissions, + id: data.id, + guildId: data.guild_id, + applicationId: data.application_id, + }); + } +} + +module.exports = ApplicationCommandPermissionsUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/AutoModerationActionExecution.js b/node_modules/discord.js/src/client/actions/AutoModerationActionExecution.js new file mode 100644 index 0000000..ad60116 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/AutoModerationActionExecution.js @@ -0,0 +1,26 @@ +'use strict'; + +const Action = require('./Action'); +const AutoModerationActionExecution = require('../../structures/AutoModerationActionExecution'); +const Events = require('../../util/Events'); + +class AutoModerationActionExecutionAction extends Action { + handle(data) { + const { client } = this; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + /** + * Emitted whenever an auto moderation rule is triggered. + * <info>This event requires the {@link PermissionFlagsBits.ManageGuild} permission.</info> + * @event Client#autoModerationActionExecution + * @param {AutoModerationActionExecution} autoModerationActionExecution The data of the execution + */ + client.emit(Events.AutoModerationActionExecution, new AutoModerationActionExecution(data, guild)); + } + + return {}; + } +} + +module.exports = AutoModerationActionExecutionAction; diff --git a/node_modules/discord.js/src/client/actions/AutoModerationRuleCreate.js b/node_modules/discord.js/src/client/actions/AutoModerationRuleCreate.js new file mode 100644 index 0000000..775b1d3 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/AutoModerationRuleCreate.js @@ -0,0 +1,27 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class AutoModerationRuleCreateAction extends Action { + handle(data) { + const { client } = this; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const autoModerationRule = guild.autoModerationRules._add(data); + + /** + * Emitted whenever an auto moderation rule is created. + * <info>This event requires the {@link PermissionFlagsBits.ManageGuild} permission.</info> + * @event Client#autoModerationRuleCreate + * @param {AutoModerationRule} autoModerationRule The created auto moderation rule + */ + client.emit(Events.AutoModerationRuleCreate, autoModerationRule); + } + + return {}; + } +} + +module.exports = AutoModerationRuleCreateAction; diff --git a/node_modules/discord.js/src/client/actions/AutoModerationRuleDelete.js b/node_modules/discord.js/src/client/actions/AutoModerationRuleDelete.js new file mode 100644 index 0000000..641822c --- /dev/null +++ b/node_modules/discord.js/src/client/actions/AutoModerationRuleDelete.js @@ -0,0 +1,31 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class AutoModerationRuleDeleteAction extends Action { + handle(data) { + const { client } = this; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const autoModerationRule = guild.autoModerationRules.cache.get(data.id); + + if (autoModerationRule) { + guild.autoModerationRules.cache.delete(autoModerationRule.id); + + /** + * Emitted whenever an auto moderation rule is deleted. + * <info>This event requires the {@link PermissionFlagsBits.ManageGuild} permission.</info> + * @event Client#autoModerationRuleDelete + * @param {AutoModerationRule} autoModerationRule The deleted auto moderation rule + */ + client.emit(Events.AutoModerationRuleDelete, autoModerationRule); + } + } + + return {}; + } +} + +module.exports = AutoModerationRuleDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/AutoModerationRuleUpdate.js b/node_modules/discord.js/src/client/actions/AutoModerationRuleUpdate.js new file mode 100644 index 0000000..56e3956 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/AutoModerationRuleUpdate.js @@ -0,0 +1,29 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class AutoModerationRuleUpdateAction extends Action { + handle(data) { + const { client } = this; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const oldAutoModerationRule = guild.autoModerationRules.cache.get(data.id)?._clone() ?? null; + const newAutoModerationRule = guild.autoModerationRules._add(data); + + /** + * Emitted whenever an auto moderation rule gets updated. + * <info>This event requires the {@link PermissionFlagsBits.ManageGuild} permission.</info> + * @event Client#autoModerationRuleUpdate + * @param {?AutoModerationRule} oldAutoModerationRule The auto moderation rule before the update + * @param {AutoModerationRule} newAutoModerationRule The auto moderation rule after the update + */ + client.emit(Events.AutoModerationRuleUpdate, oldAutoModerationRule, newAutoModerationRule); + } + + return {}; + } +} + +module.exports = AutoModerationRuleUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/ChannelCreate.js b/node_modules/discord.js/src/client/actions/ChannelCreate.js new file mode 100644 index 0000000..fdf8ddd --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ChannelCreate.js @@ -0,0 +1,23 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class ChannelCreateAction extends Action { + handle(data) { + const client = this.client; + const existing = client.channels.cache.has(data.id); + const channel = client.channels._add(data); + if (!existing && channel) { + /** + * Emitted whenever a guild channel is created. + * @event Client#channelCreate + * @param {GuildChannel} channel The channel that was created + */ + client.emit(Events.ChannelCreate, channel); + } + return { channel }; + } +} + +module.exports = ChannelCreateAction; diff --git a/node_modules/discord.js/src/client/actions/ChannelDelete.js b/node_modules/discord.js/src/client/actions/ChannelDelete.js new file mode 100644 index 0000000..acf03d9 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ChannelDelete.js @@ -0,0 +1,23 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class ChannelDeleteAction extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.id); + + if (channel) { + client.channels._remove(channel.id); + /** + * Emitted whenever a channel is deleted. + * @event Client#channelDelete + * @param {DMChannel|GuildChannel} channel The channel that was deleted + */ + client.emit(Events.ChannelDelete, channel); + } + } +} + +module.exports = ChannelDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/ChannelUpdate.js b/node_modules/discord.js/src/client/actions/ChannelUpdate.js new file mode 100644 index 0000000..7ca331a --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ChannelUpdate.js @@ -0,0 +1,42 @@ +'use strict'; + +const Action = require('./Action'); +const { createChannel } = require('../../util/Channels'); + +class ChannelUpdateAction extends Action { + handle(data) { + const client = this.client; + let channel = client.channels.cache.get(data.id); + + if (channel) { + const old = channel._update(data); + + if (channel.type !== data.type) { + const newChannel = createChannel(this.client, data, channel.guild); + + if (!newChannel) { + this.client.channels.cache.delete(channel.id); + return {}; + } + + if (channel.isTextBased() && newChannel.isTextBased()) { + for (const [id, message] of channel.messages.cache) newChannel.messages.cache.set(id, message); + } + + channel = newChannel; + this.client.channels.cache.set(channel.id, channel); + } + + return { + old, + updated: channel, + }; + } else { + client.channels._add(data); + } + + return {}; + } +} + +module.exports = ChannelUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildAuditLogEntryCreate.js b/node_modules/discord.js/src/client/actions/GuildAuditLogEntryCreate.js new file mode 100644 index 0000000..fa16de6 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildAuditLogEntryCreate.js @@ -0,0 +1,29 @@ +'use strict'; + +const Action = require('./Action'); +const GuildAuditLogsEntry = require('../../structures/GuildAuditLogsEntry'); +const Events = require('../../util/Events'); + +class GuildAuditLogEntryCreateAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + let auditLogEntry; + + if (guild) { + auditLogEntry = new GuildAuditLogsEntry(guild, data); + + /** + * Emitted whenever a guild audit log entry is created. + * @event Client#guildAuditLogEntryCreate + * @param {GuildAuditLogsEntry} auditLogEntry The entry that was created + * @param {Guild} guild The guild where the entry was created + */ + client.emit(Events.GuildAuditLogEntryCreate, auditLogEntry, guild); + } + + return { auditLogEntry }; + } +} + +module.exports = GuildAuditLogEntryCreateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildBanAdd.js b/node_modules/discord.js/src/client/actions/GuildBanAdd.js new file mode 100644 index 0000000..2ef4b11 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildBanAdd.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildBanAdd extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + /** + * Emitted whenever a member is banned from a guild. + * @event Client#guildBanAdd + * @param {GuildBan} ban The ban that occurred + */ + if (guild) client.emit(Events.GuildBanAdd, guild.bans._add(data)); + } +} + +module.exports = GuildBanAdd; diff --git a/node_modules/discord.js/src/client/actions/GuildBanRemove.js b/node_modules/discord.js/src/client/actions/GuildBanRemove.js new file mode 100644 index 0000000..8048efd --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildBanRemove.js @@ -0,0 +1,25 @@ +'use strict'; + +const Action = require('./Action'); +const GuildBan = require('../../structures/GuildBan'); +const Events = require('../../util/Events'); + +class GuildBanRemove extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + /** + * Emitted whenever a member is unbanned from a guild. + * @event Client#guildBanRemove + * @param {GuildBan} ban The ban that was removed + */ + if (guild) { + const ban = guild.bans.cache.get(data.user.id) ?? new GuildBan(client, data, guild); + guild.bans.cache.delete(ban.user.id); + client.emit(Events.GuildBanRemove, ban); + } + } +} + +module.exports = GuildBanRemove; diff --git a/node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js b/node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js new file mode 100644 index 0000000..a393167 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js @@ -0,0 +1,21 @@ +'use strict'; + +const Action = require('./Action'); + +class GuildChannelsPositionUpdate extends Action { + handle(data) { + const client = this.client; + + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + for (const partialChannel of data.channels) { + const channel = guild.channels.cache.get(partialChannel.id); + if (channel) channel.rawPosition = partialChannel.position; + } + } + + return { guild }; + } +} + +module.exports = GuildChannelsPositionUpdate; diff --git a/node_modules/discord.js/src/client/actions/GuildDelete.js b/node_modules/discord.js/src/client/actions/GuildDelete.js new file mode 100644 index 0000000..eb0a44d --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildDelete.js @@ -0,0 +1,44 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildDeleteAction extends Action { + handle(data) { + const client = this.client; + + let guild = client.guilds.cache.get(data.id); + if (guild) { + if (data.unavailable) { + // Guild is unavailable + guild.available = false; + + /** + * Emitted whenever a guild becomes unavailable, likely due to a server outage. + * @event Client#guildUnavailable + * @param {Guild} guild The guild that has become unavailable + */ + client.emit(Events.GuildUnavailable, guild); + + // Stops the GuildDelete packet thinking a guild was actually deleted, + // handles emitting of event itself + return; + } + + for (const channel of guild.channels.cache.values()) this.client.channels._remove(channel.id); + client.voice.adapters.get(data.id)?.destroy(); + + // Delete guild + client.guilds.cache.delete(guild.id); + + /** + * Emitted whenever a guild kicks the client or the guild is deleted/left. + * @event Client#guildDelete + * @param {Guild} guild The guild that was deleted + */ + client.emit(Events.GuildDelete, guild); + } + } +} + +module.exports = GuildDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/GuildEmojiCreate.js b/node_modules/discord.js/src/client/actions/GuildEmojiCreate.js new file mode 100644 index 0000000..61858cf --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildEmojiCreate.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildEmojiCreateAction extends Action { + handle(guild, createdEmoji) { + const already = guild.emojis.cache.has(createdEmoji.id); + const emoji = guild.emojis._add(createdEmoji); + /** + * Emitted whenever a custom emoji is created in a guild. + * @event Client#emojiCreate + * @param {GuildEmoji} emoji The emoji that was created + */ + if (!already) this.client.emit(Events.GuildEmojiCreate, emoji); + return { emoji }; + } +} + +module.exports = GuildEmojiCreateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildEmojiDelete.js b/node_modules/discord.js/src/client/actions/GuildEmojiDelete.js new file mode 100644 index 0000000..e3373c2 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildEmojiDelete.js @@ -0,0 +1,19 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildEmojiDeleteAction extends Action { + handle(emoji) { + emoji.guild.emojis.cache.delete(emoji.id); + /** + * Emitted whenever a custom emoji is deleted in a guild. + * @event Client#emojiDelete + * @param {GuildEmoji} emoji The emoji that was deleted + */ + this.client.emit(Events.GuildEmojiDelete, emoji); + return { emoji }; + } +} + +module.exports = GuildEmojiDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js b/node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js new file mode 100644 index 0000000..6bf9657 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildEmojiUpdateAction extends Action { + handle(current, data) { + const old = current._update(data); + /** + * Emitted whenever a custom emoji is updated in a guild. + * @event Client#emojiUpdate + * @param {GuildEmoji} oldEmoji The old emoji + * @param {GuildEmoji} newEmoji The new emoji + */ + this.client.emit(Events.GuildEmojiUpdate, old, current); + return { emoji: current }; + } +} + +module.exports = GuildEmojiUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js b/node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js new file mode 100644 index 0000000..7829db1 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js @@ -0,0 +1,34 @@ +'use strict'; + +const Action = require('./Action'); + +class GuildEmojisUpdateAction extends Action { + handle(data) { + const guild = this.client.guilds.cache.get(data.guild_id); + if (!guild?.emojis) return; + + const deletions = new Map(guild.emojis.cache); + + for (const emoji of data.emojis) { + // Determine type of emoji event + const cachedEmoji = guild.emojis.cache.get(emoji.id); + if (cachedEmoji) { + deletions.delete(emoji.id); + if (!cachedEmoji.equals(emoji)) { + // Emoji updated + this.client.actions.GuildEmojiUpdate.handle(cachedEmoji, emoji); + } + } else { + // Emoji added + this.client.actions.GuildEmojiCreate.handle(guild, emoji); + } + } + + for (const emoji of deletions.values()) { + // Emoji deleted + this.client.actions.GuildEmojiDelete.handle(emoji); + } + } +} + +module.exports = GuildEmojisUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js b/node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js new file mode 100644 index 0000000..28b9bbb --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js @@ -0,0 +1,19 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildIntegrationsUpdate extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + /** + * Emitted whenever a guild integration is updated + * @event Client#guildIntegrationsUpdate + * @param {Guild} guild The guild whose integrations were updated + */ + if (guild) client.emit(Events.GuildIntegrationsUpdate, guild); + } +} + +module.exports = GuildIntegrationsUpdate; diff --git a/node_modules/discord.js/src/client/actions/GuildMemberRemove.js b/node_modules/discord.js/src/client/actions/GuildMemberRemove.js new file mode 100644 index 0000000..45eb6c4 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildMemberRemove.js @@ -0,0 +1,31 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); +const Status = require('../../util/Status'); + +class GuildMemberRemoveAction extends Action { + handle(data, shard) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + let member = null; + if (guild) { + member = this.getMember({ user: data.user }, guild); + guild.memberCount--; + if (member) { + guild.members.cache.delete(member.id); + /** + * Emitted whenever a member leaves a guild, or is kicked. + * @event Client#guildMemberRemove + * @param {GuildMember} member The member that has left/been kicked from the guild + */ + if (shard.status === Status.Ready) client.emit(Events.GuildMemberRemove, member); + } + guild.presences.cache.delete(data.user.id); + guild.voiceStates.cache.delete(data.user.id); + } + return { guild, member }; + } +} + +module.exports = GuildMemberRemoveAction; diff --git a/node_modules/discord.js/src/client/actions/GuildMemberUpdate.js b/node_modules/discord.js/src/client/actions/GuildMemberUpdate.js new file mode 100644 index 0000000..491b361 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildMemberUpdate.js @@ -0,0 +1,44 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); +const Status = require('../../util/Status'); + +class GuildMemberUpdateAction extends Action { + handle(data, shard) { + const { client } = this; + if (data.user.username) { + const user = client.users.cache.get(data.user.id); + if (!user) { + client.users._add(data.user); + } else if (!user._equals(data.user)) { + client.actions.UserUpdate.handle(data.user); + } + } + + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + const member = this.getMember({ user: data.user }, guild); + if (member) { + const old = member._update(data); + /** + * Emitted whenever a guild member changes - i.e. new role, removed role, nickname. + * @event Client#guildMemberUpdate + * @param {GuildMember} oldMember The member before the update + * @param {GuildMember} newMember The member after the update + */ + if (shard.status === Status.Ready && !member.equals(old)) client.emit(Events.GuildMemberUpdate, old, member); + } else { + const newMember = guild.members._add(data); + /** + * Emitted whenever a member becomes available. + * @event Client#guildMemberAvailable + * @param {GuildMember} member The member that became available + */ + this.client.emit(Events.GuildMemberAvailable, newMember); + } + } + } +} + +module.exports = GuildMemberUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildRoleCreate.js b/node_modules/discord.js/src/client/actions/GuildRoleCreate.js new file mode 100644 index 0000000..461443b --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildRoleCreate.js @@ -0,0 +1,25 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildRoleCreate extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + let role; + if (guild) { + const already = guild.roles.cache.has(data.role.id); + role = guild.roles._add(data.role); + /** + * Emitted whenever a role is created. + * @event Client#roleCreate + * @param {Role} role The role that was created + */ + if (!already) client.emit(Events.GuildRoleCreate, role); + } + return { role }; + } +} + +module.exports = GuildRoleCreate; diff --git a/node_modules/discord.js/src/client/actions/GuildRoleDelete.js b/node_modules/discord.js/src/client/actions/GuildRoleDelete.js new file mode 100644 index 0000000..e043a1a --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildRoleDelete.js @@ -0,0 +1,29 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildRoleDeleteAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + let role; + + if (guild) { + role = guild.roles.cache.get(data.role_id); + if (role) { + guild.roles.cache.delete(data.role_id); + /** + * Emitted whenever a guild role is deleted. + * @event Client#roleDelete + * @param {Role} role The role that was deleted + */ + client.emit(Events.GuildRoleDelete, role); + } + } + + return { role }; + } +} + +module.exports = GuildRoleDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/GuildRoleUpdate.js b/node_modules/discord.js/src/client/actions/GuildRoleUpdate.js new file mode 100644 index 0000000..b0632c5 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildRoleUpdate.js @@ -0,0 +1,39 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildRoleUpdateAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + let old = null; + + const role = guild.roles.cache.get(data.role.id); + if (role) { + old = role._update(data.role); + /** + * Emitted whenever a guild role is updated. + * @event Client#roleUpdate + * @param {Role} oldRole The role before the update + * @param {Role} newRole The role after the update + */ + client.emit(Events.GuildRoleUpdate, old, role); + } + + return { + old, + updated: role, + }; + } + + return { + old: null, + updated: null, + }; + } +} + +module.exports = GuildRoleUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js b/node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js new file mode 100644 index 0000000..d7abca9 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js @@ -0,0 +1,21 @@ +'use strict'; + +const Action = require('./Action'); + +class GuildRolesPositionUpdate extends Action { + handle(data) { + const client = this.client; + + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + for (const partialRole of data.roles) { + const role = guild.roles.cache.get(partialRole.id); + if (role) role.rawPosition = partialRole.position; + } + } + + return { guild }; + } +} + +module.exports = GuildRolesPositionUpdate; diff --git a/node_modules/discord.js/src/client/actions/GuildScheduledEventCreate.js b/node_modules/discord.js/src/client/actions/GuildScheduledEventCreate.js new file mode 100644 index 0000000..0a2fb9b --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildScheduledEventCreate.js @@ -0,0 +1,27 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildScheduledEventCreateAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + const guildScheduledEvent = guild.scheduledEvents._add(data); + + /** + * Emitted whenever a guild scheduled event is created. + * @event Client#guildScheduledEventCreate + * @param {GuildScheduledEvent} guildScheduledEvent The created guild scheduled event + */ + client.emit(Events.GuildScheduledEventCreate, guildScheduledEvent); + + return { guildScheduledEvent }; + } + + return {}; + } +} + +module.exports = GuildScheduledEventCreateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildScheduledEventDelete.js b/node_modules/discord.js/src/client/actions/GuildScheduledEventDelete.js new file mode 100644 index 0000000..636bfc5 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildScheduledEventDelete.js @@ -0,0 +1,31 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildScheduledEventDeleteAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const guildScheduledEvent = this.getScheduledEvent(data, guild); + if (guildScheduledEvent) { + guild.scheduledEvents.cache.delete(guildScheduledEvent.id); + + /** + * Emitted whenever a guild scheduled event is deleted. + * @event Client#guildScheduledEventDelete + * @param {GuildScheduledEvent} guildScheduledEvent The deleted guild scheduled event + */ + client.emit(Events.GuildScheduledEventDelete, guildScheduledEvent); + + return { guildScheduledEvent }; + } + } + + return {}; + } +} + +module.exports = GuildScheduledEventDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/GuildScheduledEventUpdate.js b/node_modules/discord.js/src/client/actions/GuildScheduledEventUpdate.js new file mode 100644 index 0000000..7cabd85 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildScheduledEventUpdate.js @@ -0,0 +1,30 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildScheduledEventUpdateAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const oldGuildScheduledEvent = guild.scheduledEvents.cache.get(data.id)?._clone() ?? null; + const newGuildScheduledEvent = guild.scheduledEvents._add(data); + + /** + * Emitted whenever a guild scheduled event gets updated. + * @event Client#guildScheduledEventUpdate + * @param {?GuildScheduledEvent} oldGuildScheduledEvent The guild scheduled event object before the update + * @param {GuildScheduledEvent} newGuildScheduledEvent The guild scheduled event object after the update + */ + client.emit(Events.GuildScheduledEventUpdate, oldGuildScheduledEvent, newGuildScheduledEvent); + + return { oldGuildScheduledEvent, newGuildScheduledEvent }; + } + + return {}; + } +} + +module.exports = GuildScheduledEventUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildScheduledEventUserAdd.js b/node_modules/discord.js/src/client/actions/GuildScheduledEventUserAdd.js new file mode 100644 index 0000000..03520db --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildScheduledEventUserAdd.js @@ -0,0 +1,32 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildScheduledEventUserAddAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const guildScheduledEvent = this.getScheduledEvent(data, guild); + const user = this.getUser(data); + + if (guildScheduledEvent && user) { + /** + * Emitted whenever a user subscribes to a guild scheduled event + * @event Client#guildScheduledEventUserAdd + * @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event + * @param {User} user The user who subscribed + */ + client.emit(Events.GuildScheduledEventUserAdd, guildScheduledEvent, user); + + return { guildScheduledEvent, user }; + } + } + + return {}; + } +} + +module.exports = GuildScheduledEventUserAddAction; diff --git a/node_modules/discord.js/src/client/actions/GuildScheduledEventUserRemove.js b/node_modules/discord.js/src/client/actions/GuildScheduledEventUserRemove.js new file mode 100644 index 0000000..2a04849 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildScheduledEventUserRemove.js @@ -0,0 +1,32 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildScheduledEventUserRemoveAction extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + + if (guild) { + const guildScheduledEvent = this.getScheduledEvent(data, guild); + const user = this.getUser(data); + + if (guildScheduledEvent && user) { + /** + * Emitted whenever a user unsubscribes from a guild scheduled event + * @event Client#guildScheduledEventUserRemove + * @param {GuildScheduledEvent} guildScheduledEvent The guild scheduled event + * @param {User} user The user who unsubscribed + */ + client.emit(Events.GuildScheduledEventUserRemove, guildScheduledEvent, user); + + return { guildScheduledEvent, user }; + } + } + + return {}; + } +} + +module.exports = GuildScheduledEventUserRemoveAction; diff --git a/node_modules/discord.js/src/client/actions/GuildStickerCreate.js b/node_modules/discord.js/src/client/actions/GuildStickerCreate.js new file mode 100644 index 0000000..7d81de9 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildStickerCreate.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildStickerCreateAction extends Action { + handle(guild, createdSticker) { + const already = guild.stickers.cache.has(createdSticker.id); + const sticker = guild.stickers._add(createdSticker); + /** + * Emitted whenever a custom sticker is created in a guild. + * @event Client#stickerCreate + * @param {Sticker} sticker The sticker that was created + */ + if (!already) this.client.emit(Events.GuildStickerCreate, sticker); + return { sticker }; + } +} + +module.exports = GuildStickerCreateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildStickerDelete.js b/node_modules/discord.js/src/client/actions/GuildStickerDelete.js new file mode 100644 index 0000000..7fd6b57 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildStickerDelete.js @@ -0,0 +1,19 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildStickerDeleteAction extends Action { + handle(sticker) { + sticker.guild.stickers.cache.delete(sticker.id); + /** + * Emitted whenever a custom sticker is deleted in a guild. + * @event Client#stickerDelete + * @param {Sticker} sticker The sticker that was deleted + */ + this.client.emit(Events.GuildStickerDelete, sticker); + return { sticker }; + } +} + +module.exports = GuildStickerDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/GuildStickerUpdate.js b/node_modules/discord.js/src/client/actions/GuildStickerUpdate.js new file mode 100644 index 0000000..5561c7e --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildStickerUpdate.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildStickerUpdateAction extends Action { + handle(current, data) { + const old = current._update(data); + /** + * Emitted whenever a custom sticker is updated in a guild. + * @event Client#stickerUpdate + * @param {Sticker} oldSticker The old sticker + * @param {Sticker} newSticker The new sticker + */ + this.client.emit(Events.GuildStickerUpdate, old, current); + return { sticker: current }; + } +} + +module.exports = GuildStickerUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildStickersUpdate.js b/node_modules/discord.js/src/client/actions/GuildStickersUpdate.js new file mode 100644 index 0000000..ccf1d63 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildStickersUpdate.js @@ -0,0 +1,34 @@ +'use strict'; + +const Action = require('./Action'); + +class GuildStickersUpdateAction extends Action { + handle(data) { + const guild = this.client.guilds.cache.get(data.guild_id); + if (!guild?.stickers) return; + + const deletions = new Map(guild.stickers.cache); + + for (const sticker of data.stickers) { + // Determine type of sticker event + const cachedSticker = guild.stickers.cache.get(sticker.id); + if (cachedSticker) { + deletions.delete(sticker.id); + if (!cachedSticker.equals(sticker)) { + // Sticker updated + this.client.actions.GuildStickerUpdate.handle(cachedSticker, sticker); + } + } else { + // Sticker added + this.client.actions.GuildStickerCreate.handle(guild, sticker); + } + } + + for (const sticker of deletions.values()) { + // Sticker deleted + this.client.actions.GuildStickerDelete.handle(sticker); + } + } +} + +module.exports = GuildStickersUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/GuildUpdate.js b/node_modules/discord.js/src/client/actions/GuildUpdate.js new file mode 100644 index 0000000..ef1f51b --- /dev/null +++ b/node_modules/discord.js/src/client/actions/GuildUpdate.js @@ -0,0 +1,33 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class GuildUpdateAction extends Action { + handle(data) { + const client = this.client; + + const guild = client.guilds.cache.get(data.id); + if (guild) { + const old = guild._update(data); + /** + * Emitted whenever a guild is updated - e.g. name change. + * @event Client#guildUpdate + * @param {Guild} oldGuild The guild before the update + * @param {Guild} newGuild The guild after the update + */ + client.emit(Events.GuildUpdate, old, guild); + return { + old, + updated: guild, + }; + } + + return { + old: null, + updated: null, + }; + } +} + +module.exports = GuildUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/InteractionCreate.js b/node_modules/discord.js/src/client/actions/InteractionCreate.js new file mode 100644 index 0000000..434fb0c --- /dev/null +++ b/node_modules/discord.js/src/client/actions/InteractionCreate.js @@ -0,0 +1,101 @@ +'use strict'; + +const { InteractionType, ComponentType, ApplicationCommandType } = require('discord-api-types/v10'); +const Action = require('./Action'); +const AutocompleteInteraction = require('../../structures/AutocompleteInteraction'); +const ButtonInteraction = require('../../structures/ButtonInteraction'); +const ChannelSelectMenuInteraction = require('../../structures/ChannelSelectMenuInteraction'); +const ChatInputCommandInteraction = require('../../structures/ChatInputCommandInteraction'); +const MentionableSelectMenuInteraction = require('../../structures/MentionableSelectMenuInteraction'); +const MessageContextMenuCommandInteraction = require('../../structures/MessageContextMenuCommandInteraction'); +const ModalSubmitInteraction = require('../../structures/ModalSubmitInteraction'); +const RoleSelectMenuInteraction = require('../../structures/RoleSelectMenuInteraction'); +const StringSelectMenuInteraction = require('../../structures/StringSelectMenuInteraction'); +const UserContextMenuCommandInteraction = require('../../structures/UserContextMenuCommandInteraction'); +const UserSelectMenuInteraction = require('../../structures/UserSelectMenuInteraction'); +const Events = require('../../util/Events'); + +class InteractionCreateAction extends Action { + handle(data) { + const client = this.client; + + // Resolve and cache partial channels for Interaction#channel getter + const channel = data.channel && this.getChannel(data.channel); + + // Do not emit this for interactions that cache messages that are non-text-based. + let InteractionClass; + + switch (data.type) { + case InteractionType.ApplicationCommand: + switch (data.data.type) { + case ApplicationCommandType.ChatInput: + InteractionClass = ChatInputCommandInteraction; + break; + case ApplicationCommandType.User: + InteractionClass = UserContextMenuCommandInteraction; + break; + case ApplicationCommandType.Message: + if (channel && !channel.isTextBased()) return; + InteractionClass = MessageContextMenuCommandInteraction; + break; + default: + client.emit( + Events.Debug, + `[INTERACTION] Received application command interaction with unknown type: ${data.data.type}`, + ); + return; + } + break; + case InteractionType.MessageComponent: + if (channel && !channel.isTextBased()) return; + + switch (data.data.component_type) { + case ComponentType.Button: + InteractionClass = ButtonInteraction; + break; + case ComponentType.StringSelect: + InteractionClass = StringSelectMenuInteraction; + break; + case ComponentType.UserSelect: + InteractionClass = UserSelectMenuInteraction; + break; + case ComponentType.RoleSelect: + InteractionClass = RoleSelectMenuInteraction; + break; + case ComponentType.MentionableSelect: + InteractionClass = MentionableSelectMenuInteraction; + break; + case ComponentType.ChannelSelect: + InteractionClass = ChannelSelectMenuInteraction; + break; + default: + client.emit( + Events.Debug, + `[INTERACTION] Received component interaction with unknown type: ${data.data.component_type}`, + ); + return; + } + break; + case InteractionType.ApplicationCommandAutocomplete: + InteractionClass = AutocompleteInteraction; + break; + case InteractionType.ModalSubmit: + InteractionClass = ModalSubmitInteraction; + break; + default: + client.emit(Events.Debug, `[INTERACTION] Received interaction with unknown type: ${data.type}`); + return; + } + + const interaction = new InteractionClass(client, data); + + /** + * Emitted when an interaction is created. + * @event Client#interactionCreate + * @param {BaseInteraction} interaction The interaction which was created + */ + client.emit(Events.InteractionCreate, interaction); + } +} + +module.exports = InteractionCreateAction; diff --git a/node_modules/discord.js/src/client/actions/InviteCreate.js b/node_modules/discord.js/src/client/actions/InviteCreate.js new file mode 100644 index 0000000..dc03f07 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/InviteCreate.js @@ -0,0 +1,27 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class InviteCreateAction extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.channel_id); + const guild = client.guilds.cache.get(data.guild_id); + if (!channel) return false; + + const inviteData = Object.assign(data, { channel, guild }); + const invite = guild.invites._add(inviteData); + + /** + * Emitted when an invite is created. + * <info>This event requires the {@link PermissionFlagsBits.ManageChannels} permission for the channel.</info> + * @event Client#inviteCreate + * @param {Invite} invite The invite that was created + */ + client.emit(Events.InviteCreate, invite); + return { invite }; + } +} + +module.exports = InviteCreateAction; diff --git a/node_modules/discord.js/src/client/actions/InviteDelete.js b/node_modules/discord.js/src/client/actions/InviteDelete.js new file mode 100644 index 0000000..58be00c --- /dev/null +++ b/node_modules/discord.js/src/client/actions/InviteDelete.js @@ -0,0 +1,29 @@ +'use strict'; + +const Action = require('./Action'); +const Invite = require('../../structures/Invite'); +const Events = require('../../util/Events'); + +class InviteDeleteAction extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.channel_id); + const guild = client.guilds.cache.get(data.guild_id); + if (!channel) return false; + + const inviteData = Object.assign(data, { channel, guild }); + const invite = new Invite(client, inviteData); + guild.invites.cache.delete(invite.code); + + /** + * Emitted when an invite is deleted. + * <info>This event requires the {@link PermissionFlagsBits.ManageChannels} permission for the channel.</info> + * @event Client#inviteDelete + * @param {Invite} invite The invite that was deleted + */ + client.emit(Events.InviteDelete, invite); + return { invite }; + } +} + +module.exports = InviteDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/MessageCreate.js b/node_modules/discord.js/src/client/actions/MessageCreate.js new file mode 100644 index 0000000..8e011fc --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageCreate.js @@ -0,0 +1,37 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class MessageCreateAction extends Action { + handle(data) { + const client = this.client; + const channel = this.getChannel(data); + if (channel) { + if (!channel.isTextBased()) return {}; + + if (channel.isThread()) { + channel.messageCount++; + channel.totalMessageSent++; + } + + const existing = channel.messages.cache.get(data.id); + if (existing) return { message: existing }; + const message = channel.messages._add(data); + channel.lastMessageId = data.id; + + /** + * Emitted whenever a message is created. + * @event Client#messageCreate + * @param {Message} message The created message + */ + client.emit(Events.MessageCreate, message); + + return { message }; + } + + return {}; + } +} + +module.exports = MessageCreateAction; diff --git a/node_modules/discord.js/src/client/actions/MessageDelete.js b/node_modules/discord.js/src/client/actions/MessageDelete.js new file mode 100644 index 0000000..f0c74d7 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageDelete.js @@ -0,0 +1,32 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class MessageDeleteAction extends Action { + handle(data) { + const client = this.client; + const channel = this.getChannel(data); + let message; + if (channel) { + if (!channel.isTextBased()) return {}; + + if (channel.isThread()) channel.messageCount--; + + message = this.getMessage(data, channel); + if (message) { + channel.messages.cache.delete(message.id); + /** + * Emitted whenever a message is deleted. + * @event Client#messageDelete + * @param {Message} message The deleted message + */ + client.emit(Events.MessageDelete, message); + } + } + + return { message }; + } +} + +module.exports = MessageDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/MessageDeleteBulk.js b/node_modules/discord.js/src/client/actions/MessageDeleteBulk.js new file mode 100644 index 0000000..5f61d19 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageDeleteBulk.js @@ -0,0 +1,47 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class MessageDeleteBulkAction extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.channel_id); + + if (channel) { + if (!channel.isTextBased()) return {}; + + if (channel.isThread()) channel.messageCount -= data.ids.length; + + const ids = data.ids; + const messages = new Collection(); + for (const id of ids) { + const message = this.getMessage( + { + id, + guild_id: data.guild_id, + }, + channel, + false, + ); + if (message) { + messages.set(message.id, message); + channel.messages.cache.delete(id); + } + } + + /** + * Emitted whenever messages are deleted in bulk. + * @event Client#messageDeleteBulk + * @param {Collection<Snowflake, Message>} messages The deleted messages, mapped by their id + * @param {GuildTextBasedChannel} channel The channel that the messages were deleted in + */ + if (messages.size > 0) client.emit(Events.MessageBulkDelete, messages, channel); + return { messages }; + } + return {}; + } +} + +module.exports = MessageDeleteBulkAction; diff --git a/node_modules/discord.js/src/client/actions/MessageReactionAdd.js b/node_modules/discord.js/src/client/actions/MessageReactionAdd.js new file mode 100644 index 0000000..ea97bd6 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageReactionAdd.js @@ -0,0 +1,55 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); +const Partials = require('../../util/Partials'); + +/* +{ user_id: 'id', + message_id: 'id', + emoji: { name: '�', id: null }, + channel_id: 'id', + // If originating from a guild + guild_id: 'id', + member: { ..., user: { ... } } } +*/ + +class MessageReactionAdd extends Action { + handle(data, fromStructure = false) { + if (!data.emoji) return false; + + const user = this.getUserFromMember(data); + if (!user) return false; + + // Verify channel + const channel = this.getChannel(data); + if (!channel?.isTextBased()) return false; + + // Verify message + const message = this.getMessage(data, channel); + if (!message) return false; + + // Verify reaction + const includePartial = this.client.options.partials.includes(Partials.Reaction); + if (message.partial && !includePartial) return false; + const reaction = message.reactions._add({ + emoji: data.emoji, + count: message.partial ? null : 0, + me: user.id === this.client.user.id, + }); + if (!reaction) return false; + reaction._add(user); + if (fromStructure) return { message, reaction, user }; + /** + * Emitted whenever a reaction is added to a cached message. + * @event Client#messageReactionAdd + * @param {MessageReaction} messageReaction The reaction object + * @param {User} user The user that applied the guild or reaction emoji + */ + this.client.emit(Events.MessageReactionAdd, reaction, user); + + return { message, reaction, user }; + } +} + +module.exports = MessageReactionAdd; diff --git a/node_modules/discord.js/src/client/actions/MessageReactionRemove.js b/node_modules/discord.js/src/client/actions/MessageReactionRemove.js new file mode 100644 index 0000000..9ca3a8e --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageReactionRemove.js @@ -0,0 +1,45 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +/* +{ user_id: 'id', + message_id: 'id', + emoji: { name: '�', id: null }, + channel_id: 'id', + guild_id: 'id' } +*/ + +class MessageReactionRemove extends Action { + handle(data) { + if (!data.emoji) return false; + + const user = this.getUser(data); + if (!user) return false; + + // Verify channel + const channel = this.getChannel(data); + if (!channel?.isTextBased()) return false; + + // Verify message + const message = this.getMessage(data, channel); + if (!message) return false; + + // Verify reaction + const reaction = this.getReaction(data, message, user); + if (!reaction) return false; + reaction._remove(user); + /** + * Emitted whenever a reaction is removed from a cached message. + * @event Client#messageReactionRemove + * @param {MessageReaction} messageReaction The reaction object + * @param {User} user The user whose emoji or reaction emoji was removed + */ + this.client.emit(Events.MessageReactionRemove, reaction, user); + + return { message, reaction, user }; + } +} + +module.exports = MessageReactionRemove; diff --git a/node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js b/node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js new file mode 100644 index 0000000..b1c023f --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js @@ -0,0 +1,33 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class MessageReactionRemoveAll extends Action { + handle(data) { + // Verify channel + const channel = this.getChannel(data); + if (!channel?.isTextBased()) return false; + + // Verify message + const message = this.getMessage(data, channel); + if (!message) return false; + + // Copy removed reactions to emit for the event. + const removed = message.reactions.cache.clone(); + + message.reactions.cache.clear(); + this.client.emit(Events.MessageReactionRemoveAll, message, removed); + + return { message }; + } +} + +/** + * Emitted whenever all reactions are removed from a cached message. + * @event Client#messageReactionRemoveAll + * @param {Message} message The message the reactions were removed from + * @param {Collection<string|Snowflake, MessageReaction>} reactions The cached message reactions that were removed. + */ + +module.exports = MessageReactionRemoveAll; diff --git a/node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js b/node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js new file mode 100644 index 0000000..3290214 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js @@ -0,0 +1,28 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class MessageReactionRemoveEmoji extends Action { + handle(data) { + const channel = this.getChannel(data); + if (!channel?.isTextBased()) return false; + + const message = this.getMessage(data, channel); + if (!message) return false; + + const reaction = this.getReaction(data, message); + if (!reaction) return false; + if (!message.partial) message.reactions.cache.delete(reaction.emoji.id ?? reaction.emoji.name); + + /** + * Emitted when a bot removes an emoji reaction from a cached message. + * @event Client#messageReactionRemoveEmoji + * @param {MessageReaction} reaction The reaction that was removed + */ + this.client.emit(Events.MessageReactionRemoveEmoji, reaction); + return { reaction }; + } +} + +module.exports = MessageReactionRemoveEmoji; diff --git a/node_modules/discord.js/src/client/actions/MessageUpdate.js b/node_modules/discord.js/src/client/actions/MessageUpdate.js new file mode 100644 index 0000000..fe757c0 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/MessageUpdate.js @@ -0,0 +1,26 @@ +'use strict'; + +const Action = require('./Action'); + +class MessageUpdateAction extends Action { + handle(data) { + const channel = this.getChannel(data); + if (channel) { + if (!channel.isTextBased()) return {}; + + const { id, channel_id, guild_id, author, timestamp, type } = data; + const message = this.getMessage({ id, channel_id, guild_id, author, timestamp, type }, channel); + if (message) { + const old = message._update(data); + return { + old, + updated: message, + }; + } + } + + return {}; + } +} + +module.exports = MessageUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/PresenceUpdate.js b/node_modules/discord.js/src/client/actions/PresenceUpdate.js new file mode 100644 index 0000000..0b4aaab --- /dev/null +++ b/node_modules/discord.js/src/client/actions/PresenceUpdate.js @@ -0,0 +1,42 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class PresenceUpdateAction extends Action { + handle(data) { + let user = this.client.users.cache.get(data.user.id); + if (!user && data.user.username) user = this.client.users._add(data.user); + if (!user) return; + + if (data.user.username) { + if (!user._equals(data.user)) this.client.actions.UserUpdate.handle(data.user); + } + + const guild = this.client.guilds.cache.get(data.guild_id); + if (!guild) return; + + const oldPresence = guild.presences.cache.get(user.id)?._clone() ?? null; + let member = guild.members.cache.get(user.id); + if (!member && data.status !== 'offline') { + member = guild.members._add({ + user, + deaf: false, + mute: false, + }); + this.client.emit(Events.GuildMemberAvailable, member); + } + const newPresence = guild.presences._add(Object.assign(data, { guild })); + if (this.client.listenerCount(Events.PresenceUpdate) && !newPresence.equals(oldPresence)) { + /** + * Emitted whenever a guild member's presence (e.g. status, activity) is changed. + * @event Client#presenceUpdate + * @param {?Presence} oldPresence The presence before the update, if one at all + * @param {Presence} newPresence The presence after the update + */ + this.client.emit(Events.PresenceUpdate, oldPresence, newPresence); + } + } +} + +module.exports = PresenceUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/StageInstanceCreate.js b/node_modules/discord.js/src/client/actions/StageInstanceCreate.js new file mode 100644 index 0000000..4edd530 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/StageInstanceCreate.js @@ -0,0 +1,28 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class StageInstanceCreateAction extends Action { + handle(data) { + const client = this.client; + const channel = this.getChannel(data); + + if (channel) { + const stageInstance = channel.guild.stageInstances._add(data); + + /** + * Emitted whenever a stage instance is created. + * @event Client#stageInstanceCreate + * @param {StageInstance} stageInstance The created stage instance + */ + client.emit(Events.StageInstanceCreate, stageInstance); + + return { stageInstance }; + } + + return {}; + } +} + +module.exports = StageInstanceCreateAction; diff --git a/node_modules/discord.js/src/client/actions/StageInstanceDelete.js b/node_modules/discord.js/src/client/actions/StageInstanceDelete.js new file mode 100644 index 0000000..0d5da38 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/StageInstanceDelete.js @@ -0,0 +1,31 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class StageInstanceDeleteAction extends Action { + handle(data) { + const client = this.client; + const channel = this.getChannel(data); + + if (channel) { + const stageInstance = channel.guild.stageInstances._add(data); + if (stageInstance) { + channel.guild.stageInstances.cache.delete(stageInstance.id); + + /** + * Emitted whenever a stage instance is deleted. + * @event Client#stageInstanceDelete + * @param {StageInstance} stageInstance The deleted stage instance + */ + client.emit(Events.StageInstanceDelete, stageInstance); + + return { stageInstance }; + } + } + + return {}; + } +} + +module.exports = StageInstanceDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/StageInstanceUpdate.js b/node_modules/discord.js/src/client/actions/StageInstanceUpdate.js new file mode 100644 index 0000000..008a53c --- /dev/null +++ b/node_modules/discord.js/src/client/actions/StageInstanceUpdate.js @@ -0,0 +1,30 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class StageInstanceUpdateAction extends Action { + handle(data) { + const client = this.client; + const channel = this.getChannel(data); + + if (channel) { + const oldStageInstance = channel.guild.stageInstances.cache.get(data.id)?._clone() ?? null; + const newStageInstance = channel.guild.stageInstances._add(data); + + /** + * Emitted whenever a stage instance gets updated - e.g. change in topic or privacy level + * @event Client#stageInstanceUpdate + * @param {?StageInstance} oldStageInstance The stage instance before the update + * @param {StageInstance} newStageInstance The stage instance after the update + */ + client.emit(Events.StageInstanceUpdate, oldStageInstance, newStageInstance); + + return { oldStageInstance, newStageInstance }; + } + + return {}; + } +} + +module.exports = StageInstanceUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/ThreadCreate.js b/node_modules/discord.js/src/client/actions/ThreadCreate.js new file mode 100644 index 0000000..a8ff6c6 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ThreadCreate.js @@ -0,0 +1,24 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class ThreadCreateAction extends Action { + handle(data) { + const client = this.client; + const existing = client.channels.cache.has(data.id); + const thread = client.channels._add(data); + if (!existing && thread) { + /** + * Emitted whenever a thread is created or when the client user is added to a thread. + * @event Client#threadCreate + * @param {ThreadChannel} thread The thread that was created + * @param {boolean} newlyCreated Whether the thread was newly created + */ + client.emit(Events.ThreadCreate, thread, data.newly_created ?? false); + } + return { thread }; + } +} + +module.exports = ThreadCreateAction; diff --git a/node_modules/discord.js/src/client/actions/ThreadDelete.js b/node_modules/discord.js/src/client/actions/ThreadDelete.js new file mode 100644 index 0000000..3ec81a4 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ThreadDelete.js @@ -0,0 +1,26 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class ThreadDeleteAction extends Action { + handle(data) { + const client = this.client; + const thread = client.channels.cache.get(data.id); + + if (thread) { + client.channels._remove(thread.id); + + /** + * Emitted whenever a thread is deleted. + * @event Client#threadDelete + * @param {ThreadChannel} thread The thread that was deleted + */ + client.emit(Events.ThreadDelete, thread); + } + + return { thread }; + } +} + +module.exports = ThreadDeleteAction; diff --git a/node_modules/discord.js/src/client/actions/ThreadListSync.js b/node_modules/discord.js/src/client/actions/ThreadListSync.js new file mode 100644 index 0000000..b16fb85 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ThreadListSync.js @@ -0,0 +1,60 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class ThreadListSyncAction extends Action { + handle(data) { + const client = this.client; + + const guild = client.guilds.cache.get(data.guild_id); + if (!guild) return {}; + + if (data.channel_ids) { + for (const id of data.channel_ids) { + const channel = client.channels.resolve(id); + if (channel) this.removeStale(channel); + } + } else { + for (const channel of guild.channels.cache.values()) { + this.removeStale(channel); + } + } + + const syncedThreads = data.threads.reduce((coll, rawThread) => { + const thread = client.channels._add(rawThread); + return coll.set(thread.id, thread); + }, new Collection()); + + for (const rawMember of Object.values(data.members)) { + // Discord sends the thread id as id in this object + const thread = client.channels.cache.get(rawMember.id); + if (thread) { + thread.members._add(rawMember); + } + } + + /** + * Emitted whenever the client user gains access to a text or news channel that contains threads + * @event Client#threadListSync + * @param {Collection<Snowflake, ThreadChannel>} threads The threads that were synced + * @param {Guild} guild The guild that the threads were synced in + */ + client.emit(Events.ThreadListSync, syncedThreads, guild); + + return { + syncedThreads, + }; + } + + removeStale(channel) { + channel.threads?.cache.forEach(thread => { + if (!thread.archived) { + this.client.channels._remove(thread.id); + } + }); + } +} + +module.exports = ThreadListSyncAction; diff --git a/node_modules/discord.js/src/client/actions/ThreadMemberUpdate.js b/node_modules/discord.js/src/client/actions/ThreadMemberUpdate.js new file mode 100644 index 0000000..0b17f70 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ThreadMemberUpdate.js @@ -0,0 +1,30 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class ThreadMemberUpdateAction extends Action { + handle(data) { + const client = this.client; + // Discord sends the thread id as id in this object + const thread = client.channels.cache.get(data.id); + if (thread) { + const member = thread.members.cache.get(data.user_id); + if (!member) { + const newMember = thread.members._add(data); + return { newMember }; + } + const old = member._update(data); + /** + * Emitted whenever the client user's thread member is updated. + * @event Client#threadMemberUpdate + * @param {ThreadMember} oldMember The member before the update + * @param {ThreadMember} newMember The member after the update + */ + client.emit(Events.ThreadMemberUpdate, old, member); + } + return {}; + } +} + +module.exports = ThreadMemberUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/ThreadMembersUpdate.js b/node_modules/discord.js/src/client/actions/ThreadMembersUpdate.js new file mode 100644 index 0000000..c1781b9 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/ThreadMembersUpdate.js @@ -0,0 +1,47 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class ThreadMembersUpdateAction extends Action { + handle(data) { + const client = this.client; + const thread = client.channels.cache.get(data.id); + if (thread) { + thread.memberCount = data.member_count; + const addedMembers = new Collection(); + const removedMembers = new Collection(); + + data.added_members?.reduce( + (_addedMembers, addedMember) => _addedMembers.set(addedMember.user_id, thread.members._add(addedMember)), + addedMembers, + ); + + data.removed_member_ids?.reduce((removedMembersIds, removedMembersId) => { + const threadMember = this.getThreadMember(removedMembersId, thread.members); + if (threadMember) removedMembersIds.set(threadMember.id, threadMember); + thread.members.cache.delete(removedMembersId); + return removedMembersIds; + }, removedMembers); + + if (addedMembers.size === 0 && removedMembers.size === 0) { + // Uncached thread member(s) left. + return {}; + } + + /** + * Emitted whenever members are added or removed from a thread. + * <info>This event requires the {@link GatewayIntentBits.GuildMembers} privileged gateway intent.</info> + * @event Client#threadMembersUpdate + * @param {Collection<Snowflake, ThreadMember>} addedMembers The members that were added + * @param {Collection<Snowflake, ThreadMember>} removedMembers The members that were removed + * @param {ThreadChannel} thread The thread where members got updated + */ + client.emit(Events.ThreadMembersUpdate, addedMembers, removedMembers, thread); + } + return {}; + } +} + +module.exports = ThreadMembersUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/TypingStart.js b/node_modules/discord.js/src/client/actions/TypingStart.js new file mode 100644 index 0000000..4e79920 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/TypingStart.js @@ -0,0 +1,29 @@ +'use strict'; + +const Action = require('./Action'); +const Typing = require('../../structures/Typing'); +const Events = require('../../util/Events'); + +class TypingStart extends Action { + handle(data) { + const channel = this.getChannel(data); + if (!channel) return; + + if (!channel.isTextBased()) { + this.client.emit(Events.Warn, `Discord sent a typing packet to a ${channel.type} channel ${channel.id}`); + return; + } + + const user = this.getUserFromMember(data); + if (user) { + /** + * Emitted whenever a user starts typing in a channel. + * @event Client#typingStart + * @param {Typing} typing The typing state + */ + this.client.emit(Events.TypingStart, new Typing(channel, user, data)); + } + } +} + +module.exports = TypingStart; diff --git a/node_modules/discord.js/src/client/actions/UserUpdate.js b/node_modules/discord.js/src/client/actions/UserUpdate.js new file mode 100644 index 0000000..923ddf5 --- /dev/null +++ b/node_modules/discord.js/src/client/actions/UserUpdate.js @@ -0,0 +1,36 @@ +'use strict'; + +const Action = require('./Action'); +const Events = require('../../util/Events'); + +class UserUpdateAction extends Action { + handle(data) { + const client = this.client; + + const newUser = data.id === client.user.id ? client.user : client.users.cache.get(data.id); + const oldUser = newUser._update(data); + + if (!oldUser.equals(newUser)) { + /** + * Emitted whenever a user's details (e.g. username) are changed. + * Triggered by the Discord gateway events {@link Events.UserUpdate}, + * {@link Events.GuildMemberUpdate}, and {@link Events.PresenceUpdate}. + * @event Client#userUpdate + * @param {User} oldUser The user before the update + * @param {User} newUser The user after the update + */ + client.emit(Events.UserUpdate, oldUser, newUser); + return { + old: oldUser, + updated: newUser, + }; + } + + return { + old: null, + updated: null, + }; + } +} + +module.exports = UserUpdateAction; diff --git a/node_modules/discord.js/src/client/actions/VoiceStateUpdate.js b/node_modules/discord.js/src/client/actions/VoiceStateUpdate.js new file mode 100644 index 0000000..fc7400f --- /dev/null +++ b/node_modules/discord.js/src/client/actions/VoiceStateUpdate.js @@ -0,0 +1,43 @@ +'use strict'; + +const Action = require('./Action'); +const VoiceState = require('../../structures/VoiceState'); +const Events = require('../../util/Events'); + +class VoiceStateUpdate extends Action { + handle(data) { + const client = this.client; + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + // Update the state + const oldState = + guild.voiceStates.cache.get(data.user_id)?._clone() ?? new VoiceState(guild, { user_id: data.user_id }); + + const newState = guild.voiceStates._add(data); + + // Get the member + let member = guild.members.cache.get(data.user_id); + if (member && data.member) { + member._patch(data.member); + } else if (data.member?.user && data.member.joined_at) { + member = guild.members._add(data.member); + } + + // Emit event + if (member?.user.id === client.user.id) { + client.emit('debug', `[VOICE] received voice state update: ${JSON.stringify(data)}`); + client.voice.onVoiceStateUpdate(data); + } + + /** + * Emitted whenever a member changes voice state - e.g. joins/leaves a channel, mutes/unmutes. + * @event Client#voiceStateUpdate + * @param {VoiceState} oldState The voice state before the update + * @param {VoiceState} newState The voice state after the update + */ + client.emit(Events.VoiceStateUpdate, oldState, newState); + } + } +} + +module.exports = VoiceStateUpdate; diff --git a/node_modules/discord.js/src/client/actions/WebhooksUpdate.js b/node_modules/discord.js/src/client/actions/WebhooksUpdate.js new file mode 100644 index 0000000..2bf41ba --- /dev/null +++ b/node_modules/discord.js/src/client/actions/WebhooksUpdate.js @@ -0,0 +1,37 @@ +'use strict'; + +const process = require('node:process'); +const Action = require('./Action'); + +let deprecationEmitted = false; + +class WebhooksUpdate extends Action { + handle(data) { + const client = this.client; + const channel = client.channels.cache.get(data.channel_id); + if (!channel) return; + + // TODO: change to Events.WebhooksUpdate in the next major version + /** + * Emitted whenever a channel has its webhooks changed. + * @event Client#webhooksUpdate + * @param {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel} channel + * The channel that had a webhook update + */ + client.emit('webhooksUpdate', channel); + + /** + * Emitted whenever a channel has its webhooks changed. + * @event Client#webhookUpdate + * @param {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel} channel + * The channel that had a webhook update + * @deprecated Use {@link Client#event:webhooksUpdate} instead. + */ + if (client.emit('webhookUpdate', channel) && !deprecationEmitted) { + deprecationEmitted = true; + process.emitWarning('The webhookUpdate event is deprecated. Use webhooksUpdate instead.', 'DeprecationWarning'); + } + } +} + +module.exports = WebhooksUpdate; diff --git a/node_modules/discord.js/src/client/voice/ClientVoiceManager.js b/node_modules/discord.js/src/client/voice/ClientVoiceManager.js new file mode 100644 index 0000000..192e700 --- /dev/null +++ b/node_modules/discord.js/src/client/voice/ClientVoiceManager.js @@ -0,0 +1,44 @@ +'use strict'; + +const Events = require('../../util/Events'); + +/** + * Manages voice connections for the client + */ +class ClientVoiceManager { + constructor(client) { + /** + * The client that instantiated this voice manager + * @type {Client} + * @readonly + * @name ClientVoiceManager#client + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * Maps guild ids to voice adapters created for use with @discordjs/voice. + * @type {Map<Snowflake, Object>} + */ + this.adapters = new Map(); + + client.on(Events.ShardDisconnect, (_, shardId) => { + for (const [guildId, adapter] of this.adapters.entries()) { + if (client.guilds.cache.get(guildId)?.shardId === shardId) { + adapter.destroy(); + } + } + }); + } + + onVoiceServer(payload) { + this.adapters.get(payload.guild_id)?.onVoiceServerUpdate(payload); + } + + onVoiceStateUpdate(payload) { + if (payload.guild_id && payload.session_id && payload.user_id === this.client.user?.id) { + this.adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload); + } + } +} + +module.exports = ClientVoiceManager; diff --git a/node_modules/discord.js/src/client/websocket/WebSocketManager.js b/node_modules/discord.js/src/client/websocket/WebSocketManager.js new file mode 100644 index 0000000..f62610b --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/WebSocketManager.js @@ -0,0 +1,394 @@ +'use strict'; + +const EventEmitter = require('node:events'); +const process = require('node:process'); +const { setImmediate } = require('node:timers'); +const { Collection } = require('@discordjs/collection'); +const { + WebSocketManager: WSWebSocketManager, + WebSocketShardEvents: WSWebSocketShardEvents, + CompressionMethod, + CloseCodes, +} = require('@discordjs/ws'); +const { GatewayCloseCodes, GatewayDispatchEvents } = require('discord-api-types/v10'); +const WebSocketShard = require('./WebSocketShard'); +const PacketHandlers = require('./handlers'); +const { DiscordjsError, ErrorCodes } = require('../../errors'); +const Events = require('../../util/Events'); +const Status = require('../../util/Status'); +const WebSocketShardEvents = require('../../util/WebSocketShardEvents'); + +let zlib; + +try { + zlib = require('zlib-sync'); +} catch {} // eslint-disable-line no-empty + +const BeforeReadyWhitelist = [ + GatewayDispatchEvents.Ready, + GatewayDispatchEvents.Resumed, + GatewayDispatchEvents.GuildCreate, + GatewayDispatchEvents.GuildDelete, + GatewayDispatchEvents.GuildMembersChunk, + GatewayDispatchEvents.GuildMemberAdd, + GatewayDispatchEvents.GuildMemberRemove, +]; + +const WaitingForGuildEvents = [GatewayDispatchEvents.GuildCreate, GatewayDispatchEvents.GuildDelete]; + +const UNRESUMABLE_CLOSE_CODES = [ + CloseCodes.Normal, + GatewayCloseCodes.AlreadyAuthenticated, + GatewayCloseCodes.InvalidSeq, +]; + +const reasonIsDeprecated = 'the reason property is deprecated, use the code property to determine the reason'; +let deprecationEmittedForInvalidSessionEvent = false; +let deprecationEmittedForDestroyedEvent = false; + +/** + * The WebSocket manager for this client. + * <info>This class forwards raw dispatch events, + * read more about it here {@link https://discord.com/developers/docs/topics/gateway}</info> + * @extends {EventEmitter} + */ +class WebSocketManager extends EventEmitter { + constructor(client) { + super(); + + /** + * The client that instantiated this WebSocketManager + * @type {Client} + * @readonly + * @name WebSocketManager#client + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * The gateway this manager uses + * @type {?string} + */ + this.gateway = null; + + /** + * A collection of all shards this manager handles + * @type {Collection<number, WebSocketShard>} + */ + this.shards = new Collection(); + + /** + * An array of queued events before this WebSocketManager became ready + * @type {Object[]} + * @private + * @name WebSocketManager#packetQueue + */ + Object.defineProperty(this, 'packetQueue', { value: [] }); + + /** + * The current status of this WebSocketManager + * @type {Status} + */ + this.status = Status.Idle; + + /** + * If this manager was destroyed. It will prevent shards from reconnecting + * @type {boolean} + * @private + */ + this.destroyed = false; + + /** + * The internal WebSocketManager from `@discordjs/ws`. + * @type {WSWebSocketManager} + * @private + */ + this._ws = null; + } + + /** + * The average ping of all WebSocketShards + * @type {number} + * @readonly + */ + get ping() { + const sum = this.shards.reduce((a, b) => a + b.ping, 0); + return sum / this.shards.size; + } + + /** + * Emits a debug message. + * @param {string} message The debug message + * @param {?number} [shardId] The id of the shard that emitted this message, if any + * @private + */ + debug(message, shardId) { + this.client.emit( + Events.Debug, + `[WS => ${typeof shardId === 'number' ? `Shard ${shardId}` : 'Manager'}] ${message}`, + ); + } + + /** + * Connects this manager to the gateway. + * @private + */ + async connect() { + const invalidToken = new DiscordjsError(ErrorCodes.TokenInvalid); + const { shards, shardCount, intents, ws } = this.client.options; + if (this._ws && this._ws.options.token !== this.client.token) { + await this._ws.destroy({ code: CloseCodes.Normal, reason: 'Login with differing token requested' }); + this._ws = null; + } + if (!this._ws) { + const wsOptions = { + intents: intents.bitfield, + rest: this.client.rest, + token: this.client.token, + largeThreshold: ws.large_threshold, + version: ws.version, + shardIds: shards === 'auto' ? null : shards, + shardCount: shards === 'auto' ? null : shardCount, + initialPresence: ws.presence, + retrieveSessionInfo: shardId => this.shards.get(shardId).sessionInfo, + updateSessionInfo: (shardId, sessionInfo) => { + this.shards.get(shardId).sessionInfo = sessionInfo; + }, + compression: zlib ? CompressionMethod.ZlibStream : null, + }; + if (ws.buildIdentifyThrottler) wsOptions.buildIdentifyThrottler = ws.buildIdentifyThrottler; + if (ws.buildStrategy) wsOptions.buildStrategy = ws.buildStrategy; + this._ws = new WSWebSocketManager(wsOptions); + this.attachEvents(); + } + + const { + url: gatewayURL, + shards: recommendedShards, + session_start_limit: sessionStartLimit, + } = await this._ws.fetchGatewayInformation().catch(error => { + throw error.status === 401 ? invalidToken : error; + }); + + const { total, remaining } = sessionStartLimit; + + this.debug(`Fetched Gateway Information + URL: ${gatewayURL} + Recommended Shards: ${recommendedShards}`); + + this.debug(`Session Limit Information + Total: ${total} + Remaining: ${remaining}`); + + this.gateway = `${gatewayURL}/`; + + this.client.options.shardCount = await this._ws.getShardCount(); + this.client.options.shards = await this._ws.getShardIds(); + this.totalShards = this.client.options.shards.length; + for (const id of this.client.options.shards) { + if (!this.shards.has(id)) { + const shard = new WebSocketShard(this, id); + this.shards.set(id, shard); + + shard.on(WebSocketShardEvents.AllReady, unavailableGuilds => { + /** + * Emitted when a shard turns ready. + * @event Client#shardReady + * @param {number} id The shard id that turned ready + * @param {?Set<Snowflake>} unavailableGuilds Set of unavailable guild ids, if any + */ + this.client.emit(Events.ShardReady, shard.id, unavailableGuilds); + + this.checkShardsReady(); + }); + shard.status = Status.Connecting; + } + } + + await this._ws.connect(); + + this.shards.forEach(shard => { + if (shard.listenerCount(WebSocketShardEvents.InvalidSession) > 0 && !deprecationEmittedForInvalidSessionEvent) { + process.emitWarning( + 'The WebSocketShard#invalidSession event is deprecated and will never emit.', + 'DeprecationWarning', + ); + + deprecationEmittedForInvalidSessionEvent = true; + } + if (shard.listenerCount(WebSocketShardEvents.Destroyed) > 0 && !deprecationEmittedForDestroyedEvent) { + process.emitWarning( + 'The WebSocketShard#destroyed event is deprecated and will never emit.', + 'DeprecationWarning', + ); + + deprecationEmittedForDestroyedEvent = true; + } + }); + } + + /** + * Attaches event handlers to the internal WebSocketShardManager from `@discordjs/ws`. + * @private + */ + attachEvents() { + this._ws.on(WSWebSocketShardEvents.Debug, ({ message, shardId }) => this.debug(message, shardId)); + this._ws.on(WSWebSocketShardEvents.Dispatch, ({ data, shardId }) => { + this.client.emit(Events.Raw, data, shardId); + this.emit(data.t, data.d, shardId); + const shard = this.shards.get(shardId); + this.handlePacket(data, shard); + if (shard.status === Status.WaitingForGuilds && WaitingForGuildEvents.includes(data.t)) { + shard.gotGuild(data.d.id); + } + }); + + this._ws.on(WSWebSocketShardEvents.Ready, ({ data, shardId }) => { + this.shards.get(shardId).onReadyPacket(data); + }); + + this._ws.on(WSWebSocketShardEvents.Closed, ({ code, shardId }) => { + const shard = this.shards.get(shardId); + shard.emit(WebSocketShardEvents.Close, { code, reason: reasonIsDeprecated, wasClean: true }); + if (UNRESUMABLE_CLOSE_CODES.includes(code) && this.destroyed) { + shard.status = Status.Disconnected; + /** + * Emitted when a shard's WebSocket disconnects and will no longer reconnect. + * @event Client#shardDisconnect + * @param {CloseEvent} event The WebSocket close event + * @param {number} id The shard id that disconnected + */ + this.client.emit(Events.ShardDisconnect, { code, reason: reasonIsDeprecated, wasClean: true }, shardId); + this.debug(GatewayCloseCodes[code], shardId); + return; + } + + this.shards.get(shardId).status = Status.Connecting; + /** + * Emitted when a shard is attempting to reconnect or re-identify. + * @event Client#shardReconnecting + * @param {number} id The shard id that is attempting to reconnect + */ + this.client.emit(Events.ShardReconnecting, shardId); + }); + this._ws.on(WSWebSocketShardEvents.Hello, ({ shardId }) => { + const shard = this.shards.get(shardId); + if (shard.sessionInfo) { + shard.closeSequence = shard.sessionInfo.sequence; + shard.status = Status.Resuming; + } else { + shard.status = Status.Identifying; + } + }); + + this._ws.on(WSWebSocketShardEvents.Resumed, ({ shardId }) => { + const shard = this.shards.get(shardId); + shard.status = Status.Ready; + /** + * Emitted when the shard resumes successfully + * @event WebSocketShard#resumed + */ + shard.emit(WebSocketShardEvents.Resumed); + }); + + this._ws.on(WSWebSocketShardEvents.HeartbeatComplete, ({ heartbeatAt, latency, shardId }) => { + this.debug(`Heartbeat acknowledged, latency of ${latency}ms.`, shardId); + const shard = this.shards.get(shardId); + shard.lastPingTimestamp = heartbeatAt; + shard.ping = latency; + }); + + this._ws.on(WSWebSocketShardEvents.Error, ({ error, shardId }) => { + /** + * Emitted whenever a shard's WebSocket encounters a connection error. + * @event Client#shardError + * @param {Error} error The encountered error + * @param {number} shardId The shard that encountered this error + */ + this.client.emit(Events.ShardError, error, shardId); + }); + } + + /** + * Broadcasts a packet to every shard this manager handles. + * @param {Object} packet The packet to send + * @private + */ + broadcast(packet) { + for (const shardId of this.shards.keys()) this._ws.send(shardId, packet); + } + + /** + * Destroys this manager and all its shards. + * @private + */ + async destroy() { + if (this.destroyed) return; + // TODO: Make a util for getting a stack + this.debug(`Manager was destroyed. Called by:\n${new Error().stack}`); + this.destroyed = true; + await this._ws?.destroy({ code: CloseCodes.Normal }); + } + + /** + * Processes a packet and queues it if this WebSocketManager is not ready. + * @param {Object} [packet] The packet to be handled + * @param {WebSocketShard} [shard] The shard that will handle this packet + * @returns {boolean} + * @private + */ + handlePacket(packet, shard) { + if (packet && this.status !== Status.Ready) { + if (!BeforeReadyWhitelist.includes(packet.t)) { + this.packetQueue.push({ packet, shard }); + return false; + } + } + + if (this.packetQueue.length) { + const item = this.packetQueue.shift(); + setImmediate(() => { + this.handlePacket(item.packet, item.shard); + }).unref(); + } + + if (packet && PacketHandlers[packet.t]) { + PacketHandlers[packet.t](this.client, packet, shard); + } + + return true; + } + + /** + * Checks whether the client is ready to be marked as ready. + * @private + */ + checkShardsReady() { + if (this.status === Status.Ready) return; + if (this.shards.size !== this.totalShards || this.shards.some(s => s.status !== Status.Ready)) { + return; + } + + this.triggerClientReady(); + } + + /** + * Causes the client to be marked as ready and emits the ready event. + * @private + */ + triggerClientReady() { + this.status = Status.Ready; + + this.client.readyTimestamp = Date.now(); + + /** + * Emitted when the client becomes ready to start working. + * @event Client#ready + * @param {Client} client The client + */ + this.client.emit(Events.ClientReady, this.client); + + this.handlePacket(); + } +} + +module.exports = WebSocketManager; diff --git a/node_modules/discord.js/src/client/websocket/WebSocketShard.js b/node_modules/discord.js/src/client/websocket/WebSocketShard.js new file mode 100644 index 0000000..05bc225 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/WebSocketShard.js @@ -0,0 +1,231 @@ +'use strict'; + +const EventEmitter = require('node:events'); +const process = require('node:process'); +const { setTimeout, clearTimeout } = require('node:timers'); +const { GatewayIntentBits } = require('discord-api-types/v10'); +const Status = require('../../util/Status'); +const WebSocketShardEvents = require('../../util/WebSocketShardEvents'); + +let deprecationEmittedForImportant = false; +/** + * Represents a Shard's WebSocket connection + * @extends {EventEmitter} + */ +class WebSocketShard extends EventEmitter { + constructor(manager, id) { + super(); + + /** + * The WebSocketManager of the shard + * @type {WebSocketManager} + */ + this.manager = manager; + + /** + * The shard's id + * @type {number} + */ + this.id = id; + + /** + * The current status of the shard + * @type {Status} + */ + this.status = Status.Idle; + + /** + * The sequence of the shard after close + * @type {number} + * @private + */ + this.closeSequence = 0; + + /** + * The previous heartbeat ping of the shard + * @type {number} + */ + this.ping = -1; + + /** + * The last time a ping was sent (a timestamp) + * @type {number} + */ + this.lastPingTimestamp = -1; + + /** + * A set of guild ids this shard expects to receive + * @name WebSocketShard#expectedGuilds + * @type {?Set<string>} + * @private + */ + Object.defineProperty(this, 'expectedGuilds', { value: null, writable: true }); + + /** + * The ready timeout + * @name WebSocketShard#readyTimeout + * @type {?NodeJS.Timeout} + * @private + */ + Object.defineProperty(this, 'readyTimeout', { value: null, writable: true }); + + /** + * @external SessionInfo + * @see {@link https://discord.js.org/docs/packages/ws/stable/SessionInfo:Interface} + */ + + /** + * The session info used by `@discordjs/ws` package. + * @name WebSocketShard#sessionInfo + * @type {?SessionInfo} + * @private + */ + Object.defineProperty(this, 'sessionInfo', { value: null, writable: true }); + } + + /** + * Emits a debug event. + * @param {string} message The debug message + * @private + */ + debug(message) { + this.manager.debug(message, this.id); + } + + /** + * @external CloseEvent + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent} + */ + + /** + * This method is responsible to emit close event for this shard. + * This method helps the shard reconnect. + * @param {CloseEvent} [event] Close event that was received + * @deprecated + */ + emitClose( + event = { + code: 1011, + reason: 'INTERNAL_ERROR', + wasClean: false, + }, + ) { + this.debug(`[CLOSE] + Event Code: ${event.code} + Clean : ${event.wasClean} + Reason : ${event.reason ?? 'No reason received'}`); + /** + * Emitted when a shard's WebSocket closes. + * @private + * @event WebSocketShard#close + * @param {CloseEvent} event The received event + */ + this.emit(WebSocketShardEvents.Close, event); + } + + /** + * Called when the shard receives the READY payload. + * @param {Object} packet The received packet + * @private + */ + onReadyPacket(packet) { + if (!packet) { + this.debug(`Received broken packet: '${packet}'.`); + return; + } + + /** + * Emitted when the shard receives the READY payload and is now waiting for guilds + * @event WebSocketShard#ready + */ + this.emit(WebSocketShardEvents.Ready); + + this.expectedGuilds = new Set(packet.guilds.map(d => d.id)); + this.status = Status.WaitingForGuilds; + } + + /** + * Called when a GuildCreate or GuildDelete for this shard was sent after READY payload was received, + * but before we emitted the READY event. + * @param {Snowflake} guildId the id of the Guild sent in the payload + * @private + */ + gotGuild(guildId) { + this.expectedGuilds.delete(guildId); + this.checkReady(); + } + + /** + * Checks if the shard can be marked as ready + * @private + */ + checkReady() { + // Step 0. Clear the ready timeout, if it exists + if (this.readyTimeout) { + clearTimeout(this.readyTimeout); + this.readyTimeout = null; + } + // Step 1. If we don't have any other guilds pending, we are ready + if (!this.expectedGuilds.size) { + this.debug('Shard received all its guilds. Marking as fully ready.'); + this.status = Status.Ready; + + /** + * Emitted when the shard is fully ready. + * This event is emitted if: + * * all guilds were received by this shard + * * the ready timeout expired, and some guilds are unavailable + * @event WebSocketShard#allReady + * @param {?Set<string>} unavailableGuilds Set of unavailable guilds, if any + */ + this.emit(WebSocketShardEvents.AllReady); + return; + } + const hasGuildsIntent = this.manager.client.options.intents.has(GatewayIntentBits.Guilds); + // Step 2. Create a timeout that will mark the shard as ready if there are still unavailable guilds + // * The timeout is 15 seconds by default + // * This can be optionally changed in the client options via the `waitGuildTimeout` option + // * a timeout time of zero will skip this timeout, which potentially could cause the Client to miss guilds. + + const { waitGuildTimeout } = this.manager.client.options; + + this.readyTimeout = setTimeout( + () => { + this.debug( + `Shard ${hasGuildsIntent ? 'did' : 'will'} not receive any more guild packets` + + `${hasGuildsIntent ? ` in ${waitGuildTimeout} ms` : ''}.\nUnavailable guild count: ${ + this.expectedGuilds.size + }`, + ); + + this.readyTimeout = null; + this.status = Status.Ready; + + this.emit(WebSocketShardEvents.AllReady, this.expectedGuilds); + }, + hasGuildsIntent ? waitGuildTimeout : 0, + ).unref(); + } + + /** + * Adds a packet to the queue to be sent to the gateway. + * <warn>If you use this method, make sure you understand that you need to provide + * a full [Payload](https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-commands). + * Do not use this method if you don't know what you're doing.</warn> + * @param {Object} data The full packet to send + * @param {boolean} [important=false] If this packet should be added first in queue + * <warn>This parameter is **deprecated**. Important payloads are determined by their opcode instead.</warn> + */ + send(data, important = false) { + if (important && !deprecationEmittedForImportant) { + process.emitWarning( + 'Sending important payloads explicitly is deprecated. They are determined by their opcode implicitly now.', + 'DeprecationWarning', + ); + deprecationEmittedForImportant = true; + } + this.manager._ws.send(this.id, data); + } +} + +module.exports = WebSocketShard; diff --git a/node_modules/discord.js/src/client/websocket/handlers/APPLICATION_COMMAND_PERMISSIONS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/APPLICATION_COMMAND_PERMISSIONS_UPDATE.js new file mode 100644 index 0000000..73d4ec4 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/APPLICATION_COMMAND_PERMISSIONS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ApplicationCommandPermissionsUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_ACTION_EXECUTION.js b/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_ACTION_EXECUTION.js new file mode 100644 index 0000000..22463b6 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_ACTION_EXECUTION.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.AutoModerationActionExecution.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_CREATE.js new file mode 100644 index 0000000..af64b9c --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.AutoModerationRuleCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_DELETE.js new file mode 100644 index 0000000..56ec504 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.AutoModerationRuleDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_UPDATE.js new file mode 100644 index 0000000..3caf6ba --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/AUTO_MODERATION_RULE_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.AutoModerationRuleUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js new file mode 100644 index 0000000..d6d560d --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ChannelCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js new file mode 100644 index 0000000..cb9f3d8 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ChannelDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js new file mode 100644 index 0000000..c46e527 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js @@ -0,0 +1,22 @@ +'use strict'; + +const Events = require('../../../util/Events'); + +module.exports = (client, { d: data }) => { + const channel = client.channels.cache.get(data.channel_id); + const time = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null; + + if (channel) { + // Discord sends null for last_pin_timestamp if the last pinned message was removed + channel.lastPinTimestamp = time; + + /** + * Emitted whenever the pins of a channel are updated. Due to the nature of the WebSocket event, + * not much information can be provided easily here - you need to manually check the pins yourself. + * @event Client#channelPinsUpdate + * @param {TextBasedChannels} channel The channel that the pins update occurred in + * @param {Date} time The time of the pins update + */ + client.emit(Events.ChannelPinsUpdate, channel, time); + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js new file mode 100644 index 0000000..8f35121 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js @@ -0,0 +1,16 @@ +'use strict'; + +const Events = require('../../../util/Events'); + +module.exports = (client, packet) => { + const { old, updated } = client.actions.ChannelUpdate.handle(packet.d); + if (old && updated) { + /** + * Emitted whenever a channel is updated - e.g. name change, topic change, channel type change. + * @event Client#channelUpdate + * @param {DMChannel|GuildChannel} oldChannel The channel before the update + * @param {DMChannel|GuildChannel} newChannel The channel after the update + */ + client.emit(Events.ChannelUpdate, old, updated); + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_AUDIT_LOG_ENTRY_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_AUDIT_LOG_ENTRY_CREATE.js new file mode 100644 index 0000000..8623141 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_AUDIT_LOG_ENTRY_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildAuditLogEntryCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js new file mode 100644 index 0000000..d8dc0f9 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildBanAdd.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js new file mode 100644 index 0000000..8389e46 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildBanRemove.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js new file mode 100644 index 0000000..141f0ab --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js @@ -0,0 +1,33 @@ +'use strict'; + +const Events = require('../../../util/Events'); +const Status = require('../../../util/Status'); + +module.exports = (client, { d: data }, shard) => { + let guild = client.guilds.cache.get(data.id); + if (guild) { + if (!guild.available && !data.unavailable) { + // A newly available guild + guild._patch(data); + + /** + * Emitted whenever a guild becomes available. + * @event Client#guildAvailable + * @param {Guild} guild The guild that became available + */ + client.emit(Events.GuildAvailable, guild); + } + } else { + // A new guild + data.shardId = shard.id; + guild = client.guilds._add(data); + if (client.ws.status === Status.Ready) { + /** + * Emitted whenever the client joins a guild. + * @event Client#guildCreate + * @param {Guild} guild The created guild + */ + client.emit(Events.GuildCreate, guild); + } + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js new file mode 100644 index 0000000..27a3256 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js new file mode 100644 index 0000000..e23b671 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildEmojisUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js new file mode 100644 index 0000000..e90a72c --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildIntegrationsUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js new file mode 100644 index 0000000..2f61a1e --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js @@ -0,0 +1,39 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Events = require('../../../util/Events'); + +module.exports = (client, { d: data }) => { + const guild = client.guilds.cache.get(data.guild_id); + if (!guild) return; + const members = new Collection(); + + for (const member of data.members) members.set(member.user.id, guild.members._add(member)); + if (data.presences) { + for (const presence of data.presences) guild.presences._add(Object.assign(presence, { guild })); + } + + /** + * Represents the properties of a guild members chunk + * @typedef {Object} GuildMembersChunk + * @property {number} index Index of the received chunk + * @property {number} count Number of chunks the client should receive + * @property {Array<*>} notFound An array of whatever could not be found + * when using {@link GatewayOpcodes.RequestGuildMembers} + * @property {?string} nonce Nonce for this chunk + */ + + /** + * Emitted whenever a chunk of guild members is received (all members come from the same guild). + * @event Client#guildMembersChunk + * @param {Collection<Snowflake, GuildMember>} members The members in the chunk + * @param {Guild} guild The guild related to the member chunk + * @param {GuildMembersChunk} chunk Properties of the received chunk + */ + client.emit(Events.GuildMembersChunk, members, guild, { + index: data.chunk_index, + count: data.chunk_count, + notFound: data.not_found, + nonce: data.nonce, + }); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js new file mode 100644 index 0000000..fece5d7 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js @@ -0,0 +1,20 @@ +'use strict'; + +const Events = require('../../../util/Events'); +const Status = require('../../../util/Status'); + +module.exports = (client, { d: data }, shard) => { + const guild = client.guilds.cache.get(data.guild_id); + if (guild) { + guild.memberCount++; + const member = guild.members._add(data); + if (shard.status === Status.Ready) { + /** + * Emitted whenever a user joins a guild. + * @event Client#guildMemberAdd + * @param {GuildMember} member The member that has joined a guild + */ + client.emit(Events.GuildMemberAdd, member); + } + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js new file mode 100644 index 0000000..72432af --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet, shard) => { + client.actions.GuildMemberRemove.handle(packet.d, shard); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js new file mode 100644 index 0000000..cafc6bd --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet, shard) => { + client.actions.GuildMemberUpdate.handle(packet.d, shard); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js new file mode 100644 index 0000000..da9e7bc --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildRoleCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js new file mode 100644 index 0000000..cdc6353 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildRoleDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js new file mode 100644 index 0000000..3a9b62e --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildRoleUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_CREATE.js new file mode 100644 index 0000000..04ff2df --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_DELETE.js new file mode 100644 index 0000000..b660c09 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_UPDATE.js new file mode 100644 index 0000000..0064708 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_ADD.js new file mode 100644 index 0000000..d5adca2 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_ADD.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventUserAdd.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_REMOVE.js new file mode 100644 index 0000000..114df68 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_SCHEDULED_EVENT_USER_REMOVE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildScheduledEventUserRemove.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js new file mode 100644 index 0000000..e3aba61 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildStickersUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js new file mode 100644 index 0000000..fd0012a --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/INTERACTION_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/INTERACTION_CREATE.js new file mode 100644 index 0000000..5bf30fc --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/INTERACTION_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.InteractionCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js new file mode 100644 index 0000000..50a2e72 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.InviteCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js new file mode 100644 index 0000000..5971852 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.InviteDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js new file mode 100644 index 0000000..c9b79a8 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js new file mode 100644 index 0000000..85ae2bc --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js new file mode 100644 index 0000000..fbcf80f --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageDeleteBulk.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js new file mode 100644 index 0000000..e219b4a --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageReactionAdd.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js new file mode 100644 index 0000000..2980e69 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageReactionRemove.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js new file mode 100644 index 0000000..ead80f7 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageReactionRemoveAll.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js new file mode 100644 index 0000000..579444c --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.MessageReactionRemoveEmoji.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js new file mode 100644 index 0000000..c2a470b --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js @@ -0,0 +1,16 @@ +'use strict'; + +const Events = require('../../../util/Events'); + +module.exports = (client, packet) => { + const { old, updated } = client.actions.MessageUpdate.handle(packet.d); + if (old && updated) { + /** + * Emitted whenever a message is updated - e.g. embed or content change. + * @event Client#messageUpdate + * @param {Message} oldMessage The message before the update + * @param {Message} newMessage The message after the update + */ + client.emit(Events.MessageUpdate, old, updated); + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js new file mode 100644 index 0000000..bde3629 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.PresenceUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/READY.js b/node_modules/discord.js/src/client/websocket/handlers/READY.js new file mode 100644 index 0000000..82da01c --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/READY.js @@ -0,0 +1,27 @@ +'use strict'; + +const ClientApplication = require('../../../structures/ClientApplication'); +let ClientUser; + +module.exports = (client, { d: data }, shard) => { + if (client.user) { + client.user._patch(data.user); + } else { + ClientUser ??= require('../../../structures/ClientUser'); + client.user = new ClientUser(client, data.user); + client.users.cache.set(client.user.id, client.user); + } + + for (const guild of data.guilds) { + guild.shardId = shard.id; + client.guilds._add(guild); + } + + if (client.application) { + client.application._patch(data.application); + } else { + client.application = new ClientApplication(client, data.application); + } + + shard.checkReady(); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/RESUMED.js b/node_modules/discord.js/src/client/websocket/handlers/RESUMED.js new file mode 100644 index 0000000..27ed7dd --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/RESUMED.js @@ -0,0 +1,14 @@ +'use strict'; + +const Events = require('../../../util/Events'); + +module.exports = (client, packet, shard) => { + const replayed = shard.sessionInfo.sequence - shard.closeSequence; + /** + * Emitted when a shard resumes successfully. + * @event Client#shardResume + * @param {number} id The shard id that resumed + * @param {number} replayedEvents The amount of replayed events + */ + client.emit(Events.ShardResume, shard.id, replayed); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/STAGE_INSTANCE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/STAGE_INSTANCE_CREATE.js new file mode 100644 index 0000000..77ae2ff --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/STAGE_INSTANCE_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.StageInstanceCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/STAGE_INSTANCE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/STAGE_INSTANCE_DELETE.js new file mode 100644 index 0000000..e2bb627 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/STAGE_INSTANCE_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.StageInstanceDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/STAGE_INSTANCE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/STAGE_INSTANCE_UPDATE.js new file mode 100644 index 0000000..fabc84a --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/STAGE_INSTANCE_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.StageInstanceUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/THREAD_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/THREAD_CREATE.js new file mode 100644 index 0000000..d92cab0 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/THREAD_CREATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ThreadCreate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/THREAD_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/THREAD_DELETE.js new file mode 100644 index 0000000..1140a08 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/THREAD_DELETE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ThreadDelete.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/THREAD_LIST_SYNC.js b/node_modules/discord.js/src/client/websocket/handlers/THREAD_LIST_SYNC.js new file mode 100644 index 0000000..17b173a --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/THREAD_LIST_SYNC.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ThreadListSync.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/THREAD_MEMBERS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/THREAD_MEMBERS_UPDATE.js new file mode 100644 index 0000000..f3c7a73 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/THREAD_MEMBERS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ThreadMembersUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/THREAD_MEMBER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/THREAD_MEMBER_UPDATE.js new file mode 100644 index 0000000..a111b0a --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/THREAD_MEMBER_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.ThreadMemberUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/THREAD_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/THREAD_UPDATE.js new file mode 100644 index 0000000..481dcd4 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/THREAD_UPDATE.js @@ -0,0 +1,16 @@ +'use strict'; + +const Events = require('../../../util/Events'); + +module.exports = (client, packet) => { + const { old, updated } = client.actions.ChannelUpdate.handle(packet.d); + if (old && updated) { + /** + * Emitted whenever a thread is updated - e.g. name change, archive state change, locked state change. + * @event Client#threadUpdate + * @param {ThreadChannel} oldThread The thread before the update + * @param {ThreadChannel} newThread The thread after the update + */ + client.emit(Events.ThreadUpdate, old, updated); + } +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js b/node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js new file mode 100644 index 0000000..9a56a54 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.TypingStart.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js new file mode 100644 index 0000000..a02bf58 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.UserUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js new file mode 100644 index 0000000..f9cf534 --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = (client, packet) => { + client.emit('debug', `[VOICE] received voice server: ${JSON.stringify(packet)}`); + client.voice.onVoiceServer(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js new file mode 100644 index 0000000..dbff6ea --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.VoiceStateUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js new file mode 100644 index 0000000..46cacee --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.WebhooksUpdate.handle(packet.d); +}; diff --git a/node_modules/discord.js/src/client/websocket/handlers/index.js b/node_modules/discord.js/src/client/websocket/handlers/index.js new file mode 100644 index 0000000..f175dbe --- /dev/null +++ b/node_modules/discord.js/src/client/websocket/handlers/index.js @@ -0,0 +1,64 @@ +'use strict'; + +const handlers = Object.fromEntries([ + ['APPLICATION_COMMAND_PERMISSIONS_UPDATE', require('./APPLICATION_COMMAND_PERMISSIONS_UPDATE')], + ['AUTO_MODERATION_ACTION_EXECUTION', require('./AUTO_MODERATION_ACTION_EXECUTION')], + ['AUTO_MODERATION_RULE_CREATE', require('./AUTO_MODERATION_RULE_CREATE')], + ['AUTO_MODERATION_RULE_DELETE', require('./AUTO_MODERATION_RULE_DELETE')], + ['AUTO_MODERATION_RULE_UPDATE', require('./AUTO_MODERATION_RULE_UPDATE')], + ['CHANNEL_CREATE', require('./CHANNEL_CREATE')], + ['CHANNEL_DELETE', require('./CHANNEL_DELETE')], + ['CHANNEL_PINS_UPDATE', require('./CHANNEL_PINS_UPDATE')], + ['CHANNEL_UPDATE', require('./CHANNEL_UPDATE')], + ['GUILD_AUDIT_LOG_ENTRY_CREATE', require('./GUILD_AUDIT_LOG_ENTRY_CREATE')], + ['GUILD_BAN_ADD', require('./GUILD_BAN_ADD')], + ['GUILD_BAN_REMOVE', require('./GUILD_BAN_REMOVE')], + ['GUILD_CREATE', require('./GUILD_CREATE')], + ['GUILD_DELETE', require('./GUILD_DELETE')], + ['GUILD_EMOJIS_UPDATE', require('./GUILD_EMOJIS_UPDATE')], + ['GUILD_INTEGRATIONS_UPDATE', require('./GUILD_INTEGRATIONS_UPDATE')], + ['GUILD_MEMBERS_CHUNK', require('./GUILD_MEMBERS_CHUNK')], + ['GUILD_MEMBER_ADD', require('./GUILD_MEMBER_ADD')], + ['GUILD_MEMBER_REMOVE', require('./GUILD_MEMBER_REMOVE')], + ['GUILD_MEMBER_UPDATE', require('./GUILD_MEMBER_UPDATE')], + ['GUILD_ROLE_CREATE', require('./GUILD_ROLE_CREATE')], + ['GUILD_ROLE_DELETE', require('./GUILD_ROLE_DELETE')], + ['GUILD_ROLE_UPDATE', require('./GUILD_ROLE_UPDATE')], + ['GUILD_SCHEDULED_EVENT_CREATE', require('./GUILD_SCHEDULED_EVENT_CREATE')], + ['GUILD_SCHEDULED_EVENT_DELETE', require('./GUILD_SCHEDULED_EVENT_DELETE')], + ['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE')], + ['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')], + ['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')], + ['GUILD_STICKERS_UPDATE', require('./GUILD_STICKERS_UPDATE')], + ['GUILD_UPDATE', require('./GUILD_UPDATE')], + ['INTERACTION_CREATE', require('./INTERACTION_CREATE')], + ['INVITE_CREATE', require('./INVITE_CREATE')], + ['INVITE_DELETE', require('./INVITE_DELETE')], + ['MESSAGE_CREATE', require('./MESSAGE_CREATE')], + ['MESSAGE_DELETE', require('./MESSAGE_DELETE')], + ['MESSAGE_DELETE_BULK', require('./MESSAGE_DELETE_BULK')], + ['MESSAGE_REACTION_ADD', require('./MESSAGE_REACTION_ADD')], + ['MESSAGE_REACTION_REMOVE', require('./MESSAGE_REACTION_REMOVE')], + ['MESSAGE_REACTION_REMOVE_ALL', require('./MESSAGE_REACTION_REMOVE_ALL')], + ['MESSAGE_REACTION_REMOVE_EMOJI', require('./MESSAGE_REACTION_REMOVE_EMOJI')], + ['MESSAGE_UPDATE', require('./MESSAGE_UPDATE')], + ['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')], + ['READY', require('./READY')], + ['RESUMED', require('./RESUMED')], + ['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')], + ['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')], + ['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')], + ['THREAD_CREATE', require('./THREAD_CREATE')], + ['THREAD_DELETE', require('./THREAD_DELETE')], + ['THREAD_LIST_SYNC', require('./THREAD_LIST_SYNC')], + ['THREAD_MEMBERS_UPDATE', require('./THREAD_MEMBERS_UPDATE')], + ['THREAD_MEMBER_UPDATE', require('./THREAD_MEMBER_UPDATE')], + ['THREAD_UPDATE', require('./THREAD_UPDATE')], + ['TYPING_START', require('./TYPING_START')], + ['USER_UPDATE', require('./USER_UPDATE')], + ['VOICE_SERVER_UPDATE', require('./VOICE_SERVER_UPDATE')], + ['VOICE_STATE_UPDATE', require('./VOICE_STATE_UPDATE')], + ['WEBHOOKS_UPDATE', require('./WEBHOOKS_UPDATE')], +]); + +module.exports = handlers; diff --git a/node_modules/discord.js/src/errors/DJSError.js b/node_modules/discord.js/src/errors/DJSError.js new file mode 100644 index 0000000..88e0e72 --- /dev/null +++ b/node_modules/discord.js/src/errors/DJSError.js @@ -0,0 +1,48 @@ +'use strict'; + +// Heavily inspired by node's `internal/errors` module +const ErrorCodes = require('./ErrorCodes'); +const Messages = require('./Messages'); + +/** + * Extend an error of some sort into a DiscordjsError. + * @param {Error} Base Base error to extend + * @returns {DiscordjsError} + * @ignore + */ +function makeDiscordjsError(Base) { + return class DiscordjsError extends Base { + constructor(code, ...args) { + super(message(code, args)); + this.code = code; + Error.captureStackTrace?.(this, DiscordjsError); + } + + get name() { + return `${super.name} [${this.code}]`; + } + }; +} + +/** + * Format the message for an error. + * @param {string} code The error code + * @param {Array<*>} args Arguments to pass for util format or as function args + * @returns {string} Formatted string + * @ignore + */ +function message(code, args) { + if (!(code in ErrorCodes)) throw new Error('Error code must be a valid DiscordjsErrorCodes'); + const msg = Messages[code]; + if (!msg) throw new Error(`No message associated with error code: ${code}.`); + if (typeof msg === 'function') return msg(...args); + if (!args?.length) return msg; + args.unshift(msg); + return String(...args); +} + +module.exports = { + DiscordjsError: makeDiscordjsError(Error), + DiscordjsTypeError: makeDiscordjsError(TypeError), + DiscordjsRangeError: makeDiscordjsError(RangeError), +}; diff --git a/node_modules/discord.js/src/errors/ErrorCodes.js b/node_modules/discord.js/src/errors/ErrorCodes.js new file mode 100644 index 0000000..9cd2f4d --- /dev/null +++ b/node_modules/discord.js/src/errors/ErrorCodes.js @@ -0,0 +1,319 @@ +'use strict'; + +/** + * @typedef {Object} DiscordjsErrorCodes + + * @property {'ClientInvalidOption'} ClientInvalidOption + * @property {'ClientInvalidProvidedShards'} ClientInvalidProvidedShards + * @property {'ClientMissingIntents'} ClientMissingIntents + * @property {'ClientNotReady'} ClientNotReady + + * @property {'TokenInvalid'} TokenInvalid + * @property {'TokenMissing'} TokenMissing + * @property {'ApplicationCommandPermissionsTokenMissing'} ApplicationCommandPermissionsTokenMissing + + * @property {'WSCloseRequested'} WSCloseRequested + * <warn>This property is deprecated.</warn> + * @property {'WSConnectionExists'} WSConnectionExists + * <warn>This property is deprecated.</warn> + * @property {'WSNotOpen'} WSNotOpen + * <warn>This property is deprecated.</warn> + * @property {'ManagerDestroyed'} ManagerDestroyed + + * @property {'BitFieldInvalid'} BitFieldInvalid + + * @property {'ShardingInvalid'} ShardingInvalid + * <warn>This property is deprecated.</warn> + * @property {'ShardingRequired'} ShardingRequired + * <warn>This property is deprecated.</warn> + * @property {'InvalidIntents'} InvalidIntents + * <warn>This property is deprecated.</warn> + * @property {'DisallowedIntents'} DisallowedIntents + * <warn>This property is deprecated.</warn> + * @property {'ShardingNoShards'} ShardingNoShards + * @property {'ShardingInProcess'} ShardingInProcess + * @property {'ShardingInvalidEvalBroadcast'} ShardingInvalidEvalBroadcast + * @property {'ShardingShardNotFound'} ShardingShardNotFound + * @property {'ShardingAlreadySpawned'} ShardingAlreadySpawned + * @property {'ShardingProcessExists'} ShardingProcessExists + * @property {'ShardingWorkerExists'} ShardingWorkerExists + * @property {'ShardingReadyTimeout'} ShardingReadyTimeout + * @property {'ShardingReadyDisconnected'} ShardingReadyDisconnected + * @property {'ShardingReadyDied'} ShardingReadyDied + * @property {'ShardingNoChildExists'} ShardingNoChildExists + * @property {'ShardingShardMiscalculation'} ShardingShardMiscalculation + + * @property {'ColorRange'} ColorRange + * @property {'ColorConvert'} ColorConvert + + * @property {'InviteOptionsMissingChannel'} InviteOptionsMissingChannel + + * @property {'ButtonLabel'} ButtonLabel + * @property {'ButtonURL'} ButtonURL + * @property {'ButtonCustomId'} ButtonCustomId + + * @property {'SelectMenuCustomId'} SelectMenuCustomId + * @property {'SelectMenuPlaceholder'} SelectMenuPlaceholder + * @property {'SelectOptionLabel'} SelectOptionLabel + * @property {'SelectOptionValue'} SelectOptionValue + * @property {'SelectOptionDescription'} SelectOptionDescription + + * @property {'InteractionCollectorError'} InteractionCollectorError + + * @property {'FileNotFound'} FileNotFound + + * @property {'UserBannerNotFetched'} UserBannerNotFetched + * @property {'UserNoDMChannel'} UserNoDMChannel + + * @property {'VoiceNotStageChannel'} VoiceNotStageChannel + + * @property {'VoiceStateNotOwn'} VoiceStateNotOwn + * @property {'VoiceStateInvalidType'} VoiceStateInvalidType + + * @property {'ReqResourceType'} ReqResourceType + + * @property {'ImageFormat'} ImageFormat + * @property {'ImageSize'} ImageSize + + * @property {'MessageBulkDeleteType'} MessageBulkDeleteType + * @property {'MessageNonceType'} MessageNonceType + * @property {'MessageContentType'} MessageContentType + + * @property {'SplitMaxLen'} SplitMaxLen + + * @property {'BanResolveId'} BanResolveId + * @property {'FetchBanResolveId'} FetchBanResolveId + + * @property {'PruneDaysType'} PruneDaysType + + * @property {'GuildChannelResolve'} GuildChannelResolve + * @property {'GuildVoiceChannelResolve'} GuildVoiceChannelResolve + * @property {'GuildChannelOrphan'} GuildChannelOrphan + * @property {'GuildChannelUnowned'} GuildChannelUnowned + * @property {'GuildOwned'} GuildOwned + * @property {'GuildMembersTimeout'} GuildMembersTimeout + * @property {'GuildUncachedMe'} GuildUncachedMe + * @property {'ChannelNotCached'} ChannelNotCached + * @property {'StageChannelResolve'} StageChannelResolve + * @property {'GuildScheduledEventResolve'} GuildScheduledEventResolve + * @property {'FetchOwnerId'} FetchOwnerId + + * @property {'InvalidType'} InvalidType + * @property {'InvalidElement'} InvalidElement + + * @property {'MessageThreadParent'} MessageThreadParent + * @property {'MessageExistingThread'} MessageExistingThread + * @property {'ThreadInvitableType'} ThreadInvitableType + + * @property {'WebhookMessage'} WebhookMessage + * @property {'WebhookTokenUnavailable'} WebhookTokenUnavailable + * @property {'WebhookURLInvalid'} WebhookURLInvalid + * @property {'WebhookApplication'} WebhookApplication + * @property {'MessageReferenceMissing'} MessageReferenceMissing + + * @property {'EmojiType'} EmojiType + * @property {'EmojiManaged'} EmojiManaged + * @property {'MissingManageGuildExpressionsPermission'} MissingManageGuildExpressionsPermission + * @property {'MissingManageEmojisAndStickersPermission'} MissingManageEmojisAndStickersPermission + * <warn>This property is deprecated. Use `MissingManageGuildExpressionsPermission` instead.</warn> + * + * @property {'NotGuildSticker'} NotGuildSticker + + * @property {'ReactionResolveUser'} ReactionResolveUser + + * @property {'VanityURL'} VanityURL + + * @property {'InviteResolveCode'} InviteResolveCode + + * @property {'InviteNotFound'} InviteNotFound + + * @property {'DeleteGroupDMChannel'} DeleteGroupDMChannel + * @property {'FetchGroupDMChannel'} FetchGroupDMChannel + + * @property {'MemberFetchNonceLength'} MemberFetchNonceLength + + * @property {'GlobalCommandPermissions'} GlobalCommandPermissions + * @property {'GuildUncachedEntityResolve'} GuildUncachedEntityResolve + + * @property {'InteractionAlreadyReplied'} InteractionAlreadyReplied + * @property {'InteractionNotReplied'} InteractionNotReplied + * @property {'InteractionEphemeralReplied'} InteractionEphemeralReplied + * <warn>This property is deprecated.</warn> + + * @property {'CommandInteractionOptionNotFound'} CommandInteractionOptionNotFound + * @property {'CommandInteractionOptionType'} CommandInteractionOptionType + * @property {'CommandInteractionOptionEmpty'} CommandInteractionOptionEmpty + * @property {'CommandInteractionOptionNoSubcommand'} CommandInteractionOptionNoSubcommand + * @property {'CommandInteractionOptionNoSubcommandGroup'} CommandInteractionOptionNoSubcommandGroup + * @property {'CommandInteractionOptionInvalidChannelType'} CommandInteractionOptionInvalidChannelType + * @property {'AutocompleteInteractionOptionNoFocusedOption'} AutocompleteInteractionOptionNoFocusedOption + + * @property {'ModalSubmitInteractionFieldNotFound'} ModalSubmitInteractionFieldNotFound + * @property {'ModalSubmitInteractionFieldType'} ModalSubmitInteractionFieldType + + * @property {'InvalidMissingScopes'} InvalidMissingScopes + * @property {'InvalidScopesWithPermissions'} InvalidScopesWithPermissions + + * @property {'NotImplemented'} NotImplemented + + * @property {'GuildForumMessageRequired'} GuildForumMessageRequired + + * @property {'SweepFilterReturn'} SweepFilterReturn + */ + +const keys = [ + 'ClientInvalidOption', + 'ClientInvalidProvidedShards', + 'ClientMissingIntents', + 'ClientNotReady', + + 'TokenInvalid', + 'TokenMissing', + 'ApplicationCommandPermissionsTokenMissing', + + 'WSCloseRequested', + 'WSConnectionExists', + 'WSNotOpen', + 'ManagerDestroyed', + + 'BitFieldInvalid', + + 'ShardingInvalid', + 'ShardingRequired', + 'InvalidIntents', + 'DisallowedIntents', + 'ShardingNoShards', + 'ShardingInProcess', + 'ShardingInvalidEvalBroadcast', + 'ShardingShardNotFound', + 'ShardingAlreadySpawned', + 'ShardingProcessExists', + 'ShardingWorkerExists', + 'ShardingReadyTimeout', + 'ShardingReadyDisconnected', + 'ShardingReadyDied', + 'ShardingNoChildExists', + 'ShardingShardMiscalculation', + + 'ColorRange', + 'ColorConvert', + + 'InviteOptionsMissingChannel', + + 'ButtonLabel', + 'ButtonURL', + 'ButtonCustomId', + + 'SelectMenuCustomId', + 'SelectMenuPlaceholder', + 'SelectOptionLabel', + 'SelectOptionValue', + 'SelectOptionDescription', + + 'InteractionCollectorError', + + 'FileNotFound', + + 'UserBannerNotFetched', + 'UserNoDMChannel', + + 'VoiceNotStageChannel', + + 'VoiceStateNotOwn', + 'VoiceStateInvalidType', + + 'ReqResourceType', + + 'ImageFormat', + 'ImageSize', + + 'MessageBulkDeleteType', + 'MessageNonceType', + 'MessageContentType', + + 'SplitMaxLen', + + 'BanResolveId', + 'FetchBanResolveId', + + 'PruneDaysType', + + 'GuildChannelResolve', + 'GuildVoiceChannelResolve', + 'GuildChannelOrphan', + 'GuildChannelUnowned', + 'GuildOwned', + 'GuildMembersTimeout', + 'GuildUncachedMe', + 'ChannelNotCached', + 'StageChannelResolve', + 'GuildScheduledEventResolve', + 'FetchOwnerId', + + 'InvalidType', + 'InvalidElement', + + 'MessageThreadParent', + 'MessageExistingThread', + 'ThreadInvitableType', + + 'WebhookMessage', + 'WebhookTokenUnavailable', + 'WebhookURLInvalid', + 'WebhookApplication', + 'MessageReferenceMissing', + + 'EmojiType', + 'EmojiManaged', + 'MissingManageGuildExpressionsPermission', + 'MissingManageEmojisAndStickersPermission', + + 'NotGuildSticker', + + 'ReactionResolveUser', + + 'VanityURL', + + 'InviteResolveCode', + + 'InviteNotFound', + + 'DeleteGroupDMChannel', + 'FetchGroupDMChannel', + + 'MemberFetchNonceLength', + + 'GlobalCommandPermissions', + 'GuildUncachedEntityResolve', + + 'InteractionAlreadyReplied', + 'InteractionNotReplied', + 'InteractionEphemeralReplied', + + 'CommandInteractionOptionNotFound', + 'CommandInteractionOptionType', + 'CommandInteractionOptionEmpty', + 'CommandInteractionOptionNoSubcommand', + 'CommandInteractionOptionNoSubcommandGroup', + 'CommandInteractionOptionInvalidChannelType', + 'AutocompleteInteractionOptionNoFocusedOption', + + 'ModalSubmitInteractionFieldNotFound', + 'ModalSubmitInteractionFieldType', + + 'InvalidMissingScopes', + 'InvalidScopesWithPermissions', + + 'NotImplemented', + + 'SweepFilterReturn', + + 'GuildForumMessageRequired', +]; + +// JSDoc for IntelliSense purposes +/** + * @type {DiscordjsErrorCodes} + * @ignore + */ +module.exports = Object.fromEntries(keys.map(key => [key, key])); diff --git a/node_modules/discord.js/src/errors/Messages.js b/node_modules/discord.js/src/errors/Messages.js new file mode 100644 index 0000000..550219f --- /dev/null +++ b/node_modules/discord.js/src/errors/Messages.js @@ -0,0 +1,170 @@ +'use strict'; + +const DjsErrorCodes = require('./ErrorCodes'); + +const Messages = { + [DjsErrorCodes.ClientInvalidOption]: (prop, must) => `The ${prop} option must be ${must}`, + [DjsErrorCodes.ClientInvalidProvidedShards]: 'None of the provided shards were valid.', + [DjsErrorCodes.ClientMissingIntents]: 'Valid intents must be provided for the Client.', + [DjsErrorCodes.ClientNotReady]: action => `The client needs to be logged in to ${action}.`, + + [DjsErrorCodes.TokenInvalid]: 'An invalid token was provided.', + [DjsErrorCodes.TokenMissing]: 'Request to use token, but token was unavailable to the client.', + [DjsErrorCodes.ApplicationCommandPermissionsTokenMissing]: + 'Editing application command permissions requires an OAuth2 bearer token, but none was provided.', + + [DjsErrorCodes.WSCloseRequested]: 'WebSocket closed due to user request.', + [DjsErrorCodes.WSConnectionExists]: 'There is already an existing WebSocket connection.', + [DjsErrorCodes.WSNotOpen]: (data = 'data') => `WebSocket not open to send ${data}`, + [DjsErrorCodes.ManagerDestroyed]: 'Manager was destroyed.', + + [DjsErrorCodes.BitFieldInvalid]: bit => `Invalid bitfield flag or number: ${bit}.`, + + [DjsErrorCodes.ShardingInvalid]: 'Invalid shard settings were provided.', + [DjsErrorCodes.ShardingRequired]: 'This session would have handled too many guilds - Sharding is required.', + [DjsErrorCodes.InvalidIntents]: 'Invalid intent provided for WebSocket intents.', + [DjsErrorCodes.DisallowedIntents]: 'Privileged intent provided is not enabled or whitelisted.', + [DjsErrorCodes.ShardingNoShards]: 'No shards have been spawned.', + [DjsErrorCodes.ShardingInProcess]: 'Shards are still being spawned.', + [DjsErrorCodes.ShardingInvalidEvalBroadcast]: 'Script to evaluate must be a function', + [DjsErrorCodes.ShardingShardNotFound]: id => `Shard ${id} could not be found.`, + [DjsErrorCodes.ShardingAlreadySpawned]: count => `Already spawned ${count} shards.`, + [DjsErrorCodes.ShardingProcessExists]: id => `Shard ${id} already has an active process.`, + [DjsErrorCodes.ShardingWorkerExists]: id => `Shard ${id} already has an active worker.`, + [DjsErrorCodes.ShardingReadyTimeout]: id => `Shard ${id}'s Client took too long to become ready.`, + [DjsErrorCodes.ShardingReadyDisconnected]: id => `Shard ${id}'s Client disconnected before becoming ready.`, + [DjsErrorCodes.ShardingReadyDied]: id => `Shard ${id}'s process exited before its Client became ready.`, + [DjsErrorCodes.ShardingNoChildExists]: id => `Shard ${id} has no active process or worker.`, + [DjsErrorCodes.ShardingShardMiscalculation]: (shard, guild, count) => + `Calculated invalid shard ${shard} for guild ${guild} with ${count} shards.`, + + [DjsErrorCodes.ColorRange]: 'Color must be within the range 0 - 16777215 (0xFFFFFF).', + [DjsErrorCodes.ColorConvert]: 'Unable to convert color to a number.', + + [DjsErrorCodes.InviteOptionsMissingChannel]: + 'A valid guild channel must be provided when GuildScheduledEvent is EXTERNAL.', + + [DjsErrorCodes.ButtonLabel]: 'MessageButton label must be a string', + [DjsErrorCodes.ButtonURL]: 'MessageButton URL must be a string', + [DjsErrorCodes.ButtonCustomId]: 'MessageButton customId must be a string', + + [DjsErrorCodes.SelectMenuCustomId]: 'MessageSelectMenu customId must be a string', + [DjsErrorCodes.SelectMenuPlaceholder]: 'MessageSelectMenu placeholder must be a string', + [DjsErrorCodes.SelectOptionLabel]: 'MessageSelectOption label must be a string', + [DjsErrorCodes.SelectOptionValue]: 'MessageSelectOption value must be a string', + [DjsErrorCodes.SelectOptionDescription]: 'MessageSelectOption description must be a string', + + [DjsErrorCodes.InteractionCollectorError]: reason => + `Collector received no interactions before ending with reason: ${reason}`, + + [DjsErrorCodes.FileNotFound]: file => `File could not be found: ${file}`, + + [DjsErrorCodes.UserBannerNotFetched]: "You must fetch this user's banner before trying to generate its URL!", + [DjsErrorCodes.UserNoDMChannel]: 'No DM Channel exists!', + + [DjsErrorCodes.VoiceNotStageChannel]: 'You are only allowed to do this in stage channels.', + + [DjsErrorCodes.VoiceStateNotOwn]: + 'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.', + [DjsErrorCodes.VoiceStateInvalidType]: name => `${name} must be a boolean.`, + + [DjsErrorCodes.ReqResourceType]: 'The resource must be a string, Buffer or a valid file stream.', + + [DjsErrorCodes.ImageFormat]: format => `Invalid image format: ${format}`, + [DjsErrorCodes.ImageSize]: size => `Invalid image size: ${size}`, + + [DjsErrorCodes.MessageBulkDeleteType]: 'The messages must be an Array, Collection, or number.', + [DjsErrorCodes.MessageNonceType]: 'Message nonce must be an integer or a string.', + [DjsErrorCodes.MessageContentType]: 'Message content must be a string.', + + [DjsErrorCodes.SplitMaxLen]: 'Chunk exceeds the max length and contains no split characters.', + + [DjsErrorCodes.BanResolveId]: (ban = false) => `Couldn't resolve the user id to ${ban ? 'ban' : 'unban'}.`, + [DjsErrorCodes.FetchBanResolveId]: "Couldn't resolve the user id to fetch the ban.", + + [DjsErrorCodes.PruneDaysType]: 'Days must be a number', + + [DjsErrorCodes.GuildChannelResolve]: 'Could not resolve channel to a guild channel.', + [DjsErrorCodes.GuildVoiceChannelResolve]: 'Could not resolve channel to a guild voice channel.', + [DjsErrorCodes.GuildChannelOrphan]: 'Could not find a parent to this guild channel.', + [DjsErrorCodes.GuildChannelUnowned]: "The fetched channel does not belong to this manager's guild.", + [DjsErrorCodes.GuildOwned]: 'Guild is owned by the client.', + [DjsErrorCodes.GuildMembersTimeout]: "Members didn't arrive in time.", + [DjsErrorCodes.GuildUncachedMe]: 'The client user as a member of this guild is uncached.', + [DjsErrorCodes.ChannelNotCached]: 'Could not find the channel where this message came from in the cache!', + [DjsErrorCodes.StageChannelResolve]: 'Could not resolve channel to a stage channel.', + [DjsErrorCodes.GuildScheduledEventResolve]: 'Could not resolve the guild scheduled event.', + [DjsErrorCodes.FetchOwnerId]: "Couldn't resolve the guild ownerId to fetch the member.", + + [DjsErrorCodes.InvalidType]: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`, + [DjsErrorCodes.InvalidElement]: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`, + + [DjsErrorCodes.MessageThreadParent]: 'The message was not sent in a guild text or news channel', + [DjsErrorCodes.MessageExistingThread]: 'The message already has a thread', + [DjsErrorCodes.ThreadInvitableType]: type => `Invitable cannot be edited on ${type}`, + + [DjsErrorCodes.WebhookMessage]: 'The message was not sent by a webhook.', + [DjsErrorCodes.WebhookTokenUnavailable]: 'This action requires a webhook token, but none is available.', + [DjsErrorCodes.WebhookURLInvalid]: 'The provided webhook URL is not valid.', + [DjsErrorCodes.WebhookApplication]: 'This message webhook belongs to an application and cannot be fetched.', + [DjsErrorCodes.MessageReferenceMissing]: 'The message does not reference another message', + + [DjsErrorCodes.EmojiType]: 'Emoji must be a string or GuildEmoji/ReactionEmoji', + [DjsErrorCodes.EmojiManaged]: 'Emoji is managed and has no Author.', + [DjsErrorCodes.MissingManageGuildExpressionsPermission]: guild => + `Client must have Manage Guild Expressions permission in guild ${guild} to see emoji authors.`, + [DjsErrorCodes.MissingManageEmojisAndStickersPermission]: guild => + `Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`, + + [DjsErrorCodes.NotGuildSticker]: 'Sticker is a standard (non-guild) sticker and has no author.', + + [DjsErrorCodes.ReactionResolveUser]: "Couldn't resolve the user id to remove from the reaction.", + + [DjsErrorCodes.VanityURL]: 'This guild does not have the vanity URL feature enabled.', + + [DjsErrorCodes.InviteResolveCode]: 'Could not resolve the code to fetch the invite.', + + [DjsErrorCodes.InviteNotFound]: 'Could not find the requested invite.', + + [DjsErrorCodes.DeleteGroupDMChannel]: "Bots don't have access to Group DM Channels and cannot delete them", + [DjsErrorCodes.FetchGroupDMChannel]: "Bots don't have access to Group DM Channels and cannot fetch them", + + [DjsErrorCodes.MemberFetchNonceLength]: 'Nonce length must not exceed 32 characters.', + + [DjsErrorCodes.GlobalCommandPermissions]: + 'Permissions for global commands may only be fetched or modified by providing a GuildResolvable ' + + "or from a guild's application command manager.", + [DjsErrorCodes.GuildUncachedEntityResolve]: type => + `Cannot resolve ${type} from an arbitrary guild, provide an id instead`, + + [DjsErrorCodes.InteractionAlreadyReplied]: 'The reply to this interaction has already been sent or deferred.', + [DjsErrorCodes.InteractionNotReplied]: 'The reply to this interaction has not been sent or deferred.', + [DjsErrorCodes.InteractionEphemeralReplied]: 'Ephemeral responses cannot be deleted.', + + [DjsErrorCodes.CommandInteractionOptionNotFound]: name => `Required option "${name}" not found.`, + [DjsErrorCodes.CommandInteractionOptionType]: (name, type, expected) => + `Option "${name}" is of type: ${type}; expected ${expected}.`, + [DjsErrorCodes.CommandInteractionOptionEmpty]: (name, type) => + `Required option "${name}" is of type: ${type}; expected a non-empty value.`, + [DjsErrorCodes.CommandInteractionOptionNoSubcommand]: 'No subcommand specified for interaction.', + [DjsErrorCodes.CommandInteractionOptionNoSubcommandGroup]: 'No subcommand group specified for interaction.', + [DjsErrorCodes.CommandInteractionOptionInvalidChannelType]: (name, type, expected) => + `The type of channel of the option "${name}" is: ${type}; expected ${expected}.`, + [DjsErrorCodes.AutocompleteInteractionOptionNoFocusedOption]: 'No focused option for autocomplete interaction.', + + [DjsErrorCodes.ModalSubmitInteractionFieldNotFound]: customId => + `Required field with custom id "${customId}" not found.`, + [DjsErrorCodes.ModalSubmitInteractionFieldType]: (customId, type, expected) => + `Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`, + + [DjsErrorCodes.InvalidMissingScopes]: 'At least one valid scope must be provided for the invite', + [DjsErrorCodes.InvalidScopesWithPermissions]: 'Permissions cannot be set without the bot scope.', + + [DjsErrorCodes.NotImplemented]: (what, name) => `Method ${what} not implemented on ${name}.`, + + [DjsErrorCodes.SweepFilterReturn]: 'The return value of the sweepFilter function was not false or a Function', + + [DjsErrorCodes.GuildForumMessageRequired]: 'You must provide a message to create a guild forum thread', +}; + +module.exports = Messages; diff --git a/node_modules/discord.js/src/errors/index.js b/node_modules/discord.js/src/errors/index.js new file mode 100644 index 0000000..78dc5c6 --- /dev/null +++ b/node_modules/discord.js/src/errors/index.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = require('./DJSError'); +module.exports.ErrorCodes = require('./ErrorCodes'); +module.exports.Messages = require('./Messages'); diff --git a/node_modules/discord.js/src/index.js b/node_modules/discord.js/src/index.js new file mode 100644 index 0000000..5021331 --- /dev/null +++ b/node_modules/discord.js/src/index.js @@ -0,0 +1,220 @@ +'use strict'; + +const { __exportStar } = require('tslib'); + +// "Root" classes (starting points) +exports.BaseClient = require('./client/BaseClient'); +exports.Client = require('./client/Client'); +exports.Shard = require('./sharding/Shard'); +exports.ShardClientUtil = require('./sharding/ShardClientUtil'); +exports.ShardingManager = require('./sharding/ShardingManager'); +exports.WebhookClient = require('./client/WebhookClient'); + +// Errors +exports.DiscordjsError = require('./errors/DJSError').DiscordjsError; +exports.DiscordjsTypeError = require('./errors/DJSError').DiscordjsTypeError; +exports.DiscordjsRangeError = require('./errors/DJSError').DiscordjsRangeError; +exports.DiscordjsErrorCodes = require('./errors/ErrorCodes'); + +// Utilities +exports.ActivityFlagsBitField = require('./util/ActivityFlagsBitField'); +exports.ApplicationFlagsBitField = require('./util/ApplicationFlagsBitField'); +exports.AttachmentFlagsBitField = require('./util/AttachmentFlagsBitField'); +exports.BaseManager = require('./managers/BaseManager'); +exports.BitField = require('./util/BitField'); +exports.ChannelFlagsBitField = require('./util/ChannelFlagsBitField'); +exports.Collection = require('@discordjs/collection').Collection; +exports.Constants = require('./util/Constants'); +exports.Colors = require('./util/Colors'); +exports.DataResolver = require('./util/DataResolver'); +exports.Events = require('./util/Events'); +exports.Formatters = require('./util/Formatters'); +exports.GuildMemberFlagsBitField = require('./util/GuildMemberFlagsBitField').GuildMemberFlagsBitField; +exports.IntentsBitField = require('./util/IntentsBitField'); +exports.LimitedCollection = require('./util/LimitedCollection'); +exports.MessageFlagsBitField = require('./util/MessageFlagsBitField'); +exports.Options = require('./util/Options'); +exports.Partials = require('./util/Partials'); +exports.PermissionsBitField = require('./util/PermissionsBitField'); +exports.RoleFlagsBitField = require('./util/RoleFlagsBitField'); +exports.ShardEvents = require('./util/ShardEvents'); +exports.Status = require('./util/Status'); +exports.SnowflakeUtil = require('@sapphire/snowflake').DiscordSnowflake; +exports.Sweepers = require('./util/Sweepers'); +exports.SystemChannelFlagsBitField = require('./util/SystemChannelFlagsBitField'); +exports.ThreadMemberFlagsBitField = require('./util/ThreadMemberFlagsBitField'); +exports.UserFlagsBitField = require('./util/UserFlagsBitField'); +__exportStar(require('./util/Util.js'), exports); +exports.WebSocketShardEvents = require('./util/WebSocketShardEvents'); +exports.version = require('../package.json').version; + +// Managers +exports.ApplicationCommandManager = require('./managers/ApplicationCommandManager'); +exports.ApplicationCommandPermissionsManager = require('./managers/ApplicationCommandPermissionsManager'); +exports.AutoModerationRuleManager = require('./managers/AutoModerationRuleManager'); +exports.BaseGuildEmojiManager = require('./managers/BaseGuildEmojiManager'); +exports.CachedManager = require('./managers/CachedManager'); +exports.ChannelManager = require('./managers/ChannelManager'); +exports.ClientVoiceManager = require('./client/voice/ClientVoiceManager'); +exports.DataManager = require('./managers/DataManager'); +exports.DMMessageManager = require('./managers/DMMessageManager'); +exports.GuildApplicationCommandManager = require('./managers/GuildApplicationCommandManager'); +exports.GuildBanManager = require('./managers/GuildBanManager'); +exports.GuildChannelManager = require('./managers/GuildChannelManager'); +exports.GuildEmojiManager = require('./managers/GuildEmojiManager'); +exports.GuildEmojiRoleManager = require('./managers/GuildEmojiRoleManager'); +exports.GuildForumThreadManager = require('./managers/GuildForumThreadManager'); +exports.GuildInviteManager = require('./managers/GuildInviteManager'); +exports.GuildManager = require('./managers/GuildManager'); +exports.GuildMemberManager = require('./managers/GuildMemberManager'); +exports.GuildMemberRoleManager = require('./managers/GuildMemberRoleManager'); +exports.GuildMessageManager = require('./managers/GuildMessageManager'); +exports.GuildScheduledEventManager = require('./managers/GuildScheduledEventManager'); +exports.GuildStickerManager = require('./managers/GuildStickerManager'); +exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager'); +exports.MessageManager = require('./managers/MessageManager'); +exports.PermissionOverwriteManager = require('./managers/PermissionOverwriteManager'); +exports.PresenceManager = require('./managers/PresenceManager'); +exports.ReactionManager = require('./managers/ReactionManager'); +exports.ReactionUserManager = require('./managers/ReactionUserManager'); +exports.RoleManager = require('./managers/RoleManager'); +exports.StageInstanceManager = require('./managers/StageInstanceManager'); +exports.ThreadManager = require('./managers/ThreadManager'); +exports.ThreadMemberManager = require('./managers/ThreadMemberManager'); +exports.UserManager = require('./managers/UserManager'); +exports.VoiceStateManager = require('./managers/VoiceStateManager'); +exports.WebSocketManager = require('./client/websocket/WebSocketManager'); +exports.WebSocketShard = require('./client/websocket/WebSocketShard'); + +// Structures +exports.ActionRow = require('./structures/ActionRow'); +exports.ActionRowBuilder = require('./structures/ActionRowBuilder'); +exports.Activity = require('./structures/Presence').Activity; +exports.AnonymousGuild = require('./structures/AnonymousGuild'); +exports.Application = require('./structures/interfaces/Application'); +exports.ApplicationCommand = require('./structures/ApplicationCommand'); +exports.ApplicationRoleConnectionMetadata = + require('./structures/ApplicationRoleConnectionMetadata').ApplicationRoleConnectionMetadata; +exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction'); +exports.AutoModerationActionExecution = require('./structures/AutoModerationActionExecution'); +exports.AutoModerationRule = require('./structures/AutoModerationRule'); +exports.Base = require('./structures/Base'); +exports.BaseGuild = require('./structures/BaseGuild'); +exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji'); +exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel'); +exports.BaseGuildVoiceChannel = require('./structures/BaseGuildVoiceChannel'); +exports.ButtonBuilder = require('./structures/ButtonBuilder'); +exports.ButtonComponent = require('./structures/ButtonComponent'); +exports.ButtonInteraction = require('./structures/ButtonInteraction'); +exports.CategoryChannel = require('./structures/CategoryChannel'); +exports.BaseChannel = require('./structures/BaseChannel').BaseChannel; +exports.ChatInputCommandInteraction = require('./structures/ChatInputCommandInteraction'); +exports.ClientApplication = require('./structures/ClientApplication'); +exports.ClientPresence = require('./structures/ClientPresence'); +exports.ClientUser = require('./structures/ClientUser'); +exports.CommandInteraction = require('./structures/CommandInteraction'); +exports.Collector = require('./structures/interfaces/Collector'); +exports.CommandInteractionOptionResolver = require('./structures/CommandInteractionOptionResolver'); +exports.Component = require('./structures/Component'); +exports.ContextMenuCommandInteraction = require('./structures/ContextMenuCommandInteraction'); +exports.DMChannel = require('./structures/DMChannel'); +exports.Embed = require('./structures/Embed'); +exports.EmbedBuilder = require('./structures/EmbedBuilder'); +exports.Emoji = require('./structures/Emoji').Emoji; +exports.ForumChannel = require('./structures/ForumChannel'); +exports.Guild = require('./structures/Guild').Guild; +exports.GuildAuditLogs = require('./structures/GuildAuditLogs'); +exports.GuildAuditLogsEntry = require('./structures/GuildAuditLogsEntry'); +exports.GuildBan = require('./structures/GuildBan'); +exports.GuildChannel = require('./structures/GuildChannel'); +exports.GuildEmoji = require('./structures/GuildEmoji'); +exports.GuildMember = require('./structures/GuildMember').GuildMember; +exports.GuildOnboarding = require('./structures/GuildOnboarding').GuildOnboarding; +exports.GuildOnboardingPrompt = require('./structures/GuildOnboardingPrompt').GuildOnboardingPrompt; +exports.GuildOnboardingPromptOption = require('./structures/GuildOnboardingPromptOption').GuildOnboardingPromptOption; +exports.GuildPreview = require('./structures/GuildPreview'); +exports.GuildPreviewEmoji = require('./structures/GuildPreviewEmoji'); +exports.GuildScheduledEvent = require('./structures/GuildScheduledEvent').GuildScheduledEvent; +exports.GuildTemplate = require('./structures/GuildTemplate'); +exports.Integration = require('./structures/Integration'); +exports.IntegrationApplication = require('./structures/IntegrationApplication'); +exports.BaseInteraction = require('./structures/BaseInteraction'); +exports.InteractionCollector = require('./structures/InteractionCollector'); +exports.InteractionResponse = require('./structures/InteractionResponse'); +exports.InteractionWebhook = require('./structures/InteractionWebhook'); +exports.Invite = require('./structures/Invite'); +exports.InviteStageInstance = require('./structures/InviteStageInstance'); +exports.InviteGuild = require('./structures/InviteGuild'); +exports.Message = require('./structures/Message').Message; +exports.Attachment = require('./structures/Attachment'); +exports.AttachmentBuilder = require('./structures/AttachmentBuilder'); +exports.ModalBuilder = require('./structures/ModalBuilder'); +exports.MessageCollector = require('./structures/MessageCollector'); +exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction'); +exports.MessageContextMenuCommandInteraction = require('./structures/MessageContextMenuCommandInteraction'); +exports.MessageMentions = require('./structures/MessageMentions'); +exports.MessagePayload = require('./structures/MessagePayload'); +exports.MessageReaction = require('./structures/MessageReaction'); +exports.ModalSubmitInteraction = require('./structures/ModalSubmitInteraction'); +exports.ModalSubmitFields = require('./structures/ModalSubmitFields'); +exports.NewsChannel = require('./structures/NewsChannel'); +exports.OAuth2Guild = require('./structures/OAuth2Guild'); +exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel'); +exports.PermissionOverwrites = require('./structures/PermissionOverwrites'); +exports.Presence = require('./structures/Presence').Presence; +exports.ReactionCollector = require('./structures/ReactionCollector'); +exports.ReactionEmoji = require('./structures/ReactionEmoji'); +exports.RichPresenceAssets = require('./structures/Presence').RichPresenceAssets; +exports.Role = require('./structures/Role').Role; +exports.SelectMenuBuilder = require('./structures/SelectMenuBuilder'); +exports.ChannelSelectMenuBuilder = require('./structures/ChannelSelectMenuBuilder'); +exports.MentionableSelectMenuBuilder = require('./structures/MentionableSelectMenuBuilder'); +exports.RoleSelectMenuBuilder = require('./structures/RoleSelectMenuBuilder'); +exports.StringSelectMenuBuilder = require('./structures/StringSelectMenuBuilder'); +exports.UserSelectMenuBuilder = require('./structures/UserSelectMenuBuilder'); +exports.BaseSelectMenuComponent = require('./structures/BaseSelectMenuComponent'); +exports.SelectMenuComponent = require('./structures/SelectMenuComponent'); +exports.ChannelSelectMenuComponent = require('./structures/ChannelSelectMenuComponent'); +exports.MentionableSelectMenuComponent = require('./structures/MentionableSelectMenuComponent'); +exports.RoleSelectMenuComponent = require('./structures/RoleSelectMenuComponent'); +exports.StringSelectMenuComponent = require('./structures/StringSelectMenuComponent'); +exports.UserSelectMenuComponent = require('./structures/UserSelectMenuComponent'); +exports.SelectMenuInteraction = require('./structures/SelectMenuInteraction'); +exports.ChannelSelectMenuInteraction = require('./structures/ChannelSelectMenuInteraction'); +exports.MentionableSelectMenuInteraction = require('./structures/MentionableSelectMenuInteraction'); +exports.MentionableSelectMenuInteraction = require('./structures/MentionableSelectMenuInteraction'); +exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteraction'); +exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction'); +exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction'); +exports.SelectMenuOptionBuilder = require('./structures/SelectMenuOptionBuilder'); +exports.StringSelectMenuOptionBuilder = require('./structures/StringSelectMenuOptionBuilder'); +exports.StageChannel = require('./structures/StageChannel'); +exports.StageInstance = require('./structures/StageInstance').StageInstance; +exports.Sticker = require('./structures/Sticker').Sticker; +exports.StickerPack = require('./structures/StickerPack'); +exports.Team = require('./structures/Team'); +exports.TeamMember = require('./structures/TeamMember'); +exports.TextChannel = require('./structures/TextChannel'); +exports.TextInputBuilder = require('./structures/TextInputBuilder'); +exports.TextInputComponent = require('./structures/TextInputComponent'); +exports.ThreadChannel = require('./structures/ThreadChannel'); +exports.ThreadMember = require('./structures/ThreadMember'); +exports.Typing = require('./structures/Typing'); +exports.User = require('./structures/User'); +exports.UserContextMenuCommandInteraction = require('./structures/UserContextMenuCommandInteraction'); +exports.VoiceChannel = require('./structures/VoiceChannel'); +exports.VoiceRegion = require('./structures/VoiceRegion'); +exports.VoiceState = require('./structures/VoiceState'); +exports.Webhook = require('./structures/Webhook'); +exports.Widget = require('./structures/Widget'); +exports.WidgetMember = require('./structures/WidgetMember'); +exports.WelcomeChannel = require('./structures/WelcomeChannel'); +exports.WelcomeScreen = require('./structures/WelcomeScreen'); + +// External +__exportStar(require('discord-api-types/v10'), exports); +__exportStar(require('@discordjs/builders'), exports); +__exportStar(require('@discordjs/formatters'), exports); +__exportStar(require('@discordjs/rest'), exports); +__exportStar(require('@discordjs/util'), exports); +__exportStar(require('@discordjs/ws'), exports); diff --git a/node_modules/discord.js/src/managers/ApplicationCommandManager.js b/node_modules/discord.js/src/managers/ApplicationCommandManager.js new file mode 100644 index 0000000..417afc4 --- /dev/null +++ b/node_modules/discord.js/src/managers/ApplicationCommandManager.js @@ -0,0 +1,263 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { isJSONEncodable } = require('@discordjs/util'); +const { Routes } = require('discord-api-types/v10'); +const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const ApplicationCommand = require('../structures/ApplicationCommand'); +const PermissionsBitField = require('../util/PermissionsBitField'); + +/** + * Manages API methods for application commands and stores their cache. + * @extends {CachedManager} + */ +class ApplicationCommandManager extends CachedManager { + constructor(client, iterable) { + super(client, ApplicationCommand, iterable); + + /** + * The manager for permissions of arbitrary commands on arbitrary guilds + * @type {ApplicationCommandPermissionsManager} + */ + this.permissions = new ApplicationCommandPermissionsManager(this); + } + + /** + * The cache of this manager + * @type {Collection<Snowflake, ApplicationCommand>} + * @name ApplicationCommandManager#cache + */ + + _add(data, cache, guildId) { + return super._add(data, cache, { extras: [this.guild, guildId] }); + } + + /** + * The APIRouter path to the commands + * @param {Snowflake} [options.id] The application command's id + * @param {Snowflake} [options.guildId] The guild's id to use in the path, + * ignored when using a {@link GuildApplicationCommandManager} + * @returns {string} + * @private + */ + commandPath({ id, guildId } = {}) { + if (this.guild ?? guildId) { + if (id) { + return Routes.applicationGuildCommand(this.client.application.id, this.guild?.id ?? guildId, id); + } + + return Routes.applicationGuildCommands(this.client.application.id, this.guild?.id ?? guildId); + } + + if (id) { + return Routes.applicationCommand(this.client.application.id, id); + } + + return Routes.applicationCommands(this.client.application.id); + } + + /** + * Data that resolves to give an ApplicationCommand object. This can be: + * * An ApplicationCommand object + * * A Snowflake + * @typedef {ApplicationCommand|Snowflake} ApplicationCommandResolvable + */ + + /** + * Data that resolves to the data of an ApplicationCommand + * @typedef {ApplicationCommandData|APIApplicationCommand} ApplicationCommandDataResolvable + */ + + /** + * Options used to fetch data from Discord + * @typedef {Object} BaseFetchOptions + * @property {boolean} [cache=true] Whether to cache the fetched data if it wasn't already + * @property {boolean} [force=false] Whether to skip the cache check and request the API + */ + + /** + * Options used to fetch Application Commands from Discord + * @typedef {BaseFetchOptions} FetchApplicationCommandOptions + * @property {Snowflake} [guildId] The guild's id to fetch commands for, for when the guild is not cached + * @property {LocaleString} [locale] The locale to use when fetching this command + * @property {boolean} [withLocalizations] Whether to fetch all localization data + */ + + /** + * Obtains one or multiple application commands from Discord, or the cache if it's already available. + * @param {Snowflake} [id] The application command's id + * @param {FetchApplicationCommandOptions} [options] Additional options for this fetch + * @returns {Promise<ApplicationCommand|Collection<Snowflake, ApplicationCommand>>} + * @example + * // Fetch a single command + * client.application.commands.fetch('123456789012345678') + * .then(command => console.log(`Fetched command ${command.name}`)) + * .catch(console.error); + * @example + * // Fetch all commands + * guild.commands.fetch() + * .then(commands => console.log(`Fetched ${commands.size} commands`)) + * .catch(console.error); + */ + async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) { + if (typeof id === 'object') { + ({ guildId, cache = true, locale, withLocalizations } = id); + } else if (id) { + if (!force) { + const existing = this.cache.get(id); + if (existing) return existing; + } + const command = await this.client.rest.get(this.commandPath({ id, guildId })); + return this._add(command, cache); + } + + const data = await this.client.rest.get(this.commandPath({ guildId }), { + headers: { + 'X-Discord-Locale': locale, + }, + query: makeURLSearchParams({ with_localizations: withLocalizations }), + }); + return data.reduce((coll, command) => coll.set(command.id, this._add(command, cache, guildId)), new Collection()); + } + + /** + * Creates an application command. + * @param {ApplicationCommandDataResolvable} command The command + * @param {Snowflake} [guildId] The guild's id to create this command in, + * ignored when using a {@link GuildApplicationCommandManager} + * @returns {Promise<ApplicationCommand>} + * @example + * // Create a new command + * client.application.commands.create({ + * name: 'test', + * description: 'A test command', + * }) + * .then(console.log) + * .catch(console.error); + */ + async create(command, guildId) { + const data = await this.client.rest.post(this.commandPath({ guildId }), { + body: this.constructor.transformCommand(command), + }); + return this._add(data, true, guildId); + } + + /** + * Sets all the commands for this application or guild. + * @param {ApplicationCommandDataResolvable[]} commands The commands + * @param {Snowflake} [guildId] The guild's id to create the commands in, + * ignored when using a {@link GuildApplicationCommandManager} + * @returns {Promise<Collection<Snowflake, ApplicationCommand>>} + * @example + * // Set all commands to just this one + * client.application.commands.set([ + * { + * name: 'test', + * description: 'A test command', + * }, + * ]) + * .then(console.log) + * .catch(console.error); + * @example + * // Remove all commands + * guild.commands.set([]) + * .then(console.log) + * .catch(console.error); + */ + async set(commands, guildId) { + const data = await this.client.rest.put(this.commandPath({ guildId }), { + body: commands.map(c => this.constructor.transformCommand(c)), + }); + return data.reduce((coll, command) => coll.set(command.id, this._add(command, true, guildId)), new Collection()); + } + + /** + * Edits an application command. + * @param {ApplicationCommandResolvable} command The command to edit + * @param {Partial<ApplicationCommandDataResolvable>} data The data to update the command with + * @param {Snowflake} [guildId] The guild's id where the command registered, + * ignored when using a {@link GuildApplicationCommandManager} + * @returns {Promise<ApplicationCommand>} + * @example + * // Edit an existing command + * client.application.commands.edit('123456789012345678', { + * description: 'New description', + * }) + * .then(console.log) + * .catch(console.error); + */ + async edit(command, data, guildId) { + const id = this.resolveId(command); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'command', 'ApplicationCommandResolvable'); + + const patched = await this.client.rest.patch(this.commandPath({ id, guildId }), { + body: this.constructor.transformCommand(data), + }); + return this._add(patched, true, guildId); + } + + /** + * Deletes an application command. + * @param {ApplicationCommandResolvable} command The command to delete + * @param {Snowflake} [guildId] The guild's id where the command is registered, + * ignored when using a {@link GuildApplicationCommandManager} + * @returns {Promise<?ApplicationCommand>} + * @example + * // Delete a command + * guild.commands.delete('123456789012345678') + * .then(console.log) + * .catch(console.error); + */ + async delete(command, guildId) { + const id = this.resolveId(command); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'command', 'ApplicationCommandResolvable'); + + await this.client.rest.delete(this.commandPath({ id, guildId })); + + const cached = this.cache.get(id); + this.cache.delete(id); + return cached ?? null; + } + + /** + * Transforms an {@link ApplicationCommandData} object into something that can be used with the API. + * @param {ApplicationCommandDataResolvable} command The command to transform + * @returns {APIApplicationCommand} + * @private + */ + static transformCommand(command) { + if (isJSONEncodable(command)) return command.toJSON(); + + let default_member_permissions; + + if ('default_member_permissions' in command) { + default_member_permissions = command.default_member_permissions + ? new PermissionsBitField(BigInt(command.default_member_permissions)).bitfield.toString() + : command.default_member_permissions; + } + + if ('defaultMemberPermissions' in command) { + default_member_permissions = + command.defaultMemberPermissions !== null + ? new PermissionsBitField(command.defaultMemberPermissions).bitfield.toString() + : command.defaultMemberPermissions; + } + + return { + name: command.name, + name_localizations: command.nameLocalizations ?? command.name_localizations, + description: command.description, + nsfw: command.nsfw, + description_localizations: command.descriptionLocalizations ?? command.description_localizations, + type: command.type, + options: command.options?.map(o => ApplicationCommand.transformOption(o)), + default_member_permissions, + dm_permission: command.dmPermission ?? command.dm_permission, + }; + } +} + +module.exports = ApplicationCommandManager; diff --git a/node_modules/discord.js/src/managers/ApplicationCommandPermissionsManager.js b/node_modules/discord.js/src/managers/ApplicationCommandPermissionsManager.js new file mode 100644 index 0000000..2f7279a --- /dev/null +++ b/node_modules/discord.js/src/managers/ApplicationCommandPermissionsManager.js @@ -0,0 +1,434 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { ApplicationCommandPermissionType, RESTJSONErrorCodes, Routes } = require('discord-api-types/v10'); +const BaseManager = require('./BaseManager'); +const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); + +/** + * Manages API methods for permissions of Application Commands. + * @extends {BaseManager} + */ +class ApplicationCommandPermissionsManager extends BaseManager { + constructor(manager) { + super(manager.client); + + /** + * The manager or command that this manager belongs to + * @type {ApplicationCommandManager|ApplicationCommand} + * @private + */ + this.manager = manager; + + /** + * The guild that this manager acts on + * @type {?Guild} + */ + this.guild = manager.guild ?? null; + + /** + * The id of the guild that this manager acts on + * @type {?Snowflake} + */ + this.guildId = manager.guildId ?? manager.guild?.id ?? null; + + /** + * The id of the command this manager acts on + * @type {?Snowflake} + */ + this.commandId = manager.id ?? null; + } + + /** + * The APIRouter path to the commands + * @param {Snowflake} guildId The guild's id to use in the path, + * @param {Snowflake} [commandId] The application command's id + * @returns {string} + * @private + */ + permissionsPath(guildId, commandId) { + if (commandId) { + return Routes.applicationCommandPermissions(this.client.application.id, guildId, commandId); + } + + return Routes.guildApplicationCommandsPermissions(this.client.application.id, guildId); + } + + /* eslint-disable max-len */ + /** + * The object returned when fetching permissions for an application command. + * @typedef {Object} ApplicationCommandPermissions + * @property {Snowflake} id The role, user, or channel's id. Can also be a + * {@link https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-constants permission constant}. + * @property {ApplicationCommandPermissionType} type Whether this permission is for a role or a user + * @property {boolean} permission Whether the role or user has the permission to use this command + */ + /* eslint-enable max-len */ + + /** + * Options for managing permissions for one or more Application Commands + * <warn>When passing these options to a manager where `guildId` is `null`, + * `guild` is a required parameter</warn> + * @typedef {Object} BaseApplicationCommandPermissionsOptions + * @property {GuildResolvable} [guild] The guild to modify / check permissions for + * <warn>Ignored when the manager has a non-null `guildId` property</warn> + * @property {ApplicationCommandResolvable} [command] The command to modify / check permissions for + * <warn>Ignored when the manager has a non-null `commandId` property</warn> + */ + + /** + * Fetches the permissions for one or multiple commands. Providing the client's id as the "command id" will fetch + * *only* the guild level permissions + * @param {BaseApplicationCommandPermissionsOptions} [options] Options used to fetch permissions + * @returns {Promise<ApplicationCommandPermissions[]|Collection<Snowflake, ApplicationCommandPermissions[]>>} + * @example + * // Fetch permissions for one command + * guild.commands.permissions.fetch({ command: '123456789012345678' }) + * .then(perms => console.log(`Fetched ${perms.length} overwrites`)) + * .catch(console.error); + * @example + * // Fetch permissions for all commands in a guild + * client.application.commands.permissions.fetch({ guild: '123456789012345678' }) + * .then(perms => console.log(`Fetched permissions for ${perms.size} commands`)) + * .catch(console.error); + * @example + * // Fetch guild level permissions + * guild.commands.permissions.fetch({ command: client.user.id }) + * .then(perms => console.log(`Fetched ${perms.length} guild level permissions`)) + * .catch(console.error); + */ + async fetch({ guild, command } = {}) { + const { guildId, commandId } = this._validateOptions(guild, command); + if (commandId) { + const data = await this.client.rest.get(this.permissionsPath(guildId, commandId)); + return data.permissions; + } + + const data = await this.client.rest.get(this.permissionsPath(guildId)); + return data.reduce((coll, perm) => coll.set(perm.id, perm.permissions), new Collection()); + } + + /** + * Options used to set permissions for one or more Application Commands in a guild + * <warn>Omitting the `command` parameter edits the guild wide permissions + * when the manager's `commandId` is `null`</warn> + * @typedef {BaseApplicationCommandPermissionsOptions} ApplicationCommandPermissionsEditOptions + * @property {ApplicationCommandPermissions[]} permissions The new permissions for the guild or overwrite + * @property {string} token The bearer token to use that authorizes the permission edit + */ + + /** + * Sets the permissions for the guild or a command overwrite. + * @param {ApplicationCommandPermissionsEditOptions} options Options used to set permissions + * @returns {Promise<ApplicationCommandPermissions[]|Collection<Snowflake, ApplicationCommandPermissions[]>>} + * @example + * // Set a permission overwrite for a command + * client.application.commands.permissions.set({ + * guild: '892455839386304532', + * command: '123456789012345678', + * token: 'TotallyRealToken', + * permissions: [ + * { + * id: '876543210987654321', + * type: ApplicationCommandPermissionType.User, + * permission: false, + * }, + * ]}) + * .then(console.log) + * .catch(console.error); + * @example + * // Set the permissions used for the guild (commands without overwrites) + * guild.commands.permissions.set({ token: 'TotallyRealToken', permissions: [ + * { + * id: '123456789012345678', + * permissions: [{ + * id: '876543210987654321', + * type: ApplicationCommandPermissionType.User, + * permission: false, + * }], + * }, + * ]}) + * .then(console.log) + * .catch(console.error); + */ + async set({ guild, command, permissions, token } = {}) { + if (!token) { + throw new DiscordjsError(ErrorCodes.ApplicationCommandPermissionsTokenMissing); + } + let { guildId, commandId } = this._validateOptions(guild, command); + + if (!Array.isArray(permissions)) { + throw new DiscordjsTypeError( + ErrorCodes.InvalidType, + 'permissions', + 'Array of ApplicationCommandPermissions', + true, + ); + } + + if (!commandId) { + commandId = this.client.user.id; + } + const data = await this.client.rest.put(this.permissionsPath(guildId, commandId), { + body: { permissions }, + auth: false, + headers: { Authorization: `Bearer ${token}` }, + }); + return data.permissions; + } + + /** + * Add permissions to a command. + * @param {ApplicationCommandPermissionsEditOptions} options Options used to add permissions + * @returns {Promise<ApplicationCommandPermissions[]>} + * @example + * // Add a rule to block a role from using a command + * guild.commands.permissions.add({ command: '123456789012345678', token: 'TotallyRealToken', permissions: [ + * { + * id: '876543211234567890', + * type: ApplicationCommandPermissionType.Role, + * permission: false + * }, + * ]}) + * .then(console.log) + * .catch(console.error); + */ + async add({ guild, command, permissions, token } = {}) { + if (!token) { + throw new DiscordjsError(ErrorCodes.ApplicationCommandPermissionsTokenMissing); + } + let { guildId, commandId } = this._validateOptions(guild, command); + if (!commandId) { + commandId = this.client.user.id; + } + if (!Array.isArray(permissions)) { + throw new DiscordjsTypeError( + ErrorCodes.InvalidType, + 'permissions', + 'Array of ApplicationCommandPermissions', + true, + ); + } + + let existing = []; + try { + existing = await this.fetch({ guild: guildId, command: commandId }); + } catch (error) { + if (error.code !== RESTJSONErrorCodes.UnknownApplicationCommandPermissions) throw error; + } + + const newPermissions = permissions.slice(); + for (const perm of existing) { + if (!newPermissions.some(x => x.id === perm.id)) { + newPermissions.push(perm); + } + } + + return this.set({ guild: guildId, command: commandId, permissions: newPermissions, token }); + } + + /** + * A static snowflake that identifies the everyone role for application command permissions. + * It is the same as the guild id + * @typedef {Snowflake} RolePermissionConstant + */ + + /** + * A static snowflake that identifies the "all channels" entity for application command permissions. + * It will be the result of the calculation `guildId - 1` + * @typedef {Snowflake} ChannelPermissionConstant + */ + + /** + * Options used to remove permissions from a command + * <warn>Omitting the `command` parameter removes from the guild wide permissions + * when the managers `commandId` is `null`</warn> + * <warn>At least one of `users`, `roles`, and `channels` is required</warn> + * @typedef {BaseApplicationCommandPermissionsOptions} RemoveApplicationCommandPermissionsOptions + * @property {string} token The bearer token to use that authorizes the permission removal + * @property {UserResolvable[]} [users] The user(s) to remove + * @property {Array<RoleResolvable|RolePermissionConstant>} [roles] The role(s) to remove + * @property {Array<GuildChannelResolvable|ChannelPermissionConstant>} [channels] The channel(s) to remove + */ + + /** + * Remove permissions from a command. + * @param {RemoveApplicationCommandPermissionsOptions} options Options used to remove permissions + * @returns {Promise<ApplicationCommandPermissions[]>} + * @example + * // Remove a user permission from this command + * guild.commands.permissions.remove({ + * command: '123456789012345678', users: '876543210123456789', token: 'TotallyRealToken', + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Remove multiple roles from this command + * guild.commands.permissions.remove({ + * command: '123456789012345678', roles: ['876543210123456789', '765432101234567890'], token: 'TotallyRealToken', + * }) + * .then(console.log) + * .catch(console.error); + */ + async remove({ guild, command, users, roles, channels, token } = {}) { + if (!token) { + throw new DiscordjsError(ErrorCodes.ApplicationCommandPermissionsTokenMissing); + } + let { guildId, commandId } = this._validateOptions(guild, command); + if (!commandId) { + commandId = this.client.user.id; + } + + if (!users && !roles && !channels) { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'users OR roles OR channels', 'Array or Resolvable', true); + } + + let resolvedUserIds = []; + if (Array.isArray(users)) { + for (const user of users) { + const userId = this.client.users.resolveId(user); + if (!userId) throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array', 'users', user); + resolvedUserIds.push(userId); + } + } + + let resolvedRoleIds = []; + if (Array.isArray(roles)) { + for (const role of roles) { + if (typeof role === 'string') { + resolvedRoleIds.push(role); + continue; + } + if (!this.guild) throw new DiscordjsError(ErrorCodes.GuildUncachedEntityResolve, 'roles'); + const roleId = this.guild.roles.resolveId(role); + if (!roleId) throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array', 'users', role); + resolvedRoleIds.push(roleId); + } + } + + let resolvedChannelIds = []; + if (Array.isArray(channels)) { + for (const channel of channels) { + if (typeof channel === 'string') { + resolvedChannelIds.push(channel); + continue; + } + if (!this.guild) throw new DiscordjsError(ErrorCodes.GuildUncachedEntityResolve, 'channels'); + const channelId = this.guild.channels.resolveId(channel); + if (!channelId) throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array', 'channels', channel); + resolvedChannelIds.push(channelId); + } + } + + let existing = []; + try { + existing = await this.fetch({ guild: guildId, command: commandId }); + } catch (error) { + if (error.code !== RESTJSONErrorCodes.UnknownApplicationCommandPermissions) throw error; + } + + const permissions = existing.filter(perm => { + switch (perm.type) { + case ApplicationCommandPermissionType.Role: + return !resolvedRoleIds.includes(perm.id); + case ApplicationCommandPermissionType.User: + return !resolvedUserIds.includes(perm.id); + case ApplicationCommandPermissionType.Channel: + return !resolvedChannelIds.includes(perm.id); + } + return true; + }); + + return this.set({ guild: guildId, command: commandId, permissions, token }); + } + + /** + * Options used to check the existence of permissions on a command + * <warn>The `command` parameter is not optional when the managers `commandId` is `null`</warn> + * @typedef {BaseApplicationCommandPermissionsOptions} HasApplicationCommandPermissionsOptions + * @property {ApplicationCommandPermissionIdResolvable} permissionId The entity to check if a permission exists for + * on this command. + * @property {ApplicationCommandPermissionType} [permissionType] Check for a specific type of permission + */ + + /** + * Check whether a permission exists for a user, role, or channel + * @param {HasApplicationCommandPermissionsOptions} options Options used to check permissions + * @returns {Promise<boolean>} + * @example + * // Check whether a user has permission to use a command + * guild.commands.permissions.has({ command: '123456789012345678', permissionId: '876543210123456789' }) + * .then(console.log) + * .catch(console.error); + */ + async has({ guild, command, permissionId, permissionType }) { + const { guildId, commandId } = this._validateOptions(guild, command); + if (!commandId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'command', 'ApplicationCommandResolvable'); + + if (!permissionId) { + throw new DiscordjsTypeError( + ErrorCodes.InvalidType, + 'permissionId', + 'UserResolvable, RoleResolvable, ChannelResolvable, or Permission Constant', + ); + } + let resolvedId = permissionId; + if (typeof permissionId !== 'string') { + resolvedId = this.client.users.resolveId(permissionId); + if (!resolvedId) { + if (!this.guild) throw new DiscordjsError(ErrorCodes.GuildUncachedEntityResolve, 'roles'); + resolvedId = this.guild.roles.resolveId(permissionId); + } + if (!resolvedId) { + resolvedId = this.guild.channels.resolveId(permissionId); + } + if (!resolvedId) { + throw new DiscordjsTypeError( + ErrorCodes.InvalidType, + 'permissionId', + 'UserResolvable, RoleResolvable, ChannelResolvable, or Permission Constant', + ); + } + } + + let existing = []; + try { + existing = await this.fetch({ guild: guildId, command: commandId }); + } catch (error) { + if (error.code !== RESTJSONErrorCodes.UnknownApplicationCommandPermissions) throw error; + } + + // Check permission type if provided for the single edge case where a channel id is the same as the everyone role id + return existing.some(perm => perm.id === resolvedId && (permissionType ?? perm.type) === perm.type); + } + + _validateOptions(guild, command) { + const guildId = this.guildId ?? this.client.guilds.resolveId(guild); + if (!guildId) throw new DiscordjsError(ErrorCodes.GlobalCommandPermissions); + let commandId = this.commandId; + if (command && !commandId) { + commandId = this.manager.resolveId?.(command); + if (!commandId && this.guild) { + commandId = this.guild.commands.resolveId(command); + } + commandId ??= this.client.application?.commands.resolveId(command); + if (!commandId) { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'command', 'ApplicationCommandResolvable', true); + } + } + return { guildId, commandId }; + } +} + +module.exports = ApplicationCommandPermissionsManager; + +/* eslint-disable max-len */ +/** + * @external APIApplicationCommandPermissions + * @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure} + */ + +/** + * Data that resolves to an id used for an application command permission + * @typedef {UserResolvable|RoleResolvable|GuildChannelResolvable|RolePermissionConstant|ChannelPermissionConstant} ApplicationCommandPermissionIdResolvable + */ diff --git a/node_modules/discord.js/src/managers/AutoModerationRuleManager.js b/node_modules/discord.js/src/managers/AutoModerationRuleManager.js new file mode 100644 index 0000000..dd0ee4e --- /dev/null +++ b/node_modules/discord.js/src/managers/AutoModerationRuleManager.js @@ -0,0 +1,288 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const AutoModerationRule = require('../structures/AutoModerationRule'); + +/** + * Manages API methods for auto moderation rules and stores their cache. + * @extends {CachedManager} + */ +class AutoModerationRuleManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, AutoModerationRule, iterable); + + /** + * The guild this manager belongs to. + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this manager + * @type {Collection<Snowflake, AutoModerationRule>} + * @name AutoModerationRuleManager#cache + */ + + /** + * Resolves an {@link AutoModerationRuleResolvable} to an {@link AutoModerationRule} object. + * @method resolve + * @memberof AutoModerationRuleManager + * @instance + * @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve + * @returns {?AutoModerationRule} + */ + + /** + * Resolves an {@link AutoModerationRuleResolvable} to a {@link AutoModerationRule} id. + * @method resolveId + * @memberof AutoModerationRuleManager + * @instance + * @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve + * @returns {?Snowflake} + */ + + _add(data, cache) { + return super._add(data, cache, { extras: [this.guild] }); + } + + /** + * Options used to set the trigger metadata of an auto moderation rule. + * @typedef {Object} AutoModerationTriggerMetadataOptions + * @property {string[]} [keywordFilter] The substrings that will be searched for in the content + * @property {string[]} [regexPatterns] The regular expression patterns which will be matched against the content + * <info>Only Rust-flavored regular expressions are supported.</info> + * @property {AutoModerationRuleKeywordPresetType[]} [presets] + * The internally pre-defined wordsets which will be searched for in the content + * @property {string[]} [allowList] The substrings that will be exempt from triggering + * {@link AutoModerationRuleTriggerType.Keyword} and {@link AutoModerationRuleTriggerType.KeywordPreset} + * @property {?number} [mentionTotalLimit] The total number of role & user mentions allowed per message + * @property {boolean} [mentionRaidProtectionEnabled] Whether to automatically detect mention raids + */ + + /** + * Options used to set the actions of an auto moderation rule. + * @typedef {Object} AutoModerationActionOptions + * @property {AutoModerationActionType} type The type of this auto moderation rule action + * @property {AutoModerationActionMetadataOptions} [metadata] Additional metadata needed during execution + * <info>This property is required if using a `type` of + * {@link AutoModerationActionType.SendAlertMessage} or {@link AutoModerationActionType.Timeout}.</info> + */ + + /** + * Options used to set the metadata of an auto moderation rule action. + * @typedef {Object} AutoModerationActionMetadataOptions + * @property {GuildTextChannelResolvable|ThreadChannel} [channel] The channel to which content will be logged + * @property {number} [durationSeconds] The timeout duration in seconds + * @property {string} [customMessage] The custom message that is shown whenever a message is blocked + */ + + /** + * Options used to create an auto moderation rule. + * @typedef {Object} AutoModerationRuleCreateOptions + * @property {string} name The name of the auto moderation rule + * @property {AutoModerationRuleEventType} eventType The event type of the auto moderation rule + * @property {AutoModerationRuleTriggerType} triggerType The trigger type of the auto moderation rule + * @property {AutoModerationTriggerMetadataOptions} [triggerMetadata] The trigger metadata of the auto moderation rule + * <info>This property is required if using a `triggerType` of + * {@link AutoModerationRuleTriggerType.Keyword}, {@link AutoModerationRuleTriggerType.KeywordPreset}, + * or {@link AutoModerationRuleTriggerType.MentionSpam}.</info> + * @property {AutoModerationActionOptions[]} actions + * The actions that will execute when the auto moderation rule is triggered + * @property {boolean} [enabled] Whether the auto moderation rule should be enabled + * @property {Collection<Snowflake, Role>|RoleResolvable[]} [exemptRoles] + * The roles that should not be affected by the auto moderation rule + * @property {Collection<Snowflake, GuildChannel|ThreadChannel>|GuildChannelResolvable[]} [exemptChannels] + * The channels that should not be affected by the auto moderation rule + * @property {string} [reason] The reason for creating the auto moderation rule + */ + + /** + * Creates a new auto moderation rule. + * @param {AutoModerationRuleCreateOptions} options Options for creating the auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + async create({ + name, + eventType, + triggerType, + triggerMetadata, + actions, + enabled, + exemptRoles, + exemptChannels, + reason, + }) { + const data = await this.client.rest.post(Routes.guildAutoModerationRules(this.guild.id), { + body: { + name, + event_type: eventType, + trigger_type: triggerType, + trigger_metadata: triggerMetadata && { + keyword_filter: triggerMetadata.keywordFilter, + regex_patterns: triggerMetadata.regexPatterns, + presets: triggerMetadata.presets, + allow_list: triggerMetadata.allowList, + mention_total_limit: triggerMetadata.mentionTotalLimit, + mention_raid_protection_enabled: triggerMetadata.mentionRaidProtectionEnabled, + }, + actions: actions.map(action => ({ + type: action.type, + metadata: { + duration_seconds: action.metadata?.durationSeconds, + channel_id: action.metadata?.channel && this.guild.channels.resolveId(action.metadata.channel), + custom_message: action.metadata?.customMessage, + }, + })), + enabled, + exempt_roles: exemptRoles?.map(exemptRole => this.guild.roles.resolveId(exemptRole)), + exempt_channels: exemptChannels?.map(exemptChannel => this.guild.channels.resolveId(exemptChannel)), + }, + reason, + }); + + return this._add(data); + } + + /** + * Options used to edit an auto moderation rule. + * @typedef {Object} AutoModerationRuleEditOptions + * @property {string} [name] The name of the auto moderation rule + * @property {AutoModerationRuleEventType} [eventType] The event type of the auto moderation rule + * @property {AutoModerationTriggerMetadataOptions} [triggerMetadata] The trigger metadata of the auto moderation rule + * @property {AutoModerationActionOptions[]} [actions] + * The actions that will execute when the auto moderation rule is triggered + * @property {boolean} [enabled] Whether the auto moderation rule should be enabled + * @property {Collection<Snowflake, Role>|RoleResolvable[]} [exemptRoles] + * The roles that should not be affected by the auto moderation rule + * @property {Collection<Snowflake, GuildChannel|ThreadChannel>|GuildChannelResolvable[]} [exemptChannels] + * The channels that should not be affected by the auto moderation rule + * @property {string} [reason] The reason for creating the auto moderation rule + */ + + /** + * Edits an auto moderation rule. + * @param {AutoModerationRuleResolvable} autoModerationRule The auto moderation rule to edit + * @param {AutoModerationRuleEditOptions} options Options for editing the auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + async edit( + autoModerationRule, + { name, eventType, triggerMetadata, actions, enabled, exemptRoles, exemptChannels, reason }, + ) { + const autoModerationRuleId = this.resolveId(autoModerationRule); + + const data = await this.client.rest.patch(Routes.guildAutoModerationRule(this.guild.id, autoModerationRuleId), { + body: { + name, + event_type: eventType, + trigger_metadata: triggerMetadata && { + keyword_filter: triggerMetadata.keywordFilter, + regex_patterns: triggerMetadata.regexPatterns, + presets: triggerMetadata.presets, + allow_list: triggerMetadata.allowList, + mention_total_limit: triggerMetadata.mentionTotalLimit, + mention_raid_protection_enabled: triggerMetadata.mentionRaidProtectionEnabled, + }, + actions: actions?.map(action => ({ + type: action.type, + metadata: { + duration_seconds: action.metadata?.durationSeconds, + channel_id: action.metadata?.channel && this.guild.channels.resolveId(action.metadata.channel), + custom_message: action.metadata?.customMessage, + }, + })), + enabled, + exempt_roles: exemptRoles?.map(exemptRole => this.guild.roles.resolveId(exemptRole)), + exempt_channels: exemptChannels?.map(exemptChannel => this.guild.channels.resolveId(exemptChannel)), + }, + reason, + }); + + return this._add(data); + } + + /** + * Data that can be resolved to give an AutoModerationRule object. This can be: + * * An AutoModerationRule + * * A Snowflake + * @typedef {AutoModerationRule|Snowflake} AutoModerationRuleResolvable + */ + + /** + * Options used to fetch a single auto moderation rule from a guild. + * @typedef {BaseFetchOptions} FetchAutoModerationRuleOptions + * @property {AutoModerationRuleResolvable} autoModerationRule The auto moderation rule to fetch + */ + + /** + * Options used to fetch all auto moderation rules from a guild. + * @typedef {Object} FetchAutoModerationRulesOptions + * @property {boolean} [cache] Whether to cache the fetched auto moderation rules + */ + + /** + * Fetches auto moderation rules from Discord. + * @param {AutoModerationRuleResolvable|FetchAutoModerationRuleOptions|FetchAutoModerationRulesOptions} [options] + * Options for fetching auto moderation rule(s) + * @returns {Promise<AutoModerationRule|Collection<Snowflake, AutoModerationRule>>} + * @example + * // Fetch all auto moderation rules from a guild without caching + * guild.autoModerationRules.fetch({ cache: false }) + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single auto moderation rule + * guild.autoModerationRules.fetch('979083472868098119') + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single auto moderation rule without checking cache and without caching + * guild.autoModerationRules.fetch({ autoModerationRule: '979083472868098119', cache: false, force: true }) + * .then(console.log) + * .catch(console.error) + */ + fetch(options) { + if (!options) return this._fetchMany(); + const { autoModerationRule, cache, force } = options; + const resolvedAutoModerationRule = this.resolveId(autoModerationRule ?? options); + if (resolvedAutoModerationRule) { + return this._fetchSingle({ autoModerationRule: resolvedAutoModerationRule, cache, force }); + } + return this._fetchMany(options); + } + + async _fetchSingle({ autoModerationRule, cache, force = false }) { + if (!force) { + const existing = this.cache.get(autoModerationRule); + if (existing) return existing; + } + + const data = await this.client.rest.get(Routes.guildAutoModerationRule(this.guild.id, autoModerationRule)); + return this._add(data, cache); + } + + async _fetchMany(options = {}) { + const data = await this.client.rest.get(Routes.guildAutoModerationRules(this.guild.id)); + + return data.reduce( + (col, autoModerationRule) => col.set(autoModerationRule.id, this._add(autoModerationRule, options.cache)), + new Collection(), + ); + } + + /** + * Deletes an auto moderation rule. + * @param {AutoModerationRuleResolvable} autoModerationRule The auto moderation rule to delete + * @param {string} [reason] The reason for deleting the auto moderation rule + * @returns {Promise<void>} + */ + async delete(autoModerationRule, reason) { + const autoModerationRuleId = this.resolveId(autoModerationRule); + await this.client.rest.delete(Routes.guildAutoModerationRule(this.guild.id, autoModerationRuleId), { reason }); + } +} + +module.exports = AutoModerationRuleManager; diff --git a/node_modules/discord.js/src/managers/BaseGuildEmojiManager.js b/node_modules/discord.js/src/managers/BaseGuildEmojiManager.js new file mode 100644 index 0000000..89eee4c --- /dev/null +++ b/node_modules/discord.js/src/managers/BaseGuildEmojiManager.js @@ -0,0 +1,80 @@ +'use strict'; + +const CachedManager = require('./CachedManager'); +const GuildEmoji = require('../structures/GuildEmoji'); +const ReactionEmoji = require('../structures/ReactionEmoji'); +const { parseEmoji } = require('../util/Util'); + +/** + * Holds methods to resolve GuildEmojis and stores their cache. + * @extends {CachedManager} + */ +class BaseGuildEmojiManager extends CachedManager { + constructor(client, iterable) { + super(client, GuildEmoji, iterable); + } + + /** + * The cache of GuildEmojis + * @type {Collection<Snowflake, GuildEmoji>} + * @name BaseGuildEmojiManager#cache + */ + + /** + * Data that can be resolved into a GuildEmoji object. This can be: + * * A Snowflake + * * A GuildEmoji object + * * A ReactionEmoji object + * @typedef {Snowflake|GuildEmoji|ReactionEmoji} EmojiResolvable + */ + + /** + * Resolves an EmojiResolvable to an Emoji object. + * @param {EmojiResolvable} emoji The Emoji resolvable to identify + * @returns {?GuildEmoji} + */ + resolve(emoji) { + if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id); + return super.resolve(emoji); + } + + /** + * Resolves an EmojiResolvable to an Emoji id string. + * @param {EmojiResolvable} emoji The Emoji resolvable to identify + * @returns {?Snowflake} + */ + resolveId(emoji) { + if (emoji instanceof ReactionEmoji) return emoji.id; + return super.resolveId(emoji); + } + + /** + * Data that can be resolved to give an emoji identifier. This can be: + * * An EmojiResolvable + * * The `<a:name:id>`, `<:name:id>`, `a:name:id` or `name:id` emoji identifier string of an emoji + * * The Unicode representation of an emoji + * @typedef {string|EmojiResolvable} EmojiIdentifierResolvable + */ + + /** + * Resolves an EmojiResolvable to an emoji identifier. + * @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve + * @returns {?string} + */ + resolveIdentifier(emoji) { + const emojiResolvable = this.resolve(emoji); + if (emojiResolvable) return emojiResolvable.identifier; + if (emoji instanceof ReactionEmoji) return emoji.identifier; + if (typeof emoji === 'string') { + const res = parseEmoji(emoji); + if (res?.name.length) { + emoji = `${res.animated ? 'a:' : ''}${res.name}${res.id ? `:${res.id}` : ''}`; + } + if (!emoji.includes('%')) return encodeURIComponent(emoji); + return emoji; + } + return null; + } +} + +module.exports = BaseGuildEmojiManager; diff --git a/node_modules/discord.js/src/managers/BaseManager.js b/node_modules/discord.js/src/managers/BaseManager.js new file mode 100644 index 0000000..0651401 --- /dev/null +++ b/node_modules/discord.js/src/managers/BaseManager.js @@ -0,0 +1,19 @@ +'use strict'; + +/** + * Manages the API methods of a data model. + * @abstract + */ +class BaseManager { + constructor(client) { + /** + * The client that instantiated this Manager + * @name BaseManager#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + } +} + +module.exports = BaseManager; diff --git a/node_modules/discord.js/src/managers/CachedManager.js b/node_modules/discord.js/src/managers/CachedManager.js new file mode 100644 index 0000000..b4c50b1 --- /dev/null +++ b/node_modules/discord.js/src/managers/CachedManager.js @@ -0,0 +1,64 @@ +'use strict'; + +const DataManager = require('./DataManager'); +const { MakeCacheOverrideSymbol } = require('../util/Symbols'); + +/** + * Manages the API methods of a data model with a mutable cache of instances. + * @extends {DataManager} + * @abstract + */ +class CachedManager extends DataManager { + constructor(client, holds, iterable) { + super(client, holds); + + /** + * The private cache of items for this manager. + * @type {Collection} + * @private + * @readonly + * @name CachedManager#_cache + */ + Object.defineProperty(this, '_cache', { + value: this.client.options.makeCache( + this.constructor[MakeCacheOverrideSymbol] ?? this.constructor, + this.holds, + this.constructor, + ), + }); + + if (iterable) { + for (const item of iterable) { + this._add(item); + } + } + } + + /** + * The cache of items for this manager. + * @type {Collection} + * @abstract + */ + get cache() { + return this._cache; + } + + _add(data, cache = true, { id, extras = [] } = {}) { + const existing = this.cache.get(id ?? data.id); + if (existing) { + if (cache) { + existing._patch(data); + return existing; + } + const clone = existing._clone(); + clone._patch(data); + return clone; + } + + const entry = this.holds ? new this.holds(this.client, data, ...extras) : data; + if (cache) this.cache.set(id ?? entry.id, entry); + return entry; + } +} + +module.exports = CachedManager; diff --git a/node_modules/discord.js/src/managers/CategoryChannelChildManager.js b/node_modules/discord.js/src/managers/CategoryChannelChildManager.js new file mode 100644 index 0000000..347526a --- /dev/null +++ b/node_modules/discord.js/src/managers/CategoryChannelChildManager.js @@ -0,0 +1,77 @@ +'use strict'; + +const DataManager = require('./DataManager'); +const GuildChannel = require('../structures/GuildChannel'); + +/** + * Manages API methods for CategoryChannels' children. + * @extends {DataManager} + */ +class CategoryChannelChildManager extends DataManager { + constructor(channel) { + super(channel.client, GuildChannel); + /** + * The category channel this manager belongs to + * @type {CategoryChannel} + */ + this.channel = channel; + } + + /** + * The channels that are a part of this category + * @type {Collection<Snowflake, GuildChannel>} + * @readonly + */ + get cache() { + return this.guild.channels.cache.filter(c => c.parentId === this.channel.id); + } + + /** + * The guild this manager belongs to + * @type {Guild} + * @readonly + */ + get guild() { + return this.channel.guild; + } + + /** + * Options for creating a channel using {@link CategoryChannel#createChannel}. + * @typedef {Object} CategoryCreateChannelOptions + * @property {string} name The name for the new channel + * @property {ChannelType} [type=ChannelType.GuildText] The type of the new channel. + * @property {string} [topic] The topic for the new channel + * @property {boolean} [nsfw] Whether the new channel is NSFW + * @property {number} [bitrate] Bitrate of the new channel in bits (only voice) + * @property {number} [userLimit] Maximum amount of users allowed in the new channel (only voice) + * @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites] + * Permission overwrites of the new channel + * @property {number} [position] Position of the new channel + * @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds + * @property {string} [rtcRegion] The specific region of the new channel. + * @property {VideoQualityMode} [videoQualityMode] The camera video quality mode of the voice channel + * @property {GuildForumTagData[]} [availableTags] The tags that can be used in this channel (forum only). + * @property {DefaultReactionEmoji} [defaultReactionEmoji] + * The emoji to show in the add reaction button on a thread in a guild forum channel. + * @property {ThreadAutoArchiveDuration} [defaultAutoArchiveDuration] + * The default auto archive duration for all new threads in this channel + * @property {SortOrderType} [defaultSortOrder] The default sort order mode used to order posts (forum only). + * @property {ForumLayoutType} [defaultForumLayout] The default layout used to display posts (forum only). + * @property {string} [reason] Reason for creating the new channel + */ + + /** + * Creates a new channel within this category. + * <info>You cannot create a channel of type {@link ChannelType.GuildCategory} inside a CategoryChannel.</info> + * @param {CategoryCreateChannelOptions} options Options for creating the new channel + * @returns {Promise<GuildChannel>} + */ + create(options) { + return this.guild.channels.create({ + ...options, + parent: this.channel.id, + }); + } +} + +module.exports = CategoryChannelChildManager; diff --git a/node_modules/discord.js/src/managers/ChannelManager.js b/node_modules/discord.js/src/managers/ChannelManager.js new file mode 100644 index 0000000..0126d91 --- /dev/null +++ b/node_modules/discord.js/src/managers/ChannelManager.js @@ -0,0 +1,128 @@ +'use strict'; + +const process = require('node:process'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { BaseChannel } = require('../structures/BaseChannel'); +const { createChannel } = require('../util/Channels'); +const { ThreadChannelTypes } = require('../util/Constants'); +const Events = require('../util/Events'); + +let cacheWarningEmitted = false; + +/** + * A manager of channels belonging to a client + * @extends {CachedManager} + */ +class ChannelManager extends CachedManager { + constructor(client, iterable) { + super(client, BaseChannel, iterable); + const defaultCaching = + this._cache.constructor.name === 'Collection' || + this._cache.maxSize === undefined || + this._cache.maxSize === Infinity; + if (!cacheWarningEmitted && !defaultCaching) { + cacheWarningEmitted = true; + process.emitWarning( + `Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`, + 'UnsupportedCacheOverwriteWarning', + ); + } + } + + /** + * The cache of Channels + * @type {Collection<Snowflake, BaseChannel>} + * @name ChannelManager#cache + */ + + _add(data, guild, { cache = true, allowUnknownGuild = false } = {}) { + const existing = this.cache.get(data.id); + if (existing) { + if (cache) existing._patch(data); + guild?.channels?._add(existing); + if (ThreadChannelTypes.includes(existing.type)) { + existing.parent?.threads?._add(existing); + } + return existing; + } + + const channel = createChannel(this.client, data, guild, { allowUnknownGuild }); + + if (!channel) { + this.client.emit(Events.Debug, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`); + return null; + } + + if (cache && !allowUnknownGuild) this.cache.set(channel.id, channel); + + return channel; + } + + _remove(id) { + const channel = this.cache.get(id); + channel?.guild?.channels.cache.delete(id); + + for (const [code, invite] of channel?.guild?.invites.cache ?? []) { + if (invite.channelId === id) channel.guild.invites.cache.delete(code); + } + + channel?.parent?.threads?.cache.delete(id); + this.cache.delete(id); + } + + /** + * Data that can be resolved to give a Channel object. This can be: + * * A Channel object + * * A Snowflake + * @typedef {BaseChannel|Snowflake} ChannelResolvable + */ + + /** + * Resolves a ChannelResolvable to a Channel object. + * @method resolve + * @memberof ChannelManager + * @instance + * @param {ChannelResolvable} channel The channel resolvable to resolve + * @returns {?BaseChannel} + */ + + /** + * Resolves a ChannelResolvable to a channel id string. + * @method resolveId + * @memberof ChannelManager + * @instance + * @param {ChannelResolvable} channel The channel resolvable to resolve + * @returns {?Snowflake} + */ + + /** + * Options for fetching a channel from Discord + * @typedef {BaseFetchOptions} FetchChannelOptions + * @property {boolean} [allowUnknownGuild=false] Allows the channel to be returned even if the guild is not in cache, + * it will not be cached. <warn>Many of the properties and methods on the returned channel will throw errors</warn> + */ + + /** + * Obtains a channel from Discord, or the channel cache if it's already available. + * @param {Snowflake} id The channel's id + * @param {FetchChannelOptions} [options] Additional options for this fetch + * @returns {Promise<?BaseChannel>} + * @example + * // Fetch a channel by its id + * client.channels.fetch('222109930545610754') + * .then(channel => console.log(channel.name)) + * .catch(console.error); + */ + async fetch(id, { allowUnknownGuild = false, cache = true, force = false } = {}) { + if (!force) { + const existing = this.cache.get(id); + if (existing && !existing.partial) return existing; + } + + const data = await this.client.rest.get(Routes.channel(id)); + return this._add(data, null, { cache, allowUnknownGuild }); + } +} + +module.exports = ChannelManager; diff --git a/node_modules/discord.js/src/managers/DMMessageManager.js b/node_modules/discord.js/src/managers/DMMessageManager.js new file mode 100644 index 0000000..f0b3a33 --- /dev/null +++ b/node_modules/discord.js/src/managers/DMMessageManager.js @@ -0,0 +1,17 @@ +'use strict'; + +const MessageManager = require('./MessageManager'); + +/** + * Manages API methods for messages in direct message channels and holds their cache. + * @extends {MessageManager} + */ +class DMMessageManager extends MessageManager { + /** + * The channel that the messages belong to + * @name DMMessageManager#channel + * @type {DMChannel} + */ +} + +module.exports = DMMessageManager; diff --git a/node_modules/discord.js/src/managers/DataManager.js b/node_modules/discord.js/src/managers/DataManager.js new file mode 100644 index 0000000..383844e --- /dev/null +++ b/node_modules/discord.js/src/managers/DataManager.js @@ -0,0 +1,61 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const { DiscordjsError, ErrorCodes } = require('../errors'); + +/** + * Manages the API methods of a data model along with a collection of instances. + * @extends {BaseManager} + * @abstract + */ +class DataManager extends BaseManager { + constructor(client, holds) { + super(client); + + /** + * The data structure belonging to this manager. + * @name DataManager#holds + * @type {Function} + * @private + * @readonly + */ + Object.defineProperty(this, 'holds', { value: holds }); + } + + /** + * The cache of items for this manager. + * @type {Collection} + * @abstract + */ + get cache() { + throw new DiscordjsError(ErrorCodes.NotImplemented, 'get cache', this.constructor.name); + } + + /** + * Resolves a data entry to a data Object. + * @param {string|Object} idOrInstance The id or instance of something in this Manager + * @returns {?Object} An instance from this Manager + */ + resolve(idOrInstance) { + if (idOrInstance instanceof this.holds) return idOrInstance; + if (typeof idOrInstance === 'string') return this.cache.get(idOrInstance) ?? null; + return null; + } + + /** + * Resolves a data entry to an instance id. + * @param {string|Object} idOrInstance The id or instance of something in this Manager + * @returns {?Snowflake} + */ + resolveId(idOrInstance) { + if (idOrInstance instanceof this.holds) return idOrInstance.id; + if (typeof idOrInstance === 'string') return idOrInstance; + return null; + } + + valueOf() { + return this.cache; + } +} + +module.exports = DataManager; diff --git a/node_modules/discord.js/src/managers/GuildApplicationCommandManager.js b/node_modules/discord.js/src/managers/GuildApplicationCommandManager.js new file mode 100644 index 0000000..97fea5e --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildApplicationCommandManager.js @@ -0,0 +1,28 @@ +'use strict'; + +const ApplicationCommandManager = require('./ApplicationCommandManager'); +const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager'); + +/** + * An extension for guild-specific application commands. + * @extends {ApplicationCommandManager} + */ +class GuildApplicationCommandManager extends ApplicationCommandManager { + constructor(guild, iterable) { + super(guild.client, iterable); + + /** + * The guild that this manager belongs to + * @type {Guild} + */ + this.guild = guild; + + /** + * The manager for permissions of arbitrary commands on this guild + * @type {ApplicationCommandPermissionsManager} + */ + this.permissions = new ApplicationCommandPermissionsManager(this); + } +} + +module.exports = GuildApplicationCommandManager; diff --git a/node_modules/discord.js/src/managers/GuildBanManager.js b/node_modules/discord.js/src/managers/GuildBanManager.js new file mode 100644 index 0000000..d3c8a00 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildBanManager.js @@ -0,0 +1,204 @@ +'use strict'; + +const process = require('node:process'); +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors'); +const GuildBan = require('../structures/GuildBan'); +const { GuildMember } = require('../structures/GuildMember'); + +let deprecationEmittedForDeleteMessageDays = false; + +/** + * Manages API methods for guild bans and stores their cache. + * @extends {CachedManager} + */ +class GuildBanManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, GuildBan, iterable); + + /** + * The guild this Manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, GuildBan>} + * @name GuildBanManager#cache + */ + + _add(data, cache) { + return super._add(data, cache, { id: data.user.id, extras: [this.guild] }); + } + + /** + * Data that resolves to give a GuildBan object. This can be: + * * A GuildBan object + * * A User resolvable + * @typedef {GuildBan|UserResolvable} GuildBanResolvable + */ + + /** + * Resolves a GuildBanResolvable to a GuildBan object. + * @param {GuildBanResolvable} ban The ban that is in the guild + * @returns {?GuildBan} + */ + resolve(ban) { + return super.resolve(ban) ?? super.resolve(this.client.users.resolveId(ban)); + } + + /** + * Options used to fetch a single ban from a guild. + * @typedef {BaseFetchOptions} FetchBanOptions + * @property {UserResolvable} user The ban to fetch + */ + + /** + * Options used to fetch multiple bans from a guild. + * @typedef {Object} FetchBansOptions + * @property {number} [limit] The maximum number of bans to return + * @property {Snowflake} [before] Consider only bans before this id + * @property {Snowflake} [after] Consider only bans after this id + * @property {boolean} [cache] Whether to cache the fetched bans + */ + + /** + * Fetches ban(s) from Discord. + * @param {UserResolvable|FetchBanOptions|FetchBansOptions} [options] Options for fetching guild ban(s) + * @returns {Promise<GuildBan|Collection<Snowflake, GuildBan>>} + * @example + * // Fetch multiple bans from a guild + * guild.bans.fetch() + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a maximum of 5 bans from a guild without caching + * guild.bans.fetch({ limit: 5, cache: false }) + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single ban + * guild.bans.fetch('351871113346809860') + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single ban without checking cache + * guild.bans.fetch({ user, force: true }) + * .then(console.log) + * .catch(console.error) + * @example + * // Fetch a single ban without caching + * guild.bans.fetch({ user, cache: false }) + * .then(console.log) + * .catch(console.error); + */ + fetch(options) { + if (!options) return this._fetchMany(); + const { user, cache, force, limit, before, after } = options; + const resolvedUser = this.client.users.resolveId(user ?? options); + if (resolvedUser) return this._fetchSingle({ user: resolvedUser, cache, force }); + + if (!before && !after && !limit && cache === undefined) { + return Promise.reject(new DiscordjsError(ErrorCodes.FetchBanResolveId)); + } + + return this._fetchMany(options); + } + + async _fetchSingle({ user, cache, force = false }) { + if (!force) { + const existing = this.cache.get(user); + if (existing && !existing.partial) return existing; + } + + const data = await this.client.rest.get(Routes.guildBan(this.guild.id, user)); + return this._add(data, cache); + } + + async _fetchMany(options = {}) { + const data = await this.client.rest.get(Routes.guildBans(this.guild.id), { + query: makeURLSearchParams(options), + }); + + return data.reduce((col, ban) => col.set(ban.user.id, this._add(ban, options.cache)), new Collection()); + } + + /** + * Options used to ban a user from a guild. + * @typedef {Object} BanOptions + * @property {number} [deleteMessageDays] Number of days of messages to delete, must be between 0 and 7, inclusive + * <warn>This property is deprecated. Use `deleteMessageSeconds` instead.</warn> + * @property {number} [deleteMessageSeconds] Number of seconds of messages to delete, + * must be between 0 and 604800 (7 days), inclusive + * @property {string} [reason] The reason for the ban + */ + + /** + * Bans a user from the guild. + * @param {UserResolvable} user The user to ban + * @param {BanOptions} [options] Options for the ban + * @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible. + * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot + * be resolved, the user id will be the result. + * @example + * // Ban a user by id (or with a user/guild member object) + * guild.bans.create('84484653687267328') + * .then(banInfo => console.log(`Banned ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`)) + * .catch(console.error); + */ + async create(user, options = {}) { + if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); + const id = this.client.users.resolveId(user); + if (!id) throw new DiscordjsError(ErrorCodes.BanResolveId, true); + + if (options.deleteMessageDays !== undefined && !deprecationEmittedForDeleteMessageDays) { + process.emitWarning( + // eslint-disable-next-line max-len + 'The deleteMessageDays option for GuildBanManager#create() is deprecated. Use the deleteMessageSeconds option instead.', + 'DeprecationWarning', + ); + + deprecationEmittedForDeleteMessageDays = true; + } + + await this.client.rest.put(Routes.guildBan(this.guild.id, id), { + body: { + delete_message_seconds: + options.deleteMessageSeconds ?? + (options.deleteMessageDays ? options.deleteMessageDays * 24 * 60 * 60 : undefined), + }, + reason: options.reason, + }); + if (user instanceof GuildMember) return user; + const _user = this.client.users.resolve(id); + if (_user) { + return this.guild.members.resolve(_user) ?? _user; + } + return id; + } + + /** + * Unbans a user from the guild. + * @param {UserResolvable} user The user to unban + * @param {string} [reason] Reason for unbanning user + * @returns {Promise<?User>} + * @example + * // Unban a user by id (or with a user/guild member object) + * guild.bans.remove('84484653687267328') + * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`)) + * .catch(console.error); + */ + async remove(user, reason) { + const id = this.client.users.resolveId(user); + if (!id) throw new DiscordjsError(ErrorCodes.BanResolveId); + await this.client.rest.delete(Routes.guildBan(this.guild.id, id), { reason }); + return this.client.users.resolve(user); + } +} + +module.exports = GuildBanManager; diff --git a/node_modules/discord.js/src/managers/GuildChannelManager.js b/node_modules/discord.js/src/managers/GuildChannelManager.js new file mode 100644 index 0000000..7ca9287 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildChannelManager.js @@ -0,0 +1,503 @@ +'use strict'; + +const process = require('node:process'); +const { Collection } = require('@discordjs/collection'); +const { ChannelType, Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const GuildTextThreadManager = require('./GuildTextThreadManager'); +const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); +const GuildChannel = require('../structures/GuildChannel'); +const PermissionOverwrites = require('../structures/PermissionOverwrites'); +const ThreadChannel = require('../structures/ThreadChannel'); +const Webhook = require('../structures/Webhook'); +const ChannelFlagsBitField = require('../util/ChannelFlagsBitField'); +const { transformGuildForumTag, transformGuildDefaultReaction } = require('../util/Channels'); +const { ThreadChannelTypes } = require('../util/Constants'); +const DataResolver = require('../util/DataResolver'); +const { setPosition } = require('../util/Util'); + +let cacheWarningEmitted = false; + +/** + * Manages API methods for GuildChannels and stores their cache. + * @extends {CachedManager} + */ +class GuildChannelManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, GuildChannel, iterable); + const defaultCaching = + this._cache.constructor.name === 'Collection' || + this._cache.maxSize === undefined || + this._cache.maxSize === Infinity; + if (!cacheWarningEmitted && !defaultCaching) { + cacheWarningEmitted = true; + process.emitWarning( + `Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`, + 'UnsupportedCacheOverwriteWarning', + ); + } + + /** + * The guild this Manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The number of channels in this managers cache excluding thread channels + * that do not count towards a guild's maximum channels restriction. + * @type {number} + * @readonly + */ + get channelCountWithoutThreads() { + return this.cache.reduce((acc, channel) => { + if (ThreadChannelTypes.includes(channel.type)) return acc; + return ++acc; + }, 0); + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, GuildChannel|ThreadChannel>} + * @name GuildChannelManager#cache + */ + + _add(channel) { + const existing = this.cache.get(channel.id); + if (existing) return existing; + this.cache.set(channel.id, channel); + return channel; + } + + /** + * Data that can be resolved to give a Guild Channel object. This can be: + * * A GuildChannel object + * * A ThreadChannel object + * * A Snowflake + * @typedef {GuildChannel|ThreadChannel|Snowflake} GuildChannelResolvable + */ + + /** + * Resolves a GuildChannelResolvable to a Channel object. + * @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve + * @returns {?(GuildChannel|ThreadChannel)} + */ + resolve(channel) { + if (channel instanceof ThreadChannel) return super.resolve(channel.id); + return super.resolve(channel); + } + + /** + * Resolves a GuildChannelResolvable to a channel id. + * @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve + * @returns {?Snowflake} + */ + resolveId(channel) { + if (channel instanceof ThreadChannel) return super.resolveId(channel.id); + return super.resolveId(channel); + } + + /** + * Adds the target channel to a channel's followers. + * @param {NewsChannel|Snowflake} channel The channel to follow + * @param {TextChannelResolvable} targetChannel The channel where published announcements will be posted at + * @param {string} [reason] Reason for creating the webhook + * @returns {Promise<Snowflake>} Returns created target webhook id. + */ + async addFollower(channel, targetChannel, reason) { + const channelId = this.resolveId(channel); + const targetChannelId = this.resolveId(targetChannel); + if (!channelId || !targetChannelId) throw new Error(ErrorCodes.GuildChannelResolve); + const { webhook_id } = await this.client.rest.post(Routes.channelFollowers(channelId), { + body: { webhook_channel_id: targetChannelId }, + reason, + }); + return webhook_id; + } + + /** + * Options used to create a new channel in a guild. + * @typedef {CategoryCreateChannelOptions} GuildChannelCreateOptions + * @property {?CategoryChannelResolvable} [parent] Parent of the new channel + */ + + /** + * Creates a new channel in the guild. + * @param {GuildChannelCreateOptions} options Options for creating the new channel + * @returns {Promise<GuildChannel>} + * @example + * // Create a new text channel + * guild.channels.create({ name: 'new-general', reason: 'Needed a cool new channel' }) + * .then(console.log) + * .catch(console.error); + * @example + * // Create a new channel with permission overwrites + * guild.channels.create({ + * name: 'new-general', + * type: ChannelType.GuildVoice, + * permissionOverwrites: [ + * { + * id: message.author.id, + * deny: [PermissionFlagsBits.ViewChannel], + * }, + * ], + * }) + */ + async create({ + name, + type, + topic, + nsfw, + bitrate, + userLimit, + parent, + permissionOverwrites, + position, + rateLimitPerUser, + rtcRegion, + videoQualityMode, + availableTags, + defaultReactionEmoji, + defaultAutoArchiveDuration, + defaultSortOrder, + defaultForumLayout, + reason, + }) { + parent &&= this.client.channels.resolveId(parent); + permissionOverwrites &&= permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild)); + + const data = await this.client.rest.post(Routes.guildChannels(this.guild.id), { + body: { + name, + topic, + type, + nsfw, + bitrate, + user_limit: userLimit, + parent_id: parent, + position, + permission_overwrites: permissionOverwrites, + rate_limit_per_user: rateLimitPerUser, + rtc_region: rtcRegion, + video_quality_mode: videoQualityMode, + available_tags: availableTags?.map(availableTag => transformGuildForumTag(availableTag)), + default_reaction_emoji: defaultReactionEmoji && transformGuildDefaultReaction(defaultReactionEmoji), + default_auto_archive_duration: defaultAutoArchiveDuration, + default_sort_order: defaultSortOrder, + default_forum_layout: defaultForumLayout, + }, + reason, + }); + return this.client.actions.ChannelCreate.handle(data).channel; + } + + /** + * @typedef {ChannelWebhookCreateOptions} WebhookCreateOptions + * @property {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|Snowflake} channel + * The channel to create the webhook for + */ + + /** + * Creates a webhook for the channel. + * @param {WebhookCreateOptions} options Options for creating the webhook + * @returns {Promise<Webhook>} Returns the created Webhook + * @example + * // Create a webhook for the current channel + * guild.channels.createWebhook({ + * channel: '222197033908436994', + * name: 'Snek', + * avatar: 'https://i.imgur.com/mI8XcpG.jpg', + * reason: 'Needed a cool new Webhook' + * }) + * .then(console.log) + * .catch(console.error) + */ + async createWebhook({ channel, name, avatar, reason }) { + const id = this.resolveId(channel); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable'); + if (typeof avatar === 'string' && !avatar.startsWith('data:')) { + avatar = await DataResolver.resolveImage(avatar); + } + const data = await this.client.rest.post(Routes.channelWebhooks(id), { + body: { + name, + avatar, + }, + reason, + }); + return new Webhook(this.client, data); + } + + /** + * Options used to edit a guild channel. + * @typedef {Object} GuildChannelEditOptions + * @property {string} [name] The name of the channel + * @property {ChannelType} [type] The type of the channel (only conversion between text and news is supported) + * @property {number} [position] The position of the channel + * @property {?string} [topic] The topic of the text channel + * @property {boolean} [nsfw] Whether the channel is NSFW + * @property {number} [bitrate] The bitrate of the voice channel + * @property {number} [userLimit] The user limit of the voice channel + * @property {?CategoryChannelResolvable} [parent] The parent of the channel + * @property {boolean} [lockPermissions] + * Lock the permissions of the channel to what the parent's permissions are + * @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites] + * Permission overwrites for the channel + * @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the channel in seconds + * @property {ThreadAutoArchiveDuration} [defaultAutoArchiveDuration] + * The default auto archive duration for all new threads in this channel + * @property {?string} [rtcRegion] The RTC region of the channel + * @property {?VideoQualityMode} [videoQualityMode] The camera video quality mode of the channel + * @property {GuildForumTagData[]} [availableTags] The tags to set as available in a forum channel + * @property {?DefaultReactionEmoji} [defaultReactionEmoji] The emoji to set as the default reaction emoji + * @property {number} [defaultThreadRateLimitPerUser] The rate limit per user (slowmode) to set on forum posts + * @property {ChannelFlagsResolvable} [flags] The flags to set on the channel + * @property {?SortOrderType} [defaultSortOrder] The default sort order mode to set on the channel + * @property {ForumLayoutType} [defaultForumLayout] The default forum layout to set on the channel + * @property {string} [reason] Reason for editing this channel + */ + + /** + * Edits the channel. + * @param {GuildChannelResolvable} channel The channel to edit + * @param {GuildChannelEditOptions} options Options for editing the channel + * @returns {Promise<GuildChannel>} + * @example + * // Edit a channel + * guild.channels.edit('222197033908436994', { name: 'new-channel' }) + * .then(console.log) + * .catch(console.error); + */ + async edit(channel, options) { + channel = this.resolve(channel); + if (!channel) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable'); + + const parent = options.parent && this.client.channels.resolveId(options.parent); + + if (options.position !== undefined) { + await this.setPosition(channel, options.position, { position: options.position, reason: options.reason }); + } + + let permission_overwrites = options.permissionOverwrites?.map(o => PermissionOverwrites.resolve(o, this.guild)); + + if (options.lockPermissions) { + if (parent) { + const newParent = this.guild.channels.resolve(parent); + if (newParent?.type === ChannelType.GuildCategory) { + permission_overwrites = newParent.permissionOverwrites.cache.map(o => + PermissionOverwrites.resolve(o, this.guild), + ); + } + } else if (channel.parent) { + permission_overwrites = channel.parent.permissionOverwrites.cache.map(o => + PermissionOverwrites.resolve(o, this.guild), + ); + } + } + + const newData = await this.client.rest.patch(Routes.channel(channel.id), { + body: { + name: (options.name ?? channel.name).trim(), + type: options.type, + topic: options.topic, + nsfw: options.nsfw, + bitrate: options.bitrate ?? channel.bitrate, + user_limit: options.userLimit ?? channel.userLimit, + rtc_region: 'rtcRegion' in options ? options.rtcRegion : channel.rtcRegion, + video_quality_mode: options.videoQualityMode, + parent_id: parent, + lock_permissions: options.lockPermissions, + rate_limit_per_user: options.rateLimitPerUser, + default_auto_archive_duration: options.defaultAutoArchiveDuration, + permission_overwrites, + available_tags: options.availableTags?.map(availableTag => transformGuildForumTag(availableTag)), + default_reaction_emoji: + options.defaultReactionEmoji && transformGuildDefaultReaction(options.defaultReactionEmoji), + default_thread_rate_limit_per_user: options.defaultThreadRateLimitPerUser, + flags: 'flags' in options ? ChannelFlagsBitField.resolve(options.flags) : undefined, + default_sort_order: options.defaultSortOrder, + default_forum_layout: options.defaultForumLayout, + }, + reason: options.reason, + }); + + return this.client.actions.ChannelUpdate.handle(newData).updated; + } + + /** + * Sets a new position for the guild channel. + * @param {GuildChannelResolvable} channel The channel to set the position for + * @param {number} position The new position for the guild channel + * @param {SetChannelPositionOptions} options Options for setting position + * @returns {Promise<GuildChannel>} + * @example + * // Set a new channel position + * guild.channels.setPosition('222078374472843266', 2) + * .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`)) + * .catch(console.error); + */ + async setPosition(channel, position, { relative, reason } = {}) { + channel = this.resolve(channel); + if (!channel) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable'); + const updatedChannels = await setPosition( + channel, + position, + relative, + this.guild._sortedChannels(channel), + this.client, + Routes.guildChannels(this.guild.id), + reason, + ); + + this.client.actions.GuildChannelsPositionUpdate.handle({ + guild_id: this.guild.id, + channels: updatedChannels, + }); + return channel; + } + + /** + * Obtains one or more guild channels from Discord, or the channel cache if they're already available. + * @param {Snowflake} [id] The channel's id + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise<?GuildChannel|ThreadChannel|Collection<Snowflake, ?GuildChannel>>} + * @example + * // Fetch all channels from the guild (excluding threads) + * message.guild.channels.fetch() + * .then(channels => console.log(`There are ${channels.size} channels.`)) + * .catch(console.error); + * @example + * // Fetch a single channel + * message.guild.channels.fetch('222197033908436994') + * .then(channel => console.log(`The channel name is: ${channel.name}`)) + * .catch(console.error); + */ + async fetch(id, { cache = true, force = false } = {}) { + if (id && !force) { + const existing = this.cache.get(id); + if (existing) return existing; + } + + if (id) { + const data = await this.client.rest.get(Routes.channel(id)); + // Since this is the guild manager, throw if on a different guild + if (this.guild.id !== data.guild_id) throw new DiscordjsError(ErrorCodes.GuildChannelUnowned); + return this.client.channels._add(data, this.guild, { cache }); + } + + const data = await this.client.rest.get(Routes.guildChannels(this.guild.id)); + const channels = new Collection(); + for (const channel of data) channels.set(channel.id, this.client.channels._add(channel, this.guild, { cache })); + return channels; + } + + /** + * Fetches all webhooks for the channel. + * @param {GuildChannelResolvable} channel The channel to fetch webhooks for + * @returns {Promise<Collection<Snowflake, Webhook>>} + * @example + * // Fetch webhooks + * guild.channels.fetchWebhooks('769862166131245066') + * .then(hooks => console.log(`This channel has ${hooks.size} hooks`)) + * .catch(console.error); + */ + async fetchWebhooks(channel) { + const id = this.resolveId(channel); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable'); + const data = await this.client.rest.get(Routes.channelWebhooks(id)); + return data.reduce((hooks, hook) => hooks.set(hook.id, new Webhook(this.client, hook)), new Collection()); + } + + /** + * Data that can be resolved to give a Category Channel object. This can be: + * * A CategoryChannel object + * * A Snowflake + * @typedef {CategoryChannel|Snowflake} CategoryChannelResolvable + */ + + /** + * The data needed for updating a channel's position. + * @typedef {Object} ChannelPosition + * @property {GuildChannel|Snowflake} channel Channel to update + * @property {number} [position] New position for the channel + * @property {CategoryChannelResolvable} [parent] Parent channel for this channel + * @property {boolean} [lockPermissions] If the overwrites should be locked to the parents overwrites + */ + + /** + * Batch-updates the guild's channels' positions. + * <info>Only one channel's parent can be changed at a time</info> + * @param {ChannelPosition[]} channelPositions Channel positions to update + * @returns {Promise<Guild>} + * @example + * guild.channels.setPositions([{ channel: channelId, position: newChannelIndex }]) + * .then(guild => console.log(`Updated channel positions for ${guild}`)) + * .catch(console.error); + */ + async setPositions(channelPositions) { + channelPositions = channelPositions.map(r => ({ + id: this.client.channels.resolveId(r.channel), + position: r.position, + lock_permissions: r.lockPermissions, + parent_id: r.parent !== undefined ? this.resolveId(r.parent) : undefined, + })); + + await this.client.rest.patch(Routes.guildChannels(this.guild.id), { body: channelPositions }); + return this.client.actions.GuildChannelsPositionUpdate.handle({ + guild_id: this.guild.id, + channels: channelPositions, + }).guild; + } + + /** + * Data returned from fetching threads. + * @typedef {Object} FetchedThreads + * @property {Collection<Snowflake, ThreadChannel>} threads The threads that were fetched + * @property {Collection<Snowflake, ThreadMember>} members The thread members in the received threads + */ + + /** + * Obtains all active thread channels in the guild. + * @param {boolean} [cache=true] Whether to cache the fetched data + * @returns {Promise<FetchedThreads>} + * @example + * // Fetch all threads from the guild + * message.guild.channels.fetchActiveThreads() + * .then(fetched => console.log(`There are ${fetched.threads.size} threads.`)) + * .catch(console.error); + */ + async fetchActiveThreads(cache = true) { + const data = await this.rawFetchGuildActiveThreads(); + return GuildTextThreadManager._mapThreads(data, this.client, { guild: this.guild, cache }); + } + + /** + * `GET /guilds/{guild.id}/threads/active` + * @private + * @returns {Promise<RESTGetAPIGuildThreadsResult>} + */ + rawFetchGuildActiveThreads() { + return this.client.rest.get(Routes.guildActiveThreads(this.guild.id)); + } + + /** + * Deletes the channel. + * @param {GuildChannelResolvable} channel The channel to delete + * @param {string} [reason] Reason for deleting this channel + * @returns {Promise<void>} + * @example + * // Delete the channel + * guild.channels.delete('858850993013260338', 'making room for new channels') + * .then(console.log) + * .catch(console.error); + */ + async delete(channel, reason) { + const id = this.resolveId(channel); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable'); + await this.client.rest.delete(Routes.channel(id), { reason }); + this.client.actions.ChannelDelete.handle({ id }); + } +} + +module.exports = GuildChannelManager; diff --git a/node_modules/discord.js/src/managers/GuildEmojiManager.js b/node_modules/discord.js/src/managers/GuildEmojiManager.js new file mode 100644 index 0000000..61f5050 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildEmojiManager.js @@ -0,0 +1,174 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { Routes, PermissionFlagsBits } = require('discord-api-types/v10'); +const BaseGuildEmojiManager = require('./BaseGuildEmojiManager'); +const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); +const DataResolver = require('../util/DataResolver'); + +/** + * Manages API methods for GuildEmojis and stores their cache. + * @extends {BaseGuildEmojiManager} + */ +class GuildEmojiManager extends BaseGuildEmojiManager { + constructor(guild, iterable) { + super(guild.client, iterable); + + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + _add(data, cache) { + return super._add(data, cache, { extras: [this.guild] }); + } + + /** + * Options used for creating an emoji in a guild. + * @typedef {Object} GuildEmojiCreateOptions + * @property {BufferResolvable|Base64Resolvable} attachment The image for the emoji + * @property {string} name The name for the emoji + * @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles to limit the emoji to + * @property {string} [reason] The reason for creating the emoji + */ + + /** + * Creates a new custom emoji in the guild. + * @param {GuildEmojiCreateOptions} options Options for creating the emoji + * @returns {Promise<Emoji>} The created emoji + * @example + * // Create a new emoji from a URL + * guild.emojis.create({ attachment: 'https://i.imgur.com/w3duR07.png', name: 'rip' }) + * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`)) + * .catch(console.error); + * @example + * // Create a new emoji from a file on your computer + * guild.emojis.create({ attachment: './memes/banana.png', name: 'banana' }) + * .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`)) + * .catch(console.error); + */ + async create({ attachment, name, roles, reason }) { + attachment = await DataResolver.resolveImage(attachment); + if (!attachment) throw new DiscordjsTypeError(ErrorCodes.ReqResourceType); + + const body = { image: attachment, name }; + if (roles) { + if (!Array.isArray(roles) && !(roles instanceof Collection)) { + throw new DiscordjsTypeError( + ErrorCodes.InvalidType, + 'options.roles', + 'Array or Collection of Roles or Snowflakes', + true, + ); + } + body.roles = []; + for (const role of roles.values()) { + const resolvedRole = this.guild.roles.resolveId(role); + if (!resolvedRole) { + throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'options.roles', role); + } + body.roles.push(resolvedRole); + } + } + + const emoji = await this.client.rest.post(Routes.guildEmojis(this.guild.id), { body, reason }); + return this.client.actions.GuildEmojiCreate.handle(this.guild, emoji).emoji; + } + + /** + * Obtains one or more emojis from Discord, or the emoji cache if they're already available. + * @param {Snowflake} [id] The emoji's id + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise<GuildEmoji|Collection<Snowflake, GuildEmoji>>} + * @example + * // Fetch all emojis from the guild + * message.guild.emojis.fetch() + * .then(emojis => console.log(`There are ${emojis.size} emojis.`)) + * .catch(console.error); + * @example + * // Fetch a single emoji + * message.guild.emojis.fetch('222078108977594368') + * .then(emoji => console.log(`The emoji name is: ${emoji.name}`)) + * .catch(console.error); + */ + async fetch(id, { cache = true, force = false } = {}) { + if (id) { + if (!force) { + const existing = this.cache.get(id); + if (existing) return existing; + } + const emoji = await this.client.rest.get(Routes.guildEmoji(this.guild.id, id)); + return this._add(emoji, cache); + } + + const data = await this.client.rest.get(Routes.guildEmojis(this.guild.id)); + const emojis = new Collection(); + for (const emoji of data) emojis.set(emoji.id, this._add(emoji, cache)); + return emojis; + } + + /** + * Deletes an emoji. + * @param {EmojiResolvable} emoji The Emoji resolvable to delete + * @param {string} [reason] Reason for deleting the emoji + * @returns {Promise<void>} + */ + async delete(emoji, reason) { + const id = this.resolveId(emoji); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true); + await this.client.rest.delete(Routes.guildEmoji(this.guild.id, id), { reason }); + } + + /** + * Edits an emoji. + * @param {EmojiResolvable} emoji The Emoji resolvable to edit + * @param {GuildEmojiEditOptions} options The options to provide + * @returns {Promise<GuildEmoji>} + */ + async edit(emoji, options) { + const id = this.resolveId(emoji); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true); + const roles = options.roles?.map(r => this.guild.roles.resolveId(r)); + const newData = await this.client.rest.patch(Routes.guildEmoji(this.guild.id, id), { + body: { + name: options.name, + roles, + }, + reason: options.reason, + }); + const existing = this.cache.get(id); + if (existing) { + const clone = existing._clone(); + clone._patch(newData); + return clone; + } + return this._add(newData); + } + + /** + * Fetches the author for this emoji + * @param {EmojiResolvable} emoji The emoji to fetch the author of + * @returns {Promise<User>} + */ + async fetchAuthor(emoji) { + emoji = this.resolve(emoji); + if (!emoji) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true); + if (emoji.managed) { + throw new DiscordjsError(ErrorCodes.EmojiManaged); + } + + const { me } = this.guild.members; + if (!me) throw new DiscordjsError(ErrorCodes.GuildUncachedMe); + if (!me.permissions.has(PermissionFlagsBits.ManageGuildExpressions)) { + throw new DiscordjsError(ErrorCodes.MissingManageGuildExpressionsPermission, this.guild); + } + + const data = await this.client.rest.get(Routes.guildEmoji(this.guild.id, emoji.id)); + emoji._patch(data); + return emoji.author; + } +} + +module.exports = GuildEmojiManager; diff --git a/node_modules/discord.js/src/managers/GuildEmojiRoleManager.js b/node_modules/discord.js/src/managers/GuildEmojiRoleManager.js new file mode 100644 index 0000000..7b97f41 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildEmojiRoleManager.js @@ -0,0 +1,118 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const DataManager = require('./DataManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const { Role } = require('../structures/Role'); + +/** + * Manages API methods for roles belonging to emojis and stores their cache. + * @extends {DataManager} + */ +class GuildEmojiRoleManager extends DataManager { + constructor(emoji) { + super(emoji.client, Role); + + /** + * The emoji belonging to this manager + * @type {GuildEmoji} + */ + this.emoji = emoji; + /** + * The guild belonging to this manager + * @type {Guild} + */ + this.guild = emoji.guild; + } + + /** + * The cache of roles belonging to this emoji + * @type {Collection<Snowflake, Role>} + * @readonly + */ + get cache() { + return this.guild.roles.cache.filter(role => this.emoji._roles.includes(role.id)); + } + + /** + * Adds a role (or multiple roles) to the list of roles that can use this emoji. + * @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to add + * @returns {Promise<GuildEmoji>} + */ + add(roleOrRoles) { + if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection)) roleOrRoles = [roleOrRoles]; + + const resolvedRoles = []; + for (const role of roleOrRoles.values()) { + const resolvedRole = this.guild.roles.resolveId(role); + if (!resolvedRole) { + return Promise.reject(new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role)); + } + resolvedRoles.push(resolvedRole); + } + + const newRoles = [...new Set(resolvedRoles.concat(...this.cache.keys()))]; + return this.set(newRoles); + } + + /** + * Removes a role (or multiple roles) from the list of roles that can use this emoji. + * @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to remove + * @returns {Promise<GuildEmoji>} + */ + remove(roleOrRoles) { + if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection)) roleOrRoles = [roleOrRoles]; + + const resolvedRoleIds = []; + for (const role of roleOrRoles.values()) { + const roleId = this.guild.roles.resolveId(role); + if (!roleId) { + return Promise.reject(new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role)); + } + resolvedRoleIds.push(roleId); + } + + const newRoles = [...this.cache.keys()].filter(id => !resolvedRoleIds.includes(id)); + return this.set(newRoles); + } + + /** + * Sets the role(s) that can use this emoji. + * @param {Collection<Snowflake, Role>|RoleResolvable[]} roles The roles or role ids to apply + * @returns {Promise<GuildEmoji>} + * @example + * // Set the emoji's roles to a single role + * guildEmoji.roles.set(['391156570408615936']) + * .then(console.log) + * .catch(console.error); + * @example + * // Remove all roles from an emoji + * guildEmoji.roles.set([]) + * .then(console.log) + * .catch(console.error); + */ + set(roles) { + return this.emoji.edit({ roles }); + } + + clone() { + const clone = new this.constructor(this.emoji); + clone._patch([...this.cache.keys()]); + return clone; + } + + /** + * Patches the roles for this manager's cache + * @param {Snowflake[]} roles The new roles + * @private + */ + _patch(roles) { + this.emoji._roles = roles; + } + + valueOf() { + return this.cache; + } +} + +module.exports = GuildEmojiRoleManager; diff --git a/node_modules/discord.js/src/managers/GuildForumThreadManager.js b/node_modules/discord.js/src/managers/GuildForumThreadManager.js new file mode 100644 index 0000000..f830b98 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildForumThreadManager.js @@ -0,0 +1,83 @@ +'use strict'; + +const { Routes } = require('discord-api-types/v10'); +const ThreadManager = require('./ThreadManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const MessagePayload = require('../structures/MessagePayload'); + +/** + * Manages API methods for threads in forum channels and stores their cache. + * @extends {ThreadManager} + */ +class GuildForumThreadManager extends ThreadManager { + /** + * The channel this Manager belongs to + * @name GuildForumThreadManager#channel + * @type {ForumChannel} + */ + + /** + * @typedef {BaseMessageOptions} GuildForumThreadMessageCreateOptions + * @property {StickerResolvable} [stickers] The stickers to send with the message + * @property {BitFieldResolvable} [flags] The flags to send with the message + * <info>Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set.</info> + */ + + /** + * Options for creating a thread. + * @typedef {StartThreadOptions} GuildForumThreadCreateOptions + * @property {GuildForumThreadMessageCreateOptions|MessagePayload} message The message associated with the thread post + * @property {Snowflake[]} [appliedTags] The tags to apply to the thread + */ + + /** + * Creates a new thread in the channel. + * @param {GuildForumThreadCreateOptions} [options] Options to create a new thread + * @returns {Promise<ThreadChannel>} + * @example + * // Create a new forum post + * forum.threads + * .create({ + * name: 'Food Talk', + * autoArchiveDuration: ThreadAutoArchiveDuration.OneHour, + * message: { + * content: 'Discuss your favorite food!', + * }, + * reason: 'Needed a separate thread for food', + * }) + * .then(threadChannel => console.log(threadChannel)) + * .catch(console.error); + */ + async create({ + name, + autoArchiveDuration = this.channel.defaultAutoArchiveDuration, + message, + reason, + rateLimitPerUser, + appliedTags, + } = {}) { + if (!message) { + throw new DiscordjsTypeError(ErrorCodes.GuildForumMessageRequired); + } + + const { body, files } = await (message instanceof MessagePayload ? message : MessagePayload.create(this, message)) + .resolveBody() + .resolveFiles(); + + const data = await this.client.rest.post(Routes.threads(this.channel.id), { + body: { + name, + auto_archive_duration: autoArchiveDuration, + rate_limit_per_user: rateLimitPerUser, + applied_tags: appliedTags, + message: body, + }, + files, + reason, + }); + + return this.client.actions.ThreadCreate.handle(data).thread; + } +} + +module.exports = GuildForumThreadManager; diff --git a/node_modules/discord.js/src/managers/GuildInviteManager.js b/node_modules/discord.js/src/managers/GuildInviteManager.js new file mode 100644 index 0000000..f1fe3eb --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildInviteManager.js @@ -0,0 +1,214 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const Invite = require('../structures/Invite'); +const DataResolver = require('../util/DataResolver'); + +/** + * Manages API methods for GuildInvites and stores their cache. + * @extends {CachedManager} + */ +class GuildInviteManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, Invite, iterable); + + /** + * The guild this Manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this Manager + * @type {Collection<string, Invite>} + * @name GuildInviteManager#cache + */ + + _add(data, cache) { + return super._add(data, cache, { id: data.code, extras: [this.guild] }); + } + + /** + * Data that resolves to give an Invite object. This can be: + * * An invite code + * * An invite URL + * @typedef {string} InviteResolvable + */ + + /** + * Data that can be resolved to a channel that an invite can be created on. This can be: + * * TextChannel + * * VoiceChannel + * * NewsChannel + * * StageChannel + * * ForumChannel + * * Snowflake + * @typedef {TextChannel|VoiceChannel|NewsChannel|StageChannel|ForumChannel|Snowflake} + * GuildInvitableChannelResolvable + */ + + /** + * Resolves an InviteResolvable to an Invite object. + * @method resolve + * @memberof GuildInviteManager + * @instance + * @param {InviteResolvable} invite The invite resolvable to resolve + * @returns {?Invite} + */ + + /** + * Resolves an InviteResolvable to an invite code string. + * @method resolveId + * @memberof GuildInviteManager + * @instance + * @param {InviteResolvable} invite The invite resolvable to resolve + * @returns {?string} + */ + + /** + * Options used to fetch a single invite from a guild. + * @typedef {Object} FetchInviteOptions + * @property {InviteResolvable} code The invite to fetch + * @property {boolean} [cache=true] Whether or not to cache the fetched invite + * @property {boolean} [force=false] Whether to skip the cache check and request the API + */ + + /** + * Options used to fetch all invites from a guild. + * @typedef {Object} FetchInvitesOptions + * @property {GuildInvitableChannelResolvable} [channelId] + * The channel to fetch all invites from + * @property {boolean} [cache=true] Whether or not to cache the fetched invites + */ + + /** + * Fetches invite(s) from Discord. + * @param {InviteResolvable|FetchInviteOptions|FetchInvitesOptions} [options] Options for fetching guild invite(s) + * @returns {Promise<Invite|Collection<string, Invite>>} + * @example + * // Fetch all invites from a guild + * guild.invites.fetch() + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch all invites from a guild without caching + * guild.invites.fetch({ cache: false }) + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch all invites from a channel + * guild.invites.fetch({ channelId: '222197033908436994' }) + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single invite + * guild.invites.fetch('bRCvFy9') + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single invite without checking cache + * guild.invites.fetch({ code: 'bRCvFy9', force: true }) + * .then(console.log) + * .catch(console.error) + * @example + * // Fetch a single invite without caching + * guild.invites.fetch({ code: 'bRCvFy9', cache: false }) + * .then(console.log) + * .catch(console.error); + */ + fetch(options) { + if (!options) return this._fetchMany(); + if (typeof options === 'string') { + const code = DataResolver.resolveInviteCode(options); + if (!code) return Promise.reject(new DiscordjsError(ErrorCodes.InviteResolveCode)); + return this._fetchSingle({ code, cache: true }); + } + if (!options.code) { + if (options.channelId) { + const id = this.guild.channels.resolveId(options.channelId); + if (!id) return Promise.reject(new DiscordjsError(ErrorCodes.GuildChannelResolve)); + return this._fetchChannelMany(id, options.cache); + } + + if ('cache' in options) return this._fetchMany(options.cache); + return Promise.reject(new DiscordjsError(ErrorCodes.InviteResolveCode)); + } + return this._fetchSingle({ + ...options, + code: DataResolver.resolveInviteCode(options.code), + }); + } + + async _fetchSingle({ code, cache, force = false }) { + if (!force) { + const existing = this.cache.get(code); + if (existing) return existing; + } + + const invites = await this._fetchMany(cache); + const invite = invites.get(code); + if (!invite) throw new DiscordjsError(ErrorCodes.InviteNotFound); + return invite; + } + + async _fetchMany(cache) { + const data = await this.client.rest.get(Routes.guildInvites(this.guild.id)); + return data.reduce((col, invite) => col.set(invite.code, this._add(invite, cache)), new Collection()); + } + + async _fetchChannelMany(channelId, cache) { + const data = await this.client.rest.get(Routes.channelInvites(channelId)); + return data.reduce((col, invite) => col.set(invite.code, this._add(invite, cache)), new Collection()); + } + + /** + * Create an invite to the guild from the provided channel. + * @param {GuildInvitableChannelResolvable} channel The options for creating the invite from a channel. + * @param {InviteCreateOptions} [options={}] The options for creating the invite from a channel. + * @returns {Promise<Invite>} + * @example + * // Create an invite to a selected channel + * guild.invites.create('599942732013764608') + * .then(console.log) + * .catch(console.error); + */ + async create( + channel, + { temporary, maxAge, maxUses, unique, targetUser, targetApplication, targetType, reason } = {}, + ) { + const id = this.guild.channels.resolveId(channel); + if (!id) throw new DiscordjsError(ErrorCodes.GuildChannelResolve); + + const invite = await this.client.rest.post(Routes.channelInvites(id), { + body: { + temporary, + max_age: maxAge, + max_uses: maxUses, + unique, + target_user_id: this.client.users.resolveId(targetUser), + target_application_id: targetApplication?.id ?? targetApplication?.applicationId ?? targetApplication, + target_type: targetType, + }, + reason, + }); + return new Invite(this.client, invite); + } + + /** + * Deletes an invite. + * @param {InviteResolvable} invite The invite to delete + * @param {string} [reason] Reason for deleting the invite + * @returns {Promise<void>} + */ + async delete(invite, reason) { + const code = DataResolver.resolveInviteCode(invite); + + await this.client.rest.delete(Routes.invite(code), { reason }); + } +} + +module.exports = GuildInviteManager; diff --git a/node_modules/discord.js/src/managers/GuildManager.js b/node_modules/discord.js/src/managers/GuildManager.js new file mode 100644 index 0000000..1d2d4ba --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildManager.js @@ -0,0 +1,283 @@ +'use strict'; + +const process = require('node:process'); +const { setTimeout, clearTimeout } = require('node:timers'); +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { Guild } = require('../structures/Guild'); +const GuildChannel = require('../structures/GuildChannel'); +const GuildEmoji = require('../structures/GuildEmoji'); +const { GuildMember } = require('../structures/GuildMember'); +const Invite = require('../structures/Invite'); +const OAuth2Guild = require('../structures/OAuth2Guild'); +const { Role } = require('../structures/Role'); +const DataResolver = require('../util/DataResolver'); +const Events = require('../util/Events'); +const PermissionsBitField = require('../util/PermissionsBitField'); +const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField'); +const { resolveColor } = require('../util/Util'); + +let cacheWarningEmitted = false; + +/** + * Manages API methods for Guilds and stores their cache. + * @extends {CachedManager} + */ +class GuildManager extends CachedManager { + constructor(client, iterable) { + super(client, Guild, iterable); + if (!cacheWarningEmitted && this._cache.constructor.name !== 'Collection') { + cacheWarningEmitted = true; + process.emitWarning( + `Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`, + 'UnsupportedCacheOverwriteWarning', + ); + } + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, Guild>} + * @name GuildManager#cache + */ + + /** + * Data that resolves to give a Guild object. This can be: + * * A Guild object + * * A GuildChannel object + * * A GuildEmoji object + * * A Role object + * * A Snowflake + * * An Invite object + * @typedef {Guild|GuildChannel|GuildMember|GuildEmoji|Role|Snowflake|Invite} GuildResolvable + */ + + /** + * Partial data for a Role. + * @typedef {Object} PartialRoleData + * @property {Snowflake|number} [id] The role's id, used to set channel overrides. + * This is a placeholder and will be replaced by the API after consumption + * @property {string} [name] The name of the role + * @property {ColorResolvable} [color] The color of the role, either a hex string or a base 10 number + * @property {boolean} [hoist] Whether the role should be hoisted + * @property {number} [position] The position of the role + * @property {PermissionResolvable} [permissions] The permissions of the role + * @property {boolean} [mentionable] Whether the role should be mentionable + */ + + /** + * Partial overwrite data. + * @typedef {Object} PartialOverwriteData + * @property {Snowflake|number} id The id of the {@link Role} or {@link User} this overwrite belongs to + * @property {OverwriteType} [type] The type of this overwrite + * @property {PermissionResolvable} [allow] The permissions to allow + * @property {PermissionResolvable} [deny] The permissions to deny + */ + + /** + * Partial data for a Channel. + * @typedef {Object} PartialChannelData + * @property {Snowflake|number} [id] The channel's id, used to set its parent. + * This is a placeholder and will be replaced by the API after consumption + * @property {Snowflake|number} [parentId] The parent id for this channel + * @property {ChannelType.GuildText|ChannelType.GuildVoice|ChannelType.GuildCategory} [type] The type of the channel + * @property {string} name The name of the channel + * @property {?string} [topic] The topic of the text channel + * @property {boolean} [nsfw] Whether the channel is NSFW + * @property {number} [bitrate] The bitrate of the voice channel + * @property {number} [userLimit] The user limit of the channel + * @property {?string} [rtcRegion] The RTC region of the channel + * @property {VideoQualityMode} [videoQualityMode] The camera video quality mode of the channel + * @property {PartialOverwriteData[]} [permissionOverwrites] + * Overwrites of the channel + * @property {number} [rateLimitPerUser] The rate limit per user (slowmode) of the channel in seconds + */ + + /** + * Resolves a GuildResolvable to a Guild object. + * @method resolve + * @memberof GuildManager + * @instance + * @param {GuildResolvable} guild The guild resolvable to identify + * @returns {?Guild} + */ + resolve(guild) { + if ( + guild instanceof GuildChannel || + guild instanceof GuildMember || + guild instanceof GuildEmoji || + guild instanceof Role || + (guild instanceof Invite && guild.guild) + ) { + return super.resolve(guild.guild); + } + return super.resolve(guild); + } + + /** + * Resolves a {@link GuildResolvable} to a {@link Guild} id string. + * @method resolveId + * @memberof GuildManager + * @instance + * @param {GuildResolvable} guild The guild resolvable to identify + * @returns {?Snowflake} + */ + resolveId(guild) { + if ( + guild instanceof GuildChannel || + guild instanceof GuildMember || + guild instanceof GuildEmoji || + guild instanceof Role || + (guild instanceof Invite && guild.guild) + ) { + return super.resolveId(guild.guild.id); + } + return super.resolveId(guild); + } + + /** + * Options used to create a guild. + * @typedef {Object} GuildCreateOptions + * @property {string} name The name of the guild + * @property {?(BufferResolvable|Base64Resolvable)} [icon=null] The icon for the guild + * @property {GuildVerificationLevel} [verificationLevel] The verification level for the guild + * @property {GuildDefaultMessageNotifications} [defaultMessageNotifications] The default message notifications + * for the guild + * @property {GuildExplicitContentFilter} [explicitContentFilter] The explicit content filter level for the guild + * @property {PartialRoleData[]} [roles=[]] The roles for this guild, + * @property {PartialChannelData[]} [channels=[]] The channels for this guild + * @property {Snowflake|number} [afkChannelId] The AFK channel's id + * @property {number} [afkTimeout] The AFK timeout in seconds + * the first element of this array is used to change properties of the guild's everyone role. + * @property {Snowflake|number} [systemChannelId] The system channel's id + * @property {SystemChannelFlagsResolvable} [systemChannelFlags] The flags of the system channel + */ + /* eslint-enable max-len */ + + /** + * Creates a guild. + * <warn>This is only available to bots in fewer than 10 guilds.</warn> + * @param {GuildCreateOptions} options Options for creating the guild + * @returns {Promise<Guild>} The guild that was created + */ + async create({ + name, + icon = null, + verificationLevel, + defaultMessageNotifications, + explicitContentFilter, + roles = [], + channels = [], + afkChannelId, + afkTimeout, + systemChannelId, + systemChannelFlags, + }) { + const data = await this.client.rest.post(Routes.guilds(), { + body: { + name, + icon: icon && (await DataResolver.resolveImage(icon)), + verification_level: verificationLevel, + default_message_notifications: defaultMessageNotifications, + explicit_content_filter: explicitContentFilter, + roles: roles.map(({ color, permissions, ...options }) => ({ + ...options, + color: color && resolveColor(color), + permissions: permissions === undefined ? undefined : PermissionsBitField.resolve(permissions).toString(), + })), + channels: channels.map( + ({ + parentId, + userLimit, + rtcRegion, + videoQualityMode, + permissionOverwrites, + rateLimitPerUser, + ...options + }) => ({ + ...options, + parent_id: parentId, + user_limit: userLimit, + rtc_region: rtcRegion, + video_quality_mode: videoQualityMode, + permission_overwrites: permissionOverwrites?.map(({ allow, deny, ...permissionOverwriteOptions }) => ({ + ...permissionOverwriteOptions, + allow: allow === undefined ? undefined : PermissionsBitField.resolve(allow).toString(), + deny: deny === undefined ? undefined : PermissionsBitField.resolve(deny).toString(), + })), + rate_limit_per_user: rateLimitPerUser, + }), + ), + afk_channel_id: afkChannelId, + afk_timeout: afkTimeout, + system_channel_id: systemChannelId, + system_channel_flags: + systemChannelFlags === undefined ? undefined : SystemChannelFlagsBitField.resolve(systemChannelFlags), + }, + }); + + return ( + this.client.guilds.cache.get(data.id) ?? + new Promise(resolve => { + const handleGuild = guild => { + if (guild.id === data.id) { + clearTimeout(timeout); + this.client.decrementMaxListeners(); + resolve(guild); + } + }; + this.client.incrementMaxListeners(); + this.client.once(Events.GuildCreate, handleGuild); + + const timeout = setTimeout(() => { + this.client.removeListener(Events.GuildCreate, handleGuild); + this.client.decrementMaxListeners(); + resolve(this.client.guilds._add(data)); + }, 10_000).unref(); + }) + ); + } + + /** + * Options used to fetch a single guild. + * @typedef {BaseFetchOptions} FetchGuildOptions + * @property {GuildResolvable} guild The guild to fetch + * @property {boolean} [withCounts=true] Whether the approximate member and presence counts should be returned + */ + + /** + * Options used to fetch multiple guilds. + * @typedef {Object} FetchGuildsOptions + * @property {Snowflake} [before] Get guilds before this guild id + * @property {Snowflake} [after] Get guilds after this guild id + * @property {number} [limit] Maximum number of guilds to request (1-200) + */ + + /** + * Obtains one or multiple guilds from Discord, or the guild cache if it's already available. + * @param {GuildResolvable|FetchGuildOptions|FetchGuildsOptions} [options] The guild's id or options + * @returns {Promise<Guild|Collection<Snowflake, OAuth2Guild>>} + */ + async fetch(options = {}) { + const id = this.resolveId(options) ?? this.resolveId(options.guild); + + if (id) { + if (!options.force) { + const existing = this.cache.get(id); + if (existing) return existing; + } + + const data = await this.client.rest.get(Routes.guild(id), { + query: makeURLSearchParams({ with_counts: options.withCounts ?? true }), + }); + return this._add(data, options.cache); + } + + const data = await this.client.rest.get(Routes.userGuilds(), { query: makeURLSearchParams(options) }); + return data.reduce((coll, guild) => coll.set(guild.id, new OAuth2Guild(this.client, guild)), new Collection()); + } +} + +module.exports = GuildManager; diff --git a/node_modules/discord.js/src/managers/GuildMemberManager.js b/node_modules/discord.js/src/managers/GuildMemberManager.js new file mode 100644 index 0000000..a4cf09b --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildMemberManager.js @@ -0,0 +1,540 @@ +'use strict'; + +const { setTimeout, clearTimeout } = require('node:timers'); +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { Routes, GatewayOpcodes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors'); +const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel'); +const { GuildMember } = require('../structures/GuildMember'); +const { Role } = require('../structures/Role'); +const Events = require('../util/Events'); +const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField'); +const Partials = require('../util/Partials'); + +/** + * Manages API methods for GuildMembers and stores their cache. + * @extends {CachedManager} + */ +class GuildMemberManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, GuildMember, iterable); + + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, GuildMember>} + * @name GuildMemberManager#cache + */ + + _add(data, cache = true) { + return super._add(data, cache, { id: data.user.id, extras: [this.guild] }); + } + + /** + * Data that resolves to give a GuildMember object. This can be: + * * A GuildMember object + * * A User resolvable + * @typedef {GuildMember|UserResolvable} GuildMemberResolvable + */ + + /** + * Resolves a {@link GuildMemberResolvable} to a {@link GuildMember} object. + * @param {GuildMemberResolvable} member The user that is part of the guild + * @returns {?GuildMember} + */ + resolve(member) { + const memberResolvable = super.resolve(member); + if (memberResolvable) return memberResolvable; + const userResolvable = this.client.users.resolveId(member); + if (userResolvable) return super.resolve(userResolvable); + return null; + } + + /** + * Resolves a {@link GuildMemberResolvable} to a member id. + * @param {GuildMemberResolvable} member The user that is part of the guild + * @returns {?Snowflake} + */ + resolveId(member) { + const memberResolvable = super.resolveId(member); + if (memberResolvable) return memberResolvable; + const userResolvable = this.client.users.resolveId(member); + return this.cache.has(userResolvable) ? userResolvable : null; + } + + /** + * Options used to add a user to a guild using OAuth2. + * @typedef {Object} AddGuildMemberOptions + * @property {string} accessToken An OAuth2 access token for the user with the {@link OAuth2Scopes.GuildsJoin} + * scope granted to the bot's application + * @property {string} [nick] The nickname to give to the member + * <info>This property requires the {@link PermissionFlagsBits.ManageNicknames} permission.</info> + * @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles to add to the member + * <info>This property requires the {@link PermissionFlagsBits.ManageRoles} permission.</info> + * @property {boolean} [mute] Whether the member should be muted + * <info>This property requires the {@link PermissionFlagsBits.MuteMembers} permission.</info> + * @property {boolean} [deaf] Whether the member should be deafened + * <info>This property requires the {@link PermissionFlagsBits.MuteMembers} permission.</info> + * @property {boolean} [force] Whether to skip the cache check and request the API directly + * @property {boolean} [fetchWhenExisting=true] Whether to fetch the user if not cached and already a member + */ + + /** + * Adds a user to the guild using OAuth2. + * <info>This method requires the {@link PermissionFlagsBits.CreateInstantInvite} permission. + * @param {UserResolvable} user The user to add to the guild + * @param {AddGuildMemberOptions} options Options for adding the user to the guild + * @returns {Promise<GuildMember|null>} + */ + async add(user, options) { + const userId = this.client.users.resolveId(user); + if (!userId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'user', 'UserResolvable'); + if (!options.force) { + const cachedUser = this.cache.get(userId); + if (cachedUser) return cachedUser; + } + const resolvedOptions = { + access_token: options.accessToken, + nick: options.nick, + mute: options.mute, + deaf: options.deaf, + }; + if (options.roles) { + if (!Array.isArray(options.roles) && !(options.roles instanceof Collection)) { + throw new DiscordjsTypeError( + ErrorCodes.InvalidType, + 'options.roles', + 'Array or Collection of Roles or Snowflakes', + true, + ); + } + const resolvedRoles = []; + for (const role of options.roles.values()) { + const resolvedRole = this.guild.roles.resolveId(role); + if (!resolvedRole) { + throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'options.roles', role); + } + resolvedRoles.push(resolvedRole); + } + resolvedOptions.roles = resolvedRoles; + } + const data = await this.client.rest.put(Routes.guildMember(this.guild.id, userId), { body: resolvedOptions }); + // Data is an empty Uint8Array if the member is already part of the guild. + return data instanceof Uint8Array + ? options.fetchWhenExisting === false + ? null + : this.fetch(userId) + : this._add(data); + } + + /** + * The client user as a GuildMember of this guild + * @type {?GuildMember} + * @readonly + */ + get me() { + return ( + this.resolve(this.client.user.id) ?? + (this.client.options.partials.includes(Partials.GuildMember) + ? this._add({ user: { id: this.client.user.id } }, true) + : null) + ); + } + + /** + * Options used to fetch a single member from a guild. + * @typedef {BaseFetchOptions} FetchMemberOptions + * @property {UserResolvable} user The user to fetch + */ + + /** + * Options used to fetch multiple members from a guild. + * @typedef {Object} FetchMembersOptions + * @property {UserResolvable|UserResolvable[]} [user] The user(s) to fetch + * @property {?string} [query] Limit fetch to members with similar usernames + * @property {number} [limit=0] Maximum number of members to request + * @property {boolean} [withPresences=false] Whether to include the presences + * @property {number} [time=120e3] Timeout for receipt of members + * @property {?string} [nonce] Nonce for this request (32 characters max - default to base 16 now timestamp) + */ + + /** + * Fetches member(s) from a guild. + * @param {UserResolvable|FetchMemberOptions|FetchMembersOptions} [options] Options for fetching member(s). + * Omitting the parameter or providing `undefined` will fetch all members. + * @returns {Promise<GuildMember|Collection<Snowflake, GuildMember>>} + * @example + * // Fetch all members from a guild + * guild.members.fetch() + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single member + * guild.members.fetch('66564597481480192') + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch a single member without checking cache + * guild.members.fetch({ user, force: true }) + * .then(console.log) + * .catch(console.error) + * @example + * // Fetch a single member without caching + * guild.members.fetch({ user, cache: false }) + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch by an array of users including their presences + * guild.members.fetch({ user: ['66564597481480192', '191615925336670208'], withPresences: true }) + * .then(console.log) + * .catch(console.error); + * @example + * // Fetch by query + * guild.members.fetch({ query: 'hydra', limit: 1 }) + * .then(console.log) + * .catch(console.error); + */ + fetch(options) { + if (!options) return this._fetchMany(); + const { user: users, limit, withPresences, cache, force } = options; + const resolvedUser = this.client.users.resolveId(users ?? options); + if (resolvedUser && !limit && !withPresences) return this._fetchSingle({ user: resolvedUser, cache, force }); + const resolvedUsers = users?.map?.(user => this.client.users.resolveId(user)) ?? resolvedUser ?? undefined; + return this._fetchMany({ ...options, users: resolvedUsers }); + } + + async _fetchSingle({ user, cache, force = false }) { + if (!force) { + const existing = this.cache.get(user); + if (existing && !existing.partial) return existing; + } + + const data = await this.client.rest.get(Routes.guildMember(this.guild.id, user)); + return this._add(data, cache); + } + + _fetchMany({ + limit = 0, + withPresences: presences, + users, + query, + time = 120e3, + nonce = DiscordSnowflake.generate().toString(), + } = {}) { + if (nonce.length > 32) return Promise.reject(new DiscordjsRangeError(ErrorCodes.MemberFetchNonceLength)); + + return new Promise((resolve, reject) => { + if (!query && !users) query = ''; + this.guild.shard.send({ + op: GatewayOpcodes.RequestGuildMembers, + d: { + guild_id: this.guild.id, + presences, + user_ids: users, + query, + nonce, + limit, + }, + }); + const fetchedMembers = new Collection(); + let i = 0; + const handler = (members, _, chunk) => { + if (chunk.nonce !== nonce) return; + timeout.refresh(); + i++; + for (const member of members.values()) { + fetchedMembers.set(member.id, member); + } + if (members.size < 1_000 || (limit && fetchedMembers.size >= limit) || i === chunk.count) { + clearTimeout(timeout); + this.client.removeListener(Events.GuildMembersChunk, handler); + this.client.decrementMaxListeners(); + resolve(users && !Array.isArray(users) && fetchedMembers.size ? fetchedMembers.first() : fetchedMembers); + } + }; + const timeout = setTimeout(() => { + this.client.removeListener(Events.GuildMembersChunk, handler); + this.client.decrementMaxListeners(); + reject(new DiscordjsError(ErrorCodes.GuildMembersTimeout)); + }, time).unref(); + this.client.incrementMaxListeners(); + this.client.on(Events.GuildMembersChunk, handler); + }); + } + + /** + * Fetches the client user as a GuildMember of the guild. + * @param {BaseFetchOptions} [options] The options for fetching the member + * @returns {Promise<GuildMember>} + */ + fetchMe(options) { + return this.fetch({ ...options, user: this.client.user.id }); + } + + /** + * Options used for searching guild members. + * @typedef {Object} GuildSearchMembersOptions + * @property {string} query Filter members whose username or nickname start with this query + * @property {number} [limit] Maximum number of members to search + * @property {boolean} [cache=true] Whether or not to cache the fetched member(s) + */ + + /** + * Searches for members in the guild based on a query. + * @param {GuildSearchMembersOptions} options Options for searching members + * @returns {Promise<Collection<Snowflake, GuildMember>>} + */ + async search({ query, limit, cache = true } = {}) { + const data = await this.client.rest.get(Routes.guildMembersSearch(this.guild.id), { + query: makeURLSearchParams({ query, limit }), + }); + return data.reduce((col, member) => col.set(member.user.id, this._add(member, cache)), new Collection()); + } + + /** + * Options used for listing guild members. + * @typedef {Object} GuildListMembersOptions + * @property {Snowflake} [after] Limit fetching members to those with an id greater than the supplied id + * @property {number} [limit] Maximum number of members to list + * @property {boolean} [cache=true] Whether or not to cache the fetched member(s) + */ + + /** + * Lists up to 1000 members of the guild. + * @param {GuildListMembersOptions} [options] Options for listing members + * @returns {Promise<Collection<Snowflake, GuildMember>>} + */ + async list({ after, limit, cache = true } = {}) { + const query = makeURLSearchParams({ limit, after }); + const data = await this.client.rest.get(Routes.guildMembers(this.guild.id), { query }); + return data.reduce((col, member) => col.set(member.user.id, this._add(member, cache)), new Collection()); + } + + /** + * The data for editing a guild member. + * @typedef {Object} GuildMemberEditOptions + * @property {?string} [nick] The nickname to set for the member + * @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles or role ids to apply + * @property {boolean} [mute] Whether or not the member should be muted + * @property {boolean} [deaf] Whether or not the member should be deafened + * @property {GuildVoiceChannelResolvable|null} [channel] Channel to move the member to + * (if they are connected to voice), or `null` if you want to disconnect them from voice + * @property {DateResolvable|null} [communicationDisabledUntil] The date or timestamp + * for the member's communication to be disabled until. Provide `null` to enable communication again. + * @property {GuildMemberFlagsResolvable} [flags] The flags to set for the member + * @property {string} [reason] Reason for editing this user + */ + + /** + * Edits a member of the guild. + * <info>The user must be a member of the guild</info> + * @param {UserResolvable} user The member to edit + * @param {GuildMemberEditOptions} options The options to provide + * @returns {Promise<GuildMember>} + */ + async edit(user, { reason, ...options }) { + const id = this.client.users.resolveId(user); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'user', 'UserResolvable'); + + if (options.channel) { + options.channel = this.guild.channels.resolve(options.channel); + if (!(options.channel instanceof BaseGuildVoiceChannel)) { + throw new DiscordjsError(ErrorCodes.GuildVoiceChannelResolve); + } + options.channel_id = options.channel.id; + options.channel = undefined; + } else if (options.channel === null) { + options.channel_id = null; + options.channel = undefined; + } + options.roles &&= options.roles.map(role => (role instanceof Role ? role.id : role)); + + if (options.communicationDisabledUntil !== undefined) { + options.communication_disabled_until = + // eslint-disable-next-line eqeqeq + options.communicationDisabledUntil != null + ? new Date(options.communicationDisabledUntil).toISOString() + : options.communicationDisabledUntil; + } + + if (options.flags !== undefined) { + options.flags = GuildMemberFlagsBitField.resolve(options.flags); + } + + let endpoint; + if (id === this.client.user.id) { + const keys = Object.keys(options); + if (keys.length === 1 && keys[0] === 'nick') endpoint = Routes.guildMember(this.guild.id); + else endpoint = Routes.guildMember(this.guild.id, id); + } else { + endpoint = Routes.guildMember(this.guild.id, id); + } + const d = await this.client.rest.patch(endpoint, { body: options, reason }); + + const clone = this.cache.get(id)?._clone(); + clone?._patch(d); + return clone ?? this._add(d, false); + } + + /** + * Options used for pruning guild members. + * <info>It's recommended to set {@link GuildPruneMembersOptions#count options.count} + * to `false` for large guilds.</info> + * @typedef {Object} GuildPruneMembersOptions + * @property {number} [days] Number of days of inactivity required to kick + * @property {boolean} [dry=false] Get the number of users that will be kicked, without actually kicking them + * @property {boolean} [count] Whether or not to return the number of users that have been kicked. + * @property {RoleResolvable[]} [roles] Array of roles to bypass the "...and no roles" constraint when pruning + * @property {string} [reason] Reason for this prune + */ + + /** + * Prunes members from the guild based on how long they have been inactive. + * @param {GuildPruneMembersOptions} [options] Options for pruning + * @returns {Promise<number|null>} The number of members that were/will be kicked + * @example + * // See how many members will be pruned + * guild.members.prune({ dry: true }) + * .then(pruned => console.log(`This will prune ${pruned} people!`)) + * .catch(console.error); + * @example + * // Actually prune the members + * guild.members.prune({ days: 1, reason: 'too many people!' }) + * .then(pruned => console.log(`I just pruned ${pruned} people!`)) + * .catch(console.error); + * @example + * // Include members with a specified role + * guild.members.prune({ days: 7, roles: ['657259391652855808'] }) + * .then(pruned => console.log(`I just pruned ${pruned} people!`)) + * .catch(console.error); + */ + async prune({ days, dry = false, count: compute_prune_count, roles = [], reason } = {}) { + if (typeof days !== 'number') throw new DiscordjsTypeError(ErrorCodes.PruneDaysType); + + const query = { days }; + const resolvedRoles = []; + + for (const role of roles) { + const resolvedRole = this.guild.roles.resolveId(role); + if (!resolvedRole) { + throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array', 'options.roles', role); + } + resolvedRoles.push(resolvedRole); + } + + if (resolvedRoles.length) { + query.include_roles = dry ? resolvedRoles.join(',') : resolvedRoles; + } + + const endpoint = Routes.guildPrune(this.guild.id); + + const { pruned } = await (dry + ? this.client.rest.get(endpoint, { query: makeURLSearchParams(query), reason }) + : this.client.rest.post(endpoint, { body: { ...query, compute_prune_count }, reason })); + + return pruned; + } + + /** + * Kicks a user from the guild. + * <info>The user must be a member of the guild</info> + * @param {UserResolvable} user The member to kick + * @param {string} [reason] Reason for kicking + * @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible. + * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot + * be resolved, the user's id will be the result. + * @example + * // Kick a user by id (or with a user/guild member object) + * guild.members.kick('84484653687267328') + * .then(kickInfo => console.log(`Kicked ${kickInfo.user?.tag ?? kickInfo.tag ?? kickInfo}`)) + * .catch(console.error); + */ + async kick(user, reason) { + const id = this.client.users.resolveId(user); + if (!id) return Promise.reject(new DiscordjsTypeError(ErrorCodes.InvalidType, 'user', 'UserResolvable')); + + await this.client.rest.delete(Routes.guildMember(this.guild.id, id), { reason }); + + return this.resolve(user) ?? this.client.users.resolve(user) ?? id; + } + + /** + * Bans a user from the guild. + * @param {UserResolvable} user The user to ban + * @param {BanOptions} [options] Options for the ban + * @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible. + * If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot + * be resolved, the user id will be the result. + * Internally calls the GuildBanManager#create method. + * @example + * // Ban a user by id (or with a user/guild member object) + * guild.members.ban('84484653687267328') + * .then(banInfo => console.log(`Banned ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`)) + * .catch(console.error); + */ + ban(user, options) { + return this.guild.bans.create(user, options); + } + + /** + * Unbans a user from the guild. Internally calls the {@link GuildBanManager#remove} method. + * @param {UserResolvable} user The user to unban + * @param {string} [reason] Reason for unbanning user + * @returns {Promise<?User>} The user that was unbanned + * @example + * // Unban a user by id (or with a user/guild member object) + * guild.members.unban('84484653687267328') + * .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`)) + * .catch(console.error); + */ + unban(user, reason) { + return this.guild.bans.remove(user, reason); + } + + /** + * Options used for adding or removing a role from a member. + * @typedef {Object} AddOrRemoveGuildMemberRoleOptions + * @property {GuildMemberResolvable} user The user to add/remove the role from + * @property {RoleResolvable} role The role to add/remove + * @property {string} [reason] Reason for adding/removing the role + */ + + /** + * Adds a role to a member. + * @param {AddOrRemoveGuildMemberRoleOptions} options Options for adding the role + * @returns {Promise<GuildMember|User|Snowflake>} + */ + async addRole(options) { + const { user, role, reason } = options; + const userId = this.guild.members.resolveId(user); + const roleId = this.guild.roles.resolveId(role); + await this.client.rest.put(Routes.guildMemberRole(this.guild.id, userId, roleId), { reason }); + + return this.resolve(user) ?? this.client.users.resolve(user) ?? userId; + } + + /** + * Removes a role from a member. + * @param {AddOrRemoveGuildMemberRoleOptions} options Options for removing the role + * @returns {Promise<GuildMember|User|Snowflake>} + */ + async removeRole(options) { + const { user, role, reason } = options; + const userId = this.guild.members.resolveId(user); + const roleId = this.guild.roles.resolveId(role); + await this.client.rest.delete(Routes.guildMemberRole(this.guild.id, userId, roleId), { reason }); + + return this.resolve(user) ?? this.client.users.resolve(user) ?? userId; + } +} + +module.exports = GuildMemberManager; diff --git a/node_modules/discord.js/src/managers/GuildMemberRoleManager.js b/node_modules/discord.js/src/managers/GuildMemberRoleManager.js new file mode 100644 index 0000000..e530268 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildMemberRoleManager.js @@ -0,0 +1,204 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { Routes } = require('discord-api-types/v10'); +const DataManager = require('./DataManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const { Role } = require('../structures/Role'); + +/** + * Manages API methods for roles of a GuildMember and stores their cache. + * @extends {DataManager} + */ +class GuildMemberRoleManager extends DataManager { + constructor(member) { + super(member.client, Role); + + /** + * The GuildMember this manager belongs to + * @type {GuildMember} + */ + this.member = member; + + /** + * The Guild this manager belongs to + * @type {Guild} + */ + this.guild = member.guild; + } + + /** + * The roles of this member + * @type {Collection<Snowflake, Role>} + * @readonly + */ + get cache() { + const everyone = this.guild.roles.everyone; + return this.guild.roles.cache.filter(role => this.member._roles.includes(role.id)).set(everyone.id, everyone); + } + + /** + * The role of the member used to hoist them in a separate category in the users list + * @type {?Role} + * @readonly + */ + get hoist() { + const hoistedRoles = this.cache.filter(role => role.hoist); + if (!hoistedRoles.size) return null; + return hoistedRoles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev)); + } + + /** + * The role of the member used to set their role icon + * @type {?Role} + * @readonly + */ + get icon() { + const iconRoles = this.cache.filter(role => role.icon || role.unicodeEmoji); + if (!iconRoles.size) return null; + return iconRoles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev)); + } + + /** + * The role of the member used to set their color + * @type {?Role} + * @readonly + */ + get color() { + const coloredRoles = this.cache.filter(role => role.color); + if (!coloredRoles.size) return null; + return coloredRoles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev)); + } + + /** + * The role of the member with the highest position + * @type {Role} + * @readonly + */ + get highest() { + return this.cache.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev), this.cache.first()); + } + + /** + * The premium subscriber role of the guild, if present on the member + * @type {?Role} + * @readonly + */ + get premiumSubscriberRole() { + return this.cache.find(role => role.tags?.premiumSubscriberRole) ?? null; + } + + /** + * The managed role this member created when joining the guild, if any + * <info>Only ever available on bots</info> + * @type {?Role} + * @readonly + */ + get botRole() { + if (!this.member.user.bot) return null; + return this.cache.find(role => role.tags?.botId === this.member.user.id) ?? null; + } + + /** + * Adds a role (or multiple roles) to the member. + * @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to add + * @param {string} [reason] Reason for adding the role(s) + * @returns {Promise<GuildMember>} + */ + async add(roleOrRoles, reason) { + if (roleOrRoles instanceof Collection || Array.isArray(roleOrRoles)) { + const resolvedRoles = []; + for (const role of roleOrRoles.values()) { + const resolvedRole = this.guild.roles.resolveId(role); + if (!resolvedRole) { + throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role); + } + resolvedRoles.push(resolvedRole); + } + + const newRoles = [...new Set(resolvedRoles.concat(...this.cache.keys()))]; + return this.set(newRoles, reason); + } else { + roleOrRoles = this.guild.roles.resolveId(roleOrRoles); + if (roleOrRoles === null) { + throw new DiscordjsTypeError( + ErrorCodes.InvalidType, + 'roles', + 'Role, Snowflake or Array or Collection of Roles or Snowflakes', + ); + } + + await this.client.rest.put(Routes.guildMemberRole(this.guild.id, this.member.id, roleOrRoles), { reason }); + + const clone = this.member._clone(); + clone._roles = [...this.cache.keys(), roleOrRoles]; + return clone; + } + } + + /** + * Removes a role (or multiple roles) from the member. + * @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to remove + * @param {string} [reason] Reason for removing the role(s) + * @returns {Promise<GuildMember>} + */ + async remove(roleOrRoles, reason) { + if (roleOrRoles instanceof Collection || Array.isArray(roleOrRoles)) { + const resolvedRoles = []; + for (const role of roleOrRoles.values()) { + const resolvedRole = this.guild.roles.resolveId(role); + if (!resolvedRole) { + throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role); + } + resolvedRoles.push(resolvedRole); + } + + const newRoles = this.cache.filter(role => !resolvedRoles.includes(role.id)); + return this.set(newRoles, reason); + } else { + roleOrRoles = this.guild.roles.resolveId(roleOrRoles); + if (roleOrRoles === null) { + throw new DiscordjsTypeError( + ErrorCodes.InvalidType, + 'roles', + 'Role, Snowflake or Array or Collection of Roles or Snowflakes', + ); + } + + await this.client.rest.delete(Routes.guildMemberRole(this.guild.id, this.member.id, roleOrRoles), { reason }); + + const clone = this.member._clone(); + const newRoles = this.cache.filter(role => role.id !== roleOrRoles); + clone._roles = [...newRoles.keys()]; + return clone; + } + } + + /** + * Sets the roles applied to the member. + * @param {Collection<Snowflake, Role>|RoleResolvable[]} roles The roles or role ids to apply + * @param {string} [reason] Reason for applying the roles + * @returns {Promise<GuildMember>} + * @example + * // Set the member's roles to a single role + * guildMember.roles.set(['391156570408615936']) + * .then(console.log) + * .catch(console.error); + * @example + * // Remove all the roles from a member + * guildMember.roles.set([]) + * .then(member => console.log(`Member roles is now of ${member.roles.cache.size} size`)) + * .catch(console.error); + */ + set(roles, reason) { + return this.member.edit({ roles, reason }); + } + + clone() { + const clone = new this.constructor(this.member); + clone.member._roles = [...this.cache.keys()]; + return clone; + } +} + +module.exports = GuildMemberRoleManager; diff --git a/node_modules/discord.js/src/managers/GuildMessageManager.js b/node_modules/discord.js/src/managers/GuildMessageManager.js new file mode 100644 index 0000000..7a93c99 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildMessageManager.js @@ -0,0 +1,17 @@ +'use strict'; + +const MessageManager = require('./MessageManager'); + +/** + * Manages API methods for messages in a guild and holds their cache. + * @extends {MessageManager} + */ +class GuildMessageManager extends MessageManager { + /** + * The channel that the messages belong to + * @name GuildMessageManager#channel + * @type {GuildTextBasedChannel} + */ +} + +module.exports = GuildMessageManager; diff --git a/node_modules/discord.js/src/managers/GuildScheduledEventManager.js b/node_modules/discord.js/src/managers/GuildScheduledEventManager.js new file mode 100644 index 0000000..9071b60 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildScheduledEventManager.js @@ -0,0 +1,297 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { GuildScheduledEventEntityType, Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors'); +const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent'); +const DataResolver = require('../util/DataResolver'); + +/** + * Manages API methods for GuildScheduledEvents and stores their cache. + * @extends {CachedManager} + */ +class GuildScheduledEventManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, GuildScheduledEvent, iterable); + + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this manager + * @type {Collection<Snowflake, GuildScheduledEvent>} + * @name GuildScheduledEventManager#cache + */ + + /** + * Data that resolves to give a GuildScheduledEvent object. This can be: + * * A Snowflake + * * A GuildScheduledEvent object + * @typedef {Snowflake|GuildScheduledEvent} GuildScheduledEventResolvable + */ + + /** + * Options used to create a guild scheduled event. + * @typedef {Object} GuildScheduledEventCreateOptions + * @property {string} name The name of the guild scheduled event + * @property {DateResolvable} scheduledStartTime The time to schedule the event at + * @property {DateResolvable} [scheduledEndTime] The time to end the event at + * <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn> + * @property {GuildScheduledEventPrivacyLevel} privacyLevel The privacy level of the guild scheduled event + * @property {GuildScheduledEventEntityType} entityType The scheduled entity type of the event + * @property {string} [description] The description of the guild scheduled event + * @property {GuildVoiceChannelResolvable} [channel] The channel of the guild scheduled event + * <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.StageInstance} or + * {@link GuildScheduledEventEntityType.Voice}</warn> + * @property {GuildScheduledEventEntityMetadataOptions} [entityMetadata] The entity metadata of the + * guild scheduled event + * <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn> + * @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event + * @property {string} [reason] The reason for creating the guild scheduled event + */ + + /** + * Options used to set entity metadata of a guild scheduled event. + * @typedef {Object} GuildScheduledEventEntityMetadataOptions + * @property {string} [location] The location of the guild scheduled event + * <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn> + */ + + /** + * Creates a new guild scheduled event. + * @param {GuildScheduledEventCreateOptions} options Options for creating the guild scheduled event + * @returns {Promise<GuildScheduledEvent>} + */ + async create(options) { + if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); + let { + privacyLevel, + entityType, + channel, + name, + scheduledStartTime, + description, + scheduledEndTime, + entityMetadata, + reason, + image, + } = options; + + let entity_metadata, channel_id; + if (entityType === GuildScheduledEventEntityType.External) { + channel_id = channel === undefined ? channel : null; + entity_metadata = { location: entityMetadata?.location }; + } else { + channel_id = this.guild.channels.resolveId(channel); + if (!channel_id) throw new DiscordjsError(ErrorCodes.GuildVoiceChannelResolve); + entity_metadata = entityMetadata === undefined ? entityMetadata : null; + } + + const data = await this.client.rest.post(Routes.guildScheduledEvents(this.guild.id), { + body: { + channel_id, + name, + privacy_level: privacyLevel, + scheduled_start_time: new Date(scheduledStartTime).toISOString(), + scheduled_end_time: scheduledEndTime ? new Date(scheduledEndTime).toISOString() : scheduledEndTime, + description, + entity_type: entityType, + entity_metadata, + image: image && (await DataResolver.resolveImage(image)), + }, + reason, + }); + + return this._add(data); + } + + /** + * Options used to fetch a single guild scheduled event from a guild. + * @typedef {BaseFetchOptions} FetchGuildScheduledEventOptions + * @property {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to fetch + * @property {boolean} [withUserCount=true] Whether to fetch the number of users subscribed to the scheduled event + */ + + /** + * Options used to fetch multiple guild scheduled events from a guild. + * @typedef {Object} FetchGuildScheduledEventsOptions + * @property {boolean} [cache] Whether or not to cache the fetched guild scheduled events + * @property {boolean} [withUserCount=true] Whether to fetch the number of users subscribed to each scheduled event + * should be returned + */ + + /** + * Obtains one or more guild scheduled events from Discord, or the guild cache if it's already available. + * @param {GuildScheduledEventResolvable|FetchGuildScheduledEventOptions|FetchGuildScheduledEventsOptions} [options] + * The id of the guild scheduled event or options + * @returns {Promise<GuildScheduledEvent|Collection<Snowflake, GuildScheduledEvent>>} + */ + async fetch(options = {}) { + const id = this.resolveId(options.guildScheduledEvent ?? options); + + if (id) { + if (!options.force) { + const existing = this.cache.get(id); + if (existing) return existing; + } + + const data = await this.client.rest.get(Routes.guildScheduledEvent(this.guild.id, id), { + query: makeURLSearchParams({ with_user_count: options.withUserCount ?? true }), + }); + return this._add(data, options.cache); + } + + const data = await this.client.rest.get(Routes.guildScheduledEvents(this.guild.id), { + query: makeURLSearchParams({ with_user_count: options.withUserCount ?? true }), + }); + + return data.reduce( + (coll, rawGuildScheduledEventData) => + coll.set( + rawGuildScheduledEventData.id, + this.guild.scheduledEvents._add(rawGuildScheduledEventData, options.cache), + ), + new Collection(), + ); + } + + /** + * Options used to edit a guild scheduled event. + * @typedef {Object} GuildScheduledEventEditOptions + * @property {string} [name] The name of the guild scheduled event + * @property {DateResolvable} [scheduledStartTime] The time to schedule the event at + * @property {DateResolvable} [scheduledEndTime] The time to end the event at + * @property {GuildScheduledEventPrivacyLevel} [privacyLevel] The privacy level of the guild scheduled event + * @property {GuildScheduledEventEntityType} [entityType] The scheduled entity type of the event + * @property {string} [description] The description of the guild scheduled event + * @property {?GuildVoiceChannelResolvable} [channel] The channel of the guild scheduled event + * @property {GuildScheduledEventStatus} [status] The status of the guild scheduled event + * @property {GuildScheduledEventEntityMetadataOptions} [entityMetadata] The entity metadata of the + * guild scheduled event + * <warn>This can be modified only if `entityType` of the `GuildScheduledEvent` to be edited is + * {@link GuildScheduledEventEntityType.External}</warn> + * @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event + * @property {string} [reason] The reason for editing the guild scheduled event + */ + + /** + * Edits a guild scheduled event. + * @param {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to edit + * @param {GuildScheduledEventEditOptions} options Options to edit the guild scheduled event + * @returns {Promise<GuildScheduledEvent>} + */ + async edit(guildScheduledEvent, options) { + const guildScheduledEventId = this.resolveId(guildScheduledEvent); + if (!guildScheduledEventId) throw new DiscordjsError(ErrorCodes.GuildScheduledEventResolve); + + if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); + let { + privacyLevel, + entityType, + channel, + status, + name, + scheduledStartTime, + description, + scheduledEndTime, + entityMetadata, + reason, + image, + } = options; + + let entity_metadata; + if (entityMetadata) { + entity_metadata = { + location: entityMetadata.location, + }; + } + + const data = await this.client.rest.patch(Routes.guildScheduledEvent(this.guild.id, guildScheduledEventId), { + body: { + channel_id: channel === undefined ? channel : this.guild.channels.resolveId(channel), + name, + privacy_level: privacyLevel, + scheduled_start_time: scheduledStartTime ? new Date(scheduledStartTime).toISOString() : undefined, + scheduled_end_time: scheduledEndTime ? new Date(scheduledEndTime).toISOString() : scheduledEndTime, + description, + entity_type: entityType, + status, + image: image && (await DataResolver.resolveImage(image)), + entity_metadata, + }, + reason, + }); + + return this._add(data); + } + + /** + * Deletes a guild scheduled event. + * @param {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to delete + * @returns {Promise<void>} + */ + async delete(guildScheduledEvent) { + const guildScheduledEventId = this.resolveId(guildScheduledEvent); + if (!guildScheduledEventId) throw new DiscordjsError(ErrorCodes.GuildScheduledEventResolve); + + await this.client.rest.delete(Routes.guildScheduledEvent(this.guild.id, guildScheduledEventId)); + } + + /** + * Options used to fetch subscribers of a guild scheduled event + * @typedef {Object} FetchGuildScheduledEventSubscribersOptions + * @property {number} [limit] The maximum numbers of users to fetch + * @property {boolean} [withMember] Whether to fetch guild member data of the users + * @property {Snowflake} [before] Consider only users before this user id + * @property {Snowflake} [after] Consider only users after this user id + * <warn>If both `before` and `after` are provided, only `before` is respected</warn> + */ + + /** + * Represents a subscriber of a {@link GuildScheduledEvent} + * @typedef {Object} GuildScheduledEventUser + * @property {Snowflake} guildScheduledEventId The id of the guild scheduled event which the user subscribed to + * @property {User} user The user that subscribed to the guild scheduled event + * @property {?GuildMember} member The guild member associated with the user, if any + */ + + /** + * Fetches subscribers of a guild scheduled event. + * @param {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to fetch subscribers of + * @param {FetchGuildScheduledEventSubscribersOptions} [options={}] Options for fetching the subscribers + * @returns {Promise<Collection<Snowflake, GuildScheduledEventUser>>} + */ + async fetchSubscribers(guildScheduledEvent, options = {}) { + const guildScheduledEventId = this.resolveId(guildScheduledEvent); + if (!guildScheduledEventId) throw new DiscordjsError(ErrorCodes.GuildScheduledEventResolve); + + const query = makeURLSearchParams({ + limit: options.limit, + with_member: options.withMember, + before: options.before, + after: options.after, + }); + + const data = await this.client.rest.get(Routes.guildScheduledEventUsers(this.guild.id, guildScheduledEventId), { + query, + }); + + return data.reduce( + (coll, rawData) => + coll.set(rawData.user.id, { + guildScheduledEventId: rawData.guild_scheduled_event_id, + user: this.client.users._add(rawData.user), + member: rawData.member ? this.guild.members._add({ ...rawData.member, user: rawData.user }) : null, + }), + new Collection(), + ); + } +} + +module.exports = GuildScheduledEventManager; diff --git a/node_modules/discord.js/src/managers/GuildStickerManager.js b/node_modules/discord.js/src/managers/GuildStickerManager.js new file mode 100644 index 0000000..a4974ec --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildStickerManager.js @@ -0,0 +1,182 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const MessagePayload = require('../structures/MessagePayload'); +const { Sticker } = require('../structures/Sticker'); + +/** + * Manages API methods for Guild Stickers and stores their cache. + * @extends {CachedManager} + */ +class GuildStickerManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, Sticker, iterable); + + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of Guild Stickers + * @type {Collection<Snowflake, Sticker>} + * @name GuildStickerManager#cache + */ + + _add(data, cache) { + return super._add(data, cache, { extras: [this.guild] }); + } + + /** + * Options used to create a guild sticker. + * @typedef {Object} GuildStickerCreateOptions + * @property {AttachmentPayload|BufferResolvable|Stream} file The file for the sticker + * @property {string} name The name for the sticker + * @property {string} tags The Discord name of a unicode emoji representing the sticker's expression + * @property {?string} [description] The description for the sticker + * @property {string} [reason] Reason for creating the sticker + */ + + /** + * Creates a new custom sticker in the guild. + * @param {GuildStickerCreateOptions} options Options for creating a guild sticker + * @returns {Promise<Sticker>} The created sticker + * @example + * // Create a new sticker from a URL + * guild.stickers.create({ file: 'https://i.imgur.com/w3duR07.png', name: 'rip', tags: 'headstone' }) + * .then(sticker => console.log(`Created new sticker with name ${sticker.name}!`)) + * .catch(console.error); + * @example + * // Create a new sticker from a file on your computer + * guild.stickers.create({ file: './memes/banana.png', name: 'banana', tags: 'banana' }) + * .then(sticker => console.log(`Created new sticker with name ${sticker.name}!`)) + * .catch(console.error); + */ + async create({ file, name, tags, description, reason } = {}) { + const resolvedFile = await MessagePayload.resolveFile(file); + if (!resolvedFile) throw new DiscordjsTypeError(ErrorCodes.ReqResourceType); + file = { ...resolvedFile, key: 'file' }; + + const body = { name, tags, description: description ?? '' }; + + const sticker = await this.client.rest.post(Routes.guildStickers(this.guild.id), { + appendToFormData: true, + body, + files: [file], + reason, + }); + return this.client.actions.GuildStickerCreate.handle(this.guild, sticker).sticker; + } + + /** + * Data that resolves to give a Sticker object. This can be: + * * A Sticker object + * * A Snowflake + * @typedef {Sticker|Snowflake} StickerResolvable + */ + + /** + * Resolves a StickerResolvable to a Sticker object. + * @method resolve + * @memberof GuildStickerManager + * @instance + * @param {StickerResolvable} sticker The Sticker resolvable to identify + * @returns {?Sticker} + */ + + /** + * Resolves a StickerResolvable to a Sticker id string. + * @method resolveId + * @memberof GuildStickerManager + * @instance + * @param {StickerResolvable} sticker The Sticker resolvable to identify + * @returns {?Snowflake} + */ + + /** + * Edits a sticker. + * @param {StickerResolvable} sticker The sticker to edit + * @param {GuildStickerEditOptions} [options={}] The new data for the sticker + * @returns {Promise<Sticker>} + */ + async edit(sticker, options = {}) { + const stickerId = this.resolveId(sticker); + if (!stickerId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'sticker', 'StickerResolvable'); + + const d = await this.client.rest.patch(Routes.guildSticker(this.guild.id, stickerId), { + body: options, + reason: options.reason, + }); + + const existing = this.cache.get(stickerId); + if (existing) { + const clone = existing._clone(); + clone._patch(d); + return clone; + } + return this._add(d); + } + + /** + * Deletes a sticker. + * @param {StickerResolvable} sticker The sticker to delete + * @param {string} [reason] Reason for deleting this sticker + * @returns {Promise<void>} + */ + async delete(sticker, reason) { + sticker = this.resolveId(sticker); + if (!sticker) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'sticker', 'StickerResolvable'); + + await this.client.rest.delete(Routes.guildSticker(this.guild.id, sticker), { reason }); + } + + /** + * Obtains one or more stickers from Discord, or the sticker cache if they're already available. + * @param {Snowflake} [id] The Sticker's id + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise<Sticker|Collection<Snowflake, Sticker>>} + * @example + * // Fetch all stickers from the guild + * message.guild.stickers.fetch() + * .then(stickers => console.log(`There are ${stickers.size} stickers.`)) + * .catch(console.error); + * @example + * // Fetch a single sticker + * message.guild.stickers.fetch('222078108977594368') + * .then(sticker => console.log(`The sticker name is: ${sticker.name}`)) + * .catch(console.error); + */ + async fetch(id, { cache = true, force = false } = {}) { + if (id) { + if (!force) { + const existing = this.cache.get(id); + if (existing) return existing; + } + const sticker = await this.client.rest.get(Routes.guildSticker(this.guild.id, id)); + return this._add(sticker, cache); + } + + const data = await this.client.rest.get(Routes.guildStickers(this.guild.id)); + return new Collection(data.map(sticker => [sticker.id, this._add(sticker, cache)])); + } + + /** + * Fetches the user who uploaded this sticker, if this is a guild sticker. + * @param {StickerResolvable} sticker The sticker to fetch the user for + * @returns {Promise<?User>} + */ + async fetchUser(sticker) { + sticker = this.resolve(sticker); + if (!sticker) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'sticker', 'StickerResolvable'); + const data = await this.client.rest.get(Routes.guildSticker(this.guild.id, sticker.id)); + sticker._patch(data); + return sticker.user; + } +} + +module.exports = GuildStickerManager; diff --git a/node_modules/discord.js/src/managers/GuildTextThreadManager.js b/node_modules/discord.js/src/managers/GuildTextThreadManager.js new file mode 100644 index 0000000..5591845 --- /dev/null +++ b/node_modules/discord.js/src/managers/GuildTextThreadManager.js @@ -0,0 +1,91 @@ +'use strict'; + +const { ChannelType, Routes } = require('discord-api-types/v10'); +const ThreadManager = require('./ThreadManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); + +/** + * Manages API methods for {@link ThreadChannel} objects and stores their cache. + * @extends {ThreadManager} + */ +class GuildTextThreadManager extends ThreadManager { + /** + * The channel this Manager belongs to + * @name GuildTextThreadManager#channel + * @type {TextChannel|NewsChannel} + */ + + /** + * Options for creating a thread. <warn>Only one of `startMessage` or `type` can be defined.</warn> + * @typedef {StartThreadOptions} ThreadCreateOptions + * @property {MessageResolvable} [startMessage] The message to start a thread from. + * <warn>If this is defined, then the `type` of thread gets inferred automatically and cannot be changed.</warn> + * @property {ThreadChannelTypes} [type] The type of thread to create. + * Defaults to {@link ChannelType.PublicThread} if created in a {@link TextChannel} + * <warn>When creating threads in a {@link NewsChannel}, this is ignored and is always + * {@link ChannelType.AnnouncementThread}</warn> + * @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread + * <info>Can only be set when type will be {@link ChannelType.PrivateThread}</info> + */ + + /** + * Creates a new thread in the channel. + * @param {ThreadCreateOptions} [options] Options to create a new thread + * @returns {Promise<ThreadChannel>} + * @example + * // Create a new public thread + * channel.threads + * .create({ + * name: 'food-talk', + * autoArchiveDuration: ThreadAutoArchiveDuration.OneHour, + * reason: 'Needed a separate thread for food', + * }) + * .then(threadChannel => console.log(threadChannel)) + * .catch(console.error); + * @example + * // Create a new private thread + * channel.threads + * .create({ + * name: 'mod-talk', + * autoArchiveDuration: ThreadAutoArchiveDuration.OneHour, + * type: ChannelType.PrivateThread, + * reason: 'Needed a separate thread for moderation', + * }) + * .then(threadChannel => console.log(threadChannel)) + * .catch(console.error); + */ + async create({ + name, + autoArchiveDuration = this.channel.defaultAutoArchiveDuration, + startMessage, + type, + invitable, + reason, + rateLimitPerUser, + } = {}) { + let resolvedType = + this.channel.type === ChannelType.GuildAnnouncement ? ChannelType.AnnouncementThread : ChannelType.PublicThread; + let startMessageId; + if (startMessage) { + startMessageId = this.channel.messages.resolveId(startMessage); + if (!startMessageId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'startMessage', 'MessageResolvable'); + } else if (this.channel.type !== ChannelType.GuildAnnouncement) { + resolvedType = type ?? resolvedType; + } + + const data = await this.client.rest.post(Routes.threads(this.channel.id, startMessageId), { + body: { + name, + auto_archive_duration: autoArchiveDuration, + type: resolvedType, + invitable: resolvedType === ChannelType.PrivateThread ? invitable : undefined, + rate_limit_per_user: rateLimitPerUser, + }, + reason, + }); + + return this.client.actions.ThreadCreate.handle(data).thread; + } +} + +module.exports = GuildTextThreadManager; diff --git a/node_modules/discord.js/src/managers/MessageManager.js b/node_modules/discord.js/src/managers/MessageManager.js new file mode 100644 index 0000000..6fb95eb --- /dev/null +++ b/node_modules/discord.js/src/managers/MessageManager.js @@ -0,0 +1,263 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const { Message } = require('../structures/Message'); +const MessagePayload = require('../structures/MessagePayload'); +const { MakeCacheOverrideSymbol } = require('../util/Symbols'); +const { resolvePartialEmoji } = require('../util/Util'); + +/** + * Manages API methods for Messages and holds their cache. + * @extends {CachedManager} + * @abstract + */ +class MessageManager extends CachedManager { + static [MakeCacheOverrideSymbol] = MessageManager; + + constructor(channel, iterable) { + super(channel.client, Message, iterable); + + /** + * The channel that the messages belong to + * @type {TextBasedChannels} + */ + this.channel = channel; + } + + /** + * The cache of Messages + * @type {Collection<Snowflake, Message>} + * @name MessageManager#cache + */ + + _add(data, cache) { + return super._add(data, cache); + } + + /** + * Data that can be resolved to a Message object. This can be: + * * A Message + * * A Snowflake + * @typedef {Message|Snowflake} MessageResolvable + */ + + /** + * Options used to fetch a message. + * @typedef {BaseFetchOptions} FetchMessageOptions + * @property {MessageResolvable} message The message to fetch + */ + + /** + * Options used to fetch multiple messages. + * <info>The `before`, `after`, and `around` parameters are mutually exclusive.</info> + * @typedef {Object} FetchMessagesOptions + * @property {number} [limit] The maximum number of messages to return + * @property {Snowflake} [before] Consider only messages before this id + * @property {Snowflake} [after] Consider only messages after this id + * @property {Snowflake} [around] Consider only messages around this id + * @property {boolean} [cache] Whether to cache the fetched messages + */ + + /** + * Fetches message(s) from a channel. + * <info>The returned Collection does not contain reaction users of the messages if they were not cached. + * Those need to be fetched separately in such a case.</info> + * @param {MessageResolvable|FetchMessageOptions|FetchMessagesOptions} [options] Options for fetching message(s) + * @returns {Promise<Message|Collection<Snowflake, Message>>} + * @example + * // Fetch a message + * channel.messages.fetch('99539446449315840') + * .then(message => console.log(message.content)) + * .catch(console.error); + * @example + * // Fetch a maximum of 10 messages without caching + * channel.messages.fetch({ limit: 10, cache: false }) + * .then(messages => console.log(`Received ${messages.size} messages`)) + * .catch(console.error); + * @example + * // Fetch a maximum of 10 messages without caching around a message id + * channel.messages.fetch({ limit: 10, cache: false, around: '99539446449315840' }) + * .then(messages => console.log(`Received ${messages.size} messages`)) + * .catch(console.error); + * @example + * // Fetch messages and filter by a user id + * channel.messages.fetch() + * .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`)) + * .catch(console.error); + */ + fetch(options) { + if (!options) return this._fetchMany(); + const { message, cache, force } = options; + const resolvedMessage = this.resolveId(message ?? options); + if (resolvedMessage) return this._fetchSingle({ message: resolvedMessage, cache, force }); + return this._fetchMany(options); + } + + async _fetchSingle({ message, cache, force = false }) { + if (!force) { + const existing = this.cache.get(message); + if (existing && !existing.partial) return existing; + } + + const data = await this.client.rest.get(Routes.channelMessage(this.channel.id, message)); + return this._add(data, cache); + } + + async _fetchMany(options = {}) { + const data = await this.client.rest.get(Routes.channelMessages(this.channel.id), { + query: makeURLSearchParams(options), + }); + + return data.reduce((_data, message) => _data.set(message.id, this._add(message, options.cache)), new Collection()); + } + + /** + * Fetches the pinned messages of this channel and returns a collection of them. + * <info>The returned Collection does not contain any reaction data of the messages. + * Those need to be fetched separately.</info> + * @param {boolean} [cache=true] Whether to cache the message(s) + * @returns {Promise<Collection<Snowflake, Message>>} + * @example + * // Get pinned messages + * channel.messages.fetchPinned() + * .then(messages => console.log(`Received ${messages.size} messages`)) + * .catch(console.error); + */ + async fetchPinned(cache = true) { + const data = await this.client.rest.get(Routes.channelPins(this.channel.id)); + const messages = new Collection(); + for (const message of data) messages.set(message.id, this._add(message, cache)); + return messages; + } + + /** + * Resolves a {@link MessageResolvable} to a {@link Message} object. + * @method resolve + * @memberof MessageManager + * @instance + * @param {MessageResolvable} message The message resolvable to resolve + * @returns {?Message} + */ + + /** + * Resolves a {@link MessageResolvable} to a {@link Message} id. + * @method resolveId + * @memberof MessageManager + * @instance + * @param {MessageResolvable} message The message resolvable to resolve + * @returns {?Snowflake} + */ + + /** + * Options that can be passed to edit a message. + * @typedef {BaseMessageOptions} MessageEditOptions + * @property {AttachmentPayload[]} [attachments] An array of attachments to keep, + * all attachments will be kept if omitted + * @property {MessageFlags} [flags] Which flags to set for the message + * <info>Only the {@link MessageFlags.SuppressEmbeds} flag can be modified.</info> + */ + + /** + * Edits a message, even if it's not cached. + * @param {MessageResolvable} message The message to edit + * @param {string|MessageEditOptions|MessagePayload} options The options to edit the message + * @returns {Promise<Message>} + */ + async edit(message, options) { + const messageId = this.resolveId(message); + if (!messageId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable'); + + const { body, files } = await (options instanceof MessagePayload + ? options + : MessagePayload.create(message instanceof Message ? message : this, options) + ) + .resolveBody() + .resolveFiles(); + const d = await this.client.rest.patch(Routes.channelMessage(this.channel.id, messageId), { body, files }); + + const existing = this.cache.get(messageId); + if (existing) { + const clone = existing._clone(); + clone._patch(d); + return clone; + } + return this._add(d); + } + + /** + * Publishes a message in an announcement channel to all channels following it, even if it's not cached. + * @param {MessageResolvable} message The message to publish + * @returns {Promise<Message>} + */ + async crosspost(message) { + message = this.resolveId(message); + if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable'); + + const data = await this.client.rest.post(Routes.channelMessageCrosspost(this.channel.id, message)); + return this.cache.get(data.id) ?? this._add(data); + } + + /** + * Pins a message to the channel's pinned messages, even if it's not cached. + * @param {MessageResolvable} message The message to pin + * @param {string} [reason] Reason for pinning + * @returns {Promise<void>} + */ + async pin(message, reason) { + message = this.resolveId(message); + if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable'); + + await this.client.rest.put(Routes.channelPin(this.channel.id, message), { reason }); + } + + /** + * Unpins a message from the channel's pinned messages, even if it's not cached. + * @param {MessageResolvable} message The message to unpin + * @param {string} [reason] Reason for unpinning + * @returns {Promise<void>} + */ + async unpin(message, reason) { + message = this.resolveId(message); + if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable'); + + await this.client.rest.delete(Routes.channelPin(this.channel.id, message), { reason }); + } + + /** + * Adds a reaction to a message, even if it's not cached. + * @param {MessageResolvable} message The message to react to + * @param {EmojiIdentifierResolvable} emoji The emoji to react with + * @returns {Promise<void>} + */ + async react(message, emoji) { + message = this.resolveId(message); + if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable'); + + emoji = resolvePartialEmoji(emoji); + if (!emoji) throw new DiscordjsTypeError(ErrorCodes.EmojiType, 'emoji', 'EmojiIdentifierResolvable'); + + const emojiId = emoji.id + ? `${emoji.animated ? 'a:' : ''}${emoji.name}:${emoji.id}` + : encodeURIComponent(emoji.name); + + await this.client.rest.put(Routes.channelMessageOwnReaction(this.channel.id, message, emojiId)); + } + + /** + * Deletes a message, even if it's not cached. + * @param {MessageResolvable} message The message to delete + * @returns {Promise<void>} + */ + async delete(message) { + message = this.resolveId(message); + if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable'); + + await this.client.rest.delete(Routes.channelMessage(this.channel.id, message)); + } +} + +module.exports = MessageManager; diff --git a/node_modules/discord.js/src/managers/PermissionOverwriteManager.js b/node_modules/discord.js/src/managers/PermissionOverwriteManager.js new file mode 100644 index 0000000..011a649 --- /dev/null +++ b/node_modules/discord.js/src/managers/PermissionOverwriteManager.js @@ -0,0 +1,168 @@ +'use strict'; + +const process = require('node:process'); +const { Collection } = require('@discordjs/collection'); +const { OverwriteType, Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const PermissionOverwrites = require('../structures/PermissionOverwrites'); +const { Role } = require('../structures/Role'); + +let cacheWarningEmitted = false; + +/** + * Manages API methods for guild channel permission overwrites and stores their cache. + * @extends {CachedManager} + */ +class PermissionOverwriteManager extends CachedManager { + constructor(channel, iterable) { + super(channel.client, PermissionOverwrites); + if (!cacheWarningEmitted && this._cache.constructor.name !== 'Collection') { + cacheWarningEmitted = true; + process.emitWarning( + `Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`, + 'UnsupportedCacheOverwriteWarning', + ); + } + + /** + * The channel of the permission overwrite this manager belongs to + * @type {GuildChannel} + */ + this.channel = channel; + + if (iterable) { + for (const item of iterable) { + this._add(item); + } + } + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, PermissionOverwrites>} + * @name PermissionOverwriteManager#cache + */ + + _add(data, cache) { + return super._add(data, cache, { extras: [this.channel] }); + } + + /** + * Replaces the permission overwrites in this channel. + * @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} overwrites + * Permission overwrites the channel gets updated with + * @param {string} [reason] Reason for updating the channel overwrites + * @returns {Promise<GuildChannel>} + * @example + * message.channel.permissionOverwrites.set([ + * { + * id: message.author.id, + * deny: [PermissionsFlagsBit.ViewChannel], + * }, + * ], 'Needed to change permissions'); + */ + set(overwrites, reason) { + if (!Array.isArray(overwrites) && !(overwrites instanceof Collection)) { + return Promise.reject( + new DiscordjsTypeError( + ErrorCodes.InvalidType, + 'overwrites', + 'Array or Collection of Permission Overwrites', + true, + ), + ); + } + return this.channel.edit({ permissionOverwrites: overwrites, reason }); + } + + /** + * Extra information about the overwrite. + * @typedef {Object} GuildChannelOverwriteOptions + * @property {string} [reason] The reason for creating/editing this overwrite + * @property {OverwriteType} [type] The type of overwrite. Use this to bypass automatic resolution of `type` + * that results in an error for an uncached structure + */ + + /** + * Creates or edits permission overwrites for a user or role in this channel. + * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {PermissionOverwriteOptions} options The options for the update + * @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update + * @param {PermissionOverwrites} [existing] The existing overwrites to merge with this update + * @returns {Promise<GuildChannel>} + * @private + */ + async upsert(userOrRole, options, overwriteOptions = {}, existing) { + let userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole); + let { type, reason } = overwriteOptions; + if (typeof type !== 'number') { + userOrRole = this.channel.guild.roles.resolve(userOrRole) ?? this.client.users.resolve(userOrRole); + if (!userOrRole) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'parameter', 'User nor a Role'); + type = userOrRole instanceof Role ? OverwriteType.Role : OverwriteType.Member; + } + + const { allow, deny } = PermissionOverwrites.resolveOverwriteOptions(options, existing); + + await this.client.rest.put(Routes.channelPermission(this.channel.id, userOrRoleId), { + body: { id: userOrRoleId, type, allow, deny }, + reason, + }); + return this.channel; + } + + /** + * Creates permission overwrites for a user or role in this channel, or replaces them if already present. + * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {PermissionOverwriteOptions} options The options for the update + * @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update + * @returns {Promise<GuildChannel>} + * @example + * // Create or Replace permission overwrites for a message author + * message.channel.permissionOverwrites.create(message.author, { + * SendMessages: false + * }) + * .then(channel => console.log(channel.permissionOverwrites.cache.get(message.author.id))) + * .catch(console.error); + */ + create(userOrRole, options, overwriteOptions) { + return this.upsert(userOrRole, options, overwriteOptions); + } + + /** + * Edits permission overwrites for a user or role in this channel, or creates an entry if not already present. + * @param {RoleResolvable|UserResolvable} userOrRole The user or role to update + * @param {PermissionOverwriteOptions} options The options for the update + * @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update + * @returns {Promise<GuildChannel>} + * @example + * // Edit or Create permission overwrites for a message author + * message.channel.permissionOverwrites.edit(message.author, { + * SendMessages: false + * }) + * .then(channel => console.log(channel.permissionOverwrites.cache.get(message.author.id))) + * .catch(console.error); + */ + edit(userOrRole, options, overwriteOptions) { + const existing = this.cache.get( + this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole), + ); + return this.upsert(userOrRole, options, overwriteOptions, existing); + } + + /** + * Deletes permission overwrites for a user or role in this channel. + * @param {UserResolvable|RoleResolvable} userOrRole The user or role to delete + * @param {string} [reason] The reason for deleting the overwrite + * @returns {Promise<GuildChannel>} + */ + async delete(userOrRole, reason) { + const userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole); + if (!userOrRoleId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'parameter', 'User nor a Role'); + + await this.client.rest.delete(Routes.channelPermission(this.channel.id, userOrRoleId), { reason }); + return this.channel; + } +} + +module.exports = PermissionOverwriteManager; diff --git a/node_modules/discord.js/src/managers/PresenceManager.js b/node_modules/discord.js/src/managers/PresenceManager.js new file mode 100644 index 0000000..2d64834 --- /dev/null +++ b/node_modules/discord.js/src/managers/PresenceManager.js @@ -0,0 +1,58 @@ +'use strict'; + +const CachedManager = require('./CachedManager'); +const { Presence } = require('../structures/Presence'); + +/** + * Manages API methods for Presences and holds their cache. + * @extends {CachedManager} + */ +class PresenceManager extends CachedManager { + constructor(client, iterable) { + super(client, Presence, iterable); + } + + /** + * The cache of Presences + * @type {Collection<Snowflake, Presence>} + * @name PresenceManager#cache + */ + + _add(data, cache) { + return super._add(data, cache, { id: data.user.id }); + } + + /** + * Data that can be resolved to a Presence object. This can be: + * * A Presence + * * A UserResolvable + * * A Snowflake + * @typedef {Presence|UserResolvable|Snowflake} PresenceResolvable + */ + + /** + * Resolves a {@link PresenceResolvable} to a {@link Presence} object. + * @param {PresenceResolvable} presence The presence resolvable to resolve + * @returns {?Presence} + */ + resolve(presence) { + const presenceResolvable = super.resolve(presence); + if (presenceResolvable) return presenceResolvable; + const UserResolvable = this.client.users.resolveId(presence); + return super.resolve(UserResolvable); + } + + /** + * Resolves a {@link PresenceResolvable} to a {@link Presence} id. + * @param {PresenceResolvable} presence The presence resolvable to resolve + * @returns {?Snowflake} + */ + resolveId(presence) { + const presenceResolvable = super.resolveId(presence); + if (presenceResolvable) return presenceResolvable; + const userResolvable = this.client.users.resolveId(presence); + return this.cache.has(userResolvable) ? userResolvable : null; + } +} + +module.exports = PresenceManager; diff --git a/node_modules/discord.js/src/managers/ReactionManager.js b/node_modules/discord.js/src/managers/ReactionManager.js new file mode 100644 index 0000000..5535882 --- /dev/null +++ b/node_modules/discord.js/src/managers/ReactionManager.js @@ -0,0 +1,68 @@ +'use strict'; + +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const MessageReaction = require('../structures/MessageReaction'); + +/** + * Manages API methods for reactions and holds their cache. + * @extends {CachedManager} + */ +class ReactionManager extends CachedManager { + constructor(message, iterable) { + super(message.client, MessageReaction, iterable); + + /** + * The message that this manager belongs to + * @type {Message} + */ + this.message = message; + } + + _add(data, cache) { + return super._add(data, cache, { id: data.emoji.id ?? data.emoji.name, extras: [this.message] }); + } + + /** + * The reaction cache of this manager + * @type {Collection<string|Snowflake, MessageReaction>} + * @name ReactionManager#cache + */ + + /** + * Data that can be resolved to a MessageReaction object. This can be: + * * A MessageReaction + * * A Snowflake + * * The Unicode representation of an emoji + * @typedef {MessageReaction|Snowflake} MessageReactionResolvable + */ + + /** + * Resolves a {@link MessageReactionResolvable} to a {@link MessageReaction} object. + * @method resolve + * @memberof ReactionManager + * @instance + * @param {MessageReactionResolvable} reaction The MessageReaction to resolve + * @returns {?MessageReaction} + */ + + /** + * Resolves a {@link MessageReactionResolvable} to a {@link MessageReaction} id. + * @method resolveId + * @memberof ReactionManager + * @instance + * @param {MessageReactionResolvable} reaction The MessageReaction to resolve + * @returns {?Snowflake} + */ + + /** + * Removes all reactions from a message. + * @returns {Promise<Message>} + */ + async removeAll() { + await this.client.rest.delete(Routes.channelMessageAllReactions(this.message.channelId, this.message.id)); + return this.message; + } +} + +module.exports = ReactionManager; diff --git a/node_modules/discord.js/src/managers/ReactionUserManager.js b/node_modules/discord.js/src/managers/ReactionUserManager.js new file mode 100644 index 0000000..014cea8 --- /dev/null +++ b/node_modules/discord.js/src/managers/ReactionUserManager.js @@ -0,0 +1,77 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const User = require('../structures/User'); + +/** + * Manages API methods for users who reacted to a reaction and stores their cache. + * @extends {CachedManager} + */ +class ReactionUserManager extends CachedManager { + constructor(reaction, iterable) { + super(reaction.client, User, iterable); + + /** + * The reaction that this manager belongs to + * @type {MessageReaction} + */ + this.reaction = reaction; + } + + /** + * The cache of this manager + * @type {Collection<Snowflake, User>} + * @name ReactionUserManager#cache + */ + + /** + * Options used to fetch users who gave a reaction. + * @typedef {Object} FetchReactionUsersOptions + * @property {number} [limit=100] The maximum amount of users to fetch, defaults to `100` + * @property {Snowflake} [after] Limit fetching users to those with an id greater than the supplied id + */ + + /** + * Fetches all the users that gave this reaction. Resolves with a collection of users, mapped by their ids. + * @param {FetchReactionUsersOptions} [options] Options for fetching the users + * @returns {Promise<Collection<Snowflake, User>>} + */ + async fetch({ limit = 100, after } = {}) { + const message = this.reaction.message; + const query = makeURLSearchParams({ limit, after }); + const data = await this.client.rest.get( + Routes.channelMessageReaction(message.channelId, message.id, this.reaction.emoji.identifier), + { query }, + ); + const users = new Collection(); + for (const rawUser of data) { + const user = this.client.users._add(rawUser); + this.cache.set(user.id, user); + users.set(user.id, user); + } + return users; + } + + /** + * Removes a user from this reaction. + * @param {UserResolvable} [user=this.client.user] The user to remove the reaction of + * @returns {Promise<MessageReaction>} + */ + async remove(user = this.client.user) { + const userId = this.client.users.resolveId(user); + if (!userId) throw new DiscordjsError(ErrorCodes.ReactionResolveUser); + const message = this.reaction.message; + const route = + userId === this.client.user.id + ? Routes.channelMessageOwnReaction(message.channelId, message.id, this.reaction.emoji.identifier) + : Routes.channelMessageUserReaction(message.channelId, message.id, this.reaction.emoji.identifier, userId); + await this.client.rest.delete(route); + return this.reaction; + } +} + +module.exports = ReactionUserManager; diff --git a/node_modules/discord.js/src/managers/RoleManager.js b/node_modules/discord.js/src/managers/RoleManager.js new file mode 100644 index 0000000..e0c4ed7 --- /dev/null +++ b/node_modules/discord.js/src/managers/RoleManager.js @@ -0,0 +1,360 @@ +'use strict'; + +const process = require('node:process'); +const { Collection } = require('@discordjs/collection'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const { Role } = require('../structures/Role'); +const DataResolver = require('../util/DataResolver'); +const PermissionsBitField = require('../util/PermissionsBitField'); +const { setPosition, resolveColor } = require('../util/Util'); + +let cacheWarningEmitted = false; + +/** + * Manages API methods for roles and stores their cache. + * @extends {CachedManager} + */ +class RoleManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, Role, iterable); + if (!cacheWarningEmitted && this._cache.constructor.name !== 'Collection') { + cacheWarningEmitted = true; + process.emitWarning( + `Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`, + 'UnsupportedCacheOverwriteWarning', + ); + } + + /** + * The guild belonging to this manager + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The role cache of this manager + * @type {Collection<Snowflake, Role>} + * @name RoleManager#cache + */ + + _add(data, cache) { + return super._add(data, cache, { extras: [this.guild] }); + } + + /** + * Obtains a role from Discord, or the role cache if they're already available. + * @param {Snowflake} [id] The role's id + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise<?Role|Collection<Snowflake, Role>>} + * @example + * // Fetch all roles from the guild + * message.guild.roles.fetch() + * .then(roles => console.log(`There are ${roles.size} roles.`)) + * .catch(console.error); + * @example + * // Fetch a single role + * message.guild.roles.fetch('222078108977594368') + * .then(role => console.log(`The role color is: ${role.color}`)) + * .catch(console.error); + */ + async fetch(id, { cache = true, force = false } = {}) { + if (id && !force) { + const existing = this.cache.get(id); + if (existing) return existing; + } + + // We cannot fetch a single role, as of this commit's date, Discord API throws with 405 + const data = await this.client.rest.get(Routes.guildRoles(this.guild.id)); + const roles = new Collection(); + for (const role of data) roles.set(role.id, this._add(role, cache)); + return id ? roles.get(id) ?? null : roles; + } + + /** + * Data that can be resolved to a Role object. This can be: + * * A Role + * * A Snowflake + * @typedef {Role|Snowflake} RoleResolvable + */ + + /** + * Resolves a {@link RoleResolvable} to a {@link Role} object. + * @method resolve + * @memberof RoleManager + * @instance + * @param {RoleResolvable} role The role resolvable to resolve + * @returns {?Role} + */ + + /** + * Resolves a {@link RoleResolvable} to a {@link Role} id. + * @method resolveId + * @memberof RoleManager + * @instance + * @param {RoleResolvable} role The role resolvable to resolve + * @returns {?Snowflake} + */ + + /** + * Options used to create a new role. + * @typedef {Object} RoleCreateOptions + * @property {string} [name] The name of the new role + * @property {ColorResolvable} [color] The data to create the role with + * @property {boolean} [hoist] Whether or not the new role should be hoisted + * @property {PermissionResolvable} [permissions] The permissions for the new role + * @property {number} [position] The position of the new role + * @property {boolean} [mentionable] Whether or not the new role should be mentionable + * @property {?(BufferResolvable|Base64Resolvable|EmojiResolvable)} [icon] The icon for the role + * <warn>The `EmojiResolvable` should belong to the same guild as the role. + * If not, pass the emoji's URL directly</warn> + * @property {?string} [unicodeEmoji] The unicode emoji for the role + * @property {string} [reason] The reason for creating this role + */ + + /** + * Creates a new role in the guild with given information. + * <warn>The position will silently reset to 1 if an invalid one is provided, or none.</warn> + * @param {RoleCreateOptions} [options] Options for creating the new role + * @returns {Promise<Role>} + * @example + * // Create a new role + * guild.roles.create() + * .then(console.log) + * .catch(console.error); + * @example + * // Create a new role with data and a reason + * guild.roles.create({ + * name: 'Super Cool Blue People', + * color: Colors.Blue, + * reason: 'we needed a role for Super Cool People', + * }) + * .then(console.log) + * .catch(console.error); + */ + async create(options = {}) { + let { name, color, hoist, permissions, position, mentionable, reason, icon, unicodeEmoji } = options; + color &&= resolveColor(color); + if (permissions !== undefined) permissions = new PermissionsBitField(permissions); + if (icon) { + const guildEmojiURL = this.guild.emojis.resolve(icon)?.url; + icon = guildEmojiURL ? await DataResolver.resolveImage(guildEmojiURL) : await DataResolver.resolveImage(icon); + if (typeof icon !== 'string') icon = undefined; + } + + const data = await this.client.rest.post(Routes.guildRoles(this.guild.id), { + body: { + name, + color, + hoist, + permissions, + mentionable, + icon, + unicode_emoji: unicodeEmoji, + }, + reason, + }); + const { role } = this.client.actions.GuildRoleCreate.handle({ + guild_id: this.guild.id, + role: data, + }); + if (position) return this.setPosition(role, position, { reason }); + return role; + } + + /** + * Options for editing a role + * @typedef {RoleData} RoleEditOptions + * @property {string} [reason] The reason for editing this role + */ + + /** + * Edits a role of the guild. + * @param {RoleResolvable} role The role to edit + * @param {RoleEditOptions} options The options to provide + * @returns {Promise<Role>} + * @example + * // Edit a role + * guild.roles.edit('222079219327434752', { name: 'buddies' }) + * .then(updated => console.log(`Edited role name to ${updated.name}`)) + * .catch(console.error); + */ + async edit(role, options) { + role = this.resolve(role); + if (!role) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'role', 'RoleResolvable'); + + if (typeof options.position === 'number') { + await this.setPosition(role, options.position, { reason: options.reason }); + } + + let icon = options.icon; + if (icon) { + const guildEmojiURL = this.guild.emojis.resolve(icon)?.url; + icon = guildEmojiURL ? await DataResolver.resolveImage(guildEmojiURL) : await DataResolver.resolveImage(icon); + if (typeof icon !== 'string') icon = undefined; + } + + const body = { + name: options.name, + color: options.color === undefined ? undefined : resolveColor(options.color), + hoist: options.hoist, + permissions: options.permissions === undefined ? undefined : new PermissionsBitField(options.permissions), + mentionable: options.mentionable, + icon, + unicode_emoji: options.unicodeEmoji, + }; + + const d = await this.client.rest.patch(Routes.guildRole(this.guild.id, role.id), { body, reason: options.reason }); + + const clone = role._clone(); + clone._patch(d); + return clone; + } + + /** + * Deletes a role. + * @param {RoleResolvable} role The role to delete + * @param {string} [reason] Reason for deleting the role + * @returns {Promise<void>} + * @example + * // Delete a role + * guild.roles.delete('222079219327434752', 'The role needed to go') + * .then(() => console.log('Deleted the role')) + * .catch(console.error); + */ + async delete(role, reason) { + const id = this.resolveId(role); + await this.client.rest.delete(Routes.guildRole(this.guild.id, id), { reason }); + this.client.actions.GuildRoleDelete.handle({ guild_id: this.guild.id, role_id: id }); + } + + /** + * Sets the new position of the role. + * @param {RoleResolvable} role The role to change the position of + * @param {number} position The new position for the role + * @param {SetRolePositionOptions} [options] Options for setting the position + * @returns {Promise<Role>} + * @example + * // Set the position of the role + * guild.roles.setPosition('222197033908436994', 1) + * .then(updated => console.log(`Role position: ${updated.position}`)) + * .catch(console.error); + */ + async setPosition(role, position, { relative, reason } = {}) { + role = this.resolve(role); + if (!role) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'role', 'RoleResolvable'); + const updatedRoles = await setPosition( + role, + position, + relative, + this.guild._sortedRoles(), + this.client, + Routes.guildRoles(this.guild.id), + reason, + ); + + this.client.actions.GuildRolesPositionUpdate.handle({ + guild_id: this.guild.id, + roles: updatedRoles, + }); + return role; + } + + /** + * The data needed for updating a guild role's position + * @typedef {Object} GuildRolePosition + * @property {RoleResolvable} role The role's id + * @property {number} position The position to update + */ + + /** + * Batch-updates the guild's role positions + * @param {GuildRolePosition[]} rolePositions Role positions to update + * @returns {Promise<Guild>} + * @example + * guild.roles.setPositions([{ role: roleId, position: updatedRoleIndex }]) + * .then(guild => console.log(`Role positions updated for ${guild}`)) + * .catch(console.error); + */ + async setPositions(rolePositions) { + // Make sure rolePositions are prepared for API + rolePositions = rolePositions.map(o => ({ + id: this.resolveId(o.role), + position: o.position, + })); + + // Call the API to update role positions + await this.client.rest.patch(Routes.guildRoles(this.guild.id), { body: rolePositions }); + return this.client.actions.GuildRolesPositionUpdate.handle({ + guild_id: this.guild.id, + roles: rolePositions, + }).guild; + } + + /** + * Compares the positions of two roles. + * @param {RoleResolvable} role1 First role to compare + * @param {RoleResolvable} role2 Second role to compare + * @returns {number} Negative number if the first role's position is lower (second role's is higher), + * positive number if the first's is higher (second's is lower), 0 if equal + */ + comparePositions(role1, role2) { + const resolvedRole1 = this.resolve(role1); + const resolvedRole2 = this.resolve(role2); + if (!resolvedRole1 || !resolvedRole2) { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'role', 'Role nor a Snowflake'); + } + + const role1Position = resolvedRole1.position; + const role2Position = resolvedRole2.position; + + if (role1Position === role2Position) { + return Number(BigInt(resolvedRole2.id) - BigInt(resolvedRole1.id)); + } + + return role1Position - role2Position; + } + + /** + * Gets the managed role a user created when joining the guild, if any + * <info>Only ever available for bots</info> + * @param {UserResolvable} user The user to access the bot role for + * @returns {?Role} + */ + botRoleFor(user) { + const userId = this.client.users.resolveId(user); + if (!userId) return null; + return this.cache.find(role => role.tags?.botId === userId) ?? null; + } + + /** + * The `@everyone` role of the guild + * @type {Role} + * @readonly + */ + get everyone() { + return this.cache.get(this.guild.id); + } + + /** + * The premium subscriber role of the guild, if any + * @type {?Role} + * @readonly + */ + get premiumSubscriberRole() { + return this.cache.find(role => role.tags?.premiumSubscriberRole) ?? null; + } + + /** + * The role with the highest position in the cache + * @type {Role} + * @readonly + */ + get highest() { + return this.cache.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev), this.cache.first()); + } +} + +module.exports = RoleManager; diff --git a/node_modules/discord.js/src/managers/StageInstanceManager.js b/node_modules/discord.js/src/managers/StageInstanceManager.js new file mode 100644 index 0000000..ea037cf --- /dev/null +++ b/node_modules/discord.js/src/managers/StageInstanceManager.js @@ -0,0 +1,154 @@ +'use strict'; + +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors'); +const { StageInstance } = require('../structures/StageInstance'); + +/** + * Manages API methods for {@link StageInstance} objects and holds their cache. + * @extends {CachedManager} + */ +class StageInstanceManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, StageInstance, iterable); + + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, StageInstance>} + * @name StageInstanceManager#cache + */ + + /** + * Options used to create a stage instance. + * @typedef {Object} StageInstanceCreateOptions + * @property {string} topic The topic of the stage instance + * @property {StageInstancePrivacyLevel} [privacyLevel] The privacy level of the stage instance + * @property {boolean} [sendStartNotification] Whether to notify `@everyone` that the stage instance has started + */ + + /** + * Data that can be resolved to a Stage Channel object. This can be: + * * A StageChannel + * * A Snowflake + * @typedef {StageChannel|Snowflake} StageChannelResolvable + */ + + /** + * Creates a new stage instance. + * @param {StageChannelResolvable} channel The stage channel to associate the created stage instance to + * @param {StageInstanceCreateOptions} options The options to create the stage instance + * @returns {Promise<StageInstance>} + * @example + * // Create a stage instance + * guild.stageInstances.create('1234567890123456789', { + * topic: 'A very creative topic', + * privacyLevel: GuildPrivacyLevel.GuildOnly + * }) + * .then(stageInstance => console.log(stageInstance)) + * .catch(console.error); + */ + async create(channel, options) { + const channelId = this.guild.channels.resolveId(channel); + if (!channelId) throw new DiscordjsError(ErrorCodes.StageChannelResolve); + if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); + let { topic, privacyLevel, sendStartNotification } = options; + + const data = await this.client.rest.post(Routes.stageInstances(), { + body: { + channel_id: channelId, + topic, + privacy_level: privacyLevel, + send_start_notification: sendStartNotification, + }, + }); + + return this._add(data); + } + + /** + * Fetches the stage instance associated with a stage channel, if it exists. + * @param {StageChannelResolvable} channel The stage channel whose associated stage instance is to be fetched + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise<StageInstance>} + * @example + * // Fetch a stage instance + * guild.stageInstances.fetch('1234567890123456789') + * .then(stageInstance => console.log(stageInstance)) + * .catch(console.error); + */ + async fetch(channel, { cache = true, force = false } = {}) { + const channelId = this.guild.channels.resolveId(channel); + if (!channelId) throw new DiscordjsError(ErrorCodes.StageChannelResolve); + + if (!force) { + const existing = this.cache.find(stageInstance => stageInstance.channelId === channelId); + if (existing) return existing; + } + + const data = await this.client.rest.get(Routes.stageInstance(channelId)); + return this._add(data, cache); + } + + /** + * Options used to edit an existing stage instance. + * @typedef {Object} StageInstanceEditOptions + * @property {string} [topic] The new topic of the stage instance + * @property {StageInstancePrivacyLevel} [privacyLevel] The new privacy level of the stage instance + */ + + /** + * Edits an existing stage instance. + * @param {StageChannelResolvable} channel The stage channel whose associated stage instance is to be edited + * @param {StageInstanceEditOptions} options The options to edit the stage instance + * @returns {Promise<StageInstance>} + * @example + * // Edit a stage instance + * guild.stageInstances.edit('1234567890123456789', { topic: 'new topic' }) + * .then(stageInstance => console.log(stageInstance)) + * .catch(console.error); + */ + async edit(channel, options) { + if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); + const channelId = this.guild.channels.resolveId(channel); + if (!channelId) throw new DiscordjsError(ErrorCodes.StageChannelResolve); + + let { topic, privacyLevel } = options; + + const data = await this.client.rest.patch(Routes.stageInstance(channelId), { + body: { + topic, + privacy_level: privacyLevel, + }, + }); + + if (this.cache.has(data.id)) { + const clone = this.cache.get(data.id)._clone(); + clone._patch(data); + return clone; + } + + return this._add(data); + } + + /** + * Deletes an existing stage instance. + * @param {StageChannelResolvable} channel The stage channel whose associated stage instance is to be deleted + * @returns {Promise<void>} + */ + async delete(channel) { + const channelId = this.guild.channels.resolveId(channel); + if (!channelId) throw new DiscordjsError(ErrorCodes.StageChannelResolve); + + await this.client.rest.delete(Routes.stageInstance(channelId)); + } +} + +module.exports = StageInstanceManager; diff --git a/node_modules/discord.js/src/managers/ThreadManager.js b/node_modules/discord.js/src/managers/ThreadManager.js new file mode 100644 index 0000000..17569f5 --- /dev/null +++ b/node_modules/discord.js/src/managers/ThreadManager.js @@ -0,0 +1,207 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const ThreadChannel = require('../structures/ThreadChannel'); +const { MakeCacheOverrideSymbol } = require('../util/Symbols'); + +/** + * Manages API methods for thread-based channels and stores their cache. + * @extends {CachedManager} + */ +class ThreadManager extends CachedManager { + static [MakeCacheOverrideSymbol] = ThreadManager; + + constructor(channel, iterable) { + super(channel.client, ThreadChannel, iterable); + + /** + * The channel this Manager belongs to + * @type {TextChannel|NewsChannel|ForumChannel} + */ + this.channel = channel; + } + + /** + * Data that can be resolved to a Thread Channel object. This can be: + * * A ThreadChannel object + * * A Snowflake + * @typedef {ThreadChannel|Snowflake} ThreadChannelResolvable + */ + + /** + * The cache of this Manager + * @type {Collection<Snowflake, ThreadChannel>} + * @name ThreadManager#cache + */ + + _add(thread) { + const existing = this.cache.get(thread.id); + if (existing) return existing; + this.cache.set(thread.id, thread); + return thread; + } + + /** + * Resolves a {@link ThreadChannelResolvable} to a {@link ThreadChannel} object. + * @method resolve + * @memberof ThreadManager + * @instance + * @param {ThreadChannelResolvable} thread The ThreadChannel resolvable to resolve + * @returns {?ThreadChannel} + */ + + /** + * Resolves a {@link ThreadChannelResolvable} to a {@link ThreadChannel} id. + * @method resolveId + * @memberof ThreadManager + * @instance + * @param {ThreadChannelResolvable} thread The ThreadChannel resolvable to resolve + * @returns {?Snowflake} + */ + + /** + * Options for creating a thread. <warn>Only one of `startMessage` or `type` can be defined.</warn> + * @typedef {StartThreadOptions} ThreadCreateOptions + * @property {MessageResolvable} [startMessage] The message to start a thread from. <warn>If this is defined then type + * of thread gets automatically defined and cannot be changed. The provided `type` field will be ignored</warn> + * @property {ChannelType.AnnouncementThread|ChannelType.PublicThread|ChannelType.PrivateThread} [type] + * The type of thread to create. + * Defaults to {@link ChannelType.PublicThread} if created in a {@link TextChannel} + * <warn>When creating threads in a {@link NewsChannel} this is ignored and is always + * {@link ChannelType.AnnouncementThread}</warn> + * @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread + * <info>Can only be set when type will be {@link ChannelType.PrivateThread}</info> + */ + + /** + * Options for fetching multiple threads. + * @typedef {Object} FetchThreadsOptions + * @property {FetchArchivedThreadOptions} [archived] Options used to fetch archived threads + */ + + /** + * Obtains a thread from Discord, or the channel cache if it's already available. + * @param {ThreadChannelResolvable|FetchThreadsOptions} [options] The options to fetch threads. If it is a + * ThreadChannelResolvable then the specified thread will be fetched. Fetches all active threads if `undefined` + * @param {BaseFetchOptions} [cacheOptions] Additional options for this fetch. <warn>The `force` field gets ignored + * if `options` is not a {@link ThreadChannelResolvable}</warn> + * @returns {Promise<?(ThreadChannel|FetchedThreads|FetchedThreadsMore)>} + * {@link FetchedThreads} if active & {@link FetchedThreadsMore} if archived. + * @example + * // Fetch a thread by its id + * channel.threads.fetch('831955138126104859') + * .then(channel => console.log(channel.name)) + * .catch(console.error); + */ + fetch(options, { cache, force } = {}) { + if (!options) return this.fetchActive(cache); + const channel = this.client.channels.resolveId(options); + if (channel) return this.client.channels.fetch(channel, { cache, force }); + if (options.archived) { + return this.fetchArchived(options.archived, cache); + } + return this.fetchActive(cache); + } + + /** + * Data that can be resolved to a Date object. This can be: + * * A Date object + * * A number representing a timestamp + * * An [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) string + * @typedef {Date|number|string} DateResolvable + */ + + /** + * The options used to fetch archived threads. + * @typedef {Object} FetchArchivedThreadOptions + * @property {string} [type='public'] The type of threads to fetch (`public` or `private`) + * @property {boolean} [fetchAll=false] Whether to fetch **all** archived threads when `type` is `private` + * <info>This property requires the {@link PermissionFlagsBits.ManageThreads} permission if `true`.</info> + * @property {DateResolvable|ThreadChannelResolvable} [before] Only return threads that were archived before this Date + * or Snowflake + * <warn>Must be a {@link ThreadChannelResolvable} when `type` is `private` and `fetchAll` is `false`.</warn> + * @property {number} [limit] Maximum number of threads to return + */ + + /** + * Data returned from fetching multiple threads. + * @typedef {FetchedThreads} FetchedThreadsMore + * @property {?boolean} hasMore Whether there are potentially additional threads that require a subsequent call + */ + + /** + * Obtains a set of archived threads from Discord. + * <info>This method requires the {@link PermissionFlagsBits.ReadMessageHistory} permission + * in the parent channel.</info> + * @param {FetchArchivedThreadOptions} [options] The options to fetch archived threads + * @param {boolean} [cache=true] Whether to cache the new thread objects if they aren't already + * @returns {Promise<FetchedThreadsMore>} + */ + async fetchArchived({ type = 'public', fetchAll = false, before, limit } = {}, cache = true) { + let path = Routes.channelThreads(this.channel.id, type); + if (type === 'private' && !fetchAll) { + path = Routes.channelJoinedArchivedThreads(this.channel.id); + } + let timestamp; + let id; + const query = makeURLSearchParams({ limit }); + if (before !== undefined) { + if (before instanceof ThreadChannel || /^\d{17,19}$/.test(String(before))) { + id = this.resolveId(before); + timestamp = this.resolve(before)?.archivedAt?.toISOString(); + const toUse = type === 'private' && !fetchAll ? id : timestamp; + if (toUse) { + query.set('before', toUse); + } + } else { + try { + timestamp = new Date(before).toISOString(); + if (type === 'public' || fetchAll) { + query.set('before', timestamp); + } + } catch { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'before', 'DateResolvable or ThreadChannelResolvable'); + } + } + } + + const raw = await this.client.rest.get(path, { query }); + return this.constructor._mapThreads(raw, this.client, { parent: this.channel, cache }); + } + + /** + * Obtains all active threads in the channel. + * @param {boolean} [cache=true] Whether to cache the fetched data + * @returns {Promise<FetchedThreads>} + */ + async fetchActive(cache = true) { + const data = await this.channel.guild.channels.rawFetchGuildActiveThreads(); + return this.constructor._mapThreads(data, this.client, { parent: this.channel, cache }); + } + + static _mapThreads(rawThreads, client, { parent, guild, cache }) { + const threads = rawThreads.threads.reduce((coll, raw) => { + const thread = client.channels._add(raw, guild ?? parent?.guild, { cache }); + if (parent && thread.parentId !== parent.id) return coll; + return coll.set(thread.id, thread); + }, new Collection()); + + // Discord sends the thread id as id in this object + const threadMembers = rawThreads.members.reduce((coll, raw) => { + const thread = threads.get(raw.id); + return thread ? coll.set(raw.user_id, thread.members._add(raw)) : coll; + }, new Collection()); + + const response = { threads, members: threadMembers }; + + // The GET `/guilds/{guild.id}/threads/active` route does not return `has_more`. + if ('has_more' in rawThreads) response.hasMore = rawThreads.has_more; + return response; + } +} + +module.exports = ThreadManager; diff --git a/node_modules/discord.js/src/managers/ThreadMemberManager.js b/node_modules/discord.js/src/managers/ThreadMemberManager.js new file mode 100644 index 0000000..c138aa3 --- /dev/null +++ b/node_modules/discord.js/src/managers/ThreadMemberManager.js @@ -0,0 +1,182 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const ThreadMember = require('../structures/ThreadMember'); + +/** + * Manages API methods for GuildMembers and stores their cache. + * @extends {CachedManager} + */ +class ThreadMemberManager extends CachedManager { + constructor(thread, iterable) { + super(thread.client, ThreadMember, iterable); + + /** + * The thread this manager belongs to + * @type {ThreadChannel} + */ + this.thread = thread; + } + + /** + * The cache of this Manager + * @type {Collection<Snowflake, ThreadMember>} + * @name ThreadMemberManager#cache + */ + + _add(data, cache = true) { + const existing = this.cache.get(data.user_id); + if (cache) existing?._patch(data, { cache }); + if (existing) return existing; + + const member = new ThreadMember(this.thread, data, { cache }); + if (cache) this.cache.set(data.user_id, member); + return member; + } + + /** + * Fetches the client user as a ThreadMember of the thread. + * @param {BaseFetchOptions} [options] The options for fetching the member + * @returns {Promise<ThreadMember>} + */ + fetchMe(options) { + return this.fetch({ ...options, member: this.client.user.id }); + } + + /** + * The client user as a ThreadMember of this ThreadChannel + * @type {?ThreadMember} + * @readonly + */ + get me() { + return this.resolve(this.client.user.id); + } + + /** + * Data that resolves to give a ThreadMember object. This can be: + * * A ThreadMember object + * * A User resolvable + * @typedef {ThreadMember|UserResolvable} ThreadMemberResolvable + */ + + /** + * Resolves a {@link ThreadMemberResolvable} to a {@link ThreadMember} object. + * @param {ThreadMemberResolvable} member The user that is part of the thread + * @returns {?GuildMember} + */ + resolve(member) { + const memberResolvable = super.resolve(member); + if (memberResolvable) return memberResolvable; + const userResolvable = this.client.users.resolveId(member); + if (userResolvable) return super.resolve(userResolvable); + return null; + } + + /** + * Resolves a {@link ThreadMemberResolvable} to a {@link ThreadMember} id string. + * @param {ThreadMemberResolvable} member The user that is part of the guild + * @returns {?Snowflake} + */ + resolveId(member) { + const memberResolvable = super.resolveId(member); + if (memberResolvable) return memberResolvable; + const userResolvable = this.client.users.resolveId(member); + return this.cache.has(userResolvable) ? userResolvable : null; + } + + /** + * Adds a member to the thread. + * @param {UserResolvable|'@me'} member The member to add + * @param {string} [reason] The reason for adding this member + * @returns {Promise<Snowflake>} + */ + async add(member, reason) { + const id = member === '@me' ? member : this.client.users.resolveId(member); + if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'member', 'UserResolvable'); + await this.client.rest.put(Routes.threadMembers(this.thread.id, id), { reason }); + return id; + } + + /** + * Remove a user from the thread. + * @param {Snowflake|'@me'} id The id of the member to remove + * @param {string} [reason] The reason for removing this member from the thread + * @returns {Promise<Snowflake>} + */ + async remove(id, reason) { + await this.client.rest.delete(Routes.threadMembers(this.thread.id, id), { reason }); + return id; + } + + /** + * Options used to fetch a thread member. + * @typedef {BaseFetchOptions} FetchThreadMemberOptions + * @property {ThreadMemberResolvable} member The thread member to fetch + * @property {boolean} [withMember] Whether to also return the guild member associated with this thread member + */ + + /** + * Options used to fetch multiple thread members with guild member data. + * <info>With `withMember` set to `true`, pagination is enabled.</info> + * @typedef {Object} FetchThreadMembersWithGuildMemberDataOptions + * @property {true} withMember Whether to also return the guild member data + * @property {Snowflake} [after] Consider only thread members after this id + * @property {number} [limit] The maximum number of thread members to return + * @property {boolean} [cache] Whether to cache the fetched thread members and guild members + */ + + /** + * Options used to fetch multiple thread members without guild member data. + * @typedef {Object} FetchThreadMembersWithoutGuildMemberDataOptions + * @property {false} [withMember] Whether to also return the guild member data + * @property {boolean} [cache] Whether to cache the fetched thread members + */ + + /** + * Options used to fetch multiple thread members. + * @typedef {FetchThreadMembersWithGuildMemberDataOptions| + * FetchThreadMembersWithoutGuildMemberDataOptions} FetchThreadMembersOptions + */ + + /** + * Fetches thread member(s) from Discord. + * <info>This method requires the {@link GatewayIntentBits.GuildMembers} privileged gateway intent.</info> + * @param {ThreadMemberResolvable|FetchThreadMemberOptions|FetchThreadMembersOptions} [options] + * Options for fetching thread member(s) + * @returns {Promise<ThreadMember|Collection<Snowflake, ThreadMember>>} + */ + fetch(options) { + if (!options) return this._fetchMany(); + const { member, withMember, cache, force } = options; + const resolvedMember = this.resolveId(member ?? options); + if (resolvedMember) return this._fetchSingle({ member: resolvedMember, withMember, cache, force }); + return this._fetchMany(options); + } + + async _fetchSingle({ member, withMember, cache, force = false }) { + if (!force) { + const existing = this.cache.get(member); + if (existing) return existing; + } + + const data = await this.client.rest.get(Routes.threadMembers(this.thread.id, member), { + query: makeURLSearchParams({ with_member: withMember }), + }); + + return this._add(data, cache); + } + + async _fetchMany({ withMember, after, limit, cache } = {}) { + const data = await this.client.rest.get(Routes.threadMembers(this.thread.id), { + query: makeURLSearchParams({ with_member: withMember, after, limit }), + }); + + return data.reduce((col, member) => col.set(member.user_id, this._add(member, cache)), new Collection()); + } +} + +module.exports = ThreadMemberManager; diff --git a/node_modules/discord.js/src/managers/UserManager.js b/node_modules/discord.js/src/managers/UserManager.js new file mode 100644 index 0000000..24478f6 --- /dev/null +++ b/node_modules/discord.js/src/managers/UserManager.js @@ -0,0 +1,139 @@ +'use strict'; + +const { ChannelType, Routes } = require('discord-api-types/v10'); +const CachedManager = require('./CachedManager'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const { GuildMember } = require('../structures/GuildMember'); +const { Message } = require('../structures/Message'); +const ThreadMember = require('../structures/ThreadMember'); +const User = require('../structures/User'); + +/** + * Manages API methods for users and stores their cache. + * @extends {CachedManager} + */ +class UserManager extends CachedManager { + constructor(client, iterable) { + super(client, User, iterable); + } + + /** + * The cache of this manager + * @type {Collection<Snowflake, User>} + * @name UserManager#cache + */ + + /** + * Data that resolves to give a User object. This can be: + * * A User object + * * A Snowflake + * * A Message object (resolves to the message author) + * * A GuildMember object + * * A ThreadMember object + * @typedef {User|Snowflake|Message|GuildMember|ThreadMember} UserResolvable + */ + + /** + * The DM between the client's user and a user + * @param {Snowflake} userId The user id + * @returns {?DMChannel} + * @private + */ + dmChannel(userId) { + return this.client.channels.cache.find(c => c.type === ChannelType.DM && c.recipientId === userId) ?? null; + } + + /** + * Creates a {@link DMChannel} between the client and a user. + * @param {UserResolvable} user The UserResolvable to identify + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise<DMChannel>} + */ + async createDM(user, { cache = true, force = false } = {}) { + const id = this.resolveId(user); + + if (!force) { + const dmChannel = this.dmChannel(id); + if (dmChannel && !dmChannel.partial) return dmChannel; + } + + const data = await this.client.rest.post(Routes.userChannels(), { body: { recipient_id: id } }); + return this.client.channels._add(data, null, { cache }); + } + + /** + * Deletes a {@link DMChannel} (if one exists) between the client and a user. Resolves with the channel if successful. + * @param {UserResolvable} user The UserResolvable to identify + * @returns {Promise<DMChannel>} + */ + async deleteDM(user) { + const id = this.resolveId(user); + const dmChannel = this.dmChannel(id); + if (!dmChannel) throw new DiscordjsError(ErrorCodes.UserNoDMChannel); + await this.client.rest.delete(Routes.channel(dmChannel.id)); + this.client.channels._remove(dmChannel.id); + return dmChannel; + } + + /** + * Obtains a user from Discord, or the user cache if it's already available. + * @param {UserResolvable} user The user to fetch + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise<User>} + */ + async fetch(user, { cache = true, force = false } = {}) { + const id = this.resolveId(user); + if (!force) { + const existing = this.cache.get(id); + if (existing && !existing.partial) return existing; + } + + const data = await this.client.rest.get(Routes.user(id)); + return this._add(data, cache); + } + + /** + * Fetches a user's flags. + * @param {UserResolvable} user The UserResolvable to identify + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise<UserFlagsBitField>} + */ + async fetchFlags(user, options) { + return (await this.fetch(user, options)).flags; + } + + /** + * Sends a message to a user. + * @param {UserResolvable} user The UserResolvable to identify + * @param {string|MessagePayload|MessageCreateOptions} options The options to provide + * @returns {Promise<Message>} + */ + async send(user, options) { + return (await this.createDM(user)).send(options); + } + + /** + * Resolves a {@link UserResolvable} to a {@link User} object. + * @param {UserResolvable} user The UserResolvable to identify + * @returns {?User} + */ + resolve(user) { + if (user instanceof GuildMember || user instanceof ThreadMember) return user.user; + if (user instanceof Message) return user.author; + return super.resolve(user); + } + + /** + * Resolves a {@link UserResolvable} to a {@link User} id. + * @param {UserResolvable} user The UserResolvable to identify + * @returns {?Snowflake} + */ + resolveId(user) { + if (user instanceof ThreadMember) return user.id; + if (user instanceof GuildMember) return user.user.id; + if (user instanceof Message) return user.author.id; + return super.resolveId(user); + } +} + +module.exports = UserManager; diff --git a/node_modules/discord.js/src/managers/VoiceStateManager.js b/node_modules/discord.js/src/managers/VoiceStateManager.js new file mode 100644 index 0000000..c42fdd2 --- /dev/null +++ b/node_modules/discord.js/src/managers/VoiceStateManager.js @@ -0,0 +1,37 @@ +'use strict'; + +const CachedManager = require('./CachedManager'); +const VoiceState = require('../structures/VoiceState'); + +/** + * Manages API methods for VoiceStates and stores their cache. + * @extends {CachedManager} + */ +class VoiceStateManager extends CachedManager { + constructor(guild, iterable) { + super(guild.client, VoiceState, iterable); + + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of this manager + * @type {Collection<Snowflake, VoiceState>} + * @name VoiceStateManager#cache + */ + + _add(data, cache = true) { + const existing = this.cache.get(data.user_id); + if (existing) return existing._patch(data); + + const entry = new this.holds(this.guild, data); + if (cache) this.cache.set(data.user_id, entry); + return entry; + } +} + +module.exports = VoiceStateManager; diff --git a/node_modules/discord.js/src/sharding/Shard.js b/node_modules/discord.js/src/sharding/Shard.js new file mode 100644 index 0000000..f833309 --- /dev/null +++ b/node_modules/discord.js/src/sharding/Shard.js @@ -0,0 +1,475 @@ +'use strict'; + +const EventEmitter = require('node:events'); +const path = require('node:path'); +const process = require('node:process'); +const { setTimeout, clearTimeout } = require('node:timers'); +const { setTimeout: sleep } = require('node:timers/promises'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const ShardEvents = require('../util/ShardEvents'); +const { makeError, makePlainError } = require('../util/Util'); +let childProcess = null; +let Worker = null; + +/** + * A self-contained shard created by the {@link ShardingManager}. Each one has a {@link ChildProcess} that contains + * an instance of the bot and its {@link Client}. When its child process/worker exits for any reason, the shard will + * spawn a new one to replace it as necessary. + * @extends {EventEmitter} + */ +class Shard extends EventEmitter { + constructor(manager, id) { + super(); + + switch (manager.mode) { + case 'process': + childProcess = require('node:child_process'); + break; + case 'worker': + Worker = require('node:worker_threads').Worker; + break; + } + + /** + * Manager that created the shard + * @type {ShardingManager} + */ + this.manager = manager; + + /** + * The shard's id in the manager + * @type {number} + */ + this.id = id; + + /** + * Whether to pass silent flag to the shard's process (only when {@link ShardingManager#mode} is `process`) + * @type {boolean} + */ + this.silent = manager.silent; + + /** + * Arguments for the shard's process (only when {@link ShardingManager#mode} is `process`) + * @type {string[]} + */ + this.args = manager.shardArgs ?? []; + + /** + * Arguments for the shard's process executable (only when {@link ShardingManager#mode} is `process`) + * @type {string[]} + */ + this.execArgv = manager.execArgv; + + /** + * Environment variables for the shard's process, or workerData for the shard's worker + * @type {Object} + */ + this.env = Object.assign({}, process.env, { + SHARDING_MANAGER: true, + SHARDS: this.id, + SHARD_COUNT: this.manager.totalShards, + DISCORD_TOKEN: this.manager.token, + }); + + /** + * Whether the shard's {@link Client} is ready + * @type {boolean} + */ + this.ready = false; + + /** + * Process of the shard (if {@link ShardingManager#mode} is `process`) + * @type {?ChildProcess} + */ + this.process = null; + + /** + * Worker of the shard (if {@link ShardingManager#mode} is `worker`) + * @type {?Worker} + */ + this.worker = null; + + /** + * Ongoing promises for calls to {@link Shard#eval}, mapped by the `script` they were called with + * @type {Map<string, Promise>} + * @private + */ + this._evals = new Map(); + + /** + * Ongoing promises for calls to {@link Shard#fetchClientValue}, mapped by the `prop` they were called with + * @type {Map<string, Promise>} + * @private + */ + this._fetches = new Map(); + + /** + * Listener function for the {@link ChildProcess}' `exit` event + * @type {Function} + * @private + */ + this._exitListener = null; + } + + /** + * Forks a child process or creates a worker thread for the shard. + * <warn>You should not need to call this manually.</warn> + * @param {number} [timeout=30000] The amount in milliseconds to wait until the {@link Client} has become ready + * before resolving (`-1` or `Infinity` for no wait) + * @returns {Promise<ChildProcess>} + */ + spawn(timeout = 30_000) { + if (this.process) throw new DiscordjsError(ErrorCodes.ShardingProcessExists, this.id); + if (this.worker) throw new DiscordjsError(ErrorCodes.ShardingWorkerExists, this.id); + + this._exitListener = this._handleExit.bind(this, undefined, timeout); + + switch (this.manager.mode) { + case 'process': + this.process = childProcess + .fork(path.resolve(this.manager.file), this.args, { + env: this.env, + execArgv: this.execArgv, + silent: this.silent, + }) + .on('message', this._handleMessage.bind(this)) + .on('exit', this._exitListener); + break; + case 'worker': + this.worker = new Worker(path.resolve(this.manager.file), { workerData: this.env }) + .on('message', this._handleMessage.bind(this)) + .on('exit', this._exitListener); + break; + } + + this._evals.clear(); + this._fetches.clear(); + + const child = this.process ?? this.worker; + + /** + * Emitted upon the creation of the shard's child process/worker. + * @event Shard#spawn + * @param {ChildProcess|Worker} process Child process/worker that was created + */ + this.emit(ShardEvents.Spawn, child); + + if (timeout === -1 || timeout === Infinity) return Promise.resolve(child); + return new Promise((resolve, reject) => { + const cleanup = () => { + clearTimeout(spawnTimeoutTimer); + this.off('ready', onReady); + this.off('disconnect', onDisconnect); + this.off('death', onDeath); + }; + + const onReady = () => { + cleanup(); + resolve(child); + }; + + const onDisconnect = () => { + cleanup(); + reject(new DiscordjsError(ErrorCodes.ShardingReadyDisconnected, this.id)); + }; + + const onDeath = () => { + cleanup(); + reject(new DiscordjsError(ErrorCodes.ShardingReadyDied, this.id)); + }; + + const onTimeout = () => { + cleanup(); + reject(new DiscordjsError(ErrorCodes.ShardingReadyTimeout, this.id)); + }; + + const spawnTimeoutTimer = setTimeout(onTimeout, timeout); + this.once('ready', onReady); + this.once('disconnect', onDisconnect); + this.once('death', onDeath); + }); + } + + /** + * Immediately kills the shard's process/worker and does not restart it. + */ + kill() { + if (this.process) { + this.process.removeListener('exit', this._exitListener); + this.process.kill(); + } else { + this.worker.removeListener('exit', this._exitListener); + this.worker.terminate(); + } + + this._handleExit(false); + } + + /** + * Options used to respawn a shard. + * @typedef {Object} ShardRespawnOptions + * @property {number} [delay=500] How long to wait between killing the process/worker and + * restarting it (in milliseconds) + * @property {number} [timeout=30000] The amount in milliseconds to wait until the {@link Client} + * has become ready before resolving (`-1` or `Infinity` for no wait) + */ + + /** + * Kills and restarts the shard's process/worker. + * @param {ShardRespawnOptions} [options] Options for respawning the shard + * @returns {Promise<ChildProcess>} + */ + async respawn({ delay = 500, timeout = 30_000 } = {}) { + this.kill(); + if (delay > 0) await sleep(delay); + return this.spawn(timeout); + } + + /** + * Sends a message to the shard's process/worker. + * @param {*} message Message to send to the shard + * @returns {Promise<Shard>} + */ + send(message) { + return new Promise((resolve, reject) => { + if (this.process) { + this.process.send(message, err => { + if (err) reject(err); + else resolve(this); + }); + } else { + this.worker.postMessage(message); + resolve(this); + } + }); + } + + /** + * Fetches a client property value of the shard. + * @param {string} prop Name of the client property to get, using periods for nesting + * @returns {Promise<*>} + * @example + * shard.fetchClientValue('guilds.cache.size') + * .then(count => console.log(`${count} guilds in shard ${shard.id}`)) + * .catch(console.error); + */ + fetchClientValue(prop) { + // Shard is dead (maybe respawning), don't cache anything and error immediately + if (!this.process && !this.worker) { + return Promise.reject(new DiscordjsError(ErrorCodes.ShardingNoChildExists, this.id)); + } + + // Cached promise from previous call + if (this._fetches.has(prop)) return this._fetches.get(prop); + + const promise = new Promise((resolve, reject) => { + const child = this.process ?? this.worker; + + const listener = message => { + if (message?._fetchProp !== prop) return; + child.removeListener('message', listener); + this.decrementMaxListeners(child); + this._fetches.delete(prop); + if (!message._error) resolve(message._result); + else reject(makeError(message._error)); + }; + + this.incrementMaxListeners(child); + child.on('message', listener); + + this.send({ _fetchProp: prop }).catch(err => { + child.removeListener('message', listener); + this.decrementMaxListeners(child); + this._fetches.delete(prop); + reject(err); + }); + }); + + this._fetches.set(prop, promise); + return promise; + } + + /** + * Evaluates a script or function on the shard, in the context of the {@link Client}. + * @param {string|Function} script JavaScript to run on the shard + * @param {*} [context] The context for the eval + * @returns {Promise<*>} Result of the script execution + */ + eval(script, context) { + // Stringify the script if it's a Function + const _eval = typeof script === 'function' ? `(${script})(this, ${JSON.stringify(context)})` : script; + + // Shard is dead (maybe respawning), don't cache anything and error immediately + if (!this.process && !this.worker) { + return Promise.reject(new DiscordjsError(ErrorCodes.ShardingNoChildExists, this.id)); + } + + // Cached promise from previous call + if (this._evals.has(_eval)) return this._evals.get(_eval); + + const promise = new Promise((resolve, reject) => { + const child = this.process ?? this.worker; + + const listener = message => { + if (message?._eval !== _eval) return; + child.removeListener('message', listener); + this.decrementMaxListeners(child); + this._evals.delete(_eval); + if (!message._error) resolve(message._result); + else reject(makeError(message._error)); + }; + + this.incrementMaxListeners(child); + child.on('message', listener); + + this.send({ _eval }).catch(err => { + child.removeListener('message', listener); + this.decrementMaxListeners(child); + this._evals.delete(_eval); + reject(err); + }); + }); + + this._evals.set(_eval, promise); + return promise; + } + + /** + * Handles a message received from the child process/worker. + * @param {*} message Message received + * @private + */ + _handleMessage(message) { + if (message) { + // Shard is ready + if (message._ready) { + this.ready = true; + /** + * Emitted upon the shard's {@link Client#event:shardReady} event. + * @event Shard#ready + */ + this.emit(ShardEvents.Ready); + return; + } + + // Shard has disconnected + if (message._disconnect) { + this.ready = false; + /** + * Emitted upon the shard's {@link Client#event:shardDisconnect} event. + * @event Shard#disconnect + */ + this.emit(ShardEvents.Disconnect); + return; + } + + // Shard is attempting to reconnect + if (message._reconnecting) { + this.ready = false; + /** + * Emitted upon the shard's {@link Client#event:shardReconnecting} event. + * @event Shard#reconnecting + */ + this.emit(ShardEvents.Reconnecting); + return; + } + + // Shard has resumed + if (message._resume) { + this.ready = true; + /** + * Emitted upon the shard's {@link Client#event:shardResume} event. + * @event Shard#resume + */ + this.emit(ShardEvents.Resume); + return; + } + + // Shard is requesting a property fetch + if (message._sFetchProp) { + const resp = { _sFetchProp: message._sFetchProp, _sFetchPropShard: message._sFetchPropShard }; + this.manager.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then( + results => this.send({ ...resp, _result: results }), + err => this.send({ ...resp, _error: makePlainError(err) }), + ); + return; + } + + // Shard is requesting an eval broadcast + if (message._sEval) { + const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard }; + this.manager._performOnShards('eval', [message._sEval], message._sEvalShard).then( + results => this.send({ ...resp, _result: results }), + err => this.send({ ...resp, _error: makePlainError(err) }), + ); + return; + } + + // Shard is requesting a respawn of all shards + if (message._sRespawnAll) { + const { shardDelay, respawnDelay, timeout } = message._sRespawnAll; + this.manager.respawnAll({ shardDelay, respawnDelay, timeout }).catch(() => { + // Do nothing + }); + return; + } + } + + /** + * Emitted upon receiving a message from the child process/worker. + * @event Shard#message + * @param {*} message Message that was received + */ + this.emit(ShardEvents.Message, message); + } + + /** + * Handles the shard's process/worker exiting. + * @param {boolean} [respawn=this.manager.respawn] Whether to spawn the shard again + * @param {number} [timeout] The amount in milliseconds to wait until the {@link Client} + * has become ready (`-1` or `Infinity` for no wait) + * @private + */ + _handleExit(respawn = this.manager.respawn, timeout) { + /** + * Emitted upon the shard's child process/worker exiting. + * @event Shard#death + * @param {ChildProcess|Worker} process Child process/worker that exited + */ + this.emit(ShardEvents.Death, this.process ?? this.worker); + + this.ready = false; + this.process = null; + this.worker = null; + this._evals.clear(); + this._fetches.clear(); + + if (respawn) this.spawn(timeout).catch(err => this.emit(ShardEvents.Error, err)); + } + + /** + * Increments max listeners by one for a given emitter, if they are not zero. + * @param {EventEmitter|process} emitter The emitter that emits the events. + * @private + */ + incrementMaxListeners(emitter) { + const maxListeners = emitter.getMaxListeners(); + if (maxListeners !== 0) { + emitter.setMaxListeners(maxListeners + 1); + } + } + + /** + * Decrements max listeners by one for a given emitter, if they are not zero. + * @param {EventEmitter|process} emitter The emitter that emits the events. + * @private + */ + decrementMaxListeners(emitter) { + const maxListeners = emitter.getMaxListeners(); + if (maxListeners !== 0) { + emitter.setMaxListeners(maxListeners - 1); + } + } +} + +module.exports = Shard; diff --git a/node_modules/discord.js/src/sharding/ShardClientUtil.js b/node_modules/discord.js/src/sharding/ShardClientUtil.js new file mode 100644 index 0000000..c1bd4a8 --- /dev/null +++ b/node_modules/discord.js/src/sharding/ShardClientUtil.js @@ -0,0 +1,291 @@ +'use strict'; + +const process = require('node:process'); +const { calculateShardId } = require('@discordjs/util'); +const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); +const Events = require('../util/Events'); +const { makeError, makePlainError } = require('../util/Util'); + +/** + * Helper class for sharded clients spawned as a child process/worker, such as from a {@link ShardingManager}. + * Utilises IPC to send and receive data to/from the master process and other shards. + */ +class ShardClientUtil { + constructor(client, mode) { + /** + * Client for the shard + * @type {Client} + */ + this.client = client; + + /** + * Mode the shard was spawned with + * @type {ShardingManagerMode} + */ + this.mode = mode; + + /** + * Message port for the master process (only when {@link ShardClientUtil#mode} is `worker`) + * @type {?MessagePort} + */ + this.parentPort = null; + + switch (mode) { + case 'process': + process.on('message', this._handleMessage.bind(this)); + client.on(Events.ShardReady, () => { + process.send({ _ready: true }); + }); + client.on(Events.ShardDisconnect, () => { + process.send({ _disconnect: true }); + }); + client.on(Events.ShardReconnecting, () => { + process.send({ _reconnecting: true }); + }); + client.on(Events.ShardResume, () => { + process.send({ _resume: true }); + }); + break; + case 'worker': + this.parentPort = require('node:worker_threads').parentPort; + this.parentPort.on('message', this._handleMessage.bind(this)); + client.on(Events.ShardReady, () => { + this.parentPort.postMessage({ _ready: true }); + }); + client.on(Events.ShardDisconnect, () => { + this.parentPort.postMessage({ _disconnect: true }); + }); + client.on(Events.ShardReconnecting, () => { + this.parentPort.postMessage({ _reconnecting: true }); + }); + client.on(Events.ShardResume, () => { + this.parentPort.postMessage({ _resume: true }); + }); + break; + } + } + + /** + * Array of shard ids of this client + * @type {number[]} + * @readonly + */ + get ids() { + return this.client.options.shards; + } + + /** + * Total number of shards + * @type {number} + * @readonly + */ + get count() { + return this.client.options.shardCount; + } + + /** + * Sends a message to the master process. + * @param {*} message Message to send + * @returns {Promise<void>} + * @emits Shard#message + */ + send(message) { + return new Promise((resolve, reject) => { + switch (this.mode) { + case 'process': + process.send(message, err => { + if (err) reject(err); + else resolve(); + }); + break; + case 'worker': + this.parentPort.postMessage(message); + resolve(); + break; + } + }); + } + + /** + * Fetches a client property value of each shard, or a given shard. + * @param {string} prop Name of the client property to get, using periods for nesting + * @param {number} [shard] Shard to fetch property from, all if undefined + * @returns {Promise<*|Array<*>>} + * @example + * client.shard.fetchClientValues('guilds.cache.size') + * .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`)) + * .catch(console.error); + * @see {@link ShardingManager#fetchClientValues} + */ + fetchClientValues(prop, shard) { + return new Promise((resolve, reject) => { + const parent = this.parentPort ?? process; + + const listener = message => { + if (message?._sFetchProp !== prop || message._sFetchPropShard !== shard) return; + parent.removeListener('message', listener); + this.decrementMaxListeners(parent); + if (!message._error) resolve(message._result); + else reject(makeError(message._error)); + }; + this.incrementMaxListeners(parent); + parent.on('message', listener); + + this.send({ _sFetchProp: prop, _sFetchPropShard: shard }).catch(err => { + parent.removeListener('message', listener); + this.decrementMaxListeners(parent); + reject(err); + }); + }); + } + + /** + * Evaluates a script or function on all shards, or a given shard, in the context of the {@link Client}s. + * @param {Function} script JavaScript to run on each shard + * @param {BroadcastEvalOptions} [options={}] The options for the broadcast + * @returns {Promise<*|Array<*>>} Results of the script execution + * @example + * client.shard.broadcastEval(client => client.guilds.cache.size) + * .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`)) + * .catch(console.error); + * @see {@link ShardingManager#broadcastEval} + */ + broadcastEval(script, options = {}) { + return new Promise((resolve, reject) => { + const parent = this.parentPort ?? process; + if (typeof script !== 'function') { + reject(new DiscordjsTypeError(ErrorCodes.ShardingInvalidEvalBroadcast)); + return; + } + script = `(${script})(this, ${JSON.stringify(options.context)})`; + + const listener = message => { + if (message?._sEval !== script || message._sEvalShard !== options.shard) return; + parent.removeListener('message', listener); + this.decrementMaxListeners(parent); + if (!message._error) resolve(message._result); + else reject(makeError(message._error)); + }; + this.incrementMaxListeners(parent); + parent.on('message', listener); + this.send({ _sEval: script, _sEvalShard: options.shard }).catch(err => { + parent.removeListener('message', listener); + this.decrementMaxListeners(parent); + reject(err); + }); + }); + } + + /** + * Requests a respawn of all shards. + * @param {MultipleShardRespawnOptions} [options] Options for respawning shards + * @returns {Promise<void>} Resolves upon the message being sent + * @see {@link ShardingManager#respawnAll} + */ + respawnAll({ shardDelay = 5_000, respawnDelay = 500, timeout = 30_000 } = {}) { + return this.send({ _sRespawnAll: { shardDelay, respawnDelay, timeout } }); + } + + /** + * Handles an IPC message. + * @param {*} message Message received + * @private + */ + async _handleMessage(message) { + if (!message) return; + if (message._fetchProp) { + try { + const props = message._fetchProp.split('.'); + let value = this.client; + for (const prop of props) value = value[prop]; + this._respond('fetchProp', { _fetchProp: message._fetchProp, _result: value }); + } catch (err) { + this._respond('fetchProp', { _fetchProp: message._fetchProp, _error: makePlainError(err) }); + } + } else if (message._eval) { + try { + this._respond('eval', { _eval: message._eval, _result: await this.client._eval(message._eval) }); + } catch (err) { + this._respond('eval', { _eval: message._eval, _error: makePlainError(err) }); + } + } + } + + /** + * Sends a message to the master process, emitting an error from the client upon failure. + * @param {string} type Type of response to send + * @param {*} message Message to send + * @private + */ + _respond(type, message) { + this.send(message).catch(err => { + const error = new Error(`Error when sending ${type} response to master process: ${err.message}`); + error.stack = err.stack; + /** + * Emitted when the client encounters an error. + * <warn>Errors thrown within this event do not have a catch handler, it is + * recommended to not use async functions as `error` event handlers. See the + * [Node.js docs](https://nodejs.org/api/events.html#capture-rejections-of-promises) for details.</warn> + * @event Client#error + * @param {Error} error The error encountered + */ + this.client.emit(Events.Error, error); + }); + } + + /** + * Creates/gets the singleton of this class. + * @param {Client} client The client to use + * @param {ShardingManagerMode} mode Mode the shard was spawned with + * @returns {ShardClientUtil} + */ + static singleton(client, mode) { + if (!this._singleton) { + this._singleton = new this(client, mode); + } else { + client.emit( + Events.Warn, + 'Multiple clients created in child process/worker; only the first will handle sharding helpers.', + ); + } + return this._singleton; + } + + /** + * Get the shard id for a given guild id. + * @param {Snowflake} guildId Snowflake guild id to get shard id for + * @param {number} shardCount Number of shards + * @returns {number} + */ + static shardIdForGuildId(guildId, shardCount) { + const shard = calculateShardId(guildId, shardCount); + if (shard < 0) throw new DiscordjsError(ErrorCodes.ShardingShardMiscalculation, shard, guildId, shardCount); + return shard; + } + + /** + * Increments max listeners by one for a given emitter, if they are not zero. + * @param {EventEmitter|process} emitter The emitter that emits the events. + * @private + */ + incrementMaxListeners(emitter) { + const maxListeners = emitter.getMaxListeners(); + if (maxListeners !== 0) { + emitter.setMaxListeners(maxListeners + 1); + } + } + + /** + * Decrements max listeners by one for a given emitter, if they are not zero. + * @param {EventEmitter|process} emitter The emitter that emits the events. + * @private + */ + decrementMaxListeners(emitter) { + const maxListeners = emitter.getMaxListeners(); + if (maxListeners !== 0) { + emitter.setMaxListeners(maxListeners - 1); + } + } +} + +module.exports = ShardClientUtil; diff --git a/node_modules/discord.js/src/sharding/ShardingManager.js b/node_modules/discord.js/src/sharding/ShardingManager.js new file mode 100644 index 0000000..288456a --- /dev/null +++ b/node_modules/discord.js/src/sharding/ShardingManager.js @@ -0,0 +1,335 @@ +'use strict'; + +const EventEmitter = require('node:events'); +const fs = require('node:fs'); +const path = require('node:path'); +const process = require('node:process'); +const { setTimeout: sleep } = require('node:timers/promises'); +const { Collection } = require('@discordjs/collection'); +const Shard = require('./Shard'); +const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors'); +const { mergeDefault, fetchRecommendedShardCount } = require('../util/Util'); + +/** + * This is a utility class that makes multi-process sharding of a bot an easy and painless experience. + * It works by spawning a self-contained {@link ChildProcess} or {@link Worker} for each individual shard, each + * containing its own instance of your bot's {@link Client}. They all have a line of communication with the master + * process, and there are several useful methods that utilise it in order to simplify tasks that are normally difficult + * with sharding. It can spawn a specific number of shards or the amount that Discord suggests for the bot, and takes a + * path to your main bot script to launch for each one. + * @extends {EventEmitter} + */ +class ShardingManager extends EventEmitter { + /** + * The mode to spawn shards with for a {@link ShardingManager}. Can be either one of: + * * 'process' to use child processes + * * 'worker' to use [Worker threads](https://nodejs.org/api/worker_threads.html) + * @typedef {string} ShardingManagerMode + */ + + /** + * The options to spawn shards with for a {@link ShardingManager}. + * @typedef {Object} ShardingManagerOptions + * @property {string|number} [totalShards='auto'] Number of total shards of all shard managers or "auto" + * @property {string|number[]} [shardList='auto'] List of shards to spawn or "auto" + * @property {ShardingManagerMode} [mode='process'] Which mode to use for shards + * @property {boolean} [respawn=true] Whether shards should automatically respawn upon exiting + * @property {boolean} [silent=false] Whether to pass the silent flag to child process + * (only available when mode is set to 'process') + * @property {string[]} [shardArgs=[]] Arguments to pass to the shard script when spawning + * (only available when mode is set to 'process') + * @property {string[]} [execArgv=[]] Arguments to pass to the shard script executable when spawning + * (only available when mode is set to 'process') + * @property {string} [token] Token to use for automatic shard count and passing to shards + */ + + /** + * @param {string} file Path to your shard script file + * @param {ShardingManagerOptions} [options] Options for the sharding manager + */ + constructor(file, options = {}) { + super(); + options = mergeDefault( + { + totalShards: 'auto', + mode: 'process', + respawn: true, + silent: false, + shardArgs: [], + execArgv: [], + token: process.env.DISCORD_TOKEN, + }, + options, + ); + + /** + * Path to the shard script file + * @type {string} + */ + this.file = file; + if (!file) throw new DiscordjsError(ErrorCodes.ClientInvalidOption, 'File', 'specified.'); + if (!path.isAbsolute(file)) this.file = path.resolve(process.cwd(), file); + const stats = fs.statSync(this.file); + if (!stats.isFile()) throw new DiscordjsError(ErrorCodes.ClientInvalidOption, 'File', 'a file'); + + /** + * List of shards this sharding manager spawns + * @type {string|number[]} + */ + this.shardList = options.shardList ?? 'auto'; + if (this.shardList !== 'auto') { + if (!Array.isArray(this.shardList)) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'shardList', 'an array.'); + } + this.shardList = [...new Set(this.shardList)]; + if (this.shardList.length < 1) { + throw new DiscordjsRangeError(ErrorCodes.ClientInvalidOption, 'shardList', 'at least 1 id.'); + } + if ( + this.shardList.some( + shardId => typeof shardId !== 'number' || isNaN(shardId) || !Number.isInteger(shardId) || shardId < 0, + ) + ) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'shardList', 'an array of positive integers.'); + } + } + + /** + * Amount of shards that all sharding managers spawn in total + * @type {number} + */ + this.totalShards = options.totalShards || 'auto'; + if (this.totalShards !== 'auto') { + if (typeof this.totalShards !== 'number' || isNaN(this.totalShards)) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'Amount of shards', 'a number.'); + } + if (this.totalShards < 1) { + throw new DiscordjsRangeError(ErrorCodes.ClientInvalidOption, 'Amount of shards', 'at least 1.'); + } + if (!Number.isInteger(this.totalShards)) { + throw new DiscordjsRangeError(ErrorCodes.ClientInvalidOption, 'Amount of shards', 'an integer.'); + } + } + + /** + * Mode for shards to spawn with + * @type {ShardingManagerMode} + */ + this.mode = options.mode; + if (this.mode !== 'process' && this.mode !== 'worker') { + throw new DiscordjsRangeError(ErrorCodes.ClientInvalidOption, 'Sharding mode', '"process" or "worker"'); + } + + /** + * Whether shards should automatically respawn upon exiting + * @type {boolean} + */ + this.respawn = options.respawn; + + /** + * Whether to pass the silent flag to child process (only when {@link ShardingManager#mode} is `process`) + * @type {boolean} + */ + this.silent = options.silent; + + /** + * An array of arguments to pass to shards (only when {@link ShardingManager#mode} is `process`) + * @type {string[]} + */ + this.shardArgs = options.shardArgs; + + /** + * An array of arguments to pass to the executable (only when {@link ShardingManager#mode} is `process`) + * @type {string[]} + */ + this.execArgv = options.execArgv; + + /** + * Token to use for obtaining the automatic shard count, and passing to shards + * @type {?string} + */ + this.token = options.token?.replace(/^Bot\s*/i, '') ?? null; + + /** + * A collection of shards that this manager has spawned + * @type {Collection<number, Shard>} + */ + this.shards = new Collection(); + + process.env.SHARDING_MANAGER = true; + process.env.SHARDING_MANAGER_MODE = this.mode; + process.env.DISCORD_TOKEN = this.token; + } + + /** + * Creates a single shard. + * <warn>Using this method is usually not necessary if you use the spawn method.</warn> + * @param {number} [id=this.shards.size] Id of the shard to create + * <info>This is usually not necessary to manually specify.</info> + * @returns {Shard} Note that the created shard needs to be explicitly spawned using its spawn method. + */ + createShard(id = this.shards.size) { + const shard = new Shard(this, id); + this.shards.set(id, shard); + /** + * Emitted upon creating a shard. + * @event ShardingManager#shardCreate + * @param {Shard} shard Shard that was created + */ + this.emit('shardCreate', shard); + return shard; + } + + /** + * Options used to spawn multiple shards. + * @typedef {Object} MultipleShardSpawnOptions + * @property {number|string} [amount=this.totalShards] Number of shards to spawn + * @property {number} [delay=5500] How long to wait in between spawning each shard (in milliseconds) + * @property {number} [timeout=30000] The amount in milliseconds to wait until the {@link Client} has become ready + */ + + /** + * Spawns multiple shards. + * @param {MultipleShardSpawnOptions} [options] Options for spawning shards + * @returns {Promise<Collection<number, Shard>>} + */ + async spawn({ amount = this.totalShards, delay = 5500, timeout = 30_000 } = {}) { + // Obtain/verify the number of shards to spawn + if (amount === 'auto') { + amount = await fetchRecommendedShardCount(this.token); + } else { + if (typeof amount !== 'number' || isNaN(amount)) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'Amount of shards', 'a number.'); + } + if (amount < 1) throw new DiscordjsRangeError(ErrorCodes.ClientInvalidOption, 'Amount of shards', 'at least 1.'); + if (!Number.isInteger(amount)) { + throw new DiscordjsTypeError(ErrorCodes.ClientInvalidOption, 'Amount of shards', 'an integer.'); + } + } + + // Make sure this many shards haven't already been spawned + if (this.shards.size >= amount) throw new DiscordjsError(ErrorCodes.ShardingAlreadySpawned, this.shards.size); + if (this.shardList === 'auto' || this.totalShards === 'auto' || this.totalShards !== amount) { + this.shardList = [...Array(amount).keys()]; + } + if (this.totalShards === 'auto' || this.totalShards !== amount) { + this.totalShards = amount; + } + + if (this.shardList.some(shardId => shardId >= amount)) { + throw new DiscordjsRangeError( + ErrorCodes.ClientInvalidOption, + 'Amount of shards', + 'bigger than the highest shardId in the shardList option.', + ); + } + + // Spawn the shards + for (const shardId of this.shardList) { + const promises = []; + const shard = this.createShard(shardId); + promises.push(shard.spawn(timeout)); + if (delay > 0 && this.shards.size !== this.shardList.length) promises.push(sleep(delay)); + await Promise.all(promises); // eslint-disable-line no-await-in-loop + } + + return this.shards; + } + + /** + * Sends a message to all shards. + * @param {*} message Message to be sent to the shards + * @returns {Promise<Shard[]>} + */ + broadcast(message) { + const promises = []; + for (const shard of this.shards.values()) promises.push(shard.send(message)); + return Promise.all(promises); + } + + /** + * Options for {@link ShardingManager#broadcastEval} and {@link ShardClientUtil#broadcastEval}. + * @typedef {Object} BroadcastEvalOptions + * @property {number} [shard] Shard to run script on, all if undefined + * @property {*} [context] The JSON-serializable values to call the script with + */ + + /** + * Evaluates a script on all shards, or a given shard, in the context of the {@link Client}s. + * @param {Function} script JavaScript to run on each shard + * @param {BroadcastEvalOptions} [options={}] The options for the broadcast + * @returns {Promise<*|Array<*>>} Results of the script execution + */ + broadcastEval(script, options = {}) { + if (typeof script !== 'function') { + return Promise.reject(new DiscordjsTypeError(ErrorCodes.ShardingInvalidEvalBroadcast)); + } + return this._performOnShards('eval', [`(${script})(this, ${JSON.stringify(options.context)})`], options.shard); + } + + /** + * Fetches a client property value of each shard, or a given shard. + * @param {string} prop Name of the client property to get, using periods for nesting + * @param {number} [shard] Shard to fetch property from, all if undefined + * @returns {Promise<*|Array<*>>} + * @example + * manager.fetchClientValues('guilds.cache.size') + * .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`)) + * .catch(console.error); + */ + fetchClientValues(prop, shard) { + return this._performOnShards('fetchClientValue', [prop], shard); + } + + /** + * Runs a method with given arguments on all shards, or a given shard. + * @param {string} method Method name to run on each shard + * @param {Array<*>} args Arguments to pass through to the method call + * @param {number} [shard] Shard to run on, all if undefined + * @returns {Promise<*|Array<*>>} Results of the method execution + * @private + */ + _performOnShards(method, args, shard) { + if (this.shards.size === 0) return Promise.reject(new DiscordjsError(ErrorCodes.ShardingNoShards)); + + if (typeof shard === 'number') { + if (this.shards.has(shard)) return this.shards.get(shard)[method](...args); + return Promise.reject(new DiscordjsError(ErrorCodes.ShardingShardNotFound, shard)); + } + + if (this.shards.size !== this.shardList.length) { + return Promise.reject(new DiscordjsError(ErrorCodes.ShardingInProcess)); + } + + const promises = []; + for (const sh of this.shards.values()) promises.push(sh[method](...args)); + return Promise.all(promises); + } + + /** + * Options used to respawn all shards. + * @typedef {Object} MultipleShardRespawnOptions + * @property {number} [shardDelay=5000] How long to wait between shards (in milliseconds) + * @property {number} [respawnDelay=500] How long to wait between killing a shard's process and restarting it + * (in milliseconds) + * @property {number} [timeout=30000] The amount in milliseconds to wait for a shard to become ready before + * continuing to another (`-1` or `Infinity` for no wait) + */ + + /** + * Kills all running shards and respawns them. + * @param {MultipleShardRespawnOptions} [options] Options for respawning shards + * @returns {Promise<Collection<number, Shard>>} + */ + async respawnAll({ shardDelay = 5_000, respawnDelay = 500, timeout = 30_000 } = {}) { + let s = 0; + for (const shard of this.shards.values()) { + const promises = [shard.respawn({ delay: respawnDelay, timeout })]; + if (++s < this.shards.size && shardDelay > 0) promises.push(sleep(shardDelay)); + await Promise.all(promises); // eslint-disable-line no-await-in-loop + } + return this.shards; + } +} + +module.exports = ShardingManager; diff --git a/node_modules/discord.js/src/structures/ActionRow.js b/node_modules/discord.js/src/structures/ActionRow.js new file mode 100644 index 0000000..3f39691 --- /dev/null +++ b/node_modules/discord.js/src/structures/ActionRow.js @@ -0,0 +1,46 @@ +'use strict'; + +const { deprecate } = require('node:util'); +const { isJSONEncodable } = require('@discordjs/util'); +const Component = require('./Component'); +const { createComponent } = require('../util/Components'); + +/** + * Represents an action row + * @extends {Component} + */ +class ActionRow extends Component { + constructor({ components, ...data }) { + super(data); + + /** + * The components in this action row + * @type {Component[]} + * @readonly + */ + this.components = components.map(c => createComponent(c)); + } + + /** + * Creates a new action row builder from JSON data + * @method from + * @memberof ActionRow + * @param {ActionRowBuilder|ActionRow|APIActionRowComponent} other The other data + * @returns {ActionRowBuilder} + * @deprecated Use {@link ActionRowBuilder.from} instead. + */ + static from = deprecate( + other => new this(isJSONEncodable(other) ? other.toJSON() : other), + 'ActionRow.from() is deprecated. Use ActionRowBuilder.from() instead.', + ); + + /** + * Returns the API-compatible JSON for this component + * @returns {APIActionRowComponent} + */ + toJSON() { + return { ...this.data, components: this.components.map(c => c.toJSON()) }; + } +} + +module.exports = ActionRow; diff --git a/node_modules/discord.js/src/structures/ActionRowBuilder.js b/node_modules/discord.js/src/structures/ActionRowBuilder.js new file mode 100644 index 0000000..962a378 --- /dev/null +++ b/node_modules/discord.js/src/structures/ActionRowBuilder.js @@ -0,0 +1,35 @@ +'use strict'; + +const { ActionRowBuilder: BuildersActionRow } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { createComponentBuilder } = require('../util/Components'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Represents an action row builder. + * @extends {BuildersActionRow} + */ +class ActionRowBuilder extends BuildersActionRow { + constructor({ components, ...data } = {}) { + super({ + ...toSnakeCase(data), + components: components?.map(c => createComponentBuilder(c)), + }); + } + + /** + * Creates a new action row builder from JSON data + * @param {ActionRow|ActionRowBuilder|APIActionRowComponent} other The other data + * @returns {ActionRowBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } +} + +module.exports = ActionRowBuilder; + +/** + * @external BuildersActionRow + * @see {@link https://discord.js.org/docs/packages/builders/stable/ActionRowBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/AnonymousGuild.js b/node_modules/discord.js/src/structures/AnonymousGuild.js new file mode 100644 index 0000000..70931bd --- /dev/null +++ b/node_modules/discord.js/src/structures/AnonymousGuild.js @@ -0,0 +1,97 @@ +'use strict'; + +const BaseGuild = require('./BaseGuild'); + +/** + * Bundles common attributes and methods between {@link Guild} and {@link InviteGuild} + * @extends {BaseGuild} + * @abstract + */ +class AnonymousGuild extends BaseGuild { + constructor(client, data, immediatePatch = true) { + super(client, data); + if (immediatePatch) this._patch(data); + } + + _patch(data) { + if ('features' in data) this.features = data.features; + + if ('splash' in data) { + /** + * The hash of the guild invite splash image + * @type {?string} + */ + this.splash = data.splash; + } + + if ('banner' in data) { + /** + * The hash of the guild banner + * @type {?string} + */ + this.banner = data.banner; + } + + if ('description' in data) { + /** + * The description of the guild, if any + * @type {?string} + */ + this.description = data.description; + } + + if ('verification_level' in data) { + /** + * The verification level of the guild + * @type {GuildVerificationLevel} + */ + this.verificationLevel = data.verification_level; + } + + if ('vanity_url_code' in data) { + /** + * The vanity invite code of the guild, if any + * @type {?string} + */ + this.vanityURLCode = data.vanity_url_code; + } + + if ('nsfw_level' in data) { + /** + * The NSFW level of this guild + * @type {GuildNSFWLevel} + */ + this.nsfwLevel = data.nsfw_level; + } + + if ('premium_subscription_count' in data) { + /** + * The total number of boosts for this server + * @type {?number} + */ + this.premiumSubscriptionCount = data.premium_subscription_count; + } else { + this.premiumSubscriptionCount ??= null; + } + } + + /** + * The URL to this guild's banner. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + bannerURL(options = {}) { + return this.banner && this.client.rest.cdn.banner(this.id, this.banner, options); + } + + /** + * The URL to this guild's invite splash image. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + splashURL(options = {}) { + return this.splash && this.client.rest.cdn.splash(this.id, this.splash, options); + } +} + +module.exports = AnonymousGuild; diff --git a/node_modules/discord.js/src/structures/ApplicationCommand.js b/node_modules/discord.js/src/structures/ApplicationCommand.js new file mode 100644 index 0000000..bd87281 --- /dev/null +++ b/node_modules/discord.js/src/structures/ApplicationCommand.js @@ -0,0 +1,606 @@ +'use strict'; + +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { ApplicationCommandOptionType } = require('discord-api-types/v10'); +const isEqual = require('fast-deep-equal'); +const Base = require('./Base'); +const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager'); +const PermissionsBitField = require('../util/PermissionsBitField'); + +/** + * Represents an application command. + * @extends {Base} + */ +class ApplicationCommand extends Base { + constructor(client, data, guild, guildId) { + super(client); + + /** + * The command's id + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The parent application's id + * @type {Snowflake} + */ + this.applicationId = data.application_id; + + /** + * The guild this command is part of + * @type {?Guild} + */ + this.guild = guild ?? null; + + /** + * The guild's id this command is part of, this may be non-null when `guild` is `null` if the command + * was fetched from the `ApplicationCommandManager` + * @type {?Snowflake} + */ + this.guildId = guild?.id ?? guildId ?? null; + + /** + * The manager for permissions of this command on its guild or arbitrary guilds when the command is global + * @type {ApplicationCommandPermissionsManager} + */ + this.permissions = new ApplicationCommandPermissionsManager(this); + + /** + * The type of this application command + * @type {ApplicationCommandType} + */ + this.type = data.type; + + /** + * Whether this command is age-restricted (18+) + * @type {boolean} + */ + this.nsfw = data.nsfw ?? false; + + this._patch(data); + } + + _patch(data) { + if ('name' in data) { + /** + * The name of this command + * @type {string} + */ + this.name = data.name; + } + + if ('name_localizations' in data) { + /** + * The name localizations for this command + * @type {?Object<Locale, string>} + */ + this.nameLocalizations = data.name_localizations; + } else { + this.nameLocalizations ??= null; + } + + if ('name_localized' in data) { + /** + * The localized name for this command + * @type {?string} + */ + this.nameLocalized = data.name_localized; + } else { + this.nameLocalized ??= null; + } + + if ('description' in data) { + /** + * The description of this command + * @type {string} + */ + this.description = data.description; + } + + if ('description_localizations' in data) { + /** + * The description localizations for this command + * @type {?Object<Locale, string>} + */ + this.descriptionLocalizations = data.description_localizations; + } else { + this.descriptionLocalizations ??= null; + } + + if ('description_localized' in data) { + /** + * The localized description for this command + * @type {?string} + */ + this.descriptionLocalized = data.description_localized; + } else { + this.descriptionLocalized ??= null; + } + + if ('options' in data) { + /** + * The options of this command + * @type {ApplicationCommandOption[]} + */ + this.options = data.options.map(o => this.constructor.transformOption(o, true)); + } else { + this.options ??= []; + } + + if ('default_member_permissions' in data) { + /** + * The default bitfield used to determine whether this command be used in a guild + * @type {?Readonly<PermissionsBitField>} + */ + this.defaultMemberPermissions = data.default_member_permissions + ? new PermissionsBitField(BigInt(data.default_member_permissions)).freeze() + : null; + } else { + this.defaultMemberPermissions ??= null; + } + + if ('dm_permission' in data) { + /** + * Whether the command can be used in DMs + * <info>This property is always `null` on guild commands</info> + * @type {boolean|null} + */ + this.dmPermission = data.dm_permission; + } else { + this.dmPermission ??= null; + } + + if ('version' in data) { + /** + * Autoincrementing version identifier updated during substantial record changes + * @type {Snowflake} + */ + this.version = data.version; + } + } + + /** + * The timestamp the command was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the command was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The manager that this command belongs to + * @type {ApplicationCommandManager} + * @readonly + */ + get manager() { + return (this.guild ?? this.client.application).commands; + } + + /** + * Data for creating or editing an application command. + * @typedef {Object} ApplicationCommandData + * @property {string} name The name of the command, must be in all lowercase if type is + * {@link ApplicationCommandType.ChatInput} + * @property {Object<Locale, string>} [nameLocalizations] The localizations for the command name + * @property {string} description The description of the command, if type is {@link ApplicationCommandType.ChatInput} + * @property {boolean} [nsfw] Whether the command is age-restricted + * @property {Object<Locale, string>} [descriptionLocalizations] The localizations for the command description, + * if type is {@link ApplicationCommandType.ChatInput} + * @property {ApplicationCommandType} [type=ApplicationCommandType.ChatInput] The type of the command + * @property {ApplicationCommandOptionData[]} [options] Options for the command + * @property {?PermissionResolvable} [defaultMemberPermissions] The bitfield used to determine the default permissions + * a member needs in order to run the command + * @property {boolean} [dmPermission] Whether the command is enabled in DMs + */ + + /** + * An option for an application command or subcommand. + * <info>In addition to the listed properties, when used as a parameter, + * API style `snake_case` properties can be used for compatibility with generators like `@discordjs/builders`.</info> + * <warn>Note that providing a value for the `camelCase` counterpart for any `snake_case` property + * will discard the provided `snake_case` property.</warn> + * @typedef {Object} ApplicationCommandOptionData + * @property {ApplicationCommandOptionType} type The type of the option + * @property {string} name The name of the option + * @property {Object<Locale, string>} [nameLocalizations] The name localizations for the option + * @property {string} description The description of the option + * @property {Object<Locale, string>} [descriptionLocalizations] The description localizations for the option + * @property {boolean} [autocomplete] Whether the autocomplete interaction is enabled for a + * {@link ApplicationCommandOptionType.String}, {@link ApplicationCommandOptionType.Integer} or + * {@link ApplicationCommandOptionType.Number} option + * @property {boolean} [required] Whether the option is required + * @property {ApplicationCommandOptionChoiceData[]} [choices] The choices of the option for the user to pick from + * @property {ApplicationCommandOptionData[]} [options] Additional options if this option is a subcommand (group) + * @property {ChannelType[]} [channelTypes] When the option type is channel, + * the allowed types of channels that can be selected + * @property {number} [minValue] The minimum value for an {@link ApplicationCommandOptionType.Integer} or + * {@link ApplicationCommandOptionType.Number} option + * @property {number} [maxValue] The maximum value for an {@link ApplicationCommandOptionType.Integer} or + * {@link ApplicationCommandOptionType.Number} option + * @property {number} [minLength] The minimum length for an {@link ApplicationCommandOptionType.String} option + * (maximum of `6000`) + * @property {number} [maxLength] The maximum length for an {@link ApplicationCommandOptionType.String} option + * (maximum of `6000`) + */ + + /** + * @typedef {Object} ApplicationCommandOptionChoiceData + * @property {string} name The name of the choice + * @property {Object<Locale, string>} [nameLocalizations] The localized names for this choice + * @property {string|number} value The value of the choice + */ + + /** + * Edits this application command. + * @param {Partial<ApplicationCommandData>} data The data to update the command with + * @returns {Promise<ApplicationCommand>} + * @example + * // Edit the description of this command + * command.edit({ + * description: 'New description', + * }) + * .then(console.log) + * .catch(console.error); + */ + edit(data) { + return this.manager.edit(this, data, this.guildId); + } + + /** + * Edits the name of this ApplicationCommand + * @param {string} name The new name of the command + * @returns {Promise<ApplicationCommand>} + */ + setName(name) { + return this.edit({ name }); + } + + /** + * Edits the localized names of this ApplicationCommand + * @param {Object<Locale, string>} nameLocalizations The new localized names for the command + * @returns {Promise<ApplicationCommand>} + * @example + * // Edit the name localizations of this command + * command.setLocalizedNames({ + * 'en-GB': 'test', + * 'pt-BR': 'teste', + * }) + * .then(console.log) + * .catch(console.error) + */ + setNameLocalizations(nameLocalizations) { + return this.edit({ nameLocalizations }); + } + + /** + * Edits the description of this ApplicationCommand + * @param {string} description The new description of the command + * @returns {Promise<ApplicationCommand>} + */ + setDescription(description) { + return this.edit({ description }); + } + + /** + * Edits the localized descriptions of this ApplicationCommand + * @param {Object<Locale, string>} descriptionLocalizations The new localized descriptions for the command + * @returns {Promise<ApplicationCommand>} + * @example + * // Edit the description localizations of this command + * command.setDescriptionLocalizations({ + * 'en-GB': 'A test command', + * 'pt-BR': 'Um comando de teste', + * }) + * .then(console.log) + * .catch(console.error) + */ + setDescriptionLocalizations(descriptionLocalizations) { + return this.edit({ descriptionLocalizations }); + } + + /** + * Edits the default member permissions of this ApplicationCommand + * @param {?PermissionResolvable} defaultMemberPermissions The default member permissions required to run this command + * @returns {Promise<ApplicationCommand>} + */ + setDefaultMemberPermissions(defaultMemberPermissions) { + return this.edit({ defaultMemberPermissions }); + } + + /** + * Edits the DM permission of this ApplicationCommand + * @param {boolean} [dmPermission=true] Whether the command can be used in DMs + * @returns {Promise<ApplicationCommand>} + */ + setDMPermission(dmPermission = true) { + return this.edit({ dmPermission }); + } + + /** + * Edits the options of this ApplicationCommand + * @param {ApplicationCommandOptionData[]} options The options to set for this command + * @returns {Promise<ApplicationCommand>} + */ + setOptions(options) { + return this.edit({ options }); + } + + /** + * Deletes this command. + * @returns {Promise<ApplicationCommand>} + * @example + * // Delete this command + * command.delete() + * .then(console.log) + * .catch(console.error); + */ + delete() { + return this.manager.delete(this, this.guildId); + } + + /** + * Whether this command equals another command. It compares all properties, so for most operations + * it is advisable to just compare `command.id === command2.id` as it is much faster and is often + * what most users need. + * @param {ApplicationCommand|ApplicationCommandData|APIApplicationCommand} command The command to compare with + * @param {boolean} [enforceOptionOrder=false] Whether to strictly check that options and choices are in the same + * order in the array <info>The client may not always respect this ordering!</info> + * @returns {boolean} + */ + equals(command, enforceOptionOrder = false) { + // If given an id, check if the id matches + if (command.id && this.id !== command.id) return false; + + let defaultMemberPermissions = null; + let dmPermission = command.dmPermission ?? command.dm_permission; + + if ('default_member_permissions' in command) { + defaultMemberPermissions = command.default_member_permissions + ? new PermissionsBitField(BigInt(command.default_member_permissions)).bitfield + : null; + } + + if ('defaultMemberPermissions' in command) { + defaultMemberPermissions = + command.defaultMemberPermissions !== null + ? new PermissionsBitField(command.defaultMemberPermissions).bitfield + : null; + } + + // Check top level parameters + if ( + command.name !== this.name || + ('description' in command && command.description !== this.description) || + ('version' in command && command.version !== this.version) || + (command.type && command.type !== this.type) || + ('nsfw' in command && command.nsfw !== this.nsfw) || + // Future proof for options being nullable + // TODO: remove ?? 0 on each when nullable + (command.options?.length ?? 0) !== (this.options?.length ?? 0) || + defaultMemberPermissions !== (this.defaultMemberPermissions?.bitfield ?? null) || + (dmPermission !== undefined && dmPermission !== this.dmPermission) || + !isEqual(command.nameLocalizations ?? command.name_localizations ?? {}, this.nameLocalizations ?? {}) || + !isEqual( + command.descriptionLocalizations ?? command.description_localizations ?? {}, + this.descriptionLocalizations ?? {}, + ) + ) { + return false; + } + + if (command.options) { + return this.constructor.optionsEqual(this.options, command.options, enforceOptionOrder); + } + return true; + } + + /** + * Recursively checks that all options for an {@link ApplicationCommand} are equal to the provided options. + * In most cases it is better to compare using {@link ApplicationCommand#equals} + * @param {ApplicationCommandOptionData[]} existing The options on the existing command, + * should be {@link ApplicationCommand#options} + * @param {ApplicationCommandOptionData[]|APIApplicationCommandOption[]} options The options to compare against + * @param {boolean} [enforceOptionOrder=false] Whether to strictly check that options and choices are in the same + * order in the array <info>The client may not always respect this ordering!</info> + * @returns {boolean} + */ + static optionsEqual(existing, options, enforceOptionOrder = false) { + if (existing.length !== options.length) return false; + if (enforceOptionOrder) { + return existing.every((option, index) => this._optionEquals(option, options[index], enforceOptionOrder)); + } + const newOptions = new Map(options.map(option => [option.name, option])); + for (const option of existing) { + const foundOption = newOptions.get(option.name); + if (!foundOption || !this._optionEquals(option, foundOption)) return false; + } + return true; + } + + /** + * Checks that an option for an {@link ApplicationCommand} is equal to the provided option + * In most cases it is better to compare using {@link ApplicationCommand#equals} + * @param {ApplicationCommandOptionData} existing The option on the existing command, + * should be from {@link ApplicationCommand#options} + * @param {ApplicationCommandOptionData|APIApplicationCommandOption} option The option to compare against + * @param {boolean} [enforceOptionOrder=false] Whether to strictly check that options or choices are in the same + * order in their array <info>The client may not always respect this ordering!</info> + * @returns {boolean} + * @private + */ + static _optionEquals(existing, option, enforceOptionOrder = false) { + if ( + option.name !== existing.name || + option.type !== existing.type || + option.description !== existing.description || + option.autocomplete !== existing.autocomplete || + (option.required ?? + ([ApplicationCommandOptionType.Subcommand, ApplicationCommandOptionType.SubcommandGroup].includes(option.type) + ? undefined + : false)) !== existing.required || + option.choices?.length !== existing.choices?.length || + option.options?.length !== existing.options?.length || + (option.channelTypes ?? option.channel_types)?.length !== existing.channelTypes?.length || + (option.minValue ?? option.min_value) !== existing.minValue || + (option.maxValue ?? option.max_value) !== existing.maxValue || + (option.minLength ?? option.min_length) !== existing.minLength || + (option.maxLength ?? option.max_length) !== existing.maxLength || + !isEqual(option.nameLocalizations ?? option.name_localizations ?? {}, existing.nameLocalizations ?? {}) || + !isEqual( + option.descriptionLocalizations ?? option.description_localizations ?? {}, + existing.descriptionLocalizations ?? {}, + ) + ) { + return false; + } + + if (existing.choices) { + if ( + enforceOptionOrder && + !existing.choices.every( + (choice, index) => + choice.name === option.choices[index].name && + choice.value === option.choices[index].value && + isEqual( + choice.nameLocalizations ?? {}, + option.choices[index].nameLocalizations ?? option.choices[index].name_localizations ?? {}, + ), + ) + ) { + return false; + } + if (!enforceOptionOrder) { + const newChoices = new Map(option.choices.map(choice => [choice.name, choice])); + for (const choice of existing.choices) { + const foundChoice = newChoices.get(choice.name); + if (!foundChoice || foundChoice.value !== choice.value) return false; + } + } + } + + if (existing.channelTypes) { + const newTypes = option.channelTypes ?? option.channel_types; + for (const type of existing.channelTypes) { + if (!newTypes.includes(type)) return false; + } + } + + if (existing.options) { + return this.optionsEqual(existing.options, option.options, enforceOptionOrder); + } + return true; + } + + /** + * An option for an application command or subcommand. + * @typedef {Object} ApplicationCommandOption + * @property {ApplicationCommandOptionType} type The type of the option + * @property {string} name The name of the option + * @property {Object<Locale, string>} [nameLocalizations] The localizations for the option name + * @property {string} [nameLocalized] The localized name for this option + * @property {string} description The description of the option + * @property {Object<Locale, string>} [descriptionLocalizations] The localizations for the option description + * @property {string} [descriptionLocalized] The localized description for this option + * @property {boolean} [required] Whether the option is required + * @property {boolean} [autocomplete] Whether the autocomplete interaction is enabled for a + * {@link ApplicationCommandOptionType.String}, {@link ApplicationCommandOptionType.Integer} or + * {@link ApplicationCommandOptionType.Number} option + * @property {ApplicationCommandOptionChoice[]} [choices] The choices of the option for the user to pick from + * @property {ApplicationCommandOption[]} [options] Additional options if this option is a subcommand (group) + * @property {ApplicationCommandOptionAllowedChannelTypes[]} [channelTypes] When the option type is channel, + * the allowed types of channels that can be selected + * @property {number} [minValue] The minimum value for an {@link ApplicationCommandOptionType.Integer} or + * {@link ApplicationCommandOptionType.Number} option + * @property {number} [maxValue] The maximum value for an {@link ApplicationCommandOptionType.Integer} or + * {@link ApplicationCommandOptionType.Number} option + * @property {number} [minLength] The minimum length for an {@link ApplicationCommandOptionType.String} option + * (maximum of `6000`) + * @property {number} [maxLength] The maximum length for an {@link ApplicationCommandOptionType.String} option + * (maximum of `6000`) + */ + + /** + * A choice for an application command option. + * @typedef {Object} ApplicationCommandOptionChoice + * @property {string} name The name of the choice + * @property {?string} nameLocalized The localized name of the choice in the provided locale, if any + * @property {?Object<string, string>} [nameLocalizations] The localized names for this choice + * @property {string|number} value The value of the choice + */ + + /** + * Transforms an {@link ApplicationCommandOptionData} object into something that can be used with the API. + * @param {ApplicationCommandOptionData|ApplicationCommandOption} option The option to transform + * @param {boolean} [received] Whether this option has been received from Discord + * @returns {APIApplicationCommandOption} + * @private + */ + static transformOption(option, received) { + const channelTypesKey = received ? 'channelTypes' : 'channel_types'; + const minValueKey = received ? 'minValue' : 'min_value'; + const maxValueKey = received ? 'maxValue' : 'max_value'; + const minLengthKey = received ? 'minLength' : 'min_length'; + const maxLengthKey = received ? 'maxLength' : 'max_length'; + const nameLocalizationsKey = received ? 'nameLocalizations' : 'name_localizations'; + const nameLocalizedKey = received ? 'nameLocalized' : 'name_localized'; + const descriptionLocalizationsKey = received ? 'descriptionLocalizations' : 'description_localizations'; + const descriptionLocalizedKey = received ? 'descriptionLocalized' : 'description_localized'; + return { + type: option.type, + name: option.name, + [nameLocalizationsKey]: option.nameLocalizations ?? option.name_localizations, + [nameLocalizedKey]: option.nameLocalized ?? option.name_localized, + description: option.description, + [descriptionLocalizationsKey]: option.descriptionLocalizations ?? option.description_localizations, + [descriptionLocalizedKey]: option.descriptionLocalized ?? option.description_localized, + required: + option.required ?? + (option.type === ApplicationCommandOptionType.Subcommand || + option.type === ApplicationCommandOptionType.SubcommandGroup + ? undefined + : false), + autocomplete: option.autocomplete, + choices: option.choices?.map(choice => ({ + name: choice.name, + [nameLocalizedKey]: choice.nameLocalized ?? choice.name_localized, + [nameLocalizationsKey]: choice.nameLocalizations ?? choice.name_localizations, + value: choice.value, + })), + options: option.options?.map(o => this.transformOption(o, received)), + [channelTypesKey]: option.channelTypes ?? option.channel_types, + [minValueKey]: option.minValue ?? option.min_value, + [maxValueKey]: option.maxValue ?? option.max_value, + [minLengthKey]: option.minLength ?? option.min_length, + [maxLengthKey]: option.maxLength ?? option.max_length, + }; + } +} + +module.exports = ApplicationCommand; + +/* eslint-disable max-len */ +/** + * @external APIApplicationCommand + * @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure} + */ + +/** + * @external APIApplicationCommandOption + * @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure} + */ + +/** + * @external ApplicationCommandOptionAllowedChannelTypes + * @see {@link https://discord.js.org/docs/packages/builders/stable/ApplicationCommandOptionAllowedChannelTypes:TypeAlias} + */ diff --git a/node_modules/discord.js/src/structures/ApplicationRoleConnectionMetadata.js b/node_modules/discord.js/src/structures/ApplicationRoleConnectionMetadata.js new file mode 100644 index 0000000..7ed9b33 --- /dev/null +++ b/node_modules/discord.js/src/structures/ApplicationRoleConnectionMetadata.js @@ -0,0 +1,46 @@ +'use strict'; + +/** + * Role connection metadata object for an application. + */ +class ApplicationRoleConnectionMetadata { + constructor(data) { + /** + * The name of this metadata field + * @type {string} + */ + this.name = data.name; + + /** + * The name localizations for this metadata field + * @type {?Object<Locale, string>} + */ + this.nameLocalizations = data.name_localizations ?? null; + + /** + * The description of this metadata field + * @type {string} + */ + this.description = data.description; + + /** + * The description localizations for this metadata field + * @type {?Object<Locale, string>} + */ + this.descriptionLocalizations = data.description_localizations ?? null; + + /** + * The dictionary key for this metadata field + * @type {string} + */ + this.key = data.key; + + /** + * The type of this metadata field + * @type {ApplicationRoleConnectionMetadataType} + */ + this.type = data.type; + } +} + +exports.ApplicationRoleConnectionMetadata = ApplicationRoleConnectionMetadata; diff --git a/node_modules/discord.js/src/structures/Attachment.js b/node_modules/discord.js/src/structures/Attachment.js new file mode 100644 index 0000000..2576ff5 --- /dev/null +++ b/node_modules/discord.js/src/structures/Attachment.js @@ -0,0 +1,151 @@ +'use strict'; + +const AttachmentFlagsBitField = require('../util/AttachmentFlagsBitField.js'); +const { basename, flatten } = require('../util/Util'); + +/** + * @typedef {Object} AttachmentPayload + * @property {?string} name The name of the attachment + * @property {Stream|BufferResolvable} attachment The attachment in this payload + * @property {?string} description The description of the attachment + */ + +/** + * Represents an attachment + */ +class Attachment { + constructor(data) { + this.attachment = data.url; + /** + * The name of this attachment + * @type {string} + */ + this.name = data.filename; + this._patch(data); + } + + _patch(data) { + /** + * The attachment's id + * @type {Snowflake} + */ + this.id = data.id; + + if ('size' in data) { + /** + * The size of this attachment in bytes + * @type {number} + */ + this.size = data.size; + } + + if ('url' in data) { + /** + * The URL to this attachment + * @type {string} + */ + this.url = data.url; + } + + if ('proxy_url' in data) { + /** + * The Proxy URL to this attachment + * @type {string} + */ + this.proxyURL = data.proxy_url; + } + + if ('height' in data) { + /** + * The height of this attachment (if an image or video) + * @type {?number} + */ + this.height = data.height; + } else { + this.height ??= null; + } + + if ('width' in data) { + /** + * The width of this attachment (if an image or video) + * @type {?number} + */ + this.width = data.width; + } else { + this.width ??= null; + } + + if ('content_type' in data) { + /** + * The media type of this attachment + * @type {?string} + */ + this.contentType = data.content_type; + } else { + this.contentType ??= null; + } + + if ('description' in data) { + /** + * The description (alt text) of this attachment + * @type {?string} + */ + this.description = data.description; + } else { + this.description ??= null; + } + + /** + * Whether this attachment is ephemeral + * @type {boolean} + */ + this.ephemeral = data.ephemeral ?? false; + + if ('duration_secs' in data) { + /** + * The duration of this attachment in seconds + * <info>This will only be available if the attachment is an audio file.</info> + * @type {?number} + */ + this.duration = data.duration_secs; + } else { + this.duration ??= null; + } + + if ('waveform' in data) { + /** + * The base64 encoded byte array representing a sampled waveform + * <info>This will only be available if the attachment is an audio file.</info> + * @type {?string} + */ + this.waveform = data.waveform; + } else { + this.waveform ??= null; + } + + if ('flags' in data) { + /** + * The flags of this attachment + * @type {Readonly<AttachmentFlagsBitField>} + */ + this.flags = new AttachmentFlagsBitField(data.flags).freeze(); + } else { + this.flags ??= new AttachmentFlagsBitField().freeze(); + } + } + + /** + * Whether or not this attachment has been marked as a spoiler + * @type {boolean} + * @readonly + */ + get spoiler() { + return basename(this.url ?? this.name).startsWith('SPOILER_'); + } + + toJSON() { + return flatten(this); + } +} + +module.exports = Attachment; diff --git a/node_modules/discord.js/src/structures/AttachmentBuilder.js b/node_modules/discord.js/src/structures/AttachmentBuilder.js new file mode 100644 index 0000000..6c63810 --- /dev/null +++ b/node_modules/discord.js/src/structures/AttachmentBuilder.js @@ -0,0 +1,116 @@ +'use strict'; + +const { basename, flatten } = require('../util/Util'); + +/** + * Represents an attachment builder + */ +class AttachmentBuilder { + /** + * @param {BufferResolvable|Stream} attachment The file + * @param {AttachmentData} [data] Extra data + */ + constructor(attachment, data = {}) { + /** + * The file associated with this attachment. + * @type {BufferResolvable|Stream} + */ + this.attachment = attachment; + /** + * The name of this attachment + * @type {?string} + */ + this.name = data.name; + /** + * The description of the attachment + * @type {?string} + */ + this.description = data.description; + } + + /** + * Sets the description of this attachment. + * @param {string} description The description of the file + * @returns {AttachmentBuilder} This attachment + */ + setDescription(description) { + this.description = description; + return this; + } + + /** + * Sets the file of this attachment. + * @param {BufferResolvable|Stream} attachment The file + * @returns {AttachmentBuilder} This attachment + */ + setFile(attachment) { + this.attachment = attachment; + return this; + } + + /** + * Sets the name of this attachment. + * @param {string} name The name of the file + * @returns {AttachmentBuilder} This attachment + */ + setName(name) { + this.name = name; + return this; + } + + /** + * Sets whether this attachment is a spoiler + * @param {boolean} [spoiler=true] Whether the attachment should be marked as a spoiler + * @returns {AttachmentBuilder} This attachment + */ + setSpoiler(spoiler = true) { + if (spoiler === this.spoiler) return this; + + if (!spoiler) { + while (this.spoiler) { + this.name = this.name.slice('SPOILER_'.length); + } + return this; + } + this.name = `SPOILER_${this.name}`; + return this; + } + + /** + * Whether or not this attachment has been marked as a spoiler + * @type {boolean} + * @readonly + */ + get spoiler() { + return basename(this.name).startsWith('SPOILER_'); + } + + toJSON() { + return flatten(this); + } + + /** + * Makes a new builder instance from a preexisting attachment structure. + * @param {AttachmentBuilder|Attachment|AttachmentPayload} other The builder to construct a new instance from + * @returns {AttachmentBuilder} + */ + static from(other) { + return new AttachmentBuilder(other.attachment, { + name: other.name, + description: other.description, + }); + } +} + +module.exports = AttachmentBuilder; + +/** + * @external APIAttachment + * @see {@link https://discord.com/developers/docs/resources/channel#attachment-object} + */ + +/** + * @typedef {Object} AttachmentData + * @property {string} [name] The name of the attachment + * @property {string} [description] The description of the attachment + */ diff --git a/node_modules/discord.js/src/structures/AutoModerationActionExecution.js b/node_modules/discord.js/src/structures/AutoModerationActionExecution.js new file mode 100644 index 0000000..fcbc617 --- /dev/null +++ b/node_modules/discord.js/src/structures/AutoModerationActionExecution.js @@ -0,0 +1,116 @@ +'use strict'; + +const { _transformAPIAutoModerationAction } = require('../util/Transformers'); + +/** + * Represents the structure of an executed action when an {@link AutoModerationRule} is triggered. + */ +class AutoModerationActionExecution { + constructor(data, guild) { + /** + * The guild where this action was executed from. + * @type {Guild} + */ + this.guild = guild; + + /** + * The action that was executed. + * @type {AutoModerationAction} + */ + this.action = _transformAPIAutoModerationAction(data.action); + + /** + * The id of the auto moderation rule this action belongs to. + * @type {Snowflake} + */ + this.ruleId = data.rule_id; + + /** + * The trigger type of the auto moderation rule which was triggered. + * @type {AutoModerationRuleTriggerType} + */ + this.ruleTriggerType = data.rule_trigger_type; + + /** + * The id of the user that triggered this action. + * @type {Snowflake} + */ + this.userId = data.user_id; + + /** + * The id of the channel where this action was triggered from. + * @type {?Snowflake} + */ + this.channelId = data.channel_id ?? null; + + /** + * The id of the message that triggered this action. + * <info>This will not be present if the message was blocked or the content was not part of any message.</info> + * @type {?Snowflake} + */ + this.messageId = data.message_id ?? null; + + /** + * The id of any system auto moderation messages posted as a result of this action. + * @type {?Snowflake} + */ + this.alertSystemMessageId = data.alert_system_message_id ?? null; + + /** + * The content that triggered this action. + * <info>This property requires the {@link GatewayIntentBits.MessageContent} privileged gateway intent.</info> + * @type {string} + */ + this.content = data.content; + + /** + * The word or phrase configured in the rule that triggered this action. + * @type {?string} + */ + this.matchedKeyword = data.matched_keyword ?? null; + + /** + * The substring in content that triggered this action. + * @type {?string} + */ + this.matchedContent = data.matched_content ?? null; + } + + /** + * The auto moderation rule this action belongs to. + * @type {?AutoModerationRule} + * @readonly + */ + get autoModerationRule() { + return this.guild.autoModerationRules.cache.get(this.ruleId) ?? null; + } + + /** + * The channel where this action was triggered from. + * @type {?(GuildTextBasedChannel|ForumChannel)} + * @readonly + */ + get channel() { + return this.guild.channels.cache.get(this.channelId) ?? null; + } + + /** + * The user that triggered this action. + * @type {?User} + * @readonly + */ + get user() { + return this.guild.client.users.cache.get(this.userId) ?? null; + } + + /** + * The guild member that triggered this action. + * @type {?GuildMember} + * @readonly + */ + get member() { + return this.guild.members.cache.get(this.userId) ?? null; + } +} + +module.exports = AutoModerationActionExecution; diff --git a/node_modules/discord.js/src/structures/AutoModerationRule.js b/node_modules/discord.js/src/structures/AutoModerationRule.js new file mode 100644 index 0000000..e87f547 --- /dev/null +++ b/node_modules/discord.js/src/structures/AutoModerationRule.js @@ -0,0 +1,284 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Base = require('./Base'); +const { _transformAPIAutoModerationAction } = require('../util/Transformers'); + +/** + * Represents an auto moderation rule. + * @extends {Base} + */ +class AutoModerationRule extends Base { + constructor(client, data, guild) { + super(client); + + /** + * The id of this auto moderation rule. + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The guild this auto moderation rule is for. + * @type {Guild} + */ + this.guild = guild; + + /** + * The user that created this auto moderation rule. + * @type {Snowflake} + */ + this.creatorId = data.creator_id; + + /** + * The trigger type of this auto moderation rule. + * @type {AutoModerationRuleTriggerType} + */ + this.triggerType = data.trigger_type; + + this._patch(data); + } + + _patch(data) { + if ('name' in data) { + /** + * The name of this auto moderation rule. + * @type {string} + */ + this.name = data.name; + } + + if ('event_type' in data) { + /** + * The event type of this auto moderation rule. + * @type {AutoModerationRuleEventType} + */ + this.eventType = data.event_type; + } + + if ('trigger_metadata' in data) { + /** + * Additional data used to determine whether an auto moderation rule should be triggered. + * @typedef {Object} AutoModerationTriggerMetadata + * @property {string[]} keywordFilter The substrings that will be searched for in the content + * @property {string[]} regexPatterns The regular expression patterns which will be matched against the content + * <info>Only Rust-flavored regular expressions are supported.</info> + * @property {AutoModerationRuleKeywordPresetType[]} presets + * The internally pre-defined wordsets which will be searched for in the content + * @property {string[]} allowList The substrings that will be exempt from triggering + * {@link AutoModerationRuleTriggerType.Keyword} and {@link AutoModerationRuleTriggerType.KeywordPreset} + * @property {?number} mentionTotalLimit The total number of role & user mentions allowed per message + * @property {boolean} mentionRaidProtectionEnabled Whether mention raid protection is enabled + */ + + /** + * The trigger metadata of the rule. + * @type {AutoModerationTriggerMetadata} + */ + this.triggerMetadata = { + keywordFilter: data.trigger_metadata.keyword_filter ?? [], + regexPatterns: data.trigger_metadata.regex_patterns ?? [], + presets: data.trigger_metadata.presets ?? [], + allowList: data.trigger_metadata.allow_list ?? [], + mentionTotalLimit: data.trigger_metadata.mention_total_limit ?? null, + mentionRaidProtectionEnabled: data.trigger_metadata.mention_raid_protection_enabled ?? false, + }; + } + + if ('actions' in data) { + /** + * An object containing information about an auto moderation rule action. + * @typedef {Object} AutoModerationAction + * @property {AutoModerationActionType} type The type of this auto moderation rule action + * @property {AutoModerationActionMetadata} metadata Additional metadata needed during execution + */ + + /** + * Additional data used when an auto moderation rule is executed. + * @typedef {Object} AutoModerationActionMetadata + * @property {?Snowflake} channelId The id of the channel to which content will be logged + * @property {?number} durationSeconds The timeout duration in seconds + * @property {?string} customMessage The custom message that is shown whenever a message is blocked + */ + + /** + * The actions of this auto moderation rule. + * @type {AutoModerationAction[]} + */ + this.actions = data.actions.map(action => _transformAPIAutoModerationAction(action)); + } + + if ('enabled' in data) { + /** + * Whether this auto moderation rule is enabled. + * @type {boolean} + */ + this.enabled = data.enabled; + } + + if ('exempt_roles' in data) { + /** + * The roles exempt by this auto moderation rule. + * @type {Collection<Snowflake, Role>} + */ + this.exemptRoles = new Collection( + data.exempt_roles.map(exemptRole => [exemptRole, this.guild.roles.cache.get(exemptRole)]), + ); + } + + if ('exempt_channels' in data) { + /** + * The channels exempt by this auto moderation rule. + * @type {Collection<Snowflake, GuildChannel|ThreadChannel>} + */ + this.exemptChannels = new Collection( + data.exempt_channels.map(exemptChannel => [exemptChannel, this.guild.channels.cache.get(exemptChannel)]), + ); + } + } + + /** + * Edits this auto moderation rule. + * @param {AutoModerationRuleEditOptions} options Options for editing this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + edit(options) { + return this.guild.autoModerationRules.edit(this.id, options); + } + + /** + * Deletes this auto moderation rule. + * @param {string} [reason] The reason for deleting this auto moderation rule + * @returns {Promise<void>} + */ + delete(reason) { + return this.guild.autoModerationRules.delete(this.id, reason); + } + + /** + * Sets the name for this auto moderation rule. + * @param {string} name The name of this auto moderation rule + * @param {string} [reason] The reason for changing the name of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setName(name, reason) { + return this.edit({ name, reason }); + } + + /** + * Sets the event type for this auto moderation rule. + * @param {AutoModerationRuleEventType} eventType The event type of this auto moderation rule + * @param {string} [reason] The reason for changing the event type of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setEventType(eventType, reason) { + return this.edit({ eventType, reason }); + } + + /** + * Sets the keyword filter for this auto moderation rule. + * @param {string[]} keywordFilter The keyword filter of this auto moderation rule + * @param {string} [reason] The reason for changing the keyword filter of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setKeywordFilter(keywordFilter, reason) { + return this.edit({ triggerMetadata: { ...this.triggerMetadata, keywordFilter }, reason }); + } + + /** + * Sets the regular expression patterns for this auto moderation rule. + * @param {string[]} regexPatterns The regular expression patterns of this auto moderation rule + * <info>Only Rust-flavored regular expressions are supported.</info> + * @param {string} [reason] The reason for changing the regular expression patterns of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setRegexPatterns(regexPatterns, reason) { + return this.edit({ triggerMetadata: { ...this.triggerMetadata, regexPatterns }, reason }); + } + + /** + * Sets the presets for this auto moderation rule. + * @param {AutoModerationRuleKeywordPresetType[]} presets The presets of this auto moderation rule + * @param {string} [reason] The reason for changing the presets of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setPresets(presets, reason) { + return this.edit({ triggerMetadata: { ...this.triggerMetadata, presets }, reason }); + } + + /** + * Sets the allow list for this auto moderation rule. + * @param {string[]} allowList The substrings that will be exempt from triggering + * {@link AutoModerationRuleTriggerType.Keyword} and {@link AutoModerationRuleTriggerType.KeywordPreset} + * @param {string} [reason] The reason for changing the allow list of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setAllowList(allowList, reason) { + return this.edit({ triggerMetadata: { ...this.triggerMetadata, allowList }, reason }); + } + + /** + * Sets the mention total limit for this auto moderation rule. + * @param {number} mentionTotalLimit The total number of unique role and user mentions allowed per message + * @param {string} [reason] The reason for changing the mention total limit of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setMentionTotalLimit(mentionTotalLimit, reason) { + return this.edit({ triggerMetadata: { ...this.triggerMetadata, mentionTotalLimit }, reason }); + } + + /** + * Sets whether to enable mention raid protection for this auto moderation rule. + * @param {boolean} mentionRaidProtectionEnabled + * Whether to enable mention raid protection for this auto moderation rule + * @param {string} [reason] The reason for changing the mention raid protection of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setMentionRaidProtectionEnabled(mentionRaidProtectionEnabled, reason) { + return this.edit({ triggerMetadata: { ...this.triggerMetadata, mentionRaidProtectionEnabled }, reason }); + } + + /** + * Sets the actions for this auto moderation rule. + * @param {AutoModerationActionOptions[]} actions The actions of this auto moderation rule + * @param {string} [reason] The reason for changing the actions of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setActions(actions, reason) { + return this.edit({ actions, reason }); + } + + /** + * Sets whether this auto moderation rule should be enabled. + * @param {boolean} [enabled=true] Whether to enable this auto moderation rule + * @param {string} [reason] The reason for enabling or disabling this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setEnabled(enabled = true, reason) { + return this.edit({ enabled, reason }); + } + + /** + * Sets the exempt roles for this auto moderation rule. + * @param {Collection<Snowflake, Role>|RoleResolvable[]} [exemptRoles] + * The roles that should not be affected by the auto moderation rule + * @param {string} [reason] The reason for changing the exempt roles of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setExemptRoles(exemptRoles, reason) { + return this.edit({ exemptRoles, reason }); + } + + /** + * Sets the exempt channels for this auto moderation rule. + * @param {Collection<Snowflake, GuildChannel|ThreadChannel>|GuildChannelResolvable[]} [exemptChannels] + * The channels that should not be affected by the auto moderation rule + * @param {string} [reason] The reason for changing the exempt channels of this auto moderation rule + * @returns {Promise<AutoModerationRule>} + */ + setExemptChannels(exemptChannels, reason) { + return this.edit({ exemptChannels, reason }); + } +} + +module.exports = AutoModerationRule; diff --git a/node_modules/discord.js/src/structures/AutocompleteInteraction.js b/node_modules/discord.js/src/structures/AutocompleteInteraction.js new file mode 100644 index 0000000..4b7e39e --- /dev/null +++ b/node_modules/discord.js/src/structures/AutocompleteInteraction.js @@ -0,0 +1,102 @@ +'use strict'; + +const { InteractionResponseType, Routes } = require('discord-api-types/v10'); +const BaseInteraction = require('./BaseInteraction'); +const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver'); +const { DiscordjsError, ErrorCodes } = require('../errors'); + +/** + * Represents an autocomplete interaction. + * @extends {BaseInteraction} + */ +class AutocompleteInteraction extends BaseInteraction { + constructor(client, data) { + super(client, data); + + /** + * The id of the channel this interaction was sent in + * @type {Snowflake} + * @name AutocompleteInteraction#channelId + */ + + /** + * The invoked application command's id + * @type {Snowflake} + */ + this.commandId = data.data.id; + + /** + * The invoked application command's name + * @type {string} + */ + this.commandName = data.data.name; + + /** + * The invoked application command's type + * @type {ApplicationCommandType} + */ + this.commandType = data.data.type; + + /** + * The id of the guild the invoked application command is registered to + * @type {?Snowflake} + */ + this.commandGuildId = data.data.guild_id ?? null; + + /** + * Whether this interaction has already received a response + * @type {boolean} + */ + this.responded = false; + + /** + * The options passed to the command + * @type {CommandInteractionOptionResolver} + */ + this.options = new CommandInteractionOptionResolver(this.client, data.data.options ?? []); + } + + /** + * The invoked application command, if it was fetched before + * @type {?ApplicationCommand} + */ + get command() { + const id = this.commandId; + return this.guild?.commands.cache.get(id) ?? this.client.application.commands.cache.get(id) ?? null; + } + + /** + * Sends results for the autocomplete of this interaction. + * @param {ApplicationCommandOptionChoiceData[]} options The options for the autocomplete + * @returns {Promise<void>} + * @example + * // respond to autocomplete interaction + * interaction.respond([ + * { + * name: 'Option 1', + * value: 'option1', + * }, + * ]) + * .then(() => console.log('Successfully responded to the autocomplete interaction')) + * .catch(console.error); + */ + async respond(options) { + if (this.responded) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied); + + await this.client.rest.post(Routes.interactionCallback(this.id, this.token), { + body: { + type: InteractionResponseType.ApplicationCommandAutocompleteResult, + data: { + choices: options.map(({ nameLocalizations, ...option }) => ({ + ...this.client.options.jsonTransformer(option), + name_localizations: nameLocalizations, + })), + }, + }, + auth: false, + }); + this.responded = true; + } +} + +module.exports = AutocompleteInteraction; diff --git a/node_modules/discord.js/src/structures/Base.js b/node_modules/discord.js/src/structures/Base.js new file mode 100644 index 0000000..102fb21 --- /dev/null +++ b/node_modules/discord.js/src/structures/Base.js @@ -0,0 +1,43 @@ +'use strict'; + +const { flatten } = require('../util/Util'); + +/** + * Represents a data model that is identifiable by a Snowflake (i.e. Discord API data models). + * @abstract + */ +class Base { + constructor(client) { + /** + * The client that instantiated this + * @name Base#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + } + + _clone() { + return Object.assign(Object.create(this), this); + } + + _patch(data) { + return data; + } + + _update(data) { + const clone = this._clone(); + this._patch(data); + return clone; + } + + toJSON(...props) { + return flatten(this, ...props); + } + + valueOf() { + return this.id; + } +} + +module.exports = Base; diff --git a/node_modules/discord.js/src/structures/BaseChannel.js b/node_modules/discord.js/src/structures/BaseChannel.js new file mode 100644 index 0000000..346f763 --- /dev/null +++ b/node_modules/discord.js/src/structures/BaseChannel.js @@ -0,0 +1,155 @@ +'use strict'; + +const { channelLink } = require('@discordjs/builders'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { ChannelType, Routes } = require('discord-api-types/v10'); +const Base = require('./Base'); +const ChannelFlagsBitField = require('../util/ChannelFlagsBitField'); +const { ThreadChannelTypes } = require('../util/Constants'); + +/** + * Represents any channel on Discord. + * @extends {Base} + * @abstract + */ +class BaseChannel extends Base { + constructor(client, data, immediatePatch = true) { + super(client); + + /** + * The type of the channel + * @type {ChannelType} + */ + this.type = data.type; + + if (data && immediatePatch) this._patch(data); + } + + _patch(data) { + if ('flags' in data) { + /** + * The flags that are applied to the channel. + * <info>This is only `null` in a {@link PartialGroupDMChannel}. In all other cases, it is not `null`.</info> + * @type {?Readonly<ChannelFlagsBitField>} + */ + this.flags = new ChannelFlagsBitField(data.flags).freeze(); + } else { + this.flags ??= new ChannelFlagsBitField().freeze(); + } + + /** + * The channel's id + * @type {Snowflake} + */ + this.id = data.id; + } + + /** + * The timestamp the channel was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the channel was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The URL to the channel + * @type {string} + * @readonly + */ + get url() { + return this.isDMBased() ? channelLink(this.id) : channelLink(this.id, this.guildId); + } + + /** + * Whether this Channel is a partial + * <info>This is always false outside of DM channels.</info> + * @type {boolean} + * @readonly + */ + get partial() { + return false; + } + + /** + * When concatenated with a string, this automatically returns the channel's mention instead of the Channel object. + * @returns {string} + * @example + * // Logs: Hello from <#123456789012345678>! + * console.log(`Hello from ${channel}!`); + */ + toString() { + return `<#${this.id}>`; + } + + /** + * Deletes this channel. + * @returns {Promise<BaseChannel>} + * @example + * // Delete the channel + * channel.delete() + * .then(console.log) + * .catch(console.error); + */ + async delete() { + await this.client.rest.delete(Routes.channel(this.id)); + return this; + } + + /** + * Fetches this channel. + * @param {boolean} [force=true] Whether to skip the cache check and request the API + * @returns {Promise<BaseChannel>} + */ + fetch(force = true) { + return this.client.channels.fetch(this.id, { force }); + } + + /** + * Indicates whether this channel is a {@link ThreadChannel}. + * @returns {boolean} + */ + isThread() { + return ThreadChannelTypes.includes(this.type); + } + + /** + * Indicates whether this channel is {@link TextBasedChannels text-based}. + * @returns {boolean} + */ + isTextBased() { + return 'messages' in this; + } + + /** + * Indicates whether this channel is DM-based (either a {@link DMChannel} or a {@link PartialGroupDMChannel}). + * @returns {boolean} + */ + isDMBased() { + return [ChannelType.DM, ChannelType.GroupDM].includes(this.type); + } + + /** + * Indicates whether this channel is {@link BaseGuildVoiceChannel voice-based}. + * @returns {boolean} + */ + isVoiceBased() { + return 'bitrate' in this; + } + + toJSON(...props) { + return super.toJSON({ createdTimestamp: true }, ...props); + } +} + +exports.BaseChannel = BaseChannel; diff --git a/node_modules/discord.js/src/structures/BaseGuild.js b/node_modules/discord.js/src/structures/BaseGuild.js new file mode 100644 index 0000000..b12ca44 --- /dev/null +++ b/node_modules/discord.js/src/structures/BaseGuild.js @@ -0,0 +1,119 @@ +'use strict'; + +const { makeURLSearchParams } = require('@discordjs/rest'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { Routes, GuildFeature } = require('discord-api-types/v10'); +const Base = require('./Base'); + +/** + * The base class for {@link Guild}, {@link OAuth2Guild} and {@link InviteGuild}. + * @extends {Base} + * @abstract + */ +class BaseGuild extends Base { + constructor(client, data) { + super(client); + + /** + * The guild's id + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The name of this guild + * @type {string} + */ + this.name = data.name; + + /** + * The icon hash of this guild + * @type {?string} + */ + this.icon = data.icon; + + /** + * An array of features available to this guild + * @type {GuildFeature[]} + */ + this.features = data.features; + } + + /** + * The timestamp this guild was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time this guild was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The acronym that shows up in place of a guild icon + * @type {string} + * @readonly + */ + get nameAcronym() { + return this.name + .replace(/'s /g, ' ') + .replace(/\w+/g, e => e[0]) + .replace(/\s/g, ''); + } + + /** + * Whether this guild is partnered + * @type {boolean} + * @readonly + */ + get partnered() { + return this.features.includes(GuildFeature.Partnered); + } + + /** + * Whether this guild is verified + * @type {boolean} + * @readonly + */ + get verified() { + return this.features.includes(GuildFeature.Verified); + } + + /** + * The URL to this guild's icon. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + iconURL(options = {}) { + return this.icon && this.client.rest.cdn.icon(this.id, this.icon, options); + } + + /** + * Fetches this guild. + * @returns {Promise<Guild>} + */ + async fetch() { + const data = await this.client.rest.get(Routes.guild(this.id), { + query: makeURLSearchParams({ with_counts: true }), + }); + return this.client.guilds._add(data); + } + + /** + * When concatenated with a string, this automatically returns the guild's name instead of the Guild object. + * @returns {string} + */ + toString() { + return this.name; + } +} + +module.exports = BaseGuild; diff --git a/node_modules/discord.js/src/structures/BaseGuildEmoji.js b/node_modules/discord.js/src/structures/BaseGuildEmoji.js new file mode 100644 index 0000000..5a12bd9 --- /dev/null +++ b/node_modules/discord.js/src/structures/BaseGuildEmoji.js @@ -0,0 +1,56 @@ +'use strict'; + +const { Emoji } = require('./Emoji'); + +/** + * Parent class for {@link GuildEmoji} and {@link GuildPreviewEmoji}. + * @extends {Emoji} + * @abstract + */ +class BaseGuildEmoji extends Emoji { + constructor(client, data, guild) { + super(client, data); + + /** + * The guild this emoji is a part of + * @type {Guild|GuildPreview} + */ + this.guild = guild; + + this.requiresColons = null; + this.managed = null; + this.available = null; + + this._patch(data); + } + + _patch(data) { + if ('name' in data) this.name = data.name; + + if ('require_colons' in data) { + /** + * Whether or not this emoji requires colons surrounding it + * @type {?boolean} + */ + this.requiresColons = data.require_colons; + } + + if ('managed' in data) { + /** + * Whether this emoji is managed by an external service + * @type {?boolean} + */ + this.managed = data.managed; + } + + if ('available' in data) { + /** + * Whether this emoji is available + * @type {?boolean} + */ + this.available = data.available; + } + } +} + +module.exports = BaseGuildEmoji; diff --git a/node_modules/discord.js/src/structures/BaseGuildTextChannel.js b/node_modules/discord.js/src/structures/BaseGuildTextChannel.js new file mode 100644 index 0000000..f7d9d69 --- /dev/null +++ b/node_modules/discord.js/src/structures/BaseGuildTextChannel.js @@ -0,0 +1,186 @@ +'use strict'; + +const GuildChannel = require('./GuildChannel'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const GuildMessageManager = require('../managers/GuildMessageManager'); +const GuildTextThreadManager = require('../managers/GuildTextThreadManager'); + +/** + * Represents a text-based guild channel on Discord. + * @extends {GuildChannel} + * @implements {TextBasedChannel} + */ +class BaseGuildTextChannel extends GuildChannel { + constructor(guild, data, client) { + super(guild, data, client, false); + + /** + * A manager of the messages sent to this channel + * @type {GuildMessageManager} + */ + this.messages = new GuildMessageManager(this); + + /** + * A manager of the threads belonging to this channel + * @type {GuildTextThreadManager} + */ + this.threads = new GuildTextThreadManager(this); + + /** + * If the guild considers this channel NSFW + * @type {boolean} + */ + this.nsfw = Boolean(data.nsfw); + + this._patch(data); + } + + _patch(data) { + super._patch(data); + + if ('topic' in data) { + /** + * The topic of the text channel + * @type {?string} + */ + this.topic = data.topic; + } + + if ('nsfw' in data) { + this.nsfw = Boolean(data.nsfw); + } + + if ('last_message_id' in data) { + /** + * The last message id sent in the channel, if one was sent + * @type {?Snowflake} + */ + this.lastMessageId = data.last_message_id; + } + + if ('last_pin_timestamp' in data) { + /** + * The timestamp when the last pinned message was pinned, if there was one + * @type {?number} + */ + this.lastPinTimestamp = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null; + } + + if ('default_auto_archive_duration' in data) { + /** + * The default auto archive duration for newly created threads in this channel + * @type {?ThreadAutoArchiveDuration} + */ + this.defaultAutoArchiveDuration = data.default_auto_archive_duration; + } + + if ('messages' in data) { + for (const message of data.messages) this.messages._add(message); + } + } + + /** + * Sets the default auto archive duration for all newly created threads in this channel. + * @param {ThreadAutoArchiveDuration} defaultAutoArchiveDuration The new default auto archive duration + * @param {string} [reason] Reason for changing the channel's default auto archive duration + * @returns {Promise<TextChannel>} + */ + setDefaultAutoArchiveDuration(defaultAutoArchiveDuration, reason) { + return this.edit({ defaultAutoArchiveDuration, reason }); + } + + /** + * Sets the type of this channel. + * <info>Only conversion between {@link TextChannel} and {@link NewsChannel} is supported.</info> + * @param {ChannelType.GuildText|ChannelType.GuildAnnouncement} type The new channel type + * @param {string} [reason] Reason for changing the channel's type + * @returns {Promise<GuildChannel>} + */ + setType(type, reason) { + return this.edit({ type, reason }); + } + + /** + * Sets a new topic for the guild channel. + * @param {?string} topic The new topic for the guild channel + * @param {string} [reason] Reason for changing the guild channel's topic + * @returns {Promise<GuildChannel>} + * @example + * // Set a new channel topic + * channel.setTopic('needs more rate limiting') + * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`)) + * .catch(console.error); + */ + setTopic(topic, reason) { + return this.edit({ topic, reason }); + } + + /** + * Data that can be resolved to an Application. This can be: + * * An Application + * * An Activity with associated Application + * * A Snowflake + * @typedef {Application|Snowflake} ApplicationResolvable + */ + + /** + * Options used to create an invite to a guild channel. + * @typedef {Object} InviteCreateOptions + * @property {boolean} [temporary] Whether members that joined via the invite should be automatically + * kicked after 24 hours if they have not yet received a role + * @property {number} [maxAge] How long the invite should last (in seconds, 0 for forever) + * @property {number} [maxUses] Maximum number of uses + * @property {boolean} [unique] Create a unique invite, or use an existing one with similar settings + * @property {UserResolvable} [targetUser] The user whose stream to display for this invite, + * required if `targetType` is {@link InviteTargetType.Stream}, the user must be streaming in the channel + * @property {ApplicationResolvable} [targetApplication] The embedded application to open for this invite, + * required if `targetType` is {@link InviteTargetType.Stream}, the application must have the + * {@link InviteTargetType.EmbeddedApplication} flag + * @property {InviteTargetType} [targetType] The type of the target for this voice channel invite + * @property {string} [reason] The reason for creating the invite + */ + + /** + * Creates an invite to this guild channel. + * @param {InviteCreateOptions} [options={}] The options for creating the invite + * @returns {Promise<Invite>} + * @example + * // Create an invite to a channel + * channel.createInvite() + * .then(invite => console.log(`Created an invite with a code of ${invite.code}`)) + * .catch(console.error); + */ + createInvite(options) { + return this.guild.invites.create(this.id, options); + } + + /** + * Fetches a collection of invites to this guild channel. + * Resolves with a collection mapping invites by their codes. + * @param {boolean} [cache=true] Whether or not to cache the fetched invites + * @returns {Promise<Collection<string, Invite>>} + */ + fetchInvites(cache = true) { + return this.guild.invites.fetch({ channelId: this.id, cache }); + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + get lastMessage() {} + get lastPinAt() {} + send() {} + sendTyping() {} + createMessageCollector() {} + awaitMessages() {} + createMessageComponentCollector() {} + awaitMessageComponent() {} + bulkDelete() {} + fetchWebhooks() {} + createWebhook() {} + setRateLimitPerUser() {} + setNSFW() {} +} + +TextBasedChannel.applyToClass(BaseGuildTextChannel, true); + +module.exports = BaseGuildTextChannel; diff --git a/node_modules/discord.js/src/structures/BaseGuildVoiceChannel.js b/node_modules/discord.js/src/structures/BaseGuildVoiceChannel.js new file mode 100644 index 0000000..220ac6c --- /dev/null +++ b/node_modules/discord.js/src/structures/BaseGuildVoiceChannel.js @@ -0,0 +1,234 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { PermissionFlagsBits } = require('discord-api-types/v10'); +const GuildChannel = require('./GuildChannel'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const GuildMessageManager = require('../managers/GuildMessageManager'); + +/** + * Represents a voice-based guild channel on Discord. + * @extends {GuildChannel} + * @implements {TextBasedChannel} + */ +class BaseGuildVoiceChannel extends GuildChannel { + constructor(guild, data, client) { + super(guild, data, client, false); + /** + * A manager of the messages sent to this channel + * @type {GuildMessageManager} + */ + this.messages = new GuildMessageManager(this); + + /** + * If the guild considers this channel NSFW + * @type {boolean} + */ + this.nsfw = Boolean(data.nsfw); + + this._patch(data); + } + + _patch(data) { + super._patch(data); + + if ('rtc_region' in data) { + /** + * The RTC region for this voice-based channel. This region is automatically selected if `null`. + * @type {?string} + */ + this.rtcRegion = data.rtc_region; + } + + if ('bitrate' in data) { + /** + * The bitrate of this voice-based channel + * @type {number} + */ + this.bitrate = data.bitrate; + } + + if ('user_limit' in data) { + /** + * The maximum amount of users allowed in this channel. + * @type {number} + */ + this.userLimit = data.user_limit; + } + + if ('video_quality_mode' in data) { + /** + * The camera video quality mode of the channel. + * @type {?VideoQualityMode} + */ + this.videoQualityMode = data.video_quality_mode; + } else { + this.videoQualityMode ??= null; + } + + if ('last_message_id' in data) { + /** + * The last message id sent in the channel, if one was sent + * @type {?Snowflake} + */ + this.lastMessageId = data.last_message_id; + } + + if ('messages' in data) { + for (const message of data.messages) this.messages._add(message); + } + + if ('rate_limit_per_user' in data) { + /** + * The rate limit per user (slowmode) for this channel in seconds + * @type {number} + */ + this.rateLimitPerUser = data.rate_limit_per_user; + } + + if ('nsfw' in data) { + this.nsfw = data.nsfw; + } + } + + /** + * The members in this voice-based channel + * @type {Collection<Snowflake, GuildMember>} + * @readonly + */ + get members() { + const coll = new Collection(); + for (const state of this.guild.voiceStates.cache.values()) { + if (state.channelId === this.id && state.member) { + coll.set(state.id, state.member); + } + } + return coll; + } + + /** + * Checks if the voice-based channel is full + * @type {boolean} + * @readonly + */ + get full() { + return this.userLimit > 0 && this.members.size >= this.userLimit; + } + + /** + * Whether the channel is joinable by the client user + * @type {boolean} + * @readonly + */ + get joinable() { + if (!this.viewable) return false; + const permissions = this.permissionsFor(this.client.user); + if (!permissions) return false; + + // This flag allows joining even if timed out + if (permissions.has(PermissionFlagsBits.Administrator, false)) return true; + + return ( + this.guild.members.me.communicationDisabledUntilTimestamp < Date.now() && + permissions.has(PermissionFlagsBits.Connect, false) + ); + } + + /** + * Creates an invite to this guild channel. + * @param {InviteCreateOptions} [options={}] The options for creating the invite + * @returns {Promise<Invite>} + * @example + * // Create an invite to a channel + * channel.createInvite() + * .then(invite => console.log(`Created an invite with a code of ${invite.code}`)) + * .catch(console.error); + */ + createInvite(options) { + return this.guild.invites.create(this.id, options); + } + + /** + * Fetches a collection of invites to this guild channel. + * @param {boolean} [cache=true] Whether to cache the fetched invites + * @returns {Promise<Collection<string, Invite>>} + */ + fetchInvites(cache = true) { + return this.guild.invites.fetch({ channelId: this.id, cache }); + } + + /** + * Sets the bitrate of the channel. + * @param {number} bitrate The new bitrate + * @param {string} [reason] Reason for changing the channel's bitrate + * @returns {Promise<BaseGuildVoiceChannel>} + * @example + * // Set the bitrate of a voice channel + * channel.setBitrate(48_000) + * .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`)) + * .catch(console.error); + */ + setBitrate(bitrate, reason) { + return this.edit({ bitrate, reason }); + } + + /** + * Sets the RTC region of the channel. + * @param {?string} rtcRegion The new region of the channel. Set to `null` to remove a specific region for the channel + * @param {string} [reason] The reason for modifying this region. + * @returns {Promise<BaseGuildVoiceChannel>} + * @example + * // Set the RTC region to sydney + * channel.setRTCRegion('sydney'); + * @example + * // Remove a fixed region for this channel - let Discord decide automatically + * channel.setRTCRegion(null, 'We want to let Discord decide.'); + */ + setRTCRegion(rtcRegion, reason) { + return this.edit({ rtcRegion, reason }); + } + + /** + * Sets the user limit of the channel. + * @param {number} userLimit The new user limit + * @param {string} [reason] Reason for changing the user limit + * @returns {Promise<BaseGuildVoiceChannel>} + * @example + * // Set the user limit of a voice channel + * channel.setUserLimit(42) + * .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`)) + * .catch(console.error); + */ + setUserLimit(userLimit, reason) { + return this.edit({ userLimit, reason }); + } + + /** + * Sets the camera video quality mode of the channel. + * @param {VideoQualityMode} videoQualityMode The new camera video quality mode. + * @param {string} [reason] Reason for changing the camera video quality mode. + * @returns {Promise<BaseGuildVoiceChannel>} + */ + setVideoQualityMode(videoQualityMode, reason) { + return this.edit({ videoQualityMode, reason }); + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + get lastMessage() {} + send() {} + sendTyping() {} + createMessageCollector() {} + awaitMessages() {} + createMessageComponentCollector() {} + awaitMessageComponent() {} + bulkDelete() {} + fetchWebhooks() {} + createWebhook() {} + setRateLimitPerUser() {} + setNSFW() {} +} + +TextBasedChannel.applyToClass(BaseGuildVoiceChannel, true, ['lastPinAt']); + +module.exports = BaseGuildVoiceChannel; diff --git a/node_modules/discord.js/src/structures/BaseInteraction.js b/node_modules/discord.js/src/structures/BaseInteraction.js new file mode 100644 index 0000000..967350f --- /dev/null +++ b/node_modules/discord.js/src/structures/BaseInteraction.js @@ -0,0 +1,344 @@ +'use strict'; + +const { deprecate } = require('node:util'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v10'); +const Base = require('./Base'); +const { SelectMenuTypes } = require('../util/Constants'); +const PermissionsBitField = require('../util/PermissionsBitField'); + +/** + * Represents an interaction. + * @extends {Base} + * @abstract + */ +class BaseInteraction extends Base { + constructor(client, data) { + super(client); + + /** + * The interaction's type + * @type {InteractionType} + */ + this.type = data.type; + + /** + * The interaction's id + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The interaction's token + * @type {string} + * @name BaseInteraction#token + * @readonly + */ + Object.defineProperty(this, 'token', { value: data.token }); + + /** + * The application's id + * @type {Snowflake} + */ + this.applicationId = data.application_id; + + /** + * The id of the channel this interaction was sent in + * @type {?Snowflake} + */ + this.channelId = data.channel?.id ?? null; + + /** + * The id of the guild this interaction was sent in + * @type {?Snowflake} + */ + this.guildId = data.guild_id ?? null; + + /** + * The user who created this interaction + * @type {User} + */ + this.user = this.client.users._add(data.user ?? data.member.user); + + /** + * If this interaction was sent in a guild, the member which sent it + * @type {?(GuildMember|APIGuildMember)} + */ + this.member = data.member ? this.guild?.members._add(data.member) ?? data.member : null; + + /** + * The version + * @type {number} + */ + this.version = data.version; + + /** + * Set of permissions the application or bot has within the channel the interaction was sent from + * @type {?Readonly<PermissionsBitField>} + */ + this.appPermissions = data.app_permissions ? new PermissionsBitField(data.app_permissions).freeze() : null; + + /** + * The permissions of the member, if one exists, in the channel this interaction was executed in + * @type {?Readonly<PermissionsBitField>} + */ + this.memberPermissions = data.member?.permissions + ? new PermissionsBitField(data.member.permissions).freeze() + : null; + + /** + * A Discord locale string, possible values are: + * * en-US (English, US) + * * en-GB (English, UK) + * * bg (Bulgarian) + * * zh-CN (Chinese, China) + * * zh-TW (Chinese, Taiwan) + * * hr (Croatian) + * * cs (Czech) + * * da (Danish) + * * nl (Dutch) + * * fi (Finnish) + * * fr (French) + * * de (German) + * * el (Greek) + * * hi (Hindi) + * * hu (Hungarian) + * * it (Italian) + * * ja (Japanese) + * * ko (Korean) + * * lt (Lithuanian) + * * no (Norwegian) + * * pl (Polish) + * * pt-BR (Portuguese, Brazilian) + * * ro (Romanian, Romania) + * * ru (Russian) + * * es-ES (Spanish) + * * sv-SE (Swedish) + * * th (Thai) + * * tr (Turkish) + * * uk (Ukrainian) + * * vi (Vietnamese) + * @see {@link https://discord.com/developers/docs/reference#locales} + * @typedef {string} Locale + */ + + /** + * The locale of the user who invoked this interaction + * @type {Locale} + */ + this.locale = data.locale; + + /** + * The preferred locale from the guild this interaction was sent in + * @type {?Locale} + */ + this.guildLocale = data.guild_locale ?? null; + } + + /** + * The timestamp the interaction was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the interaction was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The channel this interaction was sent in + * @type {?TextBasedChannels} + * @readonly + */ + get channel() { + return this.client.channels.cache.get(this.channelId) ?? null; + } + + /** + * The guild this interaction was sent in + * @type {?Guild} + * @readonly + */ + get guild() { + return this.client.guilds.cache.get(this.guildId) ?? null; + } + + /** + * Indicates whether this interaction is received from a guild. + * @returns {boolean} + */ + inGuild() { + return Boolean(this.guildId && this.member); + } + + /** + * Indicates whether or not this interaction is both cached and received from a guild. + * @returns {boolean} + */ + inCachedGuild() { + return Boolean(this.guild && this.member); + } + + /** + * Indicates whether or not this interaction is received from an uncached guild. + * @returns {boolean} + */ + inRawGuild() { + return Boolean(this.guildId && !this.guild && this.member); + } + + /** + * Indicates whether this interaction is an {@link AutocompleteInteraction} + * @returns {boolean} + */ + isAutocomplete() { + return this.type === InteractionType.ApplicationCommandAutocomplete; + } + + /** + * Indicates whether this interaction is a {@link CommandInteraction} + * @returns {boolean} + */ + isCommand() { + return this.type === InteractionType.ApplicationCommand; + } + + /** + * Indicates whether this interaction is a {@link ChatInputCommandInteraction}. + * @returns {boolean} + */ + isChatInputCommand() { + return this.type === InteractionType.ApplicationCommand && this.commandType === ApplicationCommandType.ChatInput; + } + + /** + * Indicates whether this interaction is a {@link ContextMenuCommandInteraction} + * @returns {boolean} + */ + isContextMenuCommand() { + return ( + this.type === InteractionType.ApplicationCommand && + [ApplicationCommandType.User, ApplicationCommandType.Message].includes(this.commandType) + ); + } + + /** + * Indicates whether this interaction is a {@link MessageComponentInteraction} + * @returns {boolean} + */ + isMessageComponent() { + return this.type === InteractionType.MessageComponent; + } + + /** + * Indicates whether this interaction is a {@link ModalSubmitInteraction} + * @returns {boolean} + */ + isModalSubmit() { + return this.type === InteractionType.ModalSubmit; + } + + /** + * Indicates whether this interaction is a {@link UserContextMenuCommandInteraction} + * @returns {boolean} + */ + isUserContextMenuCommand() { + return this.isContextMenuCommand() && this.commandType === ApplicationCommandType.User; + } + + /** + * Indicates whether this interaction is a {@link MessageContextMenuCommandInteraction} + * @returns {boolean} + */ + isMessageContextMenuCommand() { + return this.isContextMenuCommand() && this.commandType === ApplicationCommandType.Message; + } + + /** + * Indicates whether this interaction is a {@link ButtonInteraction}. + * @returns {boolean} + */ + isButton() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.Button; + } + + /** + * Indicates whether this interaction is a {@link StringSelectMenuInteraction}. + * @returns {boolean} + * @deprecated Use {@link BaseInteraction#isStringSelectMenu} instead. + */ + isSelectMenu() { + return this.isStringSelectMenu(); + } + + /** + * Indicates whether this interaction is a select menu of any known type. + * @returns {boolean} + */ + isAnySelectMenu() { + return this.type === InteractionType.MessageComponent && SelectMenuTypes.includes(this.componentType); + } + + /** + * Indicates whether this interaction is a {@link StringSelectMenuInteraction}. + * @returns {boolean} + */ + isStringSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.StringSelect; + } + + /** + * Indicates whether this interaction is a {@link UserSelectMenuInteraction} + * @returns {boolean} + */ + isUserSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.UserSelect; + } + + /** + * Indicates whether this interaction is a {@link RoleSelectMenuInteraction} + * @returns {boolean} + */ + isRoleSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.RoleSelect; + } + + /** + * Indicates whether this interaction is a {@link ChannelSelectMenuInteraction} + * @returns {boolean} + */ + isChannelSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.ChannelSelect; + } + + /** + * Indicates whether this interaction is a {@link MentionableSelectMenuInteraction} + * @returns {boolean} + */ + isMentionableSelectMenu() { + return this.type === InteractionType.MessageComponent && this.componentType === ComponentType.MentionableSelect; + } + + /** + * Indicates whether this interaction can be replied to. + * @returns {boolean} + */ + isRepliable() { + return ![InteractionType.Ping, InteractionType.ApplicationCommandAutocomplete].includes(this.type); + } +} + +BaseInteraction.prototype.isSelectMenu = deprecate( + BaseInteraction.prototype.isSelectMenu, + 'BaseInteraction#isSelectMenu() is deprecated. Use BaseInteraction#isStringSelectMenu() instead.', +); + +module.exports = BaseInteraction; diff --git a/node_modules/discord.js/src/structures/BaseSelectMenuComponent.js b/node_modules/discord.js/src/structures/BaseSelectMenuComponent.js new file mode 100644 index 0000000..7177f43 --- /dev/null +++ b/node_modules/discord.js/src/structures/BaseSelectMenuComponent.js @@ -0,0 +1,56 @@ +'use strict'; + +const Component = require('./Component'); + +/** + * Represents a select menu component + * @extends {Component} + */ +class BaseSelectMenuComponent extends Component { + /** + * The placeholder for this select menu + * @type {?string} + * @readonly + */ + get placeholder() { + return this.data.placeholder ?? null; + } + + /** + * The maximum amount of options that can be selected + * @type {?number} + * @readonly + */ + get maxValues() { + return this.data.max_values ?? null; + } + + /** + * The minimum amount of options that must be selected + * @type {?number} + * @readonly + */ + get minValues() { + return this.data.min_values ?? null; + } + + /** + * The custom id of this select menu + * @type {string} + * @readonly + */ + get customId() { + return this.data.custom_id; + } + + /** + * Whether this select menu is disabled + * @type {boolean} + * @readonly + */ + get disabled() { + return this.data.disabled ?? false; + } +} + +module.exports = BaseSelectMenuComponent; diff --git a/node_modules/discord.js/src/structures/ButtonBuilder.js b/node_modules/discord.js/src/structures/ButtonBuilder.js new file mode 100644 index 0000000..ada4188 --- /dev/null +++ b/node_modules/discord.js/src/structures/ButtonBuilder.js @@ -0,0 +1,44 @@ +'use strict'; + +const { ButtonBuilder: BuildersButton } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); +const { resolvePartialEmoji } = require('../util/Util'); + +/** + * Represents a button builder. + * @extends {BuildersButton} + */ +class ButtonBuilder extends BuildersButton { + constructor({ emoji, ...data } = {}) { + super(toSnakeCase({ ...data, emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji })); + } + + /** + * Sets the emoji to display on this button + * @param {string|APIMessageComponentEmoji} emoji The emoji to display on this button + * @returns {ButtonBuilder} + */ + setEmoji(emoji) { + if (typeof emoji === 'string') { + return super.setEmoji(resolvePartialEmoji(emoji)); + } + return super.setEmoji(emoji); + } + + /** + * Creates a new button builder from JSON data + * @param {ButtonBuilder|ButtonComponent|APIButtonComponent} other The other data + * @returns {ButtonBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } +} + +module.exports = ButtonBuilder; + +/** + * @external BuildersButton + * @see {@link https://discord.js.org/docs/packages/builders/stable/ButtonBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/ButtonComponent.js b/node_modules/discord.js/src/structures/ButtonComponent.js new file mode 100644 index 0000000..7319c3a --- /dev/null +++ b/node_modules/discord.js/src/structures/ButtonComponent.js @@ -0,0 +1,65 @@ +'use strict'; + +const Component = require('./Component'); + +/** + * Represents a button component + * @extends {Component} + */ +class ButtonComponent extends Component { + /** + * The style of this button + * @type {ButtonStyle} + * @readonly + */ + get style() { + return this.data.style; + } + + /** + * The label of this button + * @type {?string} + * @readonly + */ + get label() { + return this.data.label ?? null; + } + + /** + * The emoji used in this button + * @type {?APIMessageComponentEmoji} + * @readonly + */ + get emoji() { + return this.data.emoji ?? null; + } + + /** + * Whether this button is disabled + * @type {boolean} + * @readonly + */ + get disabled() { + return this.data.disabled ?? false; + } + + /** + * The custom id of this button (only defined on non-link buttons) + * @type {?string} + * @readonly + */ + get customId() { + return this.data.custom_id ?? null; + } + + /** + * The URL of this button (only defined on link buttons) + * @type {?string} + * @readonly + */ + get url() { + return this.data.url ?? null; + } +} + +module.exports = ButtonComponent; diff --git a/node_modules/discord.js/src/structures/ButtonInteraction.js b/node_modules/discord.js/src/structures/ButtonInteraction.js new file mode 100644 index 0000000..db57592 --- /dev/null +++ b/node_modules/discord.js/src/structures/ButtonInteraction.js @@ -0,0 +1,11 @@ +'use strict'; + +const MessageComponentInteraction = require('./MessageComponentInteraction'); + +/** + * Represents a button interaction. + * @extends {MessageComponentInteraction} + */ +class ButtonInteraction extends MessageComponentInteraction {} + +module.exports = ButtonInteraction; diff --git a/node_modules/discord.js/src/structures/CategoryChannel.js b/node_modules/discord.js/src/structures/CategoryChannel.js new file mode 100644 index 0000000..d038044 --- /dev/null +++ b/node_modules/discord.js/src/structures/CategoryChannel.js @@ -0,0 +1,45 @@ +'use strict'; + +const GuildChannel = require('./GuildChannel'); +const CategoryChannelChildManager = require('../managers/CategoryChannelChildManager'); + +/** + * Represents a guild category channel on Discord. + * @extends {GuildChannel} + */ +class CategoryChannel extends GuildChannel { + /** + * The id of the parent of this channel. + * @name CategoryChannel#parentId + * @type {null} + */ + + /** + * The parent of this channel. + * @name CategoryChannel#parent + * @type {null} + * @readonly + */ + + /** + * Sets the category parent of this channel. + * <warn>It is not possible to set the parent of a CategoryChannel.</warn> + * @method setParent + * @memberof CategoryChannel + * @instance + * @param {?CategoryChannelResolvable} channel The channel to set as parent + * @param {SetParentOptions} [options={}] The options for setting the parent + * @returns {Promise<GuildChannel>} + */ + + /** + * A manager of the channels belonging to this category + * @type {CategoryChannelChildManager} + * @readonly + */ + get children() { + return new CategoryChannelChildManager(this); + } +} + +module.exports = CategoryChannel; diff --git a/node_modules/discord.js/src/structures/ChannelSelectMenuBuilder.js b/node_modules/discord.js/src/structures/ChannelSelectMenuBuilder.js new file mode 100644 index 0000000..6d99474 --- /dev/null +++ b/node_modules/discord.js/src/structures/ChannelSelectMenuBuilder.js @@ -0,0 +1,31 @@ +'use strict'; + +const { ChannelSelectMenuBuilder: BuildersChannelSelectMenu } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersChannelSelectMenu} + */ +class ChannelSelectMenuBuilder extends BuildersChannelSelectMenu { + constructor(data = {}) { + super(toSnakeCase(data)); + } + + /** + * Creates a new select menu builder from JSON data + * @param {ChannelSelectMenuBuilder|ChannelSelectMenuComponent|APIChannelSelectComponent} other The other data + * @returns {ChannelSelectMenuBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } +} + +module.exports = ChannelSelectMenuBuilder; + +/** + * @external BuildersChannelSelectMenu + * @see {@link https://discord.js.org/docs/packages/builders/stable/ChannelSelectMenuBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/ChannelSelectMenuComponent.js b/node_modules/discord.js/src/structures/ChannelSelectMenuComponent.js new file mode 100644 index 0000000..90a7063 --- /dev/null +++ b/node_modules/discord.js/src/structures/ChannelSelectMenuComponent.js @@ -0,0 +1,20 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a channel select menu component + * @extends {BaseSelectMenuComponent} + */ +class ChannelSelectMenuComponent extends BaseSelectMenuComponent { + /** + * The options in this select menu + * @type {?(ChannelType[])} + * @readonly + */ + get channelTypes() { + return this.data.channel_types ?? null; + } +} + +module.exports = ChannelSelectMenuComponent; diff --git a/node_modules/discord.js/src/structures/ChannelSelectMenuInteraction.js b/node_modules/discord.js/src/structures/ChannelSelectMenuInteraction.js new file mode 100644 index 0000000..a5e9c99 --- /dev/null +++ b/node_modules/discord.js/src/structures/ChannelSelectMenuInteraction.js @@ -0,0 +1,33 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const MessageComponentInteraction = require('./MessageComponentInteraction'); + +/** + * Represents a {@link ComponentType.ChannelSelect} select menu interaction. + * @extends {MessageComponentInteraction} + */ +class ChannelSelectMenuInteraction extends MessageComponentInteraction { + constructor(client, data) { + super(client, data); + const { resolved, values } = data.data; + + /** + * An array of the selected channel ids + * @type {Snowflake[]} + */ + this.values = values ?? []; + + /** + * Collection of the selected channels + * @type {Collection<Snowflake, BaseChannel|APIChannel>} + */ + this.channels = new Collection(); + + for (const channel of Object.values(resolved?.channels ?? {})) { + this.channels.set(channel.id, this.client.channels._add(channel, this.guild) ?? channel); + } + } +} + +module.exports = ChannelSelectMenuInteraction; diff --git a/node_modules/discord.js/src/structures/ChatInputCommandInteraction.js b/node_modules/discord.js/src/structures/ChatInputCommandInteraction.js new file mode 100644 index 0000000..35175e4 --- /dev/null +++ b/node_modules/discord.js/src/structures/ChatInputCommandInteraction.js @@ -0,0 +1,41 @@ +'use strict'; + +const CommandInteraction = require('./CommandInteraction'); +const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver'); + +/** + * Represents a command interaction. + * @extends {CommandInteraction} + */ +class ChatInputCommandInteraction extends CommandInteraction { + constructor(client, data) { + super(client, data); + + /** + * The options passed to the command. + * @type {CommandInteractionOptionResolver} + */ + this.options = new CommandInteractionOptionResolver( + this.client, + data.data.options?.map(option => this.transformOption(option, data.data.resolved)) ?? [], + this.transformResolved(data.data.resolved ?? {}), + ); + } + + /** + * Returns a string representation of the command interaction. + * This can then be copied by a user and executed again in a new command while keeping the option order. + * @returns {string} + */ + toString() { + const properties = [ + this.commandName, + this.options._group, + this.options._subcommand, + ...this.options._hoistedOptions.map(o => `${o.name}:${o.value}`), + ]; + return `/${properties.filter(Boolean).join(' ')}`; + } +} + +module.exports = ChatInputCommandInteraction; diff --git a/node_modules/discord.js/src/structures/ClientApplication.js b/node_modules/discord.js/src/structures/ClientApplication.js new file mode 100644 index 0000000..69f5134 --- /dev/null +++ b/node_modules/discord.js/src/structures/ClientApplication.js @@ -0,0 +1,222 @@ +'use strict'; + +const { Routes } = require('discord-api-types/v10'); +const { ApplicationRoleConnectionMetadata } = require('./ApplicationRoleConnectionMetadata'); +const Team = require('./Team'); +const Application = require('./interfaces/Application'); +const ApplicationCommandManager = require('../managers/ApplicationCommandManager'); +const ApplicationFlagsBitField = require('../util/ApplicationFlagsBitField'); +const PermissionsBitField = require('../util/PermissionsBitField'); + +/** + * @typedef {Object} ClientApplicationInstallParams + * @property {OAuth2Scopes[]} scopes The scopes to add the application to the server with + * @property {Readonly<PermissionsBitField>} permissions The permissions this bot will request upon joining + */ + +/** + * Represents a client application. + * @extends {Application} + */ +class ClientApplication extends Application { + constructor(client, data) { + super(client, data); + + /** + * The application command manager for this application + * @type {ApplicationCommandManager} + */ + this.commands = new ApplicationCommandManager(this.client); + } + + _patch(data) { + super._patch(data); + + /** + * The tags this application has (max of 5) + * @type {string[]} + */ + this.tags = data.tags ?? []; + + if ('install_params' in data) { + /** + * Settings for this application's default in-app authorization + * @type {?ClientApplicationInstallParams} + */ + this.installParams = { + scopes: data.install_params.scopes, + permissions: new PermissionsBitField(data.install_params.permissions).freeze(), + }; + } else { + this.installParams ??= null; + } + + if ('custom_install_url' in data) { + /** + * This application's custom installation URL + * @type {?string} + */ + this.customInstallURL = data.custom_install_url; + } else { + this.customInstallURL = null; + } + + if ('flags' in data) { + /** + * The flags this application has + * @type {ApplicationFlagsBitField} + */ + this.flags = new ApplicationFlagsBitField(data.flags).freeze(); + } + + if ('approximate_guild_count' in data) { + /** + * An approximate amount of guilds this application is in. + * @type {?number} + */ + this.approximateGuildCount = data.approximate_guild_count; + } else { + this.approximateGuildCount ??= null; + } + + if ('guild_id' in data) { + /** + * The id of the guild associated with this application. + * @type {?Snowflake} + */ + this.guildId = data.guild_id; + } else { + this.guildId ??= null; + } + + if ('cover_image' in data) { + /** + * The hash of the application's cover image + * @type {?string} + */ + this.cover = data.cover_image; + } else { + this.cover ??= null; + } + + if ('rpc_origins' in data) { + /** + * The application's RPC origins, if enabled + * @type {string[]} + */ + this.rpcOrigins = data.rpc_origins; + } else { + this.rpcOrigins ??= []; + } + + if ('bot_require_code_grant' in data) { + /** + * If this application's bot requires a code grant when using the OAuth2 flow + * @type {?boolean} + */ + this.botRequireCodeGrant = data.bot_require_code_grant; + } else { + this.botRequireCodeGrant ??= null; + } + + if ('bot_public' in data) { + /** + * If this application's bot is public + * @type {?boolean} + */ + this.botPublic = data.bot_public; + } else { + this.botPublic ??= null; + } + + if ('role_connections_verification_url' in data) { + /** + * This application's role connection verification entry point URL + * @type {?string} + */ + this.roleConnectionsVerificationURL = data.role_connections_verification_url; + } else { + this.roleConnectionsVerificationURL ??= null; + } + + /** + * The owner of this OAuth application + * @type {?(User|Team)} + */ + this.owner = data.team + ? new Team(this.client, data.team) + : data.owner + ? this.client.users._add(data.owner) + : this.owner ?? null; + } + + /** + * The guild associated with this application. + * @type {?Guild} + * @readonly + */ + get guild() { + return this.client.guilds.cache.get(this.guildId) ?? null; + } + + /** + * Whether this application is partial + * @type {boolean} + * @readonly + */ + get partial() { + return !this.name; + } + + /** + * Obtains this application from Discord. + * @returns {Promise<ClientApplication>} + */ + async fetch() { + const data = await this.client.rest.get(Routes.currentApplication()); + this._patch(data); + return this; + } + + /** + * Gets this application's role connection metadata records + * @returns {Promise<ApplicationRoleConnectionMetadata[]>} + */ + async fetchRoleConnectionMetadataRecords() { + const metadata = await this.client.rest.get(Routes.applicationRoleConnectionMetadata(this.client.user.id)); + return metadata.map(data => new ApplicationRoleConnectionMetadata(data)); + } + + /** + * Data for creating or editing an application role connection metadata. + * @typedef {Object} ApplicationRoleConnectionMetadataEditOptions + * @property {string} name The name of the metadata field + * @property {?Object<Locale, string>} [nameLocalizations] The name localizations for the metadata field + * @property {string} description The description of the metadata field + * @property {?Object<Locale, string>} [descriptionLocalizations] The description localizations for the metadata field + * @property {string} key The dictionary key of the metadata field + * @property {ApplicationRoleConnectionMetadataType} type The type of the metadata field + */ + + /** + * Updates this application's role connection metadata records + * @param {ApplicationRoleConnectionMetadataEditOptions[]} records The new role connection metadata records + * @returns {Promise<ApplicationRoleConnectionMetadata[]>} + */ + async editRoleConnectionMetadataRecords(records) { + const newRecords = await this.client.rest.put(Routes.applicationRoleConnectionMetadata(this.client.user.id), { + body: records.map(record => ({ + type: record.type, + key: record.key, + name: record.name, + name_localizations: record.nameLocalizations, + description: record.description, + description_localizations: record.descriptionLocalizations, + })), + }); + + return newRecords.map(data => new ApplicationRoleConnectionMetadata(data)); + } +} + +module.exports = ClientApplication; diff --git a/node_modules/discord.js/src/structures/ClientPresence.js b/node_modules/discord.js/src/structures/ClientPresence.js new file mode 100644 index 0000000..6dd72ee --- /dev/null +++ b/node_modules/discord.js/src/structures/ClientPresence.js @@ -0,0 +1,90 @@ +'use strict'; + +const { GatewayOpcodes, ActivityType } = require('discord-api-types/v10'); +const { Presence } = require('./Presence'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); + +/** + * Represents the client's presence. + * @extends {Presence} + */ +class ClientPresence extends Presence { + constructor(client, data = {}) { + super(client, Object.assign(data, { status: data.status ?? 'online', user: { id: null } })); + } + + /** + * Sets the client's presence + * @param {PresenceData} presence The data to set the presence to + * @returns {ClientPresence} + */ + set(presence) { + const packet = this._parse(presence); + this._patch(packet); + if (presence.shardId === undefined) { + this.client.ws.broadcast({ op: GatewayOpcodes.PresenceUpdate, d: packet }); + } else if (Array.isArray(presence.shardId)) { + for (const shardId of presence.shardId) { + this.client.ws.shards.get(shardId).send({ op: GatewayOpcodes.PresenceUpdate, d: packet }); + } + } else { + this.client.ws.shards.get(presence.shardId).send({ op: GatewayOpcodes.PresenceUpdate, d: packet }); + } + return this; + } + + /** + * Parses presence data into a packet ready to be sent to Discord + * @param {PresenceData} presence The data to parse + * @returns {APIPresence} + * @private + */ + _parse({ status, since, afk, activities }) { + const data = { + activities: [], + afk: typeof afk === 'boolean' ? afk : false, + since: typeof since === 'number' && !Number.isNaN(since) ? since : null, + status: status ?? this.status, + }; + if (activities?.length) { + for (const [i, activity] of activities.entries()) { + if (typeof activity.name !== 'string') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, `activities[${i}].name`, 'string'); + } + + activity.type ??= ActivityType.Playing; + + if (activity.type === ActivityType.Custom && !activity.state) { + activity.state = activity.name; + activity.name = 'Custom Status'; + } + + data.activities.push({ + type: activity.type, + name: activity.name, + state: activity.state, + url: activity.url, + }); + } + } else if (!activities && (status || afk || since) && this.activities.length) { + data.activities.push( + ...this.activities.map(a => ({ + name: a.name, + state: a.state ?? undefined, + type: a.type, + url: a.url ?? undefined, + })), + ); + } + + return data; + } +} + +module.exports = ClientPresence; + +/* eslint-disable max-len */ +/** + * @external APIPresence + * @see {@link https://discord.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields} + */ diff --git a/node_modules/discord.js/src/structures/ClientUser.js b/node_modules/discord.js/src/structures/ClientUser.js new file mode 100644 index 0000000..b93904c --- /dev/null +++ b/node_modules/discord.js/src/structures/ClientUser.js @@ -0,0 +1,187 @@ +'use strict'; + +const { Routes } = require('discord-api-types/v10'); +const User = require('./User'); +const DataResolver = require('../util/DataResolver'); + +/** + * Represents the logged in client's Discord user. + * @extends {User} + */ +class ClientUser extends User { + _patch(data) { + super._patch(data); + + if ('verified' in data) { + /** + * Whether or not this account has been verified + * @type {boolean} + */ + this.verified = data.verified; + } + + if ('mfa_enabled' in data) { + /** + * If the bot's {@link ClientApplication#owner Owner} has MFA enabled on their account + * @type {?boolean} + */ + this.mfaEnabled = typeof data.mfa_enabled === 'boolean' ? data.mfa_enabled : null; + } else { + this.mfaEnabled ??= null; + } + + if ('token' in data) this.client.token = data.token; + } + + /** + * Represents the client user's presence + * @type {ClientPresence} + * @readonly + */ + get presence() { + return this.client.presence; + } + + /** + * Data used to edit the logged in client + * @typedef {Object} ClientUserEditOptions + * @property {string} [username] The new username + * @property {?(BufferResolvable|Base64Resolvable)} [avatar] The new avatar + */ + + /** + * Edits the logged in client. + * @param {ClientUserEditOptions} options The options to provide + * @returns {Promise<ClientUser>} + */ + async edit({ username, avatar }) { + const data = await this.client.rest.patch(Routes.user(), { + body: { username, avatar: avatar && (await DataResolver.resolveImage(avatar)) }, + }); + + this.client.token = data.token; + this.client.rest.setToken(data.token); + const { updated } = this.client.actions.UserUpdate.handle(data); + return updated ?? this; + } + + /** + * Sets the username of the logged in client. + * <info>Changing usernames in Discord is heavily rate limited, with only 2 requests + * every hour. Use this sparingly!</info> + * @param {string} username The new username + * @returns {Promise<ClientUser>} + * @example + * // Set username + * client.user.setUsername('discordjs') + * .then(user => console.log(`My new username is ${user.username}`)) + * .catch(console.error); + */ + setUsername(username) { + return this.edit({ username }); + } + + /** + * Sets the avatar of the logged in client. + * @param {?(BufferResolvable|Base64Resolvable)} avatar The new avatar + * @returns {Promise<ClientUser>} + * @example + * // Set avatar + * client.user.setAvatar('./avatar.png') + * .then(user => console.log(`New avatar set!`)) + * .catch(console.error); + */ + setAvatar(avatar) { + return this.edit({ avatar }); + } + + /** + * Options for setting activities + * @typedef {Object} ActivitiesOptions + * @property {string} name Name of the activity + * @property {string} [state] State of the activity + * @property {ActivityType} [type] Type of the activity + * @property {string} [url] Twitch / YouTube stream URL + */ + + /** + * Data resembling a raw Discord presence. + * @typedef {Object} PresenceData + * @property {PresenceStatusData} [status] Status of the user + * @property {boolean} [afk] Whether the user is AFK + * @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 + * @returns {ClientPresence} + * @example + * // Set the client user's presence + * client.user.setPresence({ activities: [{ name: 'with discord.js' }], status: 'idle' }); + */ + setPresence(data) { + return this.client.presence.set(data); + } + + /** + * A user's status. Must be one of: + * * `online` + * * `idle` + * * `invisible` + * * `dnd` (do not disturb) + * @typedef {string} PresenceStatusData + */ + + /** + * Sets the status of the client user. + * @param {PresenceStatusData} status Status to change to + * @param {number|number[]} [shardId] Shard id(s) to have the activity set on + * @returns {ClientPresence} + * @example + * // Set the client user's status + * client.user.setStatus('idle'); + */ + setStatus(status, shardId) { + return this.setPresence({ status, shardId }); + } + + /** + * Options for setting an activity. + * @typedef {Object} ActivityOptions + * @property {string} name Name of the activity + * @property {string} [state] State of the activity + * @property {string} [url] Twitch / YouTube stream URL + * @property {ActivityType} [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 + * @param {ActivityOptions} [options] Options for setting the activity + * @returns {ClientPresence} + * @example + * // Set the client user's activity + * client.user.setActivity('discord.js', { type: ActivityType.Watching }); + */ + setActivity(name, options = {}) { + if (!name) return this.setPresence({ activities: [], shardId: options.shardId }); + + const activity = Object.assign({}, options, typeof name === 'object' ? name : { name }); + return this.setPresence({ 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 + * @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 }); + } +} + +module.exports = ClientUser; diff --git a/node_modules/discord.js/src/structures/CommandInteraction.js b/node_modules/discord.js/src/structures/CommandInteraction.js new file mode 100644 index 0000000..ec6ef40 --- /dev/null +++ b/node_modules/discord.js/src/structures/CommandInteraction.js @@ -0,0 +1,224 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Attachment = require('./Attachment'); +const BaseInteraction = require('./BaseInteraction'); +const InteractionWebhook = require('./InteractionWebhook'); +const InteractionResponses = require('./interfaces/InteractionResponses'); + +/** + * Represents a command interaction. + * @extends {BaseInteraction} + * @implements {InteractionResponses} + * @abstract + */ +class CommandInteraction extends BaseInteraction { + constructor(client, data) { + super(client, data); + + /** + * The id of the channel this interaction was sent in + * @type {Snowflake} + * @name CommandInteraction#channelId + */ + + /** + * The invoked application command's id + * @type {Snowflake} + */ + this.commandId = data.data.id; + + /** + * The invoked application command's name + * @type {string} + */ + this.commandName = data.data.name; + + /** + * The invoked application command's type + * @type {ApplicationCommandType} + */ + this.commandType = data.data.type; + + /** + * The id of the guild the invoked application command is registered to + * @type {?Snowflake} + */ + this.commandGuildId = data.data.guild_id ?? null; + + /** + * Whether the reply to this interaction has been deferred + * @type {boolean} + */ + this.deferred = false; + + /** + * Whether this interaction has already been replied to + * @type {boolean} + */ + this.replied = false; + + /** + * Whether the reply to this interaction is ephemeral + * @type {?boolean} + */ + this.ephemeral = null; + + /** + * An associated interaction webhook, can be used to further interact with this interaction + * @type {InteractionWebhook} + */ + this.webhook = new InteractionWebhook(this.client, this.applicationId, this.token); + } + + /** + * The invoked application command, if it was fetched before + * @type {?ApplicationCommand} + */ + get command() { + const id = this.commandId; + return this.guild?.commands.cache.get(id) ?? this.client.application.commands.cache.get(id) ?? null; + } + + /** + * Represents the resolved data of a received command interaction. + * @typedef {Object} CommandInteractionResolvedData + * @property {Collection<Snowflake, User>} [users] The resolved users + * @property {Collection<Snowflake, GuildMember|APIGuildMember>} [members] The resolved guild members + * @property {Collection<Snowflake, Role|APIRole>} [roles] The resolved roles + * @property {Collection<Snowflake, BaseChannel|APIChannel>} [channels] The resolved channels + * @property {Collection<Snowflake, Message|APIMessage>} [messages] The resolved messages + * @property {Collection<Snowflake, Attachment>} [attachments] The resolved attachments + */ + + /** + * Transforms the resolved received from the API. + * @param {APIInteractionDataResolved} resolved The received resolved objects + * @returns {CommandInteractionResolvedData} + * @private + */ + transformResolved({ members, users, channels, roles, messages, attachments }) { + const result = {}; + + if (members) { + result.members = new Collection(); + for (const [id, member] of Object.entries(members)) { + const user = users[id]; + result.members.set(id, this.guild?.members._add({ user, ...member }) ?? member); + } + } + + if (users) { + result.users = new Collection(); + for (const user of Object.values(users)) { + result.users.set(user.id, this.client.users._add(user)); + } + } + + if (roles) { + result.roles = new Collection(); + for (const role of Object.values(roles)) { + result.roles.set(role.id, this.guild?.roles._add(role) ?? role); + } + } + + if (channels) { + result.channels = new Collection(); + for (const channel of Object.values(channels)) { + result.channels.set(channel.id, this.client.channels._add(channel, this.guild) ?? channel); + } + } + + if (messages) { + result.messages = new Collection(); + for (const message of Object.values(messages)) { + result.messages.set(message.id, this.channel?.messages?._add(message) ?? message); + } + } + + if (attachments) { + result.attachments = new Collection(); + for (const attachment of Object.values(attachments)) { + const patched = new Attachment(attachment); + result.attachments.set(attachment.id, patched); + } + } + + return result; + } + + /** + * Represents an option of a received command interaction. + * @typedef {Object} CommandInteractionOption + * @property {string} name The name of the option + * @property {ApplicationCommandOptionType} type The type of the option + * @property {boolean} [autocomplete] Whether the autocomplete interaction is enabled for a + * {@link ApplicationCommandOptionType.String}, {@link ApplicationCommandOptionType.Integer} or + * {@link ApplicationCommandOptionType.Number} option + * @property {string|number|boolean} [value] The value of the option + * @property {CommandInteractionOption[]} [options] Additional options if this option is a + * subcommand (group) + * @property {User} [user] The resolved user + * @property {GuildMember|APIGuildMember} [member] The resolved member + * @property {GuildChannel|ThreadChannel|APIChannel} [channel] The resolved channel + * @property {Role|APIRole} [role] The resolved role + * @property {Attachment} [attachment] The resolved attachment + */ + + /** + * Transforms an option received from the API. + * @param {APIApplicationCommandOption} option The received option + * @param {APIInteractionDataResolved} resolved The resolved interaction data + * @returns {CommandInteractionOption} + * @private + */ + transformOption(option, resolved) { + const result = { + name: option.name, + type: option.type, + }; + + if ('value' in option) result.value = option.value; + if ('options' in option) result.options = option.options.map(opt => this.transformOption(opt, resolved)); + + if (resolved) { + const user = resolved.users?.[option.value]; + if (user) result.user = this.client.users._add(user); + + const member = resolved.members?.[option.value]; + if (member) result.member = this.guild?.members._add({ user, ...member }) ?? member; + + const channel = resolved.channels?.[option.value]; + if (channel) result.channel = this.client.channels._add(channel, this.guild) ?? channel; + + const role = resolved.roles?.[option.value]; + if (role) result.role = this.guild?.roles._add(role) ?? role; + + const attachment = resolved.attachments?.[option.value]; + if (attachment) result.attachment = new Attachment(attachment); + } + + return result; + } + + // These are here only for documentation purposes - they are implemented by InteractionResponses + /* eslint-disable no-empty-function */ + deferReply() {} + reply() {} + fetchReply() {} + editReply() {} + deleteReply() {} + followUp() {} + showModal() {} + awaitModalSubmit() {} +} + +InteractionResponses.applyToClass(CommandInteraction, ['deferUpdate', 'update']); + +module.exports = CommandInteraction; + +/* eslint-disable max-len */ +/** + * @external APIInteractionDataResolved + * @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure} + */ diff --git a/node_modules/discord.js/src/structures/CommandInteractionOptionResolver.js b/node_modules/discord.js/src/structures/CommandInteractionOptionResolver.js new file mode 100644 index 0000000..621dbf4 --- /dev/null +++ b/node_modules/discord.js/src/structures/CommandInteractionOptionResolver.js @@ -0,0 +1,308 @@ +'use strict'; + +const { ApplicationCommandOptionType } = require('discord-api-types/v10'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); + +/** + * A resolver for command interaction options. + */ +class CommandInteractionOptionResolver { + constructor(client, options, resolved) { + /** + * The client that instantiated this. + * @name CommandInteractionOptionResolver#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * The name of the subcommand group. + * @type {?string} + * @private + */ + this._group = null; + + /** + * The name of the subcommand. + * @type {?string} + * @private + */ + this._subcommand = null; + + /** + * The bottom-level options for the interaction. + * If there is a subcommand (or subcommand and group), this is the options for the subcommand. + * @type {CommandInteractionOption[]} + * @private + */ + this._hoistedOptions = options; + + // Hoist subcommand group if present + if (this._hoistedOptions[0]?.type === ApplicationCommandOptionType.SubcommandGroup) { + this._group = this._hoistedOptions[0].name; + this._hoistedOptions = this._hoistedOptions[0].options ?? []; + } + // Hoist subcommand if present + if (this._hoistedOptions[0]?.type === ApplicationCommandOptionType.Subcommand) { + this._subcommand = this._hoistedOptions[0].name; + this._hoistedOptions = this._hoistedOptions[0].options ?? []; + } + + /** + * The interaction options array. + * @name CommandInteractionOptionResolver#data + * @type {ReadonlyArray<CommandInteractionOption>} + * @readonly + */ + Object.defineProperty(this, 'data', { value: Object.freeze([...options]) }); + + /** + * The interaction resolved data + * @name CommandInteractionOptionResolver#resolved + * @type {?Readonly<CommandInteractionResolvedData>} + */ + Object.defineProperty(this, 'resolved', { value: resolved ? Object.freeze(resolved) : null }); + } + + /** + * Gets an option by its name. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?CommandInteractionOption} The option, if found. + */ + get(name, required = false) { + const option = this._hoistedOptions.find(opt => opt.name === name); + if (!option) { + if (required) { + throw new DiscordjsTypeError(ErrorCodes.CommandInteractionOptionNotFound, name); + } + return null; + } + return option; + } + + /** + * Gets an option by name and property and checks its type. + * @param {string} name The name of the option. + * @param {ApplicationCommandOptionType[]} allowedTypes The allowed types of the option. + * @param {string[]} properties The properties to check for for `required`. + * @param {boolean} required Whether to throw an error if the option is not found. + * @returns {?CommandInteractionOption} The option, if found. + * @private + */ + _getTypedOption(name, allowedTypes, properties, required) { + const option = this.get(name, required); + if (!option) { + return null; + } else if (!allowedTypes.includes(option.type)) { + throw new DiscordjsTypeError(ErrorCodes.CommandInteractionOptionType, name, option.type, allowedTypes.join(', ')); + } else if (required && properties.every(prop => option[prop] === null || option[prop] === undefined)) { + throw new DiscordjsTypeError(ErrorCodes.CommandInteractionOptionEmpty, name, option.type); + } + return option; + } + + /** + * Gets the selected subcommand. + * @param {boolean} [required=true] Whether to throw an error if there is no subcommand. + * @returns {?string} The name of the selected subcommand, or null if not set and not required. + */ + getSubcommand(required = true) { + if (required && !this._subcommand) { + throw new DiscordjsTypeError(ErrorCodes.CommandInteractionOptionNoSubcommand); + } + return this._subcommand; + } + + /** + * Gets the selected subcommand group. + * @param {boolean} [required=false] Whether to throw an error if there is no subcommand group. + * @returns {?string} The name of the selected subcommand group, or null if not set and not required. + */ + getSubcommandGroup(required = false) { + if (required && !this._group) { + throw new DiscordjsTypeError(ErrorCodes.CommandInteractionOptionNoSubcommandGroup); + } + return this._group; + } + + /** + * Gets a boolean option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?boolean} The value of the option, or null if not set and not required. + */ + getBoolean(name, required = false) { + const option = this._getTypedOption(name, [ApplicationCommandOptionType.Boolean], ['value'], required); + return option?.value ?? null; + } + + /** + * Gets a channel option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @param {ChannelType[]} [channelTypes=[]] The allowed types of channels. If empty, all channel types are allowed. + * @returns {?(GuildChannel|ThreadChannel|APIChannel)} + * The value of the option, or null if not set and not required. + */ + getChannel(name, required = false, channelTypes = []) { + const option = this._getTypedOption(name, [ApplicationCommandOptionType.Channel], ['channel'], required); + const channel = option?.channel ?? null; + + if (channel && channelTypes.length > 0 && !channelTypes.includes(channel.type)) { + throw new DiscordjsTypeError( + ErrorCodes.CommandInteractionOptionInvalidChannelType, + name, + channel.type, + channelTypes.join(', '), + ); + } + + return channel; + } + + /** + * Gets a string option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?string} The value of the option, or null if not set and not required. + */ + getString(name, required = false) { + const option = this._getTypedOption(name, [ApplicationCommandOptionType.String], ['value'], required); + return option?.value ?? null; + } + + /** + * Gets an integer option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?number} The value of the option, or null if not set and not required. + */ + getInteger(name, required = false) { + const option = this._getTypedOption(name, [ApplicationCommandOptionType.Integer], ['value'], required); + return option?.value ?? null; + } + + /** + * Gets a number option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?number} The value of the option, or null if not set and not required. + */ + getNumber(name, required = false) { + const option = this._getTypedOption(name, [ApplicationCommandOptionType.Number], ['value'], required); + return option?.value ?? null; + } + + /** + * Gets a user option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?User} The value of the option, or null if not set and not required. + */ + getUser(name, required = false) { + const option = this._getTypedOption( + name, + [ApplicationCommandOptionType.User, ApplicationCommandOptionType.Mentionable], + ['user'], + required, + ); + return option?.user ?? null; + } + + /** + * Gets a member option. + * @param {string} name The name of the option. + * @returns {?(GuildMember|APIGuildMember)} + * The value of the option, or null if the user is not present in the guild or the option is not set. + */ + getMember(name) { + const option = this._getTypedOption( + name, + [ApplicationCommandOptionType.User, ApplicationCommandOptionType.Mentionable], + ['member'], + false, + ); + return option?.member ?? null; + } + + /** + * Gets a role option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?(Role|APIRole)} The value of the option, or null if not set and not required. + */ + getRole(name, required = false) { + const option = this._getTypedOption( + name, + [ApplicationCommandOptionType.Role, ApplicationCommandOptionType.Mentionable], + ['role'], + required, + ); + return option?.role ?? null; + } + + /** + * Gets an attachment option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?Attachment} The value of the option, or null if not set and not required. + */ + getAttachment(name, required = false) { + const option = this._getTypedOption(name, [ApplicationCommandOptionType.Attachment], ['attachment'], required); + return option?.attachment ?? null; + } + + /** + * Gets a mentionable option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?(User|GuildMember|APIGuildMember|Role|APIRole)} + * The value of the option, or null if not set and not required. + */ + getMentionable(name, required = false) { + const option = this._getTypedOption( + name, + [ApplicationCommandOptionType.Mentionable], + ['user', 'member', 'role'], + required, + ); + return option?.member ?? option?.user ?? option?.role ?? null; + } + + /** + * Gets a message option. + * @param {string} name The name of the option. + * @param {boolean} [required=false] Whether to throw an error if the option is not found. + * @returns {?Message} + * The value of the option, or null if not set and not required. + */ + getMessage(name, required = false) { + const option = this._getTypedOption(name, ['_MESSAGE'], ['message'], required); + return option?.message ?? null; + } + + /** + * The full autocomplete option object. + * @typedef {Object} AutocompleteFocusedOption + * @property {string} name The name of the option + * @property {ApplicationCommandOptionType} type The type of the application command option + * @property {string} value The value of the option + * @property {boolean} focused Whether this option is currently in focus for autocomplete + */ + + /** + * Gets the focused option. + * @param {boolean} [getFull=false] Whether to get the full option object + * @returns {string|AutocompleteFocusedOption} + * The value of the option, or the whole option if getFull is true + */ + getFocused(getFull = false) { + const focusedOption = this._hoistedOptions.find(option => option.focused); + if (!focusedOption) throw new DiscordjsTypeError(ErrorCodes.AutocompleteInteractionOptionNoFocusedOption); + return getFull ? focusedOption : focusedOption.value; + } +} + +module.exports = CommandInteractionOptionResolver; diff --git a/node_modules/discord.js/src/structures/Component.js b/node_modules/discord.js/src/structures/Component.js new file mode 100644 index 0000000..10ba27d --- /dev/null +++ b/node_modules/discord.js/src/structures/Component.js @@ -0,0 +1,47 @@ +'use strict'; + +const isEqual = require('fast-deep-equal'); + +/** + * Represents a component + */ +class Component { + constructor(data) { + /** + * The API data associated with this component + * @type {APIMessageComponent} + */ + this.data = data; + } + + /** + * The type of the component + * @type {ComponentType} + * @readonly + */ + get type() { + return this.data.type; + } + + /** + * Whether or not the given components are equal + * @param {Component|APIMessageComponent} other The component to compare against + * @returns {boolean} + */ + equals(other) { + if (other instanceof Component) { + return isEqual(other.data, this.data); + } + return isEqual(other, this.data); + } + + /** + * Returns the API-compatible JSON for this component + * @returns {APIMessageComponent} + */ + toJSON() { + return { ...this.data }; + } +} + +module.exports = Component; diff --git a/node_modules/discord.js/src/structures/ContextMenuCommandInteraction.js b/node_modules/discord.js/src/structures/ContextMenuCommandInteraction.js new file mode 100644 index 0000000..fc49ca5 --- /dev/null +++ b/node_modules/discord.js/src/structures/ContextMenuCommandInteraction.js @@ -0,0 +1,64 @@ +'use strict'; + +const { lazy } = require('@discordjs/util'); +const { ApplicationCommandOptionType } = require('discord-api-types/v10'); +const CommandInteraction = require('./CommandInteraction'); +const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver'); + +const getMessage = lazy(() => require('./Message').Message); + +/** + * Represents a context menu interaction. + * @extends {CommandInteraction} + */ +class ContextMenuCommandInteraction extends CommandInteraction { + constructor(client, data) { + super(client, data); + /** + * The target of the interaction, parsed into options + * @type {CommandInteractionOptionResolver} + */ + this.options = new CommandInteractionOptionResolver( + this.client, + this.resolveContextMenuOptions(data.data), + this.transformResolved(data.data.resolved), + ); + + /** + * The id of the target of this interaction + * @type {Snowflake} + */ + this.targetId = data.data.target_id; + } + + /** + * Resolves and transforms options received from the API for a context menu interaction. + * @param {APIApplicationCommandInteractionData} data The interaction data + * @returns {CommandInteractionOption[]} + * @private + */ + resolveContextMenuOptions({ target_id, resolved }) { + const result = []; + + if (resolved.users?.[target_id]) { + result.push( + this.transformOption({ name: 'user', type: ApplicationCommandOptionType.User, value: target_id }, resolved), + ); + } + + if (resolved.messages?.[target_id]) { + result.push({ + name: 'message', + type: '_MESSAGE', + value: target_id, + message: + this.channel?.messages._add(resolved.messages[target_id]) ?? + new (getMessage())(this.client, resolved.messages[target_id]), + }); + } + + return result; + } +} + +module.exports = ContextMenuCommandInteraction; diff --git a/node_modules/discord.js/src/structures/DMChannel.js b/node_modules/discord.js/src/structures/DMChannel.js new file mode 100644 index 0000000..2c917c4 --- /dev/null +++ b/node_modules/discord.js/src/structures/DMChannel.js @@ -0,0 +1,129 @@ +'use strict'; + +const { userMention } = require('@discordjs/builders'); +const { ChannelType } = require('discord-api-types/v10'); +const { BaseChannel } = require('./BaseChannel'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const DMMessageManager = require('../managers/DMMessageManager'); +const Partials = require('../util/Partials'); + +/** + * Represents a direct message channel between two users. + * @extends {BaseChannel} + * @implements {TextBasedChannel} + */ +class DMChannel extends BaseChannel { + constructor(client, data) { + super(client, data); + + // Override the channel type so partials have a known type + this.type = ChannelType.DM; + + /** + * A manager of the messages belonging to this channel + * @type {DMMessageManager} + */ + this.messages = new DMMessageManager(this); + } + + _patch(data) { + super._patch(data); + + if (data.recipients) { + const recipient = data.recipients[0]; + + /** + * The recipient's id + * @type {Snowflake} + */ + this.recipientId = recipient.id; + + if ('username' in recipient || this.client.options.partials.includes(Partials.User)) { + this.client.users._add(recipient); + } + } + + if ('last_message_id' in data) { + /** + * The channel's last message id, if one was sent + * @type {?Snowflake} + */ + this.lastMessageId = data.last_message_id; + } + + if ('last_pin_timestamp' in data) { + /** + * The timestamp when the last pinned message was pinned, if there was one + * @type {?number} + */ + this.lastPinTimestamp = Date.parse(data.last_pin_timestamp); + } else { + this.lastPinTimestamp ??= null; + } + } + + /** + * Whether this DMChannel is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return this.lastMessageId === undefined; + } + + /** + * The recipient on the other end of the DM + * @type {?User} + * @readonly + */ + get recipient() { + return this.client.users.resolve(this.recipientId); + } + + /** + * Fetch this DMChannel. + * @param {boolean} [force=true] Whether to skip the cache check and request the API + * @returns {Promise<DMChannel>} + */ + fetch(force = true) { + return this.client.users.createDM(this.recipientId, { force }); + } + + /** + * When concatenated with a string, this automatically returns the recipient's mention instead of the + * DMChannel object. + * @returns {string} + * @example + * // Logs: Hello from <@123456789012345678>! + * console.log(`Hello from ${channel}!`); + */ + toString() { + return userMention(this.recipientId); + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + get lastMessage() {} + get lastPinAt() {} + send() {} + sendTyping() {} + createMessageCollector() {} + awaitMessages() {} + createMessageComponentCollector() {} + awaitMessageComponent() {} + // Doesn't work on DM channels; bulkDelete() {} + // Doesn't work on DM channels; fetchWebhooks() {} + // Doesn't work on DM channels; createWebhook() {} + // Doesn't work on DM channels; setRateLimitPerUser() {} + // Doesn't work on DM channels; setNSFW() {} +} + +TextBasedChannel.applyToClass(DMChannel, true, [ + 'bulkDelete', + 'fetchWebhooks', + 'createWebhook', + 'setRateLimitPerUser', + 'setNSFW', +]); + +module.exports = DMChannel; diff --git a/node_modules/discord.js/src/structures/DirectoryChannel.js b/node_modules/discord.js/src/structures/DirectoryChannel.js new file mode 100644 index 0000000..870eff9 --- /dev/null +++ b/node_modules/discord.js/src/structures/DirectoryChannel.js @@ -0,0 +1,36 @@ +'use strict'; + +const { BaseChannel } = require('./BaseChannel'); + +/** + * Represents a channel that displays a directory of guilds. + * @extends {BaseChannel} + */ +class DirectoryChannel extends BaseChannel { + constructor(guild, data, client) { + super(client, data); + + /** + * The guild the channel is in + * @type {InviteGuild} + */ + this.guild = guild; + + /** + * The id of the guild the channel is in + * @type {Snowflake} + */ + this.guildId = guild.id; + } + + _patch(data) { + super._patch(data); + /** + * The channel's name + * @type {string} + */ + this.name = data.name; + } +} + +module.exports = DirectoryChannel; diff --git a/node_modules/discord.js/src/structures/Embed.js b/node_modules/discord.js/src/structures/Embed.js new file mode 100644 index 0000000..dd68120 --- /dev/null +++ b/node_modules/discord.js/src/structures/Embed.js @@ -0,0 +1,220 @@ +'use strict'; + +const { embedLength } = require('@discordjs/builders'); +const isEqual = require('fast-deep-equal'); + +/** + * Represents an embed. + */ +class Embed { + constructor(data) { + /** + * The API embed data. + * @type {APIEmbed} + * @readonly + */ + this.data = { ...data }; + } + + /** + * An array of fields of this embed. + * @type {Array<APIEmbedField>} + * @readonly + */ + get fields() { + return this.data.fields ?? []; + } + + /** + * The title of this embed. + * @type {?string} + * @readonly + */ + get title() { + return this.data.title ?? null; + } + + /** + * The description of this embed. + * @type {?string} + * @readonly + */ + get description() { + return this.data.description ?? null; + } + + /** + * The URL of this embed. + * @type {?string} + * @readonly + */ + get url() { + return this.data.url ?? null; + } + + /** + * The color of this embed. + * @type {?number} + * @readonly + */ + get color() { + return this.data.color ?? null; + } + + /** + * The timestamp of this embed. This is in an ISO 8601 format. + * @type {?string} + * @readonly + */ + get timestamp() { + return this.data.timestamp ?? null; + } + + /** + * @typedef {Object} EmbedAssetData + * @property {?string} url The URL of the image + * @property {?string} proxyURL The proxy URL of the image + * @property {?number} height The height of the image + * @property {?number} width The width of the image + */ + + /** + * The thumbnail of this embed. + * @type {?EmbedAssetData} + * @readonly + */ + get thumbnail() { + if (!this.data.thumbnail) return null; + return { + url: this.data.thumbnail.url, + proxyURL: this.data.thumbnail.proxy_url, + height: this.data.thumbnail.height, + width: this.data.thumbnail.width, + }; + } + + /** + * The image of this embed. + * @type {?EmbedAssetData} + * @readonly + */ + get image() { + if (!this.data.image) return null; + return { + url: this.data.image.url, + proxyURL: this.data.image.proxy_url, + height: this.data.image.height, + width: this.data.image.width, + }; + } + + /** + * The video of this embed. + * @type {?EmbedAssetData} + * @readonly + */ + get video() { + if (!this.data.video) return null; + return { + url: this.data.video.url, + proxyURL: this.data.video.proxy_url, + height: this.data.video.height, + width: this.data.video.width, + }; + } + + /** + * @typedef {Object} EmbedAuthorData + * @property {string} name The name of the author + * @property {?string} url The URL of the author + * @property {?string} iconURL The icon URL of the author + * @property {?string} proxyIconURL The proxy icon URL of the author + */ + + /** + * The author of this embed. + * @type {?EmbedAuthorData} + * @readonly + */ + get author() { + if (!this.data.author) return null; + return { + name: this.data.author.name, + url: this.data.author.url, + iconURL: this.data.author.icon_url, + proxyIconURL: this.data.author.proxy_icon_url, + }; + } + + /** + * The provider of this embed. + * @type {?APIEmbedProvider} + * @readonly + */ + get provider() { + return this.data.provider ?? null; + } + + /** + * @typedef {Object} EmbedFooterData + * @property {string} text The text of the footer + * @property {?string} iconURL The URL of the icon + * @property {?string} proxyIconURL The proxy URL of the icon + */ + + /** + * The footer of this embed. + * @type {?EmbedFooterData} + * @readonly + */ + get footer() { + if (!this.data.footer) return null; + return { + text: this.data.footer.text, + iconURL: this.data.footer.icon_url, + proxyIconURL: this.data.footer.proxy_icon_url, + }; + } + + /** + * The accumulated length for the embed title, description, fields, footer text, and author name. + * @type {number} + * @readonly + */ + get length() { + return embedLength(this.data); + } + + /** + * The hex color of this embed. + * @type {?string} + * @readonly + */ + get hexColor() { + return typeof this.data.color === 'number' + ? `#${this.data.color.toString(16).padStart(6, '0')}` + : this.data.color ?? null; + } + + /** + * Returns the API-compatible JSON for this embed. + * @returns {APIEmbed} + */ + toJSON() { + return { ...this.data }; + } + + /** + * Whether the given embeds are equal. + * @param {Embed|APIEmbed} other The embed to compare against + * @returns {boolean} + */ + equals(other) { + if (other instanceof Embed) { + return isEqual(other.data, this.data); + } + return isEqual(other, this.data); + } +} + +module.exports = Embed; diff --git a/node_modules/discord.js/src/structures/EmbedBuilder.js b/node_modules/discord.js/src/structures/EmbedBuilder.js new file mode 100644 index 0000000..10e445c --- /dev/null +++ b/node_modules/discord.js/src/structures/EmbedBuilder.js @@ -0,0 +1,50 @@ +'use strict'; + +const { EmbedBuilder: BuildersEmbed, embedLength } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); +const { resolveColor } = require('../util/Util'); + +/** + * Represents an embed builder. + * @extends {BuildersEmbed} + */ +class EmbedBuilder extends BuildersEmbed { + constructor(data) { + super(toSnakeCase(data)); + } + + /** + * Sets the color of this embed + * @param {?ColorResolvable} color The color of the embed + * @returns {EmbedBuilder} + */ + setColor(color) { + return super.setColor(color && resolveColor(color)); + } + + /** + * Creates a new embed builder from JSON data + * @param {EmbedBuilder|Embed|APIEmbed} other The other data + * @returns {EmbedBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } + + /** + * The accumulated length for the embed title, description, fields, footer text, and author name. + * @type {number} + * @readonly + */ + get length() { + return embedLength(this.data); + } +} + +module.exports = EmbedBuilder; + +/** + * @external BuildersEmbed + * @see {@link https://discord.js.org/docs/packages/builders/stable/EmbedBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/Emoji.js b/node_modules/discord.js/src/structures/Emoji.js new file mode 100644 index 0000000..409d292 --- /dev/null +++ b/node_modules/discord.js/src/structures/Emoji.js @@ -0,0 +1,108 @@ +'use strict'; + +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const Base = require('./Base'); + +/** + * Represents raw emoji data from the API + * @typedef {APIEmoji} RawEmoji + * @property {?Snowflake} id The emoji's id + * @property {?string} name The emoji's name + * @property {?boolean} animated Whether the emoji is animated + */ + +/** + * Represents an emoji, see {@link GuildEmoji} and {@link ReactionEmoji}. + * @extends {Base} + */ +class Emoji extends Base { + constructor(client, emoji) { + super(client); + /** + * Whether or not the emoji is animated + * @type {?boolean} + */ + this.animated = emoji.animated ?? null; + + /** + * The emoji's name + * @type {?string} + */ + this.name = emoji.name ?? null; + + /** + * The emoji's id + * @type {?Snowflake} + */ + this.id = emoji.id; + } + + /** + * The identifier of this emoji, used for message reactions + * @type {string} + * @readonly + */ + get identifier() { + if (this.id) return `${this.animated ? 'a:' : ''}${this.name}:${this.id}`; + return encodeURIComponent(this.name); + } + + /** + * The URL to the emoji file if it's a custom emoji + * @type {?string} + * @readonly + */ + get url() { + return this.id && this.client.rest.cdn.emoji(this.id, this.animated ? 'gif' : 'png'); + } + + /** + * The timestamp the emoji was created at, or null if unicode + * @type {?number} + * @readonly + */ + get createdTimestamp() { + return this.id && DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the emoji was created at, or null if unicode + * @type {?Date} + * @readonly + */ + get createdAt() { + return this.id && new Date(this.createdTimestamp); + } + + /** + * When concatenated with a string, this automatically returns the text required to form a graphical emoji on Discord + * instead of the Emoji object. + * @returns {string} + * @example + * // Send a custom emoji from a guild: + * const emoji = guild.emojis.cache.first(); + * msg.channel.send(`Hello! ${emoji}`); + * @example + * // Send the emoji used in a reaction to the channel the reaction is part of + * reaction.message.channel.send(`The emoji used was: ${reaction.emoji}`); + */ + toString() { + return this.id ? `<${this.animated ? 'a' : ''}:${this.name}:${this.id}>` : this.name; + } + + toJSON() { + return super.toJSON({ + guild: 'guildId', + createdTimestamp: true, + url: true, + identifier: true, + }); + } +} + +exports.Emoji = Emoji; + +/** + * @external APIEmoji + * @see {@link https://discord.com/developers/docs/resources/emoji#emoji-object} + */ diff --git a/node_modules/discord.js/src/structures/ForumChannel.js b/node_modules/discord.js/src/structures/ForumChannel.js new file mode 100644 index 0000000..87e6478 --- /dev/null +++ b/node_modules/discord.js/src/structures/ForumChannel.js @@ -0,0 +1,264 @@ +'use strict'; + +const GuildChannel = require('./GuildChannel'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const GuildForumThreadManager = require('../managers/GuildForumThreadManager'); +const { transformAPIGuildForumTag, transformAPIGuildDefaultReaction } = require('../util/Channels'); + +/** + * @typedef {Object} GuildForumTagEmoji + * @property {?Snowflake} id The id of a guild's custom emoji + * @property {?string} name The unicode character of the emoji + */ + +/** + * @typedef {Object} GuildForumTag + * @property {Snowflake} id The id of the tag + * @property {string} name The name of the tag + * @property {boolean} moderated Whether this tag can only be added to or removed from threads + * by a member with the `ManageThreads` permission + * @property {?GuildForumTagEmoji} emoji The emoji of this tag + */ + +/** + * @typedef {Object} GuildForumTagData + * @property {Snowflake} [id] The id of the tag + * @property {string} name The name of the tag + * @property {boolean} [moderated] Whether this tag can only be added to or removed from threads + * by a member with the `ManageThreads` permission + * @property {?GuildForumTagEmoji} [emoji] The emoji of this tag + */ + +/** + * @typedef {Object} DefaultReactionEmoji + * @property {?Snowflake} id The id of a guild's custom emoji + * @property {?string} name The unicode character of the emoji + */ + +/** + * Represents a channel that only contains threads + * @extends {GuildChannel} + * @implements {TextBasedChannel} + */ +class ForumChannel extends GuildChannel { + constructor(guild, data, client) { + super(guild, data, client, false); + + /** + * A manager of the threads belonging to this channel + * @type {GuildForumThreadManager} + */ + this.threads = new GuildForumThreadManager(this); + + this._patch(data); + } + + _patch(data) { + super._patch(data); + if ('available_tags' in data) { + /** + * The set of tags that can be used in this channel. + * @type {GuildForumTag[]} + */ + this.availableTags = data.available_tags.map(tag => transformAPIGuildForumTag(tag)); + } else { + this.availableTags ??= []; + } + + if ('default_reaction_emoji' in data) { + /** + * The emoji to show in the add reaction button on a thread in a guild forum channel + * @type {?DefaultReactionEmoji} + */ + this.defaultReactionEmoji = data.default_reaction_emoji + ? transformAPIGuildDefaultReaction(data.default_reaction_emoji) + : null; + } else { + this.defaultReactionEmoji ??= null; + } + + if ('default_thread_rate_limit_per_user' in data) { + /** + * The initial rate limit per user (slowmode) to set on newly created threads in a channel. + * @type {?number} + */ + this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user; + } else { + this.defaultThreadRateLimitPerUser ??= null; + } + + if ('rate_limit_per_user' in data) { + /** + * The rate limit per user (slowmode) for this channel. + * @type {?number} + */ + this.rateLimitPerUser = data.rate_limit_per_user; + } else { + this.rateLimitPerUser ??= null; + } + + if ('default_auto_archive_duration' in data) { + /** + * The default auto archive duration for newly created threads in this channel. + * @type {?ThreadAutoArchiveDuration} + */ + this.defaultAutoArchiveDuration = data.default_auto_archive_duration; + } else { + this.defaultAutoArchiveDuration ??= null; + } + + if ('nsfw' in data) { + /** + * If this channel is considered NSFW. + * @type {boolean} + */ + this.nsfw = data.nsfw; + } else { + this.nsfw ??= false; + } + + if ('topic' in data) { + /** + * The topic of this channel. + * @type {?string} + */ + this.topic = data.topic; + } + + if ('default_sort_order' in data) { + /** + * The default sort order mode used to order posts + * @type {?SortOrderType} + */ + this.defaultSortOrder = data.default_sort_order; + } else { + this.defaultSortOrder ??= null; + } + + /** + * The default layout type used to display posts + * @type {ForumLayoutType} + */ + this.defaultForumLayout = data.default_forum_layout; + } + + /** + * Sets the available tags for this forum channel + * @param {GuildForumTagData[]} availableTags The tags to set as available in this channel + * @param {string} [reason] Reason for changing the available tags + * @returns {Promise<ForumChannel>} + */ + setAvailableTags(availableTags, reason) { + return this.edit({ availableTags, reason }); + } + + /** + * Sets the default reaction emoji for this channel + * @param {?DefaultReactionEmoji} defaultReactionEmoji The emoji to set as the default reaction emoji + * @param {string} [reason] Reason for changing the default reaction emoji + * @returns {Promise<ForumChannel>} + */ + setDefaultReactionEmoji(defaultReactionEmoji, reason) { + return this.edit({ defaultReactionEmoji, reason }); + } + + /** + * Sets the default rate limit per user (slowmode) for new threads in this channel + * @param {number} defaultThreadRateLimitPerUser The rate limit to set on newly created threads in this channel + * @param {string} [reason] Reason for changing the default rate limit + * @returns {Promise<ForumChannel>} + */ + setDefaultThreadRateLimitPerUser(defaultThreadRateLimitPerUser, reason) { + return this.edit({ defaultThreadRateLimitPerUser, reason }); + } + + /** + * Creates an invite to this guild channel. + * @param {InviteCreateOptions} [options={}] The options for creating the invite + * @returns {Promise<Invite>} + * @example + * // Create an invite to a channel + * channel.createInvite() + * .then(invite => console.log(`Created an invite with a code of ${invite.code}`)) + * .catch(console.error); + */ + createInvite(options) { + return this.guild.invites.create(this.id, options); + } + + /** + * Fetches a collection of invites to this guild channel. + * Resolves with a collection mapping invites by their codes. + * @param {boolean} [cache=true] Whether to cache the fetched invites + * @returns {Promise<Collection<string, Invite>>} + */ + fetchInvites(cache) { + return this.guild.invites.fetch({ channelId: this.id, cache }); + } + + /** + * Sets the default auto archive duration for all newly created threads in this channel. + * @param {ThreadAutoArchiveDuration} defaultAutoArchiveDuration The new default auto archive duration + * @param {string} [reason] Reason for changing the channel's default auto archive duration + * @returns {Promise<ForumChannel>} + */ + setDefaultAutoArchiveDuration(defaultAutoArchiveDuration, reason) { + return this.edit({ defaultAutoArchiveDuration, reason }); + } + + /** + * Sets a new topic for the guild channel. + * @param {?string} topic The new topic for the guild channel + * @param {string} [reason] Reason for changing the guild channel's topic + * @returns {Promise<ForumChannel>} + * @example + * // Set a new channel topic + * channel.setTopic('needs more rate limiting') + * .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`)) + * .catch(console.error); + */ + setTopic(topic, reason) { + return this.edit({ topic, reason }); + } + + /** + * Sets the default sort order mode used to order posts + * @param {?SortOrderType} defaultSortOrder The default sort order mode to set on this channel + * @param {string} [reason] Reason for changing the default sort order + * @returns {Promise<ForumChannel>} + */ + setDefaultSortOrder(defaultSortOrder, reason) { + return this.edit({ defaultSortOrder, reason }); + } + + /** + * Sets the default forum layout type used to display posts + * @param {ForumLayoutType} defaultForumLayout The default forum layout type to set on this channel + * @param {string} [reason] Reason for changing the default forum layout + * @returns {Promise<ForumChannel>} + */ + setDefaultForumLayout(defaultForumLayout, reason) { + return this.edit({ defaultForumLayout, reason }); + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + createWebhook() {} + fetchWebhooks() {} + setNSFW() {} + setRateLimitPerUser() {} +} + +TextBasedChannel.applyToClass(ForumChannel, true, [ + 'send', + 'lastMessage', + 'lastPinAt', + 'bulkDelete', + 'sendTyping', + 'createMessageCollector', + 'awaitMessages', + 'createMessageComponentCollector', + 'awaitMessageComponent', +]); + +module.exports = ForumChannel; diff --git a/node_modules/discord.js/src/structures/Guild.js b/node_modules/discord.js/src/structures/Guild.js new file mode 100644 index 0000000..f07e9b4 --- /dev/null +++ b/node_modules/discord.js/src/structures/Guild.js @@ -0,0 +1,1367 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); +const { ChannelType, GuildPremiumTier, Routes, GuildFeature } = require('discord-api-types/v10'); +const AnonymousGuild = require('./AnonymousGuild'); +const GuildAuditLogs = require('./GuildAuditLogs'); +const { GuildOnboarding } = require('./GuildOnboarding'); +const GuildPreview = require('./GuildPreview'); +const GuildTemplate = require('./GuildTemplate'); +const Integration = require('./Integration'); +const Webhook = require('./Webhook'); +const WelcomeScreen = require('./WelcomeScreen'); +const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); +const AutoModerationRuleManager = require('../managers/AutoModerationRuleManager'); +const GuildApplicationCommandManager = require('../managers/GuildApplicationCommandManager'); +const GuildBanManager = require('../managers/GuildBanManager'); +const GuildChannelManager = require('../managers/GuildChannelManager'); +const GuildEmojiManager = require('../managers/GuildEmojiManager'); +const GuildInviteManager = require('../managers/GuildInviteManager'); +const GuildMemberManager = require('../managers/GuildMemberManager'); +const GuildScheduledEventManager = require('../managers/GuildScheduledEventManager'); +const GuildStickerManager = require('../managers/GuildStickerManager'); +const PresenceManager = require('../managers/PresenceManager'); +const RoleManager = require('../managers/RoleManager'); +const StageInstanceManager = require('../managers/StageInstanceManager'); +const VoiceStateManager = require('../managers/VoiceStateManager'); +const DataResolver = require('../util/DataResolver'); +const Status = require('../util/Status'); +const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField'); +const { discordSort, getSortableGroupTypes } = require('../util/Util'); + +/** + * Represents a guild (or a server) on Discord. + * <info>It's recommended to see if a guild is available before performing operations or reading data from it. You can + * check this with {@link Guild#available}.</info> + * @extends {AnonymousGuild} + */ +class Guild extends AnonymousGuild { + constructor(client, data) { + super(client, data, false); + + /** + * A manager of the application commands belonging to this guild + * @type {GuildApplicationCommandManager} + */ + this.commands = new GuildApplicationCommandManager(this); + + /** + * A manager of the members belonging to this guild + * @type {GuildMemberManager} + */ + this.members = new GuildMemberManager(this); + + /** + * A manager of the channels belonging to this guild + * @type {GuildChannelManager} + */ + this.channels = new GuildChannelManager(this); + + /** + * A manager of the bans belonging to this guild + * @type {GuildBanManager} + */ + this.bans = new GuildBanManager(this); + + /** + * A manager of the roles belonging to this guild + * @type {RoleManager} + */ + this.roles = new RoleManager(this); + + /** + * A manager of the presences belonging to this guild + * @type {PresenceManager} + */ + this.presences = new PresenceManager(this.client); + + /** + * A manager of the voice states of this guild + * @type {VoiceStateManager} + */ + this.voiceStates = new VoiceStateManager(this); + + /** + * A manager of the stage instances of this guild + * @type {StageInstanceManager} + */ + this.stageInstances = new StageInstanceManager(this); + + /** + * A manager of the invites of this guild + * @type {GuildInviteManager} + */ + this.invites = new GuildInviteManager(this); + + /** + * A manager of the scheduled events of this guild + * @type {GuildScheduledEventManager} + */ + this.scheduledEvents = new GuildScheduledEventManager(this); + + /** + * A manager of the auto moderation rules of this guild. + * @type {AutoModerationRuleManager} + */ + this.autoModerationRules = new AutoModerationRuleManager(this); + + if (!data) return; + if (data.unavailable) { + /** + * Whether the guild is available to access. If it is not available, it indicates a server outage + * @type {boolean} + */ + this.available = false; + } else { + this._patch(data); + if (!data.channels) this.available = false; + } + + /** + * The id of the shard this Guild belongs to. + * @type {number} + */ + this.shardId = data.shardId; + } + + /** + * The Shard this Guild belongs to. + * @type {WebSocketShard} + * @readonly + */ + get shard() { + return this.client.ws.shards.get(this.shardId); + } + + _patch(data) { + super._patch(data); + this.id = data.id; + if ('name' in data) this.name = data.name; + if ('icon' in data) this.icon = data.icon; + if ('unavailable' in data) { + this.available = !data.unavailable; + } else { + this.available ??= true; + } + + if ('discovery_splash' in data) { + /** + * The hash of the guild discovery splash image + * @type {?string} + */ + this.discoverySplash = data.discovery_splash; + } + + if ('member_count' in data) { + /** + * The full amount of members in this guild + * @type {number} + */ + this.memberCount = data.member_count; + } + + if ('large' in data) { + /** + * Whether the guild is "large" (has more than {@link WebsocketOptions large_threshold} members, 50 by default) + * @type {boolean} + */ + this.large = Boolean(data.large); + } + + if ('premium_progress_bar_enabled' in data) { + /** + * Whether this guild has its premium (boost) progress bar enabled + * @type {boolean} + */ + this.premiumProgressBarEnabled = data.premium_progress_bar_enabled; + } + + if ('application_id' in data) { + /** + * The id of the application that created this guild (if applicable) + * @type {?Snowflake} + */ + this.applicationId = data.application_id; + } + + if ('afk_timeout' in data) { + /** + * The time in seconds before a user is counted as "away from keyboard" + * @type {?number} + */ + this.afkTimeout = data.afk_timeout; + } + + if ('afk_channel_id' in data) { + /** + * The id of the voice channel where AFK members are moved + * @type {?Snowflake} + */ + this.afkChannelId = data.afk_channel_id; + } + + if ('system_channel_id' in data) { + /** + * The system channel's id + * @type {?Snowflake} + */ + this.systemChannelId = data.system_channel_id; + } + + if ('premium_tier' in data) { + /** + * The premium tier of this guild + * @type {GuildPremiumTier} + */ + this.premiumTier = data.premium_tier; + } + + if ('widget_enabled' in data) { + /** + * Whether widget images are enabled on this guild + * @type {?boolean} + */ + this.widgetEnabled = data.widget_enabled; + } else { + this.widgetEnabled ??= null; + } + + if ('widget_channel_id' in data) { + /** + * The widget channel's id, if enabled + * @type {?string} + */ + this.widgetChannelId = data.widget_channel_id; + } else { + this.widgetChannelId ??= null; + } + + if ('explicit_content_filter' in data) { + /** + * The explicit content filter level of the guild + * @type {GuildExplicitContentFilter} + */ + this.explicitContentFilter = data.explicit_content_filter; + } + + if ('mfa_level' in data) { + /** + * The required MFA level for this guild + * @type {GuildMFALevel} + */ + this.mfaLevel = data.mfa_level; + } + + if ('joined_at' in data) { + /** + * The timestamp the client user joined the guild at + * @type {number} + */ + this.joinedTimestamp = Date.parse(data.joined_at); + } + + if ('default_message_notifications' in data) { + /** + * The default message notification level of the guild + * @type {GuildDefaultMessageNotifications} + */ + this.defaultMessageNotifications = data.default_message_notifications; + } + + if ('system_channel_flags' in data) { + /** + * The value set for the guild's system channel flags + * @type {Readonly<SystemChannelFlagsBitField>} + */ + this.systemChannelFlags = new SystemChannelFlagsBitField(data.system_channel_flags).freeze(); + } + + if ('max_members' in data) { + /** + * The maximum amount of members the guild can have + * @type {?number} + */ + this.maximumMembers = data.max_members; + } else { + this.maximumMembers ??= null; + } + + if ('max_presences' in data) { + /** + * The maximum amount of presences the guild can have (this is `null` for all but the largest of guilds) + * <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info> + * @type {?number} + */ + this.maximumPresences = data.max_presences; + } else { + this.maximumPresences ??= null; + } + + if ('max_video_channel_users' in data) { + /** + * The maximum amount of users allowed in a video channel. + * @type {?number} + */ + this.maxVideoChannelUsers = data.max_video_channel_users; + } else { + this.maxVideoChannelUsers ??= null; + } + + if ('max_stage_video_channel_users' in data) { + /** + * The maximum amount of users allowed in a stage video channel. + * @type {?number} + */ + this.maxStageVideoChannelUsers = data.max_stage_video_channel_users; + } else { + this.maxStageVideoChannelUsers ??= null; + } + + if ('approximate_member_count' in data) { + /** + * The approximate amount of members the guild has + * <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info> + * @type {?number} + */ + this.approximateMemberCount = data.approximate_member_count; + } else { + this.approximateMemberCount ??= null; + } + + if ('approximate_presence_count' in data) { + /** + * The approximate amount of presences the guild has + * <info>You will need to fetch the guild using {@link Guild#fetch} if you want to receive this parameter</info> + * @type {?number} + */ + this.approximatePresenceCount = data.approximate_presence_count; + } else { + this.approximatePresenceCount ??= null; + } + + /** + * The use count of the vanity URL code of the guild, if any + * <info>You will need to fetch this parameter using {@link Guild#fetchVanityData} if you want to receive it</info> + * @type {?number} + */ + this.vanityURLUses ??= null; + + if ('rules_channel_id' in data) { + /** + * The rules channel's id for the guild + * @type {?Snowflake} + */ + this.rulesChannelId = data.rules_channel_id; + } + + if ('public_updates_channel_id' in data) { + /** + * The community updates channel's id for the guild + * @type {?Snowflake} + */ + this.publicUpdatesChannelId = data.public_updates_channel_id; + } + + if ('preferred_locale' in data) { + /** + * The preferred locale of the guild, defaults to `en-US` + * @type {Locale} + */ + this.preferredLocale = data.preferred_locale; + } + + if ('safety_alerts_channel_id' in data) { + /** + * The safety alerts channel's id for the guild + * @type {?Snowflake} + */ + this.safetyAlertsChannelId = data.safety_alerts_channel_id; + } else { + this.safetyAlertsChannelId ??= null; + } + + if (data.channels) { + this.channels.cache.clear(); + for (const rawChannel of data.channels) { + this.client.channels._add(rawChannel, this); + } + } + + if (data.threads) { + for (const rawThread of data.threads) { + this.client.channels._add(rawThread, this); + } + } + + if (data.roles) { + this.roles.cache.clear(); + for (const role of data.roles) this.roles._add(role); + } + + if (data.members) { + this.members.cache.clear(); + for (const guildUser of data.members) this.members._add(guildUser); + } + + if ('owner_id' in data) { + /** + * The user id of this guild's owner + * @type {Snowflake} + */ + this.ownerId = data.owner_id; + } + + if (data.presences) { + for (const presence of data.presences) { + this.presences._add(Object.assign(presence, { guild: this })); + } + } + + if (data.stage_instances) { + this.stageInstances.cache.clear(); + for (const stageInstance of data.stage_instances) { + this.stageInstances._add(stageInstance); + } + } + + if (data.guild_scheduled_events) { + this.scheduledEvents.cache.clear(); + for (const scheduledEvent of data.guild_scheduled_events) { + this.scheduledEvents._add(scheduledEvent); + } + } + + if (data.voice_states) { + this.voiceStates.cache.clear(); + for (const voiceState of data.voice_states) { + this.voiceStates._add(voiceState); + } + } + + if (!this.emojis) { + /** + * A manager of the emojis belonging to this guild + * @type {GuildEmojiManager} + */ + this.emojis = new GuildEmojiManager(this); + if (data.emojis) for (const emoji of data.emojis) this.emojis._add(emoji); + } else if (data.emojis) { + this.client.actions.GuildEmojisUpdate.handle({ + guild_id: this.id, + emojis: data.emojis, + }); + } + + if (!this.stickers) { + /** + * A manager of the stickers belonging to this guild + * @type {GuildStickerManager} + */ + this.stickers = new GuildStickerManager(this); + if (data.stickers) for (const sticker of data.stickers) this.stickers._add(sticker); + } else if (data.stickers) { + this.client.actions.GuildStickersUpdate.handle({ + guild_id: this.id, + stickers: data.stickers, + }); + } + } + + /** + * The time the client user joined the guild + * @type {Date} + * @readonly + */ + get joinedAt() { + return new Date(this.joinedTimestamp); + } + + /** + * The URL to this guild's discovery splash image. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + discoverySplashURL(options = {}) { + return this.discoverySplash && this.client.rest.cdn.discoverySplash(this.id, this.discoverySplash, options); + } + + /** + * Fetches the owner of the guild. + * If the member object isn't needed, use {@link Guild#ownerId} instead. + * @param {BaseFetchOptions} [options] The options for fetching the member + * @returns {Promise<GuildMember>} + */ + async fetchOwner(options) { + if (!this.ownerId) { + throw new DiscordjsError(ErrorCodes.FetchOwnerId); + } + const member = await this.members.fetch({ ...options, user: this.ownerId }); + return member; + } + + /** + * AFK voice channel for this guild + * @type {?VoiceChannel} + * @readonly + */ + get afkChannel() { + return this.client.channels.resolve(this.afkChannelId); + } + + /** + * System channel for this guild + * @type {?TextChannel} + * @readonly + */ + get systemChannel() { + return this.client.channels.resolve(this.systemChannelId); + } + + /** + * Widget channel for this guild + * @type {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel)} + * @readonly + */ + get widgetChannel() { + return this.client.channels.resolve(this.widgetChannelId); + } + + /** + * Rules channel for this guild + * @type {?TextChannel} + * @readonly + */ + get rulesChannel() { + return this.client.channels.resolve(this.rulesChannelId); + } + + /** + * Public updates channel for this guild + * @type {?TextChannel} + * @readonly + */ + get publicUpdatesChannel() { + return this.client.channels.resolve(this.publicUpdatesChannelId); + } + + /** + * Safety alerts channel for this guild + * @type {?TextChannel} + * @readonly + */ + get safetyAlertsChannel() { + return this.client.channels.resolve(this.safetyAlertsChannelId); + } + + /** + * The maximum bitrate available for this guild + * @type {number} + * @readonly + */ + get maximumBitrate() { + if (this.features.includes(GuildFeature.VIPRegions)) { + return 384_000; + } + + switch (this.premiumTier) { + case GuildPremiumTier.Tier1: + return 128_000; + case GuildPremiumTier.Tier2: + return 256_000; + case GuildPremiumTier.Tier3: + return 384_000; + default: + return 96_000; + } + } + + /** + * Fetches a collection of integrations to this guild. + * Resolves with a collection mapping integrations by their ids. + * @returns {Promise<Collection<Snowflake|string, Integration>>} + * @example + * // Fetch integrations + * guild.fetchIntegrations() + * .then(integrations => console.log(`Fetched ${integrations.size} integrations`)) + * .catch(console.error); + */ + async fetchIntegrations() { + const data = await this.client.rest.get(Routes.guildIntegrations(this.id)); + return data.reduce( + (collection, integration) => collection.set(integration.id, new Integration(this.client, integration, this)), + new Collection(), + ); + } + + /** + * Fetches a collection of templates from this guild. + * Resolves with a collection mapping templates by their codes. + * @returns {Promise<Collection<string, GuildTemplate>>} + */ + async fetchTemplates() { + const templates = await this.client.rest.get(Routes.guildTemplates(this.id)); + return templates.reduce((col, data) => col.set(data.code, new GuildTemplate(this.client, data)), new Collection()); + } + + /** + * Fetches the welcome screen for this guild. + * @returns {Promise<WelcomeScreen>} + */ + async fetchWelcomeScreen() { + const data = await this.client.rest.get(Routes.guildWelcomeScreen(this.id)); + return new WelcomeScreen(this, data); + } + + /** + * Creates a template for the guild. + * @param {string} name The name for the template + * @param {string} [description] The description for the template + * @returns {Promise<GuildTemplate>} + */ + async createTemplate(name, description) { + const data = await this.client.rest.post(Routes.guildTemplates(this.id), { body: { name, description } }); + return new GuildTemplate(this.client, data); + } + + /** + * Obtains a guild preview for this guild from Discord. + * @returns {Promise<GuildPreview>} + */ + async fetchPreview() { + const data = await this.client.rest.get(Routes.guildPreview(this.id)); + return new GuildPreview(this.client, data); + } + + /** + * An object containing information about a guild's vanity invite. + * @typedef {Object} Vanity + * @property {?string} code Vanity invite code + * @property {number} uses How many times this invite has been used + */ + + /** + * Fetches the vanity URL invite object to this guild. + * Resolves with an object containing the vanity URL invite code and the use count + * @returns {Promise<Vanity>} + * @example + * // Fetch invite data + * guild.fetchVanityData() + * .then(res => { + * console.log(`Vanity URL: https://discord.gg/${res.code} with ${res.uses} uses`); + * }) + * .catch(console.error); + */ + async fetchVanityData() { + const data = await this.client.rest.get(Routes.guildVanityUrl(this.id)); + this.vanityURLCode = data.code; + this.vanityURLUses = data.uses; + + return data; + } + + /** + * Fetches all webhooks for the guild. + * @returns {Promise<Collection<Snowflake, Webhook>>} + * @example + * // Fetch webhooks + * guild.fetchWebhooks() + * .then(webhooks => console.log(`Fetched ${webhooks.size} webhooks`)) + * .catch(console.error); + */ + async fetchWebhooks() { + const apiHooks = await this.client.rest.get(Routes.guildWebhooks(this.id)); + const hooks = new Collection(); + for (const hook of apiHooks) hooks.set(hook.id, new Webhook(this.client, hook)); + return hooks; + } + + /** + * Fetches the guild widget data, requires the widget to be enabled. + * @returns {Promise<Widget>} + * @example + * // Fetches the guild widget data + * guild.fetchWidget() + * .then(widget => console.log(`The widget shows ${widget.channels.size} channels`)) + * .catch(console.error); + */ + fetchWidget() { + return this.client.fetchGuildWidget(this.id); + } + + /** + * Data for the Guild Widget Settings object + * @typedef {Object} GuildWidgetSettings + * @property {boolean} enabled Whether the widget is enabled + * @property {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel)} channel The widget invite channel + */ + + /** + * The Guild Widget Settings object + * @typedef {Object} GuildWidgetSettingsData + * @property {boolean} enabled Whether the widget is enabled + * @property {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|Snowflake)} channel + * The widget invite channel + */ + + /** + * Fetches the guild widget settings. + * @returns {Promise<GuildWidgetSettings>} + * @example + * // Fetches the guild widget settings + * guild.fetchWidgetSettings() + * .then(widget => console.log(`The widget is ${widget.enabled ? 'enabled' : 'disabled'}`)) + * .catch(console.error); + */ + async fetchWidgetSettings() { + const data = await this.client.rest.get(Routes.guildWidgetSettings(this.id)); + this.widgetEnabled = data.enabled; + this.widgetChannelId = data.channel_id; + return { + enabled: data.enabled, + channel: data.channel_id ? this.channels.cache.get(data.channel_id) : null, + }; + } + + /** + * Options used to fetch audit logs. + * @typedef {Object} GuildAuditLogsFetchOptions + * @property {Snowflake|GuildAuditLogsEntry} [before] Consider only entries before this entry + * @property {Snowflake|GuildAuditLogsEntry} [after] Consider only entries after this entry + * @property {number} [limit] The number of entries to return + * @property {UserResolvable} [user] Only return entries for actions made by this user + * @property {?AuditLogEvent} [type] Only return entries for this action type + */ + + /** + * Fetches audit logs for this guild. + * @param {GuildAuditLogsFetchOptions} [options={}] Options for fetching audit logs + * @returns {Promise<GuildAuditLogs>} + * @example + * // Output audit log entries + * guild.fetchAuditLogs() + * .then(audit => console.log(audit.entries.first())) + * .catch(console.error); + */ + async fetchAuditLogs({ before, after, limit, user, type } = {}) { + const query = makeURLSearchParams({ + before: before?.id ?? before, + after: after?.id ?? after, + limit, + action_type: type, + }); + + if (user) { + const userId = this.client.users.resolveId(user); + if (!userId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'user', 'UserResolvable'); + query.set('user_id', userId); + } + + const data = await this.client.rest.get(Routes.guildAuditLog(this.id), { query }); + return new GuildAuditLogs(this, data); + } + + /** + * Fetches the guild onboarding data for this guild. + * @returns {Promise<GuildOnboarding>} + */ + async fetchOnboarding() { + const data = await this.client.rest.get(Routes.guildOnboarding(this.id)); + return new GuildOnboarding(this.client, data); + } + + /** + * The data for editing a guild. + * @typedef {Object} GuildEditOptions + * @property {string} [name] The name of the guild + * @property {?GuildVerificationLevel} [verificationLevel] The verification level of the guild + * @property {?GuildDefaultMessageNotifications} [defaultMessageNotifications] The default message + * notification level of the guild + * @property {?GuildExplicitContentFilter} [explicitContentFilter] The level of the explicit content filter + * @property {?VoiceChannelResolvable} [afkChannel] The AFK channel of the guild + * @property {number} [afkTimeout] The AFK timeout of the guild + * @property {?(BufferResolvable|Base64Resolvable)} [icon] The icon of the guild + * @property {GuildMemberResolvable} [owner] The owner of the guild + * @property {?(BufferResolvable|Base64Resolvable)} [splash] The invite splash image of the guild + * @property {?(BufferResolvable|Base64Resolvable)} [discoverySplash] The discovery splash image of the guild + * @property {?(BufferResolvable|Base64Resolvable)} [banner] The banner of the guild + * @property {?TextChannelResolvable} [systemChannel] The system channel of the guild + * @property {SystemChannelFlagsResolvable} [systemChannelFlags] The system channel flags of the guild + * @property {?TextChannelResolvable} [rulesChannel] The rules channel of the guild + * @property {?TextChannelResolvable} [publicUpdatesChannel] The community updates channel of the guild + * @property {?TextChannelResolvable} [safetyAlertsChannel] The safety alerts channel of the guild + * @property {?string} [preferredLocale] The preferred locale of the guild + * @property {GuildFeature[]} [features] The features of the guild + * @property {?string} [description] The discovery description of the guild + * @property {boolean} [premiumProgressBarEnabled] Whether the guild's premium progress bar is enabled + * @property {string} [reason] Reason for editing this guild + */ + + /** + * Data that can be resolved to a Text Channel object. This can be: + * * A TextChannel + * * A Snowflake + * @typedef {TextChannel|Snowflake} TextChannelResolvable + */ + + /** + * Data that can be resolved to a Voice Channel object. This can be: + * * A VoiceChannel + * * A Snowflake + * @typedef {VoiceChannel|Snowflake} VoiceChannelResolvable + */ + + /** + * Updates the guild with new information - e.g. a new name. + * @param {GuildEditOptions} options The options to provide + * @returns {Promise<Guild>} + * @example + * // Set the guild name + * guild.edit({ + * name: 'Discord Guild', + * }) + * .then(updated => console.log(`New guild name ${updated}`)) + * .catch(console.error); + */ + async edit({ + verificationLevel, + defaultMessageNotifications, + explicitContentFilter, + afkChannel, + afkTimeout, + icon, + owner, + splash, + discoverySplash, + banner, + systemChannel, + systemChannelFlags, + rulesChannel, + publicUpdatesChannel, + preferredLocale, + premiumProgressBarEnabled, + safetyAlertsChannel, + ...options + }) { + const data = await this.client.rest.patch(Routes.guild(this.id), { + body: { + ...options, + verification_level: verificationLevel, + default_message_notifications: defaultMessageNotifications, + explicit_content_filter: explicitContentFilter, + afk_channel_id: afkChannel && this.client.channels.resolveId(afkChannel), + afk_timeout: afkTimeout, + icon: icon && (await DataResolver.resolveImage(icon)), + owner_id: owner && this.client.users.resolveId(owner), + splash: splash && (await DataResolver.resolveImage(splash)), + discovery_splash: discoverySplash && (await DataResolver.resolveImage(discoverySplash)), + banner: banner && (await DataResolver.resolveImage(banner)), + system_channel_id: systemChannel && this.client.channels.resolveId(systemChannel), + system_channel_flags: + systemChannelFlags === undefined ? undefined : SystemChannelFlagsBitField.resolve(systemChannelFlags), + rules_channel_id: rulesChannel && this.client.channels.resolveId(rulesChannel), + public_updates_channel_id: publicUpdatesChannel && this.client.channels.resolveId(publicUpdatesChannel), + preferred_locale: preferredLocale, + premium_progress_bar_enabled: premiumProgressBarEnabled, + safety_alerts_channel_id: safetyAlertsChannel && this.client.channels.resolveId(safetyAlertsChannel), + }, + reason: options.reason, + }); + + return this.client.actions.GuildUpdate.handle(data).updated; + } + + /** + * Welcome channel data + * @typedef {Object} WelcomeChannelData + * @property {string} description The description to show for this welcome channel + * @property {TextChannel|NewsChannel|ForumChannel|Snowflake} channel The channel to link for this welcome channel + * @property {EmojiIdentifierResolvable} [emoji] The emoji to display for this welcome channel + */ + + /** + * Welcome screen edit data + * @typedef {Object} WelcomeScreenEditOptions + * @property {boolean} [enabled] Whether the welcome screen is enabled + * @property {string} [description] The description for the welcome screen + * @property {WelcomeChannelData[]} [welcomeChannels] The welcome channel data for the welcome screen + */ + + /** + * Data that can be resolved to a GuildTextChannel object. This can be: + * * A TextChannel + * * A NewsChannel + * * A Snowflake + * @typedef {TextChannel|NewsChannel|Snowflake} GuildTextChannelResolvable + */ + + /** + * Data that can be resolved to a GuildVoiceChannel object. This can be: + * * A VoiceChannel + * * A StageChannel + * * A Snowflake + * @typedef {VoiceChannel|StageChannel|Snowflake} GuildVoiceChannelResolvable + */ + + /** + * Updates the guild's welcome screen + * @param {WelcomeScreenEditOptions} options The options to provide + * @returns {Promise<WelcomeScreen>} + * @example + * guild.editWelcomeScreen({ + * description: 'Hello World', + * enabled: true, + * welcomeChannels: [ + * { + * description: 'foobar', + * channel: '222197033908436994', + * } + * ], + * }) + */ + async editWelcomeScreen(options) { + const { enabled, description, welcomeChannels } = options; + const welcome_channels = welcomeChannels?.map(welcomeChannelData => { + const emoji = this.emojis.resolve(welcomeChannelData.emoji); + return { + emoji_id: emoji?.id, + emoji_name: emoji?.name ?? welcomeChannelData.emoji, + channel_id: this.channels.resolveId(welcomeChannelData.channel), + description: welcomeChannelData.description, + }; + }); + + const patchData = await this.client.rest.patch(Routes.guildWelcomeScreen(this.id), { + body: { + welcome_channels, + description, + enabled, + }, + }); + return new WelcomeScreen(this, patchData); + } + + /** + * Edits the level of the explicit content filter. + * @param {?GuildExplicitContentFilter} explicitContentFilter The new level of the explicit content filter + * @param {string} [reason] Reason for changing the level of the guild's explicit content filter + * @returns {Promise<Guild>} + */ + setExplicitContentFilter(explicitContentFilter, reason) { + return this.edit({ explicitContentFilter, reason }); + } + + /** + * Edits the setting of the default message notifications of the guild. + * @param {?GuildDefaultMessageNotifications} defaultMessageNotifications + * The new default message notification level of the guild + * @param {string} [reason] Reason for changing the setting of the default message notifications + * @returns {Promise<Guild>} + */ + setDefaultMessageNotifications(defaultMessageNotifications, reason) { + return this.edit({ defaultMessageNotifications, reason }); + } + + /** + * Edits the flags of the default message notifications of the guild. + * @param {SystemChannelFlagsResolvable} systemChannelFlags The new flags for the default message notifications + * @param {string} [reason] Reason for changing the flags of the default message notifications + * @returns {Promise<Guild>} + */ + setSystemChannelFlags(systemChannelFlags, reason) { + return this.edit({ systemChannelFlags, reason }); + } + + /** + * Edits the name of the guild. + * @param {string} name The new name of the guild + * @param {string} [reason] Reason for changing the guild's name + * @returns {Promise<Guild>} + * @example + * // Edit the guild name + * guild.setName('Discord Guild') + * .then(updated => console.log(`Updated guild name to ${updated.name}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name, reason }); + } + + /** + * Edits the verification level of the guild. + * @param {?GuildVerificationLevel} verificationLevel The new verification level of the guild + * @param {string} [reason] Reason for changing the guild's verification level + * @returns {Promise<Guild>} + * @example + * // Edit the guild verification level + * guild.setVerificationLevel(1) + * .then(updated => console.log(`Updated guild verification level to ${guild.verificationLevel}`)) + * .catch(console.error); + */ + setVerificationLevel(verificationLevel, reason) { + return this.edit({ verificationLevel, reason }); + } + + /** + * Edits the AFK channel of the guild. + * @param {?VoiceChannelResolvable} afkChannel The new AFK channel + * @param {string} [reason] Reason for changing the guild's AFK channel + * @returns {Promise<Guild>} + * @example + * // Edit the guild AFK channel + * guild.setAFKChannel(channel) + * .then(updated => console.log(`Updated guild AFK channel to ${guild.afkChannel.name}`)) + * .catch(console.error); + */ + setAFKChannel(afkChannel, reason) { + return this.edit({ afkChannel, reason }); + } + + /** + * Edits the system channel of the guild. + * @param {?TextChannelResolvable} systemChannel The new system channel + * @param {string} [reason] Reason for changing the guild's system channel + * @returns {Promise<Guild>} + * @example + * // Edit the guild system channel + * guild.setSystemChannel(channel) + * .then(updated => console.log(`Updated guild system channel to ${guild.systemChannel.name}`)) + * .catch(console.error); + */ + setSystemChannel(systemChannel, reason) { + return this.edit({ systemChannel, reason }); + } + + /** + * Edits the AFK timeout of the guild. + * @param {number} afkTimeout The time in seconds that a user must be idle to be considered AFK + * @param {string} [reason] Reason for changing the guild's AFK timeout + * @returns {Promise<Guild>} + * @example + * // Edit the guild AFK channel + * guild.setAFKTimeout(60) + * .then(updated => console.log(`Updated guild AFK timeout to ${guild.afkTimeout}`)) + * .catch(console.error); + */ + setAFKTimeout(afkTimeout, reason) { + return this.edit({ afkTimeout, reason }); + } + + /** + * Sets a new guild icon. + * @param {?(Base64Resolvable|BufferResolvable)} icon The new icon of the guild + * @param {string} [reason] Reason for changing the guild's icon + * @returns {Promise<Guild>} + * @example + * // Edit the guild icon + * guild.setIcon('./icon.png') + * .then(updated => console.log('Updated the guild icon')) + * .catch(console.error); + */ + setIcon(icon, reason) { + return this.edit({ icon, reason }); + } + + /** + * Sets a new owner of the guild. + * @param {GuildMemberResolvable} owner The new owner of the guild + * @param {string} [reason] Reason for setting the new owner + * @returns {Promise<Guild>} + * @example + * // Edit the guild owner + * guild.setOwner(guild.members.cache.first()) + * .then(guild => guild.fetchOwner()) + * .then(owner => console.log(`Updated the guild owner to ${owner.displayName}`)) + * .catch(console.error); + */ + setOwner(owner, reason) { + return this.edit({ owner, reason }); + } + + /** + * Sets a new guild invite splash image. + * @param {?(Base64Resolvable|BufferResolvable)} splash The new invite splash image of the guild + * @param {string} [reason] Reason for changing the guild's invite splash image + * @returns {Promise<Guild>} + * @example + * // Edit the guild splash + * guild.setSplash('./splash.png') + * .then(updated => console.log('Updated the guild splash')) + * .catch(console.error); + */ + setSplash(splash, reason) { + return this.edit({ splash, reason }); + } + + /** + * Sets a new guild discovery splash image. + * @param {?(Base64Resolvable|BufferResolvable)} discoverySplash The new discovery splash image of the guild + * @param {string} [reason] Reason for changing the guild's discovery splash image + * @returns {Promise<Guild>} + * @example + * // Edit the guild discovery splash + * guild.setDiscoverySplash('./discoverysplash.png') + * .then(updated => console.log('Updated the guild discovery splash')) + * .catch(console.error); + */ + setDiscoverySplash(discoverySplash, reason) { + return this.edit({ discoverySplash, reason }); + } + + /** + * Sets a new guild banner. + * @param {?(Base64Resolvable|BufferResolvable)} banner The new banner of the guild + * @param {string} [reason] Reason for changing the guild's banner + * @returns {Promise<Guild>} + * @example + * guild.setBanner('./banner.png') + * .then(updated => console.log('Updated the guild banner')) + * .catch(console.error); + */ + setBanner(banner, reason) { + return this.edit({ banner, reason }); + } + + /** + * Edits the rules channel of the guild. + * @param {?TextChannelResolvable} rulesChannel The new rules channel + * @param {string} [reason] Reason for changing the guild's rules channel + * @returns {Promise<Guild>} + * @example + * // Edit the guild rules channel + * guild.setRulesChannel(channel) + * .then(updated => console.log(`Updated guild rules channel to ${guild.rulesChannel.name}`)) + * .catch(console.error); + */ + setRulesChannel(rulesChannel, reason) { + return this.edit({ rulesChannel, reason }); + } + + /** + * Edits the community updates channel of the guild. + * @param {?TextChannelResolvable} publicUpdatesChannel The new community updates channel + * @param {string} [reason] Reason for changing the guild's community updates channel + * @returns {Promise<Guild>} + * @example + * // Edit the guild community updates channel + * guild.setPublicUpdatesChannel(channel) + * .then(updated => console.log(`Updated guild community updates channel to ${guild.publicUpdatesChannel.name}`)) + * .catch(console.error); + */ + setPublicUpdatesChannel(publicUpdatesChannel, reason) { + return this.edit({ publicUpdatesChannel, reason }); + } + + /** + * Edits the preferred locale of the guild. + * @param {?Locale} preferredLocale The new preferred locale of the guild + * @param {string} [reason] Reason for changing the guild's preferred locale + * @returns {Promise<Guild>} + * @example + * // Edit the guild preferred locale + * guild.setPreferredLocale('en-US') + * .then(updated => console.log(`Updated guild preferred locale to ${guild.preferredLocale}`)) + * .catch(console.error); + */ + setPreferredLocale(preferredLocale, reason) { + return this.edit({ preferredLocale, reason }); + } + + /** + * Edits the enabled state of the guild's premium progress bar + * @param {boolean} [enabled=true] The new enabled state of the guild's premium progress bar + * @param {string} [reason] Reason for changing the state of the guild's premium progress bar + * @returns {Promise<Guild>} + */ + setPremiumProgressBarEnabled(enabled = true, reason) { + return this.edit({ premiumProgressBarEnabled: enabled, reason }); + } + + /** + * Edits the safety alerts channel of the guild. + * @param {?TextChannelResolvable} safetyAlertsChannel The new safety alerts channel + * @param {string} [reason] Reason for changing the guild's safety alerts channel + * @returns {Promise<Guild>} + * @example + * // Edit the guild safety alerts channel + * guild.setSafetyAlertsChannel(channel) + * .then(updated => console.log(`Updated guild safety alerts channel to ${updated.safetyAlertsChannel.name}`)) + * .catch(console.error); + */ + setSafetyAlertsChannel(safetyAlertsChannel, reason) { + return this.edit({ safetyAlertsChannel, reason }); + } + + /** + * Edits the guild's widget settings. + * @param {GuildWidgetSettingsData} settings The widget settings for the guild + * @param {string} [reason] Reason for changing the guild's widget settings + * @returns {Promise<Guild>} + */ + async setWidgetSettings(settings, reason) { + await this.client.rest.patch(Routes.guildWidgetSettings(this.id), { + body: { + enabled: settings.enabled, + channel_id: this.channels.resolveId(settings.channel), + }, + reason, + }); + return this; + } + + /** + * Sets the guild's MFA level + * <info>An elevated MFA level requires guild moderators to have 2FA enabled.</info> + * @param {GuildMFALevel} level The MFA level + * @param {string} [reason] Reason for changing the guild's MFA level + * @returns {Promise<Guild>} + * @example + * // Set the MFA level of the guild to Elevated + * guild.setMFALevel(GuildMFALevel.Elevated) + * .then(guild => console.log("Set guild's MFA level to Elevated")) + * .catch(console.error); + */ + async setMFALevel(level, reason) { + await this.client.rest.post(Routes.guildMFA(this.id), { + body: { + level, + }, + reason, + }); + return this; + } + + /** + * Leaves the guild. + * @returns {Promise<Guild>} + * @example + * // Leave a guild + * guild.leave() + * .then(guild => console.log(`Left the guild: ${guild.name}`)) + * .catch(console.error); + */ + async leave() { + if (this.ownerId === this.client.user.id) throw new DiscordjsError(ErrorCodes.GuildOwned); + await this.client.rest.delete(Routes.userGuild(this.id)); + return this; + } + + /** + * Deletes the guild. + * @returns {Promise<Guild>} + * @example + * // Delete a guild + * guild.delete() + * .then(g => console.log(`Deleted the guild ${g}`)) + * .catch(console.error); + */ + async delete() { + await this.client.rest.delete(Routes.guild(this.id)); + return this; + } + + /** + * Sets whether this guild's invites are disabled. + * @param {boolean} [disabled=true] Whether the invites are disabled + * @returns {Promise<Guild>} + */ + async disableInvites(disabled = true) { + const features = this.features.filter(feature => feature !== GuildFeature.InvitesDisabled); + if (disabled) features.push(GuildFeature.InvitesDisabled); + return this.edit({ features }); + } + + /** + * Whether this guild equals another guild. It compares all properties, so for most operations + * it is advisable to just compare `guild.id === guild2.id` as it is much faster and is often + * what most users need. + * @param {Guild} guild The guild to compare with + * @returns {boolean} + */ + equals(guild) { + return ( + guild && + guild instanceof this.constructor && + this.id === guild.id && + this.available === guild.available && + this.splash === guild.splash && + this.discoverySplash === guild.discoverySplash && + this.name === guild.name && + this.memberCount === guild.memberCount && + this.large === guild.large && + this.icon === guild.icon && + this.ownerId === guild.ownerId && + this.verificationLevel === guild.verificationLevel && + (this.features === guild.features || + (this.features.length === guild.features.length && + this.features.every((feat, i) => feat === guild.features[i]))) + ); + } + + toJSON() { + const json = super.toJSON({ + available: false, + createdTimestamp: true, + nameAcronym: true, + presences: false, + voiceStates: false, + }); + json.iconURL = this.iconURL(); + json.splashURL = this.splashURL(); + json.discoverySplashURL = this.discoverySplashURL(); + json.bannerURL = this.bannerURL(); + return json; + } + + /** + * The voice state adapter for this guild that can be used with @discordjs/voice to play audio in voice + * and stage channels. + * @type {Function} + * @readonly + */ + get voiceAdapterCreator() { + return methods => { + this.client.voice.adapters.set(this.id, methods); + return { + sendPayload: data => { + if (this.shard.status !== Status.Ready) return false; + this.shard.send(data); + return true; + }, + destroy: () => { + this.client.voice.adapters.delete(this.id); + }, + }; + }; + } + + /** + * Creates a collection of this guild's roles, sorted by their position and ids. + * @returns {Collection<Snowflake, Role>} + * @private + */ + _sortedRoles() { + return discordSort(this.roles.cache); + } + + /** + * Creates a collection of this guild's or a specific category's channels, sorted by their position and ids. + * @param {GuildChannel} [channel] Category to get the channels of + * @returns {Collection<Snowflake, GuildChannel>} + * @private + */ + _sortedChannels(channel) { + const channelIsCategory = channel.type === ChannelType.GuildCategory; + const types = getSortableGroupTypes(channel.type); + return discordSort( + this.channels.cache.filter(c => types.includes(c.type) && (channelIsCategory || c.parentId === channel.parentId)), + ); + } +} + +exports.Guild = Guild; + +/** + * @external APIGuild + * @see {@link https://discord.com/developers/docs/resources/guild#guild-object} + */ diff --git a/node_modules/discord.js/src/structures/GuildAuditLogs.js b/node_modules/discord.js/src/structures/GuildAuditLogs.js new file mode 100644 index 0000000..2ce13a8 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildAuditLogs.js @@ -0,0 +1,91 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const ApplicationCommand = require('./ApplicationCommand'); +const GuildAuditLogsEntry = require('./GuildAuditLogsEntry'); +const Integration = require('./Integration'); +const Webhook = require('./Webhook'); +const { flatten } = require('../util/Util'); + +/** + * Audit logs entries are held in this class. + */ +class GuildAuditLogs { + constructor(guild, data) { + if (data.users) for (const user of data.users) guild.client.users._add(user); + if (data.threads) for (const thread of data.threads) guild.client.channels._add(thread, guild); + /** + * Cached webhooks + * @type {Collection<Snowflake, Webhook>} + * @private + */ + this.webhooks = new Collection(); + if (data.webhooks) { + for (const hook of data.webhooks) { + this.webhooks.set(hook.id, new Webhook(guild.client, hook)); + } + } + + /** + * Cached integrations + * @type {Collection<Snowflake|string, Integration>} + * @private + */ + this.integrations = new Collection(); + if (data.integrations) { + for (const integration of data.integrations) { + this.integrations.set(integration.id, new Integration(guild.client, integration, guild)); + } + } + + /** + * Cached {@link GuildScheduledEvent}s. + * @type {Collection<Snowflake, GuildScheduledEvent>} + * @private + */ + this.guildScheduledEvents = data.guild_scheduled_events.reduce( + (guildScheduledEvents, guildScheduledEvent) => + guildScheduledEvents.set(guildScheduledEvent.id, guild.scheduledEvents._add(guildScheduledEvent)), + new Collection(), + ); + + /** + * Cached application commands, includes application commands from other applications + * @type {Collection<Snowflake, ApplicationCommand>} + * @private + */ + this.applicationCommands = new Collection(); + if (data.application_commands) { + for (const command of data.application_commands) { + this.applicationCommands.set(command.id, new ApplicationCommand(guild.client, command, guild)); + } + } + + /** + * Cached auto moderation rules. + * @type {Collection<Snowflake, AutoModerationRule>} + * @private + */ + this.autoModerationRules = data.auto_moderation_rules.reduce( + (autoModerationRules, autoModerationRule) => + autoModerationRules.set(autoModerationRule.id, guild.autoModerationRules._add(autoModerationRule)), + new Collection(), + ); + + /** + * The entries for this guild's audit logs + * @type {Collection<Snowflake, GuildAuditLogsEntry>} + */ + this.entries = new Collection(); + for (const item of data.audit_log_entries) { + const entry = new GuildAuditLogsEntry(guild, item, this); + this.entries.set(entry.id, entry); + } + } + + toJSON() { + return flatten(this); + } +} + +module.exports = GuildAuditLogs; diff --git a/node_modules/discord.js/src/structures/GuildAuditLogsEntry.js b/node_modules/discord.js/src/structures/GuildAuditLogsEntry.js new file mode 100644 index 0000000..febbd12 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildAuditLogsEntry.js @@ -0,0 +1,528 @@ +'use strict'; + +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { AuditLogOptionsType, AuditLogEvent } = require('discord-api-types/v10'); +const AutoModerationRule = require('./AutoModerationRule'); +const { GuildScheduledEvent } = require('./GuildScheduledEvent'); +const Integration = require('./Integration'); +const Invite = require('./Invite'); +const { StageInstance } = require('./StageInstance'); +const { Sticker } = require('./Sticker'); +const Webhook = require('./Webhook'); +const Partials = require('../util/Partials'); +const { flatten } = require('../util/Util'); + +const Targets = { + All: 'All', + Guild: 'Guild', + GuildScheduledEvent: 'GuildScheduledEvent', + Channel: 'Channel', + User: 'User', + Role: 'Role', + Invite: 'Invite', + Webhook: 'Webhook', + Emoji: 'Emoji', + Message: 'Message', + Integration: 'Integration', + StageInstance: 'StageInstance', + Sticker: 'Sticker', + Thread: 'Thread', + ApplicationCommand: 'ApplicationCommand', + AutoModeration: 'AutoModeration', + Unknown: 'Unknown', +}; + +/** + * The target of a guild audit log entry. It can be one of: + * * A guild + * * A channel + * * A user + * * A role + * * An invite + * * A webhook + * * An emoji + * * A message + * * An integration + * * A stage instance + * * A sticker + * * A guild scheduled event + * * A thread + * * An application command + * * An auto moderation rule + * * An object with an id key if target was deleted or fake entity + * * An object where the keys represent either the new value or the old value + * @typedef {?(Object|Guild|BaseChannel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance|Sticker| + * GuildScheduledEvent|ApplicationCommand|AutoModerationRule)} AuditLogEntryTarget + */ + +/** + * The action type of an entry, e.g. `Create`. Here are the available types: + * * Create + * * Delete + * * Update + * * All + * @typedef {string} AuditLogActionType + */ + +/** + * The target type of an entry. Here are the available types: + * * Guild + * * Channel + * * User + * * Role + * * Invite + * * Webhook + * * Emoji + * * Message + * * Integration + * * StageInstance + * * Sticker + * * Thread + * * GuildScheduledEvent + * * ApplicationCommandPermission + * @typedef {string} AuditLogTargetType + */ + +/** + * Audit logs entry. + */ +class GuildAuditLogsEntry { + /** + * Key mirror of all available audit log targets. + * @type {Object<string, string>} + * @memberof GuildAuditLogsEntry + */ + static Targets = Targets; + + constructor(guild, data, logs) { + /** + * The target type of this entry + * @type {AuditLogTargetType} + */ + this.targetType = GuildAuditLogsEntry.targetType(data.action_type); + const targetType = this.targetType; + + /** + * The action type of this entry + * @type {AuditLogActionType} + */ + this.actionType = GuildAuditLogsEntry.actionType(data.action_type); + + /** + * The type of action that occurred. + * @type {AuditLogEvent} + */ + this.action = data.action_type; + + /** + * The reason of this entry + * @type {?string} + */ + this.reason = data.reason ?? null; + + /** + * The id of the user that executed this entry + * @type {?Snowflake} + */ + this.executorId = data.user_id; + + /** + * The user that executed this entry + * @type {?User} + */ + this.executor = data.user_id + ? guild.client.options.partials.includes(Partials.User) + ? guild.client.users._add({ id: data.user_id }) + : guild.client.users.cache.get(data.user_id) ?? null + : null; + + /** + * An entry in the audit log representing a specific change. + * @typedef {Object} AuditLogChange + * @property {string} key The property that was changed, e.g. `nick` for nickname changes + * <warn>For application command permissions updates the key is the id of the user, channel, + * role, or a permission constant that was updated instead of an actual property name</warn> + * @property {*} [old] The old value of the change, e.g. for nicknames, the old nickname + * @property {*} [new] The new value of the change, e.g. for nicknames, the new nickname + */ + + /** + * Specific property changes + * @type {AuditLogChange[]} + */ + this.changes = data.changes?.map(c => ({ key: c.key, old: c.old_value, new: c.new_value })) ?? []; + + /** + * The entry's id + * @type {Snowflake} + */ + this.id = data.id; + + /** + * Any extra data from the entry + * @type {?(Object|Role|GuildMember)} + */ + this.extra = null; + switch (data.action_type) { + case AuditLogEvent.MemberPrune: + this.extra = { + removed: Number(data.options.members_removed), + days: Number(data.options.delete_member_days), + }; + break; + + case AuditLogEvent.MemberMove: + case AuditLogEvent.MessageDelete: + case AuditLogEvent.MessageBulkDelete: + this.extra = { + channel: guild.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id }, + count: Number(data.options.count), + }; + break; + + case AuditLogEvent.MessagePin: + case AuditLogEvent.MessageUnpin: + this.extra = { + channel: guild.client.channels.cache.get(data.options.channel_id) ?? { id: data.options.channel_id }, + messageId: data.options.message_id, + }; + break; + + case AuditLogEvent.MemberDisconnect: + this.extra = { + count: Number(data.options.count), + }; + break; + + case AuditLogEvent.ChannelOverwriteCreate: + case AuditLogEvent.ChannelOverwriteUpdate: + case AuditLogEvent.ChannelOverwriteDelete: + switch (data.options.type) { + case AuditLogOptionsType.Role: + this.extra = guild.roles.cache.get(data.options.id) ?? { + id: data.options.id, + name: data.options.role_name, + type: AuditLogOptionsType.Role, + }; + break; + + case AuditLogOptionsType.Member: + this.extra = guild.members.cache.get(data.options.id) ?? { + id: data.options.id, + type: AuditLogOptionsType.Member, + }; + break; + + default: + break; + } + break; + + case AuditLogEvent.StageInstanceCreate: + case AuditLogEvent.StageInstanceDelete: + case AuditLogEvent.StageInstanceUpdate: + this.extra = { + channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id }, + }; + break; + + case AuditLogEvent.ApplicationCommandPermissionUpdate: + this.extra = { + applicationId: data.options.application_id, + }; + break; + + case AuditLogEvent.AutoModerationBlockMessage: + case AuditLogEvent.AutoModerationFlagToChannel: + case AuditLogEvent.AutoModerationUserCommunicationDisabled: + this.extra = { + autoModerationRuleName: data.options.auto_moderation_rule_name, + autoModerationRuleTriggerType: data.options.auto_moderation_rule_trigger_type, + channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id }, + }; + break; + + default: + break; + } + + /** + * The id of the target of this entry + * @type {?Snowflake} + */ + this.targetId = data.target_id; + + /** + * The target of this entry + * @type {?AuditLogEntryTarget} + */ + this.target = null; + if (targetType === Targets.Unknown) { + this.target = this.changes.reduce((o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, {}); + this.target.id = data.target_id; + // MemberDisconnect and similar types do not provide a target_id. + } else if (targetType === Targets.User && data.target_id) { + this.target = guild.client.options.partials.includes(Partials.User) + ? guild.client.users._add({ id: data.target_id }) + : guild.client.users.cache.get(data.target_id) ?? null; + } else if (targetType === Targets.Guild) { + this.target = guild.client.guilds.cache.get(data.target_id); + } else if (targetType === Targets.Webhook) { + this.target = + logs?.webhooks.get(data.target_id) ?? + new Webhook( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { + id: data.target_id, + guild_id: guild.id, + }, + ), + ); + } else if (targetType === Targets.Invite) { + let change = this.changes.find(c => c.key === 'code'); + change = change.new ?? change.old; + + this.target = + guild.invites.cache.get(change) ?? + new Invite( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { guild }, + ), + ); + } else if (targetType === Targets.Message) { + // Discord sends a channel id for the MessageBulkDelete action type. + this.target = + data.action_type === AuditLogEvent.MessageBulkDelete + ? guild.channels.cache.get(data.target_id) ?? { id: data.target_id } + : guild.client.users.cache.get(data.target_id) ?? null; + } else if (targetType === Targets.Integration) { + this.target = + logs?.integrations.get(data.target_id) ?? + new Integration( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { id: data.target_id }, + ), + guild, + ); + } else if (targetType === Targets.Channel || targetType === Targets.Thread) { + this.target = + guild.channels.cache.get(data.target_id) ?? + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { id: data.target_id }, + ); + } else if (targetType === Targets.StageInstance) { + this.target = + guild.stageInstances.cache.get(data.target_id) ?? + new StageInstance( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { + id: data.target_id, + channel_id: data.options?.channel_id, + guild_id: guild.id, + }, + ), + ); + } else if (targetType === Targets.Sticker) { + this.target = + guild.stickers.cache.get(data.target_id) ?? + new Sticker( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { id: data.target_id }, + ), + ); + } else if (targetType === Targets.GuildScheduledEvent) { + this.target = + guild.scheduledEvents.cache.get(data.target_id) ?? + new GuildScheduledEvent( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { id: data.target_id, guild_id: guild.id }, + ), + ); + } else if (targetType === Targets.ApplicationCommand) { + this.target = logs?.applicationCommands.get(data.target_id) ?? { id: data.target_id }; + } else if (targetType === Targets.AutoModeration) { + this.target = + guild.autoModerationRules.cache.get(data.target_id) ?? + new AutoModerationRule( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { id: data.target_id, guild_id: guild.id }, + ), + guild, + ); + } else if (data.target_id) { + this.target = guild[`${targetType.toLowerCase()}s`]?.cache.get(data.target_id) ?? { id: data.target_id }; + } + } + + /** + * Finds the target type of a guild audit log entry. + * @param {AuditLogEvent} target The action target + * @returns {AuditLogTargetType} + */ + static targetType(target) { + if (target < 10) return Targets.Guild; + if (target < 20) return Targets.Channel; + if (target < 30) return Targets.User; + if (target < 40) return Targets.Role; + if (target < 50) return Targets.Invite; + if (target < 60) return Targets.Webhook; + if (target < 70) return Targets.Emoji; + if (target < 80) return Targets.Message; + if (target < 83) return Targets.Integration; + if (target < 86) return Targets.StageInstance; + if (target < 100) return Targets.Sticker; + if (target < 110) return Targets.GuildScheduledEvent; + if (target < 120) return Targets.Thread; + if (target < 130) return Targets.ApplicationCommand; + if (target >= 140 && target < 150) return Targets.AutoModeration; + return Targets.Unknown; + } + + /** + * Finds the action type from the guild audit log entry action. + * @param {AuditLogEvent} action The action target + * @returns {AuditLogActionType} + */ + static actionType(action) { + if ( + [ + AuditLogEvent.ChannelCreate, + AuditLogEvent.ChannelOverwriteCreate, + AuditLogEvent.MemberBanRemove, + AuditLogEvent.BotAdd, + AuditLogEvent.RoleCreate, + AuditLogEvent.InviteCreate, + AuditLogEvent.WebhookCreate, + AuditLogEvent.EmojiCreate, + AuditLogEvent.MessagePin, + AuditLogEvent.IntegrationCreate, + AuditLogEvent.StageInstanceCreate, + AuditLogEvent.StickerCreate, + AuditLogEvent.GuildScheduledEventCreate, + AuditLogEvent.ThreadCreate, + AuditLogEvent.AutoModerationRuleCreate, + AuditLogEvent.AutoModerationBlockMessage, + ].includes(action) + ) { + return 'Create'; + } + + if ( + [ + AuditLogEvent.ChannelDelete, + AuditLogEvent.ChannelOverwriteDelete, + AuditLogEvent.MemberKick, + AuditLogEvent.MemberPrune, + AuditLogEvent.MemberBanAdd, + AuditLogEvent.MemberDisconnect, + AuditLogEvent.RoleDelete, + AuditLogEvent.InviteDelete, + AuditLogEvent.WebhookDelete, + AuditLogEvent.EmojiDelete, + AuditLogEvent.MessageDelete, + AuditLogEvent.MessageBulkDelete, + AuditLogEvent.MessageUnpin, + AuditLogEvent.IntegrationDelete, + AuditLogEvent.StageInstanceDelete, + AuditLogEvent.StickerDelete, + AuditLogEvent.GuildScheduledEventDelete, + AuditLogEvent.ThreadDelete, + AuditLogEvent.AutoModerationRuleDelete, + ].includes(action) + ) { + return 'Delete'; + } + + if ( + [ + AuditLogEvent.GuildUpdate, + AuditLogEvent.ChannelUpdate, + AuditLogEvent.ChannelOverwriteUpdate, + AuditLogEvent.MemberUpdate, + AuditLogEvent.MemberRoleUpdate, + AuditLogEvent.MemberMove, + AuditLogEvent.RoleUpdate, + AuditLogEvent.InviteUpdate, + AuditLogEvent.WebhookUpdate, + AuditLogEvent.EmojiUpdate, + AuditLogEvent.IntegrationUpdate, + AuditLogEvent.StageInstanceUpdate, + AuditLogEvent.StickerUpdate, + AuditLogEvent.GuildScheduledEventUpdate, + AuditLogEvent.ThreadUpdate, + AuditLogEvent.ApplicationCommandPermissionUpdate, + AuditLogEvent.AutoModerationRuleUpdate, + ].includes(action) + ) { + return 'Update'; + } + + return 'All'; + } + + /** + * The timestamp this entry was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time this entry was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + toJSON() { + return flatten(this, { createdTimestamp: true }); + } +} + +module.exports = GuildAuditLogsEntry; diff --git a/node_modules/discord.js/src/structures/GuildBan.js b/node_modules/discord.js/src/structures/GuildBan.js new file mode 100644 index 0000000..9c5a10e --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildBan.js @@ -0,0 +1,59 @@ +'use strict'; + +const Base = require('./Base'); + +/** + * Represents a ban in a guild on Discord. + * @extends {Base} + */ +class GuildBan extends Base { + constructor(client, data, guild) { + super(client); + + /** + * The guild in which the ban is + * @type {Guild} + */ + this.guild = guild; + + this._patch(data); + } + + _patch(data) { + if ('user' in data) { + /** + * The user this ban applies to + * @type {User} + */ + this.user = this.client.users._add(data.user, true); + } + + if ('reason' in data) { + /** + * The reason for the ban + * @type {?string} + */ + this.reason = data.reason; + } + } + + /** + * Whether this GuildBan is partial. If the reason is not provided the value is null + * @type {boolean} + * @readonly + */ + get partial() { + return !('reason' in this); + } + + /** + * Fetches this GuildBan. + * @param {boolean} [force=true] Whether to skip the cache check and request the API + * @returns {Promise<GuildBan>} + */ + fetch(force = true) { + return this.guild.bans.fetch({ user: this.user, cache: true, force }); + } +} + +module.exports = GuildBan; diff --git a/node_modules/discord.js/src/structures/GuildChannel.js b/node_modules/discord.js/src/structures/GuildChannel.js new file mode 100644 index 0000000..c066c71 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildChannel.js @@ -0,0 +1,472 @@ +'use strict'; + +const { Snowflake } = require('@sapphire/snowflake'); +const { PermissionFlagsBits, ChannelType } = require('discord-api-types/v10'); +const { BaseChannel } = require('./BaseChannel'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const PermissionOverwriteManager = require('../managers/PermissionOverwriteManager'); +const { VoiceBasedChannelTypes } = require('../util/Constants'); +const PermissionsBitField = require('../util/PermissionsBitField'); +const { getSortableGroupTypes } = require('../util/Util'); + +/** + * Represents a guild channel from any of the following: + * - {@link TextChannel} + * - {@link VoiceChannel} + * - {@link CategoryChannel} + * - {@link NewsChannel} + * - {@link StageChannel} + * - {@link ForumChannel} + * @extends {BaseChannel} + * @abstract + */ +class GuildChannel extends BaseChannel { + constructor(guild, data, client, immediatePatch = true) { + super(client, data, false); + + /** + * The guild the channel is in + * @type {Guild} + */ + this.guild = guild; + + /** + * The id of the guild the channel is in + * @type {Snowflake} + */ + this.guildId = guild?.id ?? data.guild_id; + /** + * A manager of permission overwrites that belong to this channel + * @type {PermissionOverwriteManager} + */ + this.permissionOverwrites = new PermissionOverwriteManager(this); + + if (data && immediatePatch) this._patch(data); + } + + _patch(data) { + super._patch(data); + + if ('name' in data) { + /** + * The name of the guild channel + * @type {string} + */ + this.name = data.name; + } + + if ('position' in data) { + /** + * The raw position of the channel from Discord + * @type {number} + */ + this.rawPosition = data.position; + } + + if ('guild_id' in data) { + this.guildId = data.guild_id; + } + + if ('parent_id' in data) { + /** + * The id of the category parent of this channel + * @type {?Snowflake} + */ + this.parentId = data.parent_id; + } else { + this.parentId ??= null; + } + + if ('permission_overwrites' in data) { + this.permissionOverwrites.cache.clear(); + for (const overwrite of data.permission_overwrites) { + this.permissionOverwrites._add(overwrite); + } + } + } + + _clone() { + const clone = super._clone(); + clone.permissionOverwrites = new PermissionOverwriteManager(clone, this.permissionOverwrites.cache.values()); + return clone; + } + + /** + * The category parent of this channel + * @type {?CategoryChannel} + * @readonly + */ + get parent() { + return this.guild.channels.resolve(this.parentId); + } + + /** + * If the permissionOverwrites match the parent channel, null if no parent + * @type {?boolean} + * @readonly + */ + get permissionsLocked() { + if (!this.parent) return null; + + // Get all overwrites + const overwriteIds = new Set([ + ...this.permissionOverwrites.cache.keys(), + ...this.parent.permissionOverwrites.cache.keys(), + ]); + + // Compare all overwrites + return [...overwriteIds].every(key => { + const channelVal = this.permissionOverwrites.cache.get(key); + const parentVal = this.parent.permissionOverwrites.cache.get(key); + + // Handle empty overwrite + if ( + (!channelVal && + parentVal.deny.bitfield === PermissionsBitField.DefaultBit && + parentVal.allow.bitfield === PermissionsBitField.DefaultBit) || + (!parentVal && + channelVal.deny.bitfield === PermissionsBitField.DefaultBit && + channelVal.allow.bitfield === PermissionsBitField.DefaultBit) + ) { + return true; + } + + // Compare overwrites + return ( + channelVal !== undefined && + parentVal !== undefined && + channelVal.deny.bitfield === parentVal.deny.bitfield && + channelVal.allow.bitfield === parentVal.allow.bitfield + ); + }); + } + + /** + * The position of the channel + * @type {number} + * @readonly + */ + get position() { + const selfIsCategory = this.type === ChannelType.GuildCategory; + const types = getSortableGroupTypes(this.type); + + let count = 0; + for (const channel of this.guild.channels.cache.values()) { + if (!types.includes(channel.type)) continue; + if (!selfIsCategory && channel.parentId !== this.parentId) continue; + if (this.rawPosition === channel.rawPosition) { + if (Snowflake.compare(channel.id, this.id) === -1) count++; + } else if (this.rawPosition > channel.rawPosition) { + count++; + } + } + + return count; + } + + /** + * Gets the overall set of permissions for a member or role in this channel, taking into account channel overwrites. + * @param {GuildMemberResolvable|RoleResolvable} memberOrRole The member or role to obtain the overall permissions for + * @param {boolean} [checkAdmin=true] Whether having the {@link PermissionFlagsBits.Administrator} permission + * will return all permissions + * @returns {?Readonly<PermissionsBitField>} + */ + permissionsFor(memberOrRole, checkAdmin = true) { + const member = this.guild.members.resolve(memberOrRole); + if (member) return this.memberPermissions(member, checkAdmin); + const role = this.guild.roles.resolve(memberOrRole); + return role && this.rolePermissions(role, checkAdmin); + } + + overwritesFor(member, verified = false, roles = null) { + if (!verified) member = this.guild.members.resolve(member); + if (!member) return []; + + roles ??= member.roles.cache; + const roleOverwrites = []; + let memberOverwrites; + let everyoneOverwrites; + + for (const overwrite of this.permissionOverwrites.cache.values()) { + if (overwrite.id === this.guild.id) { + everyoneOverwrites = overwrite; + } else if (roles.has(overwrite.id)) { + roleOverwrites.push(overwrite); + } else if (overwrite.id === member.id) { + memberOverwrites = overwrite; + } + } + + return { + everyone: everyoneOverwrites, + roles: roleOverwrites, + member: memberOverwrites, + }; + } + + /** + * Gets the overall set of permissions for a member in this channel, taking into account channel overwrites. + * @param {GuildMember} member The member to obtain the overall permissions for + * @param {boolean} checkAdmin Whether having the {@link PermissionFlagsBits.Administrator} permission + * will return all permissions + * @returns {Readonly<PermissionsBitField>} + * @private + */ + memberPermissions(member, checkAdmin) { + if (checkAdmin && member.id === this.guild.ownerId) { + return new PermissionsBitField(PermissionsBitField.All).freeze(); + } + + const roles = member.roles.cache; + const permissions = new PermissionsBitField(roles.map(role => role.permissions)); + + if (checkAdmin && permissions.has(PermissionFlagsBits.Administrator)) { + return new PermissionsBitField(PermissionsBitField.All).freeze(); + } + + const overwrites = this.overwritesFor(member, true, roles); + + return permissions + .remove(overwrites.everyone?.deny ?? PermissionsBitField.DefaultBit) + .add(overwrites.everyone?.allow ?? PermissionsBitField.DefaultBit) + .remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.deny) : PermissionsBitField.DefaultBit) + .add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allow) : PermissionsBitField.DefaultBit) + .remove(overwrites.member?.deny ?? PermissionsBitField.DefaultBit) + .add(overwrites.member?.allow ?? PermissionsBitField.DefaultBit) + .freeze(); + } + + /** + * Gets the overall set of permissions for a role in this channel, taking into account channel overwrites. + * @param {Role} role The role to obtain the overall permissions for + * @param {boolean} checkAdmin Whether having the {@link PermissionFlagsBits.Administrator} permission + * will return all permissions + * @returns {Readonly<PermissionsBitField>} + * @private + */ + rolePermissions(role, checkAdmin) { + if (checkAdmin && role.permissions.has(PermissionFlagsBits.Administrator)) { + return new PermissionsBitField(PermissionsBitField.All).freeze(); + } + + const everyoneOverwrites = this.permissionOverwrites.cache.get(this.guild.id); + const roleOverwrites = this.permissionOverwrites.cache.get(role.id); + + return role.permissions + .remove(everyoneOverwrites?.deny ?? PermissionsBitField.DefaultBit) + .add(everyoneOverwrites?.allow ?? PermissionsBitField.DefaultBit) + .remove(roleOverwrites?.deny ?? PermissionsBitField.DefaultBit) + .add(roleOverwrites?.allow ?? PermissionsBitField.DefaultBit) + .freeze(); + } + + /** + * Locks in the permission overwrites from the parent channel. + * @returns {Promise<GuildChannel>} + */ + lockPermissions() { + if (!this.parent) return Promise.reject(new DiscordjsError(ErrorCodes.GuildChannelOrphan)); + const permissionOverwrites = this.parent.permissionOverwrites.cache.map(overwrite => overwrite.toJSON()); + return this.edit({ permissionOverwrites }); + } + + /** + * A collection of cached members of this channel, mapped by their ids. + * Members that can view this channel, if the channel is text-based. + * Members in the channel, if the channel is voice-based. + * @type {Collection<Snowflake, GuildMember>} + * @readonly + */ + get members() { + return this.guild.members.cache.filter(m => this.permissionsFor(m).has(PermissionFlagsBits.ViewChannel, false)); + } + + /** + * Edits the channel. + * @param {GuildChannelEditOptions} options The options to provide + * @returns {Promise<GuildChannel>} + * @example + * // Edit a channel + * channel.edit({ name: 'new-channel' }) + * .then(console.log) + * .catch(console.error); + */ + edit(options) { + return this.guild.channels.edit(this, options); + } + + /** + * Sets a new name for the guild channel. + * @param {string} name The new name for the guild channel + * @param {string} [reason] Reason for changing the guild channel's name + * @returns {Promise<GuildChannel>} + * @example + * // Set a new channel name + * channel.setName('not_general') + * .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name, reason }); + } + + /** + * Options used to set the parent of a channel. + * @typedef {Object} SetParentOptions + * @property {boolean} [lockPermissions=true] Whether to lock the permissions to what the parent's permissions are + * @property {string} [reason] The reason for modifying the parent of the channel + */ + + /** + * Sets the parent of this channel. + * @param {?CategoryChannelResolvable} channel The category channel to set as the parent + * @param {SetParentOptions} [options={}] The options for setting the parent + * @returns {Promise<GuildChannel>} + * @example + * // Add a parent to a channel + * message.channel.setParent('355908108431917066', { lockPermissions: false }) + * .then(channel => console.log(`New parent of ${message.channel.name}: ${channel.name}`)) + * .catch(console.error); + */ + setParent(channel, { lockPermissions = true, reason } = {}) { + return this.edit({ + parent: channel ?? null, + lockPermissions, + reason, + }); + } + + /** + * Options used to set the position of a channel. + * @typedef {Object} SetChannelPositionOptions + * @property {boolean} [relative=false] Whether or not to change the position relative to its current value + * @property {string} [reason] The reason for changing the position + */ + + /** + * Sets a new position for the guild channel. + * @param {number} position The new position for the guild channel + * @param {SetChannelPositionOptions} [options] Options for setting position + * @returns {Promise<GuildChannel>} + * @example + * // Set a new channel position + * channel.setPosition(2) + * .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`)) + * .catch(console.error); + */ + setPosition(position, options = {}) { + return this.guild.channels.setPosition(this, position, options); + } + + /** + * Options used to clone a guild channel. + * @typedef {GuildChannelCreateOptions} GuildChannelCloneOptions + * @property {string} [name=this.name] Name of the new channel + */ + + /** + * Clones this channel. + * @param {GuildChannelCloneOptions} [options] The options for cloning this channel + * @returns {Promise<GuildChannel>} + */ + clone(options = {}) { + return this.guild.channels.create({ + name: options.name ?? this.name, + permissionOverwrites: this.permissionOverwrites.cache, + topic: this.topic, + type: this.type, + nsfw: this.nsfw, + parent: this.parent, + bitrate: this.bitrate, + userLimit: this.userLimit, + rateLimitPerUser: this.rateLimitPerUser, + position: this.rawPosition, + reason: null, + ...options, + }); + } + + /** + * Checks if this channel has the same type, topic, position, name, overwrites, and id as another channel. + * In most cases, a simple `channel.id === channel2.id` will do, and is much faster too. + * @param {GuildChannel} channel Channel to compare with + * @returns {boolean} + */ + equals(channel) { + let equal = + channel && + this.id === channel.id && + this.type === channel.type && + this.topic === channel.topic && + this.position === channel.position && + this.name === channel.name; + + if (equal) { + if (this.permissionOverwrites && channel.permissionOverwrites) { + equal = this.permissionOverwrites.cache.equals(channel.permissionOverwrites.cache); + } else { + equal = !this.permissionOverwrites && !channel.permissionOverwrites; + } + } + + return equal; + } + + /** + * Whether the channel is deletable by the client user + * @type {boolean} + * @readonly + */ + get deletable() { + return this.manageable && this.guild.rulesChannelId !== this.id && this.guild.publicUpdatesChannelId !== this.id; + } + + /** + * Whether the channel is manageable by the client user + * @type {boolean} + * @readonly + */ + get manageable() { + if (this.client.user.id === this.guild.ownerId) return true; + const permissions = this.permissionsFor(this.client.user); + if (!permissions) return false; + + // This flag allows managing even if timed out + if (permissions.has(PermissionFlagsBits.Administrator, false)) return true; + if (this.guild.members.me.communicationDisabledUntilTimestamp > Date.now()) return false; + + const bitfield = VoiceBasedChannelTypes.includes(this.type) + ? PermissionFlagsBits.ManageChannels | PermissionFlagsBits.Connect + : PermissionFlagsBits.ViewChannel | PermissionFlagsBits.ManageChannels; + return permissions.has(bitfield, false); + } + + /** + * Whether the channel is viewable by the client user + * @type {boolean} + * @readonly + */ + get viewable() { + if (this.client.user.id === this.guild.ownerId) return true; + const permissions = this.permissionsFor(this.client.user); + if (!permissions) return false; + return permissions.has(PermissionFlagsBits.ViewChannel, false); + } + + /** + * Deletes this channel. + * @param {string} [reason] Reason for deleting this channel + * @returns {Promise<GuildChannel>} + * @example + * // Delete the channel + * channel.delete('making room for new channels') + * .then(console.log) + * .catch(console.error); + */ + async delete(reason) { + await this.guild.channels.delete(this.id, reason); + return this; + } +} + +module.exports = GuildChannel; diff --git a/node_modules/discord.js/src/structures/GuildEmoji.js b/node_modules/discord.js/src/structures/GuildEmoji.js new file mode 100644 index 0000000..0035a36 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildEmoji.js @@ -0,0 +1,148 @@ +'use strict'; + +const { PermissionFlagsBits } = require('discord-api-types/v10'); +const BaseGuildEmoji = require('./BaseGuildEmoji'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const GuildEmojiRoleManager = require('../managers/GuildEmojiRoleManager'); + +/** + * Represents a custom emoji. + * @extends {BaseGuildEmoji} + */ +class GuildEmoji extends BaseGuildEmoji { + constructor(client, data, guild) { + super(client, data, guild); + + /** + * The user who created this emoji + * @type {?User} + */ + this.author = null; + + /** + * Array of role ids this emoji is active for + * @name GuildEmoji#_roles + * @type {Snowflake[]} + * @private + */ + Object.defineProperty(this, '_roles', { value: [], writable: true }); + + this._patch(data); + } + + /** + * The guild this emoji is part of + * @type {Guild} + * @name GuildEmoji#guild + */ + + _clone() { + const clone = super._clone(); + clone._roles = this._roles.slice(); + return clone; + } + + _patch(data) { + super._patch(data); + + if (data.user) this.author = this.client.users._add(data.user); + if (data.roles) this._roles = data.roles; + } + + /** + * Whether the emoji is deletable by the client user + * @type {boolean} + * @readonly + */ + get deletable() { + if (!this.guild.members.me) throw new DiscordjsError(ErrorCodes.GuildUncachedMe); + return !this.managed && this.guild.members.me.permissions.has(PermissionFlagsBits.ManageGuildExpressions); + } + + /** + * A manager for roles this emoji is active for. + * @type {GuildEmojiRoleManager} + * @readonly + */ + get roles() { + return new GuildEmojiRoleManager(this); + } + + /** + * Fetches the author for this emoji + * @returns {Promise<User>} + */ + fetchAuthor() { + return this.guild.emojis.fetchAuthor(this); + } + + /** + * Data for editing an emoji. + * @typedef {Object} GuildEmojiEditOptions + * @property {string} [name] The name of the emoji + * @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] Roles to restrict emoji to + * @property {string} [reason] Reason for editing this emoji + */ + + /** + * Edits the emoji. + * @param {GuildEmojiEditOptions} options The options to provide + * @returns {Promise<GuildEmoji>} + * @example + * // Edit an emoji + * emoji.edit({ name: 'newemoji' }) + * .then(e => console.log(`Edited emoji ${e}`)) + * .catch(console.error); + */ + edit(options) { + return this.guild.emojis.edit(this.id, options); + } + + /** + * Sets the name of the emoji. + * @param {string} name The new name for the emoji + * @param {string} [reason] Reason for changing the emoji's name + * @returns {Promise<GuildEmoji>} + */ + setName(name, reason) { + return this.edit({ name, reason }); + } + + /** + * Deletes the emoji. + * @param {string} [reason] Reason for deleting the emoji + * @returns {Promise<GuildEmoji>} + */ + async delete(reason) { + await this.guild.emojis.delete(this.id, reason); + return this; + } + + /** + * Whether this emoji is the same as another one. + * @param {GuildEmoji|APIEmoji} other The emoji to compare it to + * @returns {boolean} + */ + equals(other) { + if (other instanceof GuildEmoji) { + return ( + other.id === this.id && + other.name === this.name && + other.managed === this.managed && + other.available === this.available && + other.requiresColons === this.requiresColons && + other.roles.cache.size === this.roles.cache.size && + other.roles.cache.every(role => this.roles.cache.has(role.id)) + ); + } else { + return ( + other.id === this.id && + other.name === this.name && + other.roles.length === this.roles.cache.size && + other.roles.every(role => this.roles.cache.has(role)) + ); + } + } +} + +module.exports = GuildEmoji; diff --git a/node_modules/discord.js/src/structures/GuildMember.js b/node_modules/discord.js/src/structures/GuildMember.js new file mode 100644 index 0000000..8806b50 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildMember.js @@ -0,0 +1,520 @@ +'use strict'; + +const { PermissionFlagsBits } = require('discord-api-types/v10'); +const Base = require('./Base'); +const VoiceState = require('./VoiceState'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const GuildMemberRoleManager = require('../managers/GuildMemberRoleManager'); +const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField'); +const PermissionsBitField = require('../util/PermissionsBitField'); + +/** + * Represents a member of a guild on Discord. + * @implements {TextBasedChannel} + * @extends {Base} + */ +class GuildMember extends Base { + constructor(client, data, guild) { + super(client); + + /** + * The guild that this member is part of + * @type {Guild} + */ + this.guild = guild; + + /** + * The timestamp the member joined the guild at + * @type {?number} + */ + this.joinedTimestamp = null; + + /** + * The last timestamp this member started boosting the guild + * @type {?number} + */ + this.premiumSinceTimestamp = null; + + /** + * The nickname of this member, if they have one + * @type {?string} + */ + this.nickname = null; + + /** + * Whether this member has yet to pass the guild's membership gate + * @type {?boolean} + */ + this.pending = null; + + /** + * The timestamp this member's timeout will be removed + * @type {?number} + */ + this.communicationDisabledUntilTimestamp = null; + + /** + * The role ids of the member + * @name GuildMember#_roles + * @type {Snowflake[]} + * @private + */ + Object.defineProperty(this, '_roles', { value: [], writable: true }); + + if (data) this._patch(data); + } + + _patch(data) { + if ('user' in data) { + /** + * The user that this guild member instance represents + * @type {?User} + */ + this.user = this.client.users._add(data.user, true); + } + + if ('nick' in data) this.nickname = data.nick; + if ('avatar' in data) { + /** + * The guild member's avatar hash + * @type {?string} + */ + this.avatar = data.avatar; + } else if (typeof this.avatar !== 'string') { + this.avatar = null; + } + if ('joined_at' in data) this.joinedTimestamp = Date.parse(data.joined_at); + if ('premium_since' in data) { + this.premiumSinceTimestamp = data.premium_since ? Date.parse(data.premium_since) : null; + } + if ('roles' in data) this._roles = data.roles; + + if ('pending' in data) { + this.pending = data.pending; + } else if (!this.partial) { + // See https://github.com/discordjs/discord.js/issues/6546 for more info. + this.pending ??= false; + } + + if ('communication_disabled_until' in data) { + this.communicationDisabledUntilTimestamp = + data.communication_disabled_until && Date.parse(data.communication_disabled_until); + } + + if ('flags' in data) { + /** + * The flags of this member + * @type {Readonly<GuildMemberFlagsBitField>} + */ + this.flags = new GuildMemberFlagsBitField(data.flags).freeze(); + } else { + this.flags ??= new GuildMemberFlagsBitField().freeze(); + } + } + + _clone() { + const clone = super._clone(); + clone._roles = this._roles.slice(); + return clone; + } + + /** + * Whether this GuildMember is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return this.joinedTimestamp === null; + } + + /** + * A manager for the roles belonging to this member + * @type {GuildMemberRoleManager} + * @readonly + */ + get roles() { + return new GuildMemberRoleManager(this); + } + + /** + * The voice state of this member + * @type {VoiceState} + * @readonly + */ + get voice() { + return this.guild.voiceStates.cache.get(this.id) ?? new VoiceState(this.guild, { user_id: this.id }); + } + + /** + * A link to the member's guild avatar. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + avatarURL(options = {}) { + return this.avatar && this.client.rest.cdn.guildMemberAvatar(this.guild.id, this.id, this.avatar, options); + } + + /** + * A link to the member's guild avatar if they have one. + * Otherwise, a link to their {@link User#displayAvatarURL} will be returned. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {string} + */ + displayAvatarURL(options) { + return this.avatarURL(options) ?? this.user.displayAvatarURL(options); + } + + /** + * The time this member joined the guild + * @type {?Date} + * @readonly + */ + get joinedAt() { + return this.joinedTimestamp && new Date(this.joinedTimestamp); + } + + /** + * The time this member's timeout will be removed + * @type {?Date} + * @readonly + */ + get communicationDisabledUntil() { + return this.communicationDisabledUntilTimestamp && new Date(this.communicationDisabledUntilTimestamp); + } + + /** + * The last time this member started boosting the guild + * @type {?Date} + * @readonly + */ + get premiumSince() { + return this.premiumSinceTimestamp && new Date(this.premiumSinceTimestamp); + } + + /** + * The presence of this guild member + * @type {?Presence} + * @readonly + */ + get presence() { + return this.guild.presences.resolve(this.id); + } + + /** + * The displayed color of this member in base 10 + * @type {number} + * @readonly + */ + get displayColor() { + return this.roles.color?.color ?? 0; + } + + /** + * The displayed color of this member in hexadecimal + * @type {string} + * @readonly + */ + get displayHexColor() { + return this.roles.color?.hexColor ?? '#000000'; + } + + /** + * The member's id + * @type {Snowflake} + * @readonly + */ + get id() { + return this.user.id; + } + + /** + * The DM between the client's user and this member + * @type {?DMChannel} + * @readonly + */ + get dmChannel() { + return this.client.users.dmChannel(this.id); + } + + /** + * The nickname of this member, or their user display name if they don't have one + * @type {?string} + * @readonly + */ + get displayName() { + return this.nickname ?? this.user.displayName; + } + + /** + * The overall set of permissions for this member, taking only roles and owner status into account + * @type {Readonly<PermissionsBitField>} + * @readonly + */ + get permissions() { + if (this.user.id === this.guild.ownerId) return new PermissionsBitField(PermissionsBitField.All).freeze(); + return new PermissionsBitField(this.roles.cache.map(role => role.permissions)).freeze(); + } + + /** + * Whether the client user is above this user in the hierarchy, according to role position and guild ownership. + * This is a prerequisite for many moderative actions. + * @type {boolean} + * @readonly + */ + get manageable() { + if (this.user.id === this.guild.ownerId) return false; + if (this.user.id === this.client.user.id) return false; + if (this.client.user.id === this.guild.ownerId) return true; + if (!this.guild.members.me) throw new DiscordjsError(ErrorCodes.GuildUncachedMe); + return this.guild.members.me.roles.highest.comparePositionTo(this.roles.highest) > 0; + } + + /** + * Whether this member is kickable by the client user + * @type {boolean} + * @readonly + */ + get kickable() { + if (!this.guild.members.me) throw new DiscordjsError(ErrorCodes.GuildUncachedMe); + return this.manageable && this.guild.members.me.permissions.has(PermissionFlagsBits.KickMembers); + } + + /** + * Whether this member is bannable by the client user + * @type {boolean} + * @readonly + */ + get bannable() { + if (!this.guild.members.me) throw new DiscordjsError(ErrorCodes.GuildUncachedMe); + return this.manageable && this.guild.members.me.permissions.has(PermissionFlagsBits.BanMembers); + } + + /** + * Whether this member is moderatable by the client user + * @type {boolean} + * @readonly + */ + get moderatable() { + return ( + !this.permissions.has(PermissionFlagsBits.Administrator) && + this.manageable && + (this.guild.members.me?.permissions.has(PermissionFlagsBits.ModerateMembers) ?? false) + ); + } + + /** + * Whether this member is currently timed out + * @returns {boolean} + */ + isCommunicationDisabled() { + return this.communicationDisabledUntilTimestamp > Date.now(); + } + + /** + * Returns `channel.permissionsFor(guildMember)`. Returns permissions for a member in a guild channel, + * taking into account roles and permission overwrites. + * @param {GuildChannelResolvable} channel The guild channel to use as context + * @returns {Readonly<PermissionsBitField>} + */ + permissionsIn(channel) { + channel = this.guild.channels.resolve(channel); + if (!channel) throw new DiscordjsError(ErrorCodes.GuildChannelResolve); + return channel.permissionsFor(this); + } + + /** + * Edits this member. + * @param {GuildMemberEditOptions} options The options to provide + * @returns {Promise<GuildMember>} + */ + edit(options) { + return this.guild.members.edit(this, options); + } + + /** + * Sets the flags for this member. + * @param {GuildMemberFlagsResolvable} flags The flags to set + * @param {string} [reason] Reason for setting the flags + * @returns {Promise<GuildMember>} + */ + setFlags(flags, reason) { + return this.edit({ flags, reason }); + } + + /** + * Sets the nickname for this member. + * @param {?string} nick The nickname for the guild member, or `null` if you want to reset their nickname + * @param {string} [reason] Reason for setting the nickname + * @returns {Promise<GuildMember>} + * @example + * // Set a nickname for a guild member + * guildMember.setNickname('cool nickname', 'Needed a new nickname') + * .then(member => console.log(`Set nickname of ${member.user.username}`)) + * .catch(console.error); + * @example + * // Remove a nickname for a guild member + * guildMember.setNickname(null, 'No nicknames allowed!') + * .then(member => console.log(`Removed nickname for ${member.user.username}`)) + * .catch(console.error); + */ + setNickname(nick, reason) { + return this.edit({ nick, reason }); + } + + /** + * Creates a DM channel between the client and this member. + * @param {boolean} [force=false] Whether to skip the cache check and request the API + * @returns {Promise<DMChannel>} + */ + createDM(force = false) { + return this.user.createDM(force); + } + + /** + * Deletes any DMs with this member. + * @returns {Promise<DMChannel>} + */ + deleteDM() { + return this.user.deleteDM(); + } + + /** + * Kicks this member from the guild. + * @param {string} [reason] Reason for kicking user + * @returns {Promise<GuildMember>} + */ + kick(reason) { + return this.guild.members.kick(this, reason); + } + + /** + * Bans this guild member. + * @param {BanOptions} [options] Options for the ban + * @returns {Promise<GuildMember>} + * @example + * // Ban a guild member, deleting a week's worth of messages + * guildMember.ban({ deleteMessageSeconds: 60 * 60 * 24 * 7, reason: 'They deserved it' }) + * .then(console.log) + * .catch(console.error); + */ + ban(options) { + return this.guild.bans.create(this, options); + } + + /** + * Times this guild member out. + * @param {DateResolvable|null} communicationDisabledUntil The date or timestamp + * for the member's communication to be disabled until. Provide `null` to remove the timeout. + * @param {string} [reason] The reason for this timeout. + * @returns {Promise<GuildMember>} + * @example + * // Time a guild member out for 5 minutes + * guildMember.disableCommunicationUntil(Date.now() + (5 * 60 * 1000), 'They deserved it') + * .then(console.log) + * .catch(console.error); + * @example + * // Remove the timeout of a guild member + * guildMember.disableCommunicationUntil(null) + * .then(member => console.log(`Removed timeout for ${member.displayName}`)) + * .catch(console.error); + */ + disableCommunicationUntil(communicationDisabledUntil, reason) { + return this.edit({ communicationDisabledUntil, reason }); + } + + /** + * Times this guild member out. + * @param {number|null} timeout The time in milliseconds + * for the member's communication to be disabled until. Provide `null` to remove the timeout. + * @param {string} [reason] The reason for this timeout. + * @returns {Promise<GuildMember>} + * @example + * // Time a guild member out for 5 minutes + * guildMember.timeout(5 * 60 * 1000, 'They deserved it') + * .then(console.log) + * .catch(console.error); + */ + timeout(timeout, reason) { + return this.disableCommunicationUntil(timeout && Date.now() + timeout, reason); + } + + /** + * Fetches this GuildMember. + * @param {boolean} [force=true] Whether to skip the cache check and request the API + * @returns {Promise<GuildMember>} + */ + fetch(force = true) { + return this.guild.members.fetch({ user: this.id, cache: true, force }); + } + + /** + * Whether this guild member equals another guild member. It compares all properties, so for most + * comparison it is advisable to just compare `member.id === member2.id` as it is significantly faster + * and is often what most users need. + * @param {GuildMember} member The member to compare with + * @returns {boolean} + */ + equals(member) { + return ( + member instanceof this.constructor && + this.id === member.id && + this.partial === member.partial && + this.guild.id === member.guild.id && + this.joinedTimestamp === member.joinedTimestamp && + this.nickname === member.nickname && + this.avatar === member.avatar && + this.pending === member.pending && + this.communicationDisabledUntilTimestamp === member.communicationDisabledUntilTimestamp && + this.flags.bitfield === member.flags.bitfield && + (this._roles === member._roles || + (this._roles.length === member._roles.length && this._roles.every((role, i) => role === member._roles[i]))) + ); + } + + /** + * When concatenated with a string, this automatically returns the user's mention instead of the GuildMember object. + * @returns {string} + * @example + * // Logs: Hello from <@123456789012345678>! + * console.log(`Hello from ${member}!`); + */ + toString() { + return this.user.toString(); + } + + toJSON() { + const json = super.toJSON({ + guild: 'guildId', + user: 'userId', + displayName: true, + roles: true, + }); + json.avatarURL = this.avatarURL(); + json.displayAvatarURL = this.displayAvatarURL(); + return json; + } +} + +/** + * Sends a message to this user. + * @method send + * @memberof GuildMember + * @instance + * @param {string|MessagePayload|MessageCreateOptions} options The options to provide + * @returns {Promise<Message>} + * @example + * // Send a direct message + * guildMember.send('Hello!') + * .then(message => console.log(`Sent message: ${message.content} to ${guildMember.displayName}`)) + * .catch(console.error); + */ + +TextBasedChannel.applyToClass(GuildMember); + +exports.GuildMember = GuildMember; + +/** + * @external APIGuildMember + * @see {@link https://discord.com/developers/docs/resources/guild#guild-member-object} + */ diff --git a/node_modules/discord.js/src/structures/GuildOnboarding.js b/node_modules/discord.js/src/structures/GuildOnboarding.js new file mode 100644 index 0000000..119f905 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildOnboarding.js @@ -0,0 +1,58 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Base = require('./Base'); +const { GuildOnboardingPrompt } = require('./GuildOnboardingPrompt'); + +/** + * Represents the onboarding data of a guild. + * @extends {Base} + */ +class GuildOnboarding extends Base { + constructor(client, data) { + super(client); + + /** + * The id of the guild this onboarding data is for + * @type {Snowflake} + */ + this.guildId = data.guild_id; + + const guild = this.guild; + + /** + * The prompts shown during onboarding and in customize community + * @type {Collection<Snowflake, GuildOnboardingPrompt>} + */ + this.prompts = data.prompts.reduce( + (prompts, prompt) => prompts.set(prompt.id, new GuildOnboardingPrompt(client, prompt, this.guildId)), + new Collection(), + ); + + /** + * The ids of the channels that new members get opted into automatically + * @type {Collection<Snowflake, GuildChannel>} + */ + this.defaultChannels = data.default_channel_ids.reduce( + (channels, channelId) => channels.set(channelId, guild.channels.cache.get(channelId)), + new Collection(), + ); + + /** + * Whether onboarding is enabled + * @type {boolean} + */ + this.enabled = data.enabled; + } + + /** + * The guild this onboarding is from + * @type {Guild} + * @readonly + */ + get guild() { + return this.client.guilds.cache.get(this.guildId); + } +} + +exports.GuildOnboarding = GuildOnboarding; diff --git a/node_modules/discord.js/src/structures/GuildOnboardingPrompt.js b/node_modules/discord.js/src/structures/GuildOnboardingPrompt.js new file mode 100644 index 0000000..4de3f5d --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildOnboardingPrompt.js @@ -0,0 +1,78 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Base = require('./Base'); +const { GuildOnboardingPromptOption } = require('./GuildOnboardingPromptOption'); + +/** + * Represents the data of a prompt of a guilds onboarding. + * @extends {Base} + */ +class GuildOnboardingPrompt extends Base { + constructor(client, data, guildId) { + super(client); + + /** + * The id of the guild this onboarding prompt is from + * @type {Snowflake} + */ + this.guildId = guildId; + + /** + * The id of the prompt + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The options available within the prompt + * @type {Collection<Snowflake, GuildOnboardingPromptOption>} + */ + this.options = data.options.reduce( + (options, option) => options.set(option.id, new GuildOnboardingPromptOption(client, option, guildId)), + new Collection(), + ); + + /** + * The title of the prompt + * @type {string} + */ + this.title = data.title; + + /** + * Whether users are limited to selecting one option for the prompt + * @type {boolean} + */ + this.singleSelect = data.single_select; + + /** + * Whether the prompt is required before a user completes the onboarding flow + * @type {boolean} + */ + this.required = data.required; + + /** + * Whether the prompt is present in the onboarding flow. + * If `false`, the prompt will only appear in the Channels & Roles tab + * @type {boolean} + */ + this.inOnboarding = data.in_onboarding; + + /** + * The type of the prompt + * @type {GuildOnboardingPromptType} + */ + this.type = data.type; + } + + /** + * The guild this onboarding prompt is from + * @type {Guild} + * @readonly + */ + get guild() { + return this.client.guilds.cache.get(this.guildId); + } +} + +exports.GuildOnboardingPrompt = GuildOnboardingPrompt; diff --git a/node_modules/discord.js/src/structures/GuildOnboardingPromptOption.js b/node_modules/discord.js/src/structures/GuildOnboardingPromptOption.js new file mode 100644 index 0000000..3002144 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildOnboardingPromptOption.js @@ -0,0 +1,84 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Base = require('./Base'); +const { resolvePartialEmoji } = require('../util/Util'); + +/** + * Represents the data of an option from a prompt of a guilds onboarding. + * @extends {Base} + */ +class GuildOnboardingPromptOption extends Base { + constructor(client, data, guildId) { + super(client); + + /** + * The id of the guild this onboarding prompt option is from + * @type {Snowflake} + */ + this.guildId = guildId; + + const guild = this.guild; + + /** + * The id of the option + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The channels a member is added to when the option is selected + * @type {Collection<Snowflake, GuildChannel>} + */ + this.channels = data.channel_ids.reduce( + (channels, channelId) => channels.set(channelId, guild.channels.cache.get(channelId)), + new Collection(), + ); + + /** + * The roles assigned to a member when the option is selected + * @type {Collection<Snowflake, Role>} + */ + this.roles = data.role_ids.reduce( + (roles, roleId) => roles.set(roleId, guild.roles.cache.get(roleId)), + new Collection(), + ); + + /** + * The data for an emoji of a guilds onboarding prompt option + * @typedef {Object} GuildOnboardingPromptOptionEmoji + * @property {?Snowflake} id The id of the emoji + * @property {string} name The name of the emoji + * @property {boolean} animated Whether the emoji is animated + */ + + /** + * The emoji of the option + * @type {?GuildOnboardingPromptOptionEmoji} + */ + this.emoji = resolvePartialEmoji(data.emoji); + + /** + * The title of the option + * @type {string} + */ + this.title = data.title; + + /** + * The description of the option + * @type {?string} + */ + this.description = data.description; + } + + /** + * The guild this onboarding prompt option is from + * @type {Guild} + * @readonly + */ + get guild() { + return this.client.guilds.cache.get(this.guildId); + } +} + +exports.GuildOnboardingPromptOption = GuildOnboardingPromptOption; diff --git a/node_modules/discord.js/src/structures/GuildPreview.js b/node_modules/discord.js/src/structures/GuildPreview.js new file mode 100644 index 0000000..6ff2026 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildPreview.js @@ -0,0 +1,193 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { Routes } = require('discord-api-types/v10'); +const Base = require('./Base'); +const GuildPreviewEmoji = require('./GuildPreviewEmoji'); +const { Sticker } = require('./Sticker'); + +/** + * Represents the data about the guild any bot can preview, connected to the specified guild. + * @extends {Base} + */ +class GuildPreview extends Base { + constructor(client, data) { + super(client); + + if (!data) return; + + this._patch(data); + } + + _patch(data) { + /** + * The id of this guild + * @type {string} + */ + this.id = data.id; + + if ('name' in data) { + /** + * The name of this guild + * @type {string} + */ + this.name = data.name; + } + + if ('icon' in data) { + /** + * The icon of this guild + * @type {?string} + */ + this.icon = data.icon; + } + + if ('splash' in data) { + /** + * The splash icon of this guild + * @type {?string} + */ + this.splash = data.splash; + } + + if ('discovery_splash' in data) { + /** + * The discovery splash icon of this guild + * @type {?string} + */ + this.discoverySplash = data.discovery_splash; + } + + if ('features' in data) { + /** + * An array of enabled guild features + * @type {GuildFeature[]} + */ + this.features = data.features; + } + + if ('approximate_member_count' in data) { + /** + * The approximate count of members in this guild + * @type {number} + */ + this.approximateMemberCount = data.approximate_member_count; + } + + if ('approximate_presence_count' in data) { + /** + * The approximate count of online members in this guild + * @type {number} + */ + this.approximatePresenceCount = data.approximate_presence_count; + } + + if ('description' in data) { + /** + * The description for this guild + * @type {?string} + */ + this.description = data.description; + } else { + this.description ??= null; + } + + if (!this.emojis) { + /** + * Collection of emojis belonging to this guild + * @type {Collection<Snowflake, GuildPreviewEmoji>} + */ + this.emojis = new Collection(); + } else { + this.emojis.clear(); + } + for (const emoji of data.emojis) { + this.emojis.set(emoji.id, new GuildPreviewEmoji(this.client, emoji, this)); + } + + /** + * Collection of stickers belonging to this guild + * @type {Collection<Snowflake, Sticker>} + */ + this.stickers = data.stickers.reduce( + (stickers, sticker) => stickers.set(sticker.id, new Sticker(this.client, sticker)), + new Collection(), + ); + } + + /** + * The timestamp this guild was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time this guild was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The URL to this guild's splash. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + splashURL(options = {}) { + return this.splash && this.client.rest.cdn.splash(this.id, this.splash, options); + } + + /** + * The URL to this guild's discovery splash. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + discoverySplashURL(options = {}) { + return this.discoverySplash && this.client.rest.cdn.discoverySplash(this.id, this.discoverySplash, options); + } + + /** + * The URL to this guild's icon. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + iconURL(options = {}) { + return this.icon && this.client.rest.cdn.icon(this.id, this.icon, options); + } + + /** + * Fetches this guild. + * @returns {Promise<GuildPreview>} + */ + async fetch() { + const data = await this.client.rest.get(Routes.guildPreview(this.id)); + this._patch(data); + return this; + } + + /** + * When concatenated with a string, this automatically returns the guild's name instead of the Guild object. + * @returns {string} + * @example + * // Logs: Hello from My Guild! + * console.log(`Hello from ${previewGuild}!`); + */ + toString() { + return this.name; + } + + toJSON() { + const json = super.toJSON(); + json.iconURL = this.iconURL(); + json.splashURL = this.splashURL(); + return json; + } +} + +module.exports = GuildPreview; diff --git a/node_modules/discord.js/src/structures/GuildPreviewEmoji.js b/node_modules/discord.js/src/structures/GuildPreviewEmoji.js new file mode 100644 index 0000000..144b41d --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildPreviewEmoji.js @@ -0,0 +1,27 @@ +'use strict'; + +const BaseGuildEmoji = require('./BaseGuildEmoji'); + +/** + * Represents an instance of an emoji belonging to a public guild obtained through Discord's preview endpoint. + * @extends {BaseGuildEmoji} + */ +class GuildPreviewEmoji extends BaseGuildEmoji { + /** + * The public guild this emoji is part of + * @type {GuildPreview} + * @name GuildPreviewEmoji#guild + */ + + constructor(client, data, guild) { + super(client, data, guild); + + /** + * The roles this emoji is active for + * @type {Snowflake[]} + */ + this.roles = data.roles; + } +} + +module.exports = GuildPreviewEmoji; diff --git a/node_modules/discord.js/src/structures/GuildScheduledEvent.js b/node_modules/discord.js/src/structures/GuildScheduledEvent.js new file mode 100644 index 0000000..e9a37b2 --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildScheduledEvent.js @@ -0,0 +1,439 @@ +'use strict'; + +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { GuildScheduledEventStatus, GuildScheduledEventEntityType, RouteBases } = require('discord-api-types/v10'); +const Base = require('./Base'); +const { DiscordjsError, ErrorCodes } = require('../errors'); + +/** + * Represents a scheduled event in a {@link Guild}. + * @extends {Base} + */ +class GuildScheduledEvent extends Base { + constructor(client, data) { + super(client); + + /** + * The id of the guild scheduled event + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The id of the guild this guild scheduled event belongs to + * @type {Snowflake} + */ + this.guildId = data.guild_id; + + this._patch(data); + } + + _patch(data) { + if ('channel_id' in data) { + /** + * The channel id in which the scheduled event will be hosted, + * or `null` if entity type is {@link GuildScheduledEventEntityType.External} + * @type {?Snowflake} + */ + this.channelId = data.channel_id; + } else { + this.channelId ??= null; + } + + if ('creator_id' in data) { + /** + * The id of the user that created this guild scheduled event + * @type {?Snowflake} + */ + this.creatorId = data.creator_id; + } else { + this.creatorId ??= null; + } + + /** + * The name of the guild scheduled event + * @type {string} + */ + this.name = data.name; + + if ('description' in data) { + /** + * The description of the guild scheduled event + * @type {?string} + */ + this.description = data.description; + } else { + this.description ??= null; + } + + /** + * The timestamp the guild scheduled event will start at + * <info>This can be potentially `null` only when it's an {@link AuditLogEntryTarget}</info> + * @type {?number} + */ + this.scheduledStartTimestamp = data.scheduled_start_time ? Date.parse(data.scheduled_start_time) : null; + + /** + * The timestamp the guild scheduled event will end at, + * or `null` if the event does not have a scheduled time to end + * @type {?number} + */ + this.scheduledEndTimestamp = data.scheduled_end_time ? Date.parse(data.scheduled_end_time) : null; + + /** + * The privacy level of the guild scheduled event + * @type {GuildScheduledEventPrivacyLevel} + */ + this.privacyLevel = data.privacy_level; + + /** + * The status of the guild scheduled event + * @type {GuildScheduledEventStatus} + */ + this.status = data.status; + + /** + * The type of hosting entity associated with the scheduled event + * @type {GuildScheduledEventEntityType} + */ + this.entityType = data.entity_type; + + if ('entity_id' in data) { + /** + * The id of the hosting entity associated with the scheduled event + * @type {?Snowflake} + */ + this.entityId = data.entity_id; + } else { + this.entityId ??= null; + } + + if ('user_count' in data) { + /** + * The number of users who are subscribed to this guild scheduled event + * @type {?number} + */ + this.userCount = data.user_count; + } else { + this.userCount ??= null; + } + + if ('creator' in data) { + /** + * The user that created this guild scheduled event + * @type {?User} + */ + this.creator = this.client.users._add(data.creator); + } else { + this.creator ??= this.client.users.resolve(this.creatorId); + } + + /* eslint-disable max-len */ + /** + * Represents the additional metadata for a {@link GuildScheduledEvent} + * @see {@link https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-metadata} + * @typedef {Object} GuildScheduledEventEntityMetadata + * @property {?string} location The location of the guild scheduled event + */ + /* eslint-enable max-len */ + + if ('entity_metadata' in data) { + if (data.entity_metadata) { + /** + * Additional metadata + * @type {?GuildScheduledEventEntityMetadata} + */ + this.entityMetadata = { + location: data.entity_metadata.location ?? this.entityMetadata?.location ?? null, + }; + } else { + this.entityMetadata = null; + } + } else { + this.entityMetadata ??= null; + } + + if ('image' in data) { + /** + * The cover image hash for this scheduled event + * @type {?string} + */ + this.image = data.image; + } else { + this.image ??= null; + } + } + + /** + * The URL of this scheduled event's cover image + * @param {BaseImageURLOptions} [options={}] Options for image URL + * @returns {?string} + */ + coverImageURL(options = {}) { + return this.image && this.client.rest.cdn.guildScheduledEventCover(this.id, this.image, options); + } + + /** + * The timestamp the guild scheduled event was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the guild scheduled event was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The time the guild scheduled event will start at + * <info>This can be potentially `null` only when it's an {@link AuditLogEntryTarget}</info> + * @type {?Date} + * @readonly + */ + get scheduledStartAt() { + return this.scheduledStartTimestamp && new Date(this.scheduledStartTimestamp); + } + + /** + * The time the guild scheduled event will end at, + * or `null` if the event does not have a scheduled time to end + * @type {?Date} + * @readonly + */ + get scheduledEndAt() { + return this.scheduledEndTimestamp && new Date(this.scheduledEndTimestamp); + } + + /** + * The channel associated with this scheduled event + * @type {?(VoiceChannel|StageChannel)} + * @readonly + */ + get channel() { + return this.client.channels.resolve(this.channelId); + } + + /** + * The guild this scheduled event belongs to + * @type {?Guild} + * @readonly + */ + get guild() { + return this.client.guilds.resolve(this.guildId); + } + + /** + * The URL to the guild scheduled event + * @type {string} + * @readonly + */ + get url() { + return `${RouteBases.scheduledEvent}/${this.guildId}/${this.id}`; + } + + /** + * Options used to create an invite URL to a {@link GuildScheduledEvent} + * @typedef {InviteCreateOptions} GuildScheduledEventInviteURLCreateOptions + * @property {GuildInvitableChannelResolvable} [channel] The channel to create the invite in. + * <warn>This is required when the `entityType` of `GuildScheduledEvent` is + * {@link GuildScheduledEventEntityType.External}, gets ignored otherwise</warn> + */ + + /** + * Creates an invite URL to this guild scheduled event. + * @param {GuildScheduledEventInviteURLCreateOptions} [options] The options to create the invite + * @returns {Promise<string>} + */ + async createInviteURL(options) { + let channelId = this.channelId; + if (this.entityType === GuildScheduledEventEntityType.External) { + if (!options?.channel) throw new DiscordjsError(ErrorCodes.InviteOptionsMissingChannel); + channelId = this.guild.channels.resolveId(options.channel); + if (!channelId) throw new DiscordjsError(ErrorCodes.GuildChannelResolve); + } + const invite = await this.guild.invites.create(channelId, options); + return `${RouteBases.invite}/${invite.code}?event=${this.id}`; + } + + /** + * Edits this guild scheduled event. + * @param {GuildScheduledEventEditOptions} options The options to edit the guild scheduled event + * @returns {Promise<GuildScheduledEvent>} + * @example + * // Edit a guild scheduled event + * guildScheduledEvent.edit({ name: 'Party' }) + * .then(guildScheduledEvent => console.log(guildScheduledEvent)) + * .catch(console.error); + */ + edit(options) { + return this.guild.scheduledEvents.edit(this.id, options); + } + + /** + * Deletes this guild scheduled event. + * @returns {Promise<GuildScheduledEvent>} + * @example + * // Delete a guild scheduled event + * guildScheduledEvent.delete() + * .then(guildScheduledEvent => console.log(guildScheduledEvent)) + * .catch(console.error); + */ + async delete() { + await this.guild.scheduledEvents.delete(this.id); + return this; + } + + /** + * Sets a new name for the guild scheduled event. + * @param {string} name The new name of the guild scheduled event + * @param {string} [reason] The reason for changing the name + * @returns {Promise<GuildScheduledEvent>} + * @example + * // Set name of a guild scheduled event + * guildScheduledEvent.setName('Birthday Party') + * .then(guildScheduledEvent => console.log(`Set the name to: ${guildScheduledEvent.name}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name, reason }); + } + + /** + * Sets a new time to schedule the event at. + * @param {DateResolvable} scheduledStartTime The time to schedule the event at + * @param {string} [reason] The reason for changing the scheduled start time + * @returns {Promise<GuildScheduledEvent>} + * @example + * // Set start time of a guild scheduled event + * guildScheduledEvent.setScheduledStartTime('2022-09-24T00:00:00+05:30') + * .then(guildScheduledEvent => console.log(`Set the start time to: ${guildScheduledEvent.scheduledStartTime}`)) + * .catch(console.error); + */ + setScheduledStartTime(scheduledStartTime, reason) { + return this.edit({ scheduledStartTime, reason }); + } + + // TODO: scheduledEndTime gets reset on passing null but it hasn't been documented + /** + * Sets a new time to end the event at. + * @param {DateResolvable} scheduledEndTime The time to end the event at + * @param {string} [reason] The reason for changing the scheduled end time + * @returns {Promise<GuildScheduledEvent>} + * @example + * // Set end time of a guild scheduled event + * guildScheduledEvent.setScheduledEndTime('2022-09-25T00:00:00+05:30') + * .then(guildScheduledEvent => console.log(`Set the end time to: ${guildScheduledEvent.scheduledEndTime}`)) + * .catch(console.error); + */ + setScheduledEndTime(scheduledEndTime, reason) { + return this.edit({ scheduledEndTime, reason }); + } + + /** + * Sets the new description of the guild scheduled event. + * @param {string} description The description of the guild scheduled event + * @param {string} [reason] The reason for changing the description + * @returns {Promise<GuildScheduledEvent>} + * @example + * // Set description of a guild scheduled event + * guildScheduledEvent.setDescription('A virtual birthday party') + * .then(guildScheduledEvent => console.log(`Set the description to: ${guildScheduledEvent.description}`)) + * .catch(console.error); + */ + setDescription(description, reason) { + return this.edit({ description, reason }); + } + + /** + * Sets the new status of the guild scheduled event. + * <info>If you're working with TypeScript, use this method in conjunction with status type-guards + * like {@link GuildScheduledEvent#isScheduled} to get only valid status as suggestion</info> + * @param {GuildScheduledEventStatus} status The status of the guild scheduled event + * @param {string} [reason] The reason for changing the status + * @returns {Promise<GuildScheduledEvent>} + * @example + * // Set status of a guild scheduled event + * guildScheduledEvent.setStatus(GuildScheduledEventStatus.Active) + * .then(guildScheduledEvent => console.log(`Set the status to: ${guildScheduledEvent.status}`)) + * .catch(console.error); + */ + setStatus(status, reason) { + return this.edit({ status, reason }); + } + + /** + * Sets the new location of the guild scheduled event. + * @param {string} location The location of the guild scheduled event + * @param {string} [reason] The reason for changing the location + * @returns {Promise<GuildScheduledEvent>} + * @example + * // Set location of a guild scheduled event + * guildScheduledEvent.setLocation('Earth') + * .then(guildScheduledEvent => console.log(`Set the location to: ${guildScheduledEvent.entityMetadata.location}`)) + * .catch(console.error); + */ + setLocation(location, reason) { + return this.edit({ entityMetadata: { location }, reason }); + } + + /** + * Fetches subscribers of this guild scheduled event. + * @param {FetchGuildScheduledEventSubscribersOptions} [options] Options for fetching the subscribers + * @returns {Promise<Collection<Snowflake, GuildScheduledEventUser>>} + */ + fetchSubscribers(options) { + return this.guild.scheduledEvents.fetchSubscribers(this.id, options); + } + + /** + * When concatenated with a string, this automatically concatenates the event's URL instead of the object. + * @returns {string} + * @example + * // Logs: Event: https://discord.com/events/412345678901234567/499876543211234567 + * console.log(`Event: ${guildScheduledEvent}`); + */ + toString() { + return this.url; + } + + /** + * Indicates whether this guild scheduled event has an {@link GuildScheduledEventStatus.Active} status. + * @returns {boolean} + */ + isActive() { + return this.status === GuildScheduledEventStatus.Active; + } + + /** + * Indicates whether this guild scheduled event has a {@link GuildScheduledEventStatus.Canceled} status. + * @returns {boolean} + */ + isCanceled() { + return this.status === GuildScheduledEventStatus.Canceled; + } + + /** + * Indicates whether this guild scheduled event has a {@link GuildScheduledEventStatus.Completed} status. + * @returns {boolean} + */ + isCompleted() { + return this.status === GuildScheduledEventStatus.Completed; + } + + /** + * Indicates whether this guild scheduled event has a {@link GuildScheduledEventStatus.Scheduled} status. + * @returns {boolean} + */ + isScheduled() { + return this.status === GuildScheduledEventStatus.Scheduled; + } +} + +exports.GuildScheduledEvent = GuildScheduledEvent; diff --git a/node_modules/discord.js/src/structures/GuildTemplate.js b/node_modules/discord.js/src/structures/GuildTemplate.js new file mode 100644 index 0000000..c1e219b --- /dev/null +++ b/node_modules/discord.js/src/structures/GuildTemplate.js @@ -0,0 +1,241 @@ +'use strict'; + +const { setTimeout, clearTimeout } = require('node:timers'); +const { RouteBases, Routes } = require('discord-api-types/v10'); +const Base = require('./Base'); +const DataResolver = require('../util/DataResolver'); +const Events = require('../util/Events'); + +/** + * Represents the template for a guild. + * @extends {Base} + */ +class GuildTemplate extends Base { + /** + * A regular expression that matches guild template links. + * The `code` group property is present on the `exec()` result of this expression. + * @type {RegExp} + * @memberof GuildTemplate + */ + static GuildTemplatesPattern = /discord(?:app)?\.(?:com\/template|new)\/(?<code>[\w-]{2,255})/i; + + constructor(client, data) { + super(client); + this._patch(data); + } + + _patch(data) { + if ('code' in data) { + /** + * The unique code of this template + * @type {string} + */ + this.code = data.code; + } + + if ('name' in data) { + /** + * The name of this template + * @type {string} + */ + this.name = data.name; + } + + if ('description' in data) { + /** + * The description of this template + * @type {?string} + */ + this.description = data.description; + } + + if ('usage_count' in data) { + /** + * The amount of times this template has been used + * @type {number} + */ + this.usageCount = data.usage_count; + } + + if ('creator_id' in data) { + /** + * The id of the user that created this template + * @type {Snowflake} + */ + this.creatorId = data.creator_id; + } + + if ('creator' in data) { + /** + * The user that created this template + * @type {User} + */ + this.creator = this.client.users._add(data.creator); + } + + if ('created_at' in data) { + /** + * The timestamp of when this template was created at + * @type {number} + */ + this.createdTimestamp = Date.parse(data.created_at); + } + + if ('updated_at' in data) { + /** + * The timestamp of when this template was last synced to the guild + * @type {number} + */ + this.updatedTimestamp = Date.parse(data.updated_at); + } + + if ('source_guild_id' in data) { + /** + * The id of the guild that this template belongs to + * @type {Snowflake} + */ + this.guildId = data.source_guild_id; + } + + if ('serialized_source_guild' in data) { + /** + * The data of the guild that this template would create + * @type {APIGuild} + */ + this.serializedGuild = data.serialized_source_guild; + } + + /** + * Whether this template has unsynced changes + * @type {?boolean} + */ + this.unSynced = 'is_dirty' in data ? Boolean(data.is_dirty) : null; + + return this; + } + + /** + * Creates a guild based on this template. + * <warn>This is only available to bots in fewer than 10 guilds.</warn> + * @param {string} name The name of the guild + * @param {BufferResolvable|Base64Resolvable} [icon] The icon for the guild + * @returns {Promise<Guild>} + */ + async createGuild(name, icon) { + const { client } = this; + const data = await client.rest.post(Routes.template(this.code), { + body: { + name, + icon: await DataResolver.resolveImage(icon), + }, + }); + + if (client.guilds.cache.has(data.id)) return client.guilds.cache.get(data.id); + + return new Promise(resolve => { + const resolveGuild = guild => { + client.off(Events.GuildCreate, handleGuild); + client.decrementMaxListeners(); + resolve(guild); + }; + + const handleGuild = guild => { + if (guild.id === data.id) { + clearTimeout(timeout); + resolveGuild(guild); + } + }; + + client.incrementMaxListeners(); + client.on(Events.GuildCreate, handleGuild); + + const timeout = setTimeout(() => resolveGuild(client.guilds._add(data)), 10_000).unref(); + }); + } + + /** + * Options used to edit a guild template. + * @typedef {Object} GuildTemplateEditOptions + * @property {string} [name] The name of this template + * @property {string} [description] The description of this template + */ + + /** + * Updates the metadata of this template. + * @param {GuildTemplateEditOptions} [options] Options for editing the template + * @returns {Promise<GuildTemplate>} + */ + async edit({ name, description } = {}) { + const data = await this.client.rest.patch(Routes.guildTemplate(this.guildId, this.code), { + body: { name, description }, + }); + return this._patch(data); + } + + /** + * Deletes this template. + * @returns {Promise<GuildTemplate>} + */ + async delete() { + await this.client.rest.delete(Routes.guildTemplate(this.guildId, this.code)); + return this; + } + + /** + * Syncs this template to the current state of the guild. + * @returns {Promise<GuildTemplate>} + */ + async sync() { + const data = await this.client.rest.put(Routes.guildTemplate(this.guildId, this.code)); + return this._patch(data); + } + + /** + * The time when this template was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The time when this template was last synced to the guild + * @type {Date} + * @readonly + */ + get updatedAt() { + return new Date(this.updatedTimestamp); + } + + /** + * The guild that this template belongs to + * @type {?Guild} + * @readonly + */ + get guild() { + return this.client.guilds.resolve(this.guildId); + } + + /** + * The URL of this template + * @type {string} + * @readonly + */ + get url() { + return `${RouteBases.template}/${this.code}`; + } + + /** + * When concatenated with a string, this automatically returns the template's code instead of the template object. + * @returns {string} + * @example + * // Logs: Template: FKvmczH2HyUf + * console.log(`Template: ${guildTemplate}!`); + */ + toString() { + return this.code; + } +} + +module.exports = GuildTemplate; diff --git a/node_modules/discord.js/src/structures/Integration.js b/node_modules/discord.js/src/structures/Integration.js new file mode 100644 index 0000000..fa9777b --- /dev/null +++ b/node_modules/discord.js/src/structures/Integration.js @@ -0,0 +1,220 @@ +'use strict'; + +const { Routes } = require('discord-api-types/v10'); +const Base = require('./Base'); +const IntegrationApplication = require('./IntegrationApplication'); + +/** + * The information account for an integration + * @typedef {Object} IntegrationAccount + * @property {Snowflake|string} id The id of the account + * @property {string} name The name of the account + */ + +/** + * The type of an {@link Integration}. This can be: + * * `twitch` + * * `youtube` + * * `discord` + * * `guild_subscription` + * @typedef {string} IntegrationType + */ + +/** + * Represents a guild integration. + * @extends {Base} + */ +class Integration extends Base { + constructor(client, data, guild) { + super(client); + + /** + * The guild this integration belongs to + * @type {Guild} + */ + this.guild = guild; + + /** + * The integration id + * @type {Snowflake|string} + */ + this.id = data.id; + + /** + * The integration name + * @type {string} + */ + this.name = data.name; + + /** + * The integration type + * @type {IntegrationType} + */ + this.type = data.type; + + /** + * Whether this integration is enabled + * @type {?boolean} + */ + this.enabled = data.enabled ?? null; + + if ('syncing' in data) { + /** + * Whether this integration is syncing + * @type {?boolean} + */ + this.syncing = data.syncing; + } else { + this.syncing ??= null; + } + + /** + * The role that this integration uses for subscribers + * @type {?Role} + */ + this.role = this.guild.roles.resolve(data.role_id); + + if ('enable_emoticons' in data) { + /** + * Whether emoticons should be synced for this integration (twitch only currently) + * @type {?boolean} + */ + this.enableEmoticons = data.enable_emoticons; + } else { + this.enableEmoticons ??= null; + } + + if (data.user) { + /** + * The user for this integration + * @type {?User} + */ + this.user = this.client.users._add(data.user); + } else { + this.user ??= null; + } + + /** + * The account integration information + * @type {IntegrationAccount} + */ + this.account = data.account; + + if ('synced_at' in data) { + /** + * The timestamp at which this integration was last synced at + * @type {?number} + */ + this.syncedTimestamp = Date.parse(data.synced_at); + } else { + this.syncedTimestamp ??= null; + } + + if ('subscriber_count' in data) { + /** + * How many subscribers this integration has + * @type {?number} + */ + this.subscriberCount = data.subscriber_count; + } else { + this.subscriberCount ??= null; + } + + if ('revoked' in data) { + /** + * Whether this integration has been revoked + * @type {?boolean} + */ + this.revoked = data.revoked; + } else { + this.revoked ??= null; + } + + this._patch(data); + } + + /** + * The date at which this integration was last synced at + * @type {?Date} + * @readonly + */ + get syncedAt() { + return this.syncedTimestamp && new Date(this.syncedTimestamp); + } + + /** + * All roles that are managed by this integration + * @type {Collection<Snowflake, Role>} + * @readonly + */ + get roles() { + const roles = this.guild.roles.cache; + return roles.filter(role => role.tags?.integrationId === this.id); + } + + _patch(data) { + if ('expire_behavior' in data) { + /** + * The behavior of expiring subscribers + * @type {?IntegrationExpireBehavior} + */ + this.expireBehavior = data.expire_behavior; + } else { + this.expireBehavior ??= null; + } + + if ('expire_grace_period' in data) { + /** + * The grace period (in days) before expiring subscribers + * @type {?number} + */ + this.expireGracePeriod = data.expire_grace_period; + } else { + this.expireGracePeriod ??= null; + } + + if ('application' in data) { + if (this.application) { + this.application._patch(data.application); + } else { + /** + * The application for this integration + * @type {?IntegrationApplication} + */ + this.application = new IntegrationApplication(this.client, data.application); + } + } else { + this.application ??= null; + } + + if ('scopes' in data) { + /** + * The scopes this application has been authorized for + * @type {OAuth2Scopes[]} + */ + this.scopes = data.scopes; + } else { + this.scopes ??= []; + } + } + + /** + * Deletes this integration. + * @returns {Promise<Integration>} + * @param {string} [reason] Reason for deleting this integration + */ + async delete(reason) { + await this.client.rest.delete(Routes.guildIntegration(this.guild.id, this.id), { reason }); + return this; + } + + toJSON() { + return super.toJSON({ + role: 'roleId', + guild: 'guildId', + user: 'userId', + }); + } +} + +module.exports = Integration; diff --git a/node_modules/discord.js/src/structures/IntegrationApplication.js b/node_modules/discord.js/src/structures/IntegrationApplication.js new file mode 100644 index 0000000..4985008 --- /dev/null +++ b/node_modules/discord.js/src/structures/IntegrationApplication.js @@ -0,0 +1,85 @@ +'use strict'; + +const Application = require('./interfaces/Application'); + +/** + * Represents an Integration's OAuth2 Application. + * @extends {Application} + */ +class IntegrationApplication extends Application { + _patch(data) { + super._patch(data); + + if ('bot' in data) { + /** + * The bot user for this application + * @type {?User} + */ + this.bot = this.client.users._add(data.bot); + } else { + this.bot ??= null; + } + + if ('terms_of_service_url' in data) { + /** + * The URL of the application's terms of service + * @type {?string} + */ + this.termsOfServiceURL = data.terms_of_service_url; + } else { + this.termsOfServiceURL ??= null; + } + + if ('privacy_policy_url' in data) { + /** + * The URL of the application's privacy policy + * @type {?string} + */ + this.privacyPolicyURL = data.privacy_policy_url; + } else { + this.privacyPolicyURL ??= null; + } + + if ('rpc_origins' in data) { + /** + * The Array of RPC origin URLs + * @type {string[]} + */ + this.rpcOrigins = data.rpc_origins; + } else { + this.rpcOrigins ??= []; + } + + if ('hook' in data) { + /** + * Whether the application can be default hooked by the client + * @type {?boolean} + */ + this.hook = data.hook; + } else { + this.hook ??= null; + } + + if ('cover_image' in data) { + /** + * The hash of the application's cover image + * @type {?string} + */ + this.cover = data.cover_image; + } else { + this.cover ??= null; + } + + if ('verify_key' in data) { + /** + * The hex-encoded key for verification in interactions and the GameSDK's GetTicket + * @type {?string} + */ + this.verifyKey = data.verify_key; + } else { + this.verifyKey ??= null; + } + } +} + +module.exports = IntegrationApplication; diff --git a/node_modules/discord.js/src/structures/InteractionCollector.js b/node_modules/discord.js/src/structures/InteractionCollector.js new file mode 100644 index 0000000..bb8e6c7 --- /dev/null +++ b/node_modules/discord.js/src/structures/InteractionCollector.js @@ -0,0 +1,269 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Collector = require('./interfaces/Collector'); +const Events = require('../util/Events'); + +/** + * @typedef {CollectorOptions} InteractionCollectorOptions + * @property {TextBasedChannelsResolvable} [channel] The channel to listen to interactions from + * @property {ComponentType} [componentType] The type of component to listen for + * @property {GuildResolvable} [guild] The guild to listen to interactions from + * @property {InteractionType} [interactionType] The type of interaction to listen for + * @property {number} [max] The maximum total amount of interactions to collect + * @property {number} [maxComponents] The maximum number of components to collect + * @property {number} [maxUsers] The maximum number of users to interact + * @property {Message|APIMessage} [message] The message to listen to interactions from + * @property {InteractionResponse} [interactionResponse] The interaction response to listen + * to message component interactions from + */ + +/** + * Collects interactions. + * Will automatically stop if the message ({@link Client#event:messageDelete messageDelete} or + * {@link Client#event:messageDeleteBulk messageDeleteBulk}), + * channel ({@link Client#event:channelDelete channelDelete}), or + * guild ({@link Client#event:guildDelete guildDelete}) is deleted. + * <info>Interaction collectors that do not specify `time` or `idle` may be prone to always running. + * Ensure your interaction collectors end via either of these options or manual cancellation.</info> + * @extends {Collector} + */ +class InteractionCollector extends Collector { + /** + * @param {Client} client The client on which to collect interactions + * @param {InteractionCollectorOptions} [options={}] The options to apply to this collector + */ + constructor(client, options = {}) { + super(client, options); + + /** + * The message from which to collect interactions, if provided + * @type {?Snowflake} + */ + this.messageId = options.message?.id ?? options.interactionResponse?.interaction.message?.id ?? null; + + /** + * The message interaction id from which to collect interactions, if provided + * @type {?Snowflake} + */ + this.messageInteractionId = options.interactionResponse?.id ?? null; + + /** + * The channel from which to collect interactions, if provided + * @type {?Snowflake} + */ + this.channelId = + options.interactionResponse?.interaction.channelId ?? + options.message?.channelId ?? + options.message?.channel_id ?? + this.client.channels.resolveId(options.channel); + + /** + * The guild from which to collect interactions, if provided + * @type {?Snowflake} + */ + this.guildId = + options.interactionResponse?.interaction.guildId ?? + options.message?.guildId ?? + options.message?.guild_id ?? + this.client.guilds.resolveId(options.channel?.guild) ?? + this.client.guilds.resolveId(options.guild); + + /** + * The type of interaction to collect + * @type {?InteractionType} + */ + this.interactionType = options.interactionType ?? null; + + /** + * The type of component to collect + * @type {?ComponentType} + */ + this.componentType = options.componentType ?? null; + + /** + * The users that have interacted with this collector + * @type {Collection<Snowflake, User>} + */ + this.users = new Collection(); + + /** + * The total number of interactions collected + * @type {number} + */ + this.total = 0; + + this.client.incrementMaxListeners(); + + const bulkDeleteListener = messages => { + if (messages.has(this.messageId)) this.stop('messageDelete'); + }; + + if (this.messageId || this.messageInteractionId) { + this._handleMessageDeletion = this._handleMessageDeletion.bind(this); + this.client.on(Events.MessageDelete, this._handleMessageDeletion); + this.client.on(Events.MessageBulkDelete, bulkDeleteListener); + } + + if (this.channelId) { + this._handleChannelDeletion = this._handleChannelDeletion.bind(this); + this._handleThreadDeletion = this._handleThreadDeletion.bind(this); + this.client.on(Events.ChannelDelete, this._handleChannelDeletion); + this.client.on(Events.ThreadDelete, this._handleThreadDeletion); + } + + if (this.guildId) { + this._handleGuildDeletion = this._handleGuildDeletion.bind(this); + this.client.on(Events.GuildDelete, this._handleGuildDeletion); + } + + this.client.on(Events.InteractionCreate, this.handleCollect); + + this.once('end', () => { + this.client.removeListener(Events.InteractionCreate, this.handleCollect); + this.client.removeListener(Events.MessageDelete, this._handleMessageDeletion); + this.client.removeListener(Events.MessageBulkDelete, bulkDeleteListener); + this.client.removeListener(Events.ChannelDelete, this._handleChannelDeletion); + this.client.removeListener(Events.ThreadDelete, this._handleThreadDeletion); + this.client.removeListener(Events.GuildDelete, this._handleGuildDeletion); + this.client.decrementMaxListeners(); + }); + + this.on('collect', interaction => { + this.total++; + this.users.set(interaction.user.id, interaction.user); + }); + } + + /** + * Handles an incoming interaction for possible collection. + * @param {BaseInteraction} interaction The interaction to possibly collect + * @returns {?Snowflake} + * @private + */ + collect(interaction) { + /** + * Emitted whenever an interaction is collected. + * @event InteractionCollector#collect + * @param {BaseInteraction} interaction The interaction that was collected + */ + + if (this.interactionType && interaction.type !== this.interactionType) return null; + if (this.componentType && interaction.componentType !== this.componentType) return null; + if (this.messageId && interaction.message?.id !== this.messageId) return null; + if ( + this.messageInteractionId && + interaction.message?.interaction?.id && + interaction.message.interaction.id !== this.messageInteractionId + ) { + return null; + } + if (this.channelId && interaction.channelId !== this.channelId) return null; + if (this.guildId && interaction.guildId !== this.guildId) return null; + + return interaction.id; + } + + /** + * Handles an interaction for possible disposal. + * @param {BaseInteraction} interaction The interaction that could be disposed of + * @returns {?Snowflake} + */ + dispose(interaction) { + /** + * Emitted whenever an interaction is disposed of. + * @event InteractionCollector#dispose + * @param {BaseInteraction} interaction The interaction that was disposed of + */ + if (this.type && interaction.type !== this.type) return null; + if (this.componentType && interaction.componentType !== this.componentType) return null; + if (this.messageId && interaction.message?.id !== this.messageId) return null; + if ( + this.messageInteractionId && + interaction.message?.interaction?.id && + interaction.message.interaction.id !== this.messageInteractionId + ) { + return null; + } + if (this.channelId && interaction.channelId !== this.channelId) return null; + if (this.guildId && interaction.guildId !== this.guildId) return null; + + return interaction.id; + } + + /** + * Empties this interaction collector. + */ + empty() { + this.total = 0; + this.collected.clear(); + this.users.clear(); + this.checkEnd(); + } + + /** + * The reason this collector has ended with, or null if it hasn't ended yet + * @type {?string} + * @readonly + */ + get endReason() { + if (this.options.max && this.total >= this.options.max) return 'limit'; + if (this.options.maxComponents && this.collected.size >= this.options.maxComponents) return 'componentLimit'; + if (this.options.maxUsers && this.users.size >= this.options.maxUsers) return 'userLimit'; + return super.endReason; + } + + /** + * Handles checking if the message has been deleted, and if so, stops the collector with the reason 'messageDelete'. + * @private + * @param {Message} message The message that was deleted + * @returns {void} + */ + _handleMessageDeletion(message) { + if (message.id === this.messageId) { + this.stop('messageDelete'); + } + + if (message.interaction?.id === this.messageInteractionId) { + this.stop('messageDelete'); + } + } + + /** + * Handles checking if the channel has been deleted, and if so, stops the collector with the reason 'channelDelete'. + * @private + * @param {GuildChannel} channel The channel that was deleted + * @returns {void} + */ + _handleChannelDeletion(channel) { + if (channel.id === this.channelId || channel.threads?.cache.has(this.channelId)) { + this.stop('channelDelete'); + } + } + + /** + * Handles checking if the thread has been deleted, and if so, stops the collector with the reason 'threadDelete'. + * @private + * @param {ThreadChannel} thread The thread that was deleted + * @returns {void} + */ + _handleThreadDeletion(thread) { + if (thread.id === this.channelId) { + this.stop('threadDelete'); + } + } + + /** + * Handles checking if the guild has been deleted, and if so, stops the collector with the reason 'guildDelete'. + * @private + * @param {Guild} guild The guild that was deleted + * @returns {void} + */ + _handleGuildDeletion(guild) { + if (guild.id === this.guildId) { + this.stop('guildDelete'); + } + } +} + +module.exports = InteractionCollector; diff --git a/node_modules/discord.js/src/structures/InteractionResponse.js b/node_modules/discord.js/src/structures/InteractionResponse.js new file mode 100644 index 0000000..9b372e3 --- /dev/null +++ b/node_modules/discord.js/src/structures/InteractionResponse.js @@ -0,0 +1,102 @@ +'use strict'; + +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { InteractionType } = require('discord-api-types/v10'); +const { DiscordjsError, ErrorCodes } = require('../errors'); + +/** + * Represents an interaction's response + */ +class InteractionResponse { + constructor(interaction, id) { + /** + * The interaction associated with the interaction response + * @type {BaseInteraction} + */ + this.interaction = interaction; + /** + * The id of the original interaction response + * @type {Snowflake} + */ + this.id = id ?? interaction.id; + this.client = interaction.client; + } + + /** + * The timestamp the interaction response was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the interaction response was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * Collects a single component interaction that passes the filter. + * The Promise will reject if the time expires. + * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector + * @returns {Promise<MessageComponentInteraction>} + */ + awaitMessageComponent(options = {}) { + const _options = { ...options, max: 1 }; + return new Promise((resolve, reject) => { + const collector = this.createMessageComponentCollector(_options); + collector.once('end', (interactions, reason) => { + const interaction = interactions.first(); + if (interaction) resolve(interaction); + else reject(new DiscordjsError(ErrorCodes.InteractionCollectorError, reason)); + }); + }); + } + + /** + * Creates a message component interaction collector + * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector + * @returns {InteractionCollector} + */ + createMessageComponentCollector(options = {}) { + return new InteractionCollector(this.client, { + ...options, + interactionResponse: this, + interactionType: InteractionType.MessageComponent, + }); + } + + /** + * Fetches the response as a {@link Message} object. + * @returns {Promise<Message>} + */ + fetch() { + return this.interaction.fetchReply(); + } + + /** + * Deletes the response. + * @returns {Promise<void>} + */ + delete() { + return this.interaction.deleteReply(); + } + + /** + * Edits the response. + * @param {string|MessagePayload|WebhookMessageEditOptions} options The new options for the response. + * @returns {Promise<Message>} + */ + edit(options) { + return this.interaction.editReply(options); + } +} + +// eslint-disable-next-line import/order +const InteractionCollector = require('./InteractionCollector'); +module.exports = InteractionResponse; diff --git a/node_modules/discord.js/src/structures/InteractionWebhook.js b/node_modules/discord.js/src/structures/InteractionWebhook.js new file mode 100644 index 0000000..58eb531 --- /dev/null +++ b/node_modules/discord.js/src/structures/InteractionWebhook.js @@ -0,0 +1,59 @@ +'use strict'; + +const Webhook = require('./Webhook'); + +/** + * Represents a webhook for an Interaction + * @implements {Webhook} + */ +class InteractionWebhook { + /** + * @param {Client} client The instantiating client + * @param {Snowflake} id The application's id + * @param {string} token The interaction's token + */ + constructor(client, id, token) { + /** + * The client that instantiated the interaction webhook + * @name InteractionWebhook#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + this.id = id; + Object.defineProperty(this, 'token', { value: token, writable: true, configurable: true }); + } + + // These are here only for documentation purposes - they are implemented by Webhook + /* eslint-disable no-empty-function */ + /** + * Sends a message with this webhook. + * @param {string|MessagePayload|InteractionReplyOptions} options The content for the reply + * @returns {Promise<Message>} + */ + + send() {} + + /** + * Gets a message that was sent by this webhook. + * @param {Snowflake|'@original'} message The id of the message to fetch + * @returns {Promise<Message>} Returns the message sent by this webhook + */ + + fetchMessage() {} + + /** + * Edits a message that was sent by this webhook. + * @param {MessageResolvable|'@original'} message The message to edit + * @param {string|MessagePayload|WebhookMessageEditOptions} options The options to provide + * @returns {Promise<Message>} Returns the message edited by this webhook + */ + + editMessage() {} + deleteMessage() {} + get url() {} +} + +Webhook.applyToClass(InteractionWebhook, ['sendSlackMessage', 'edit', 'delete', 'createdTimestamp', 'createdAt']); + +module.exports = InteractionWebhook; diff --git a/node_modules/discord.js/src/structures/Invite.js b/node_modules/discord.js/src/structures/Invite.js new file mode 100644 index 0000000..19014ff --- /dev/null +++ b/node_modules/discord.js/src/structures/Invite.js @@ -0,0 +1,322 @@ +'use strict'; + +const { RouteBases, Routes, PermissionFlagsBits } = require('discord-api-types/v10'); +const Base = require('./Base'); +const { GuildScheduledEvent } = require('./GuildScheduledEvent'); +const IntegrationApplication = require('./IntegrationApplication'); +const InviteStageInstance = require('./InviteStageInstance'); +const { DiscordjsError, ErrorCodes } = require('../errors'); + +/** + * Represents an invitation to a guild channel. + * @extends {Base} + */ +class Invite extends Base { + /** + * A regular expression that matches Discord invite links. + * The `code` group property is present on the `exec()` result of this expression. + * @type {RegExp} + * @memberof Invite + */ + static InvitesPattern = /discord(?:(?:app)?\.com\/invite|\.gg(?:\/invite)?)\/(?<code>[\w-]{2,255})/i; + + constructor(client, data) { + super(client); + this._patch(data); + } + + _patch(data) { + const InviteGuild = require('./InviteGuild'); + /** + * The guild the invite is for including welcome screen data if present + * @type {?(Guild|InviteGuild)} + */ + this.guild ??= null; + if (data.guild) { + this.guild = this.client.guilds.resolve(data.guild.id) ?? new InviteGuild(this.client, data.guild); + } + + if ('code' in data) { + /** + * The code for this invite + * @type {string} + */ + this.code = data.code; + } + + if ('approximate_presence_count' in data) { + /** + * The approximate number of online members of the guild this invite is for + * <info>This is only available when the invite was fetched through {@link Client#fetchInvite}.</info> + * @type {?number} + */ + this.presenceCount = data.approximate_presence_count; + } else { + this.presenceCount ??= null; + } + + if ('approximate_member_count' in data) { + /** + * The approximate total number of members of the guild this invite is for + * <info>This is only available when the invite was fetched through {@link Client#fetchInvite}.</info> + * @type {?number} + */ + this.memberCount = data.approximate_member_count; + } else { + this.memberCount ??= null; + } + + if ('temporary' in data) { + /** + * Whether or not this invite only grants temporary membership + * <info>This is only available when the invite was fetched through {@link GuildInviteManager#fetch} + * or created through {@link GuildInviteManager#create}.</info> + * @type {?boolean} + */ + this.temporary = data.temporary ?? null; + } else { + this.temporary ??= null; + } + + if ('max_age' in data) { + /** + * The maximum age of the invite, in seconds, 0 if never expires + * <info>This is only available when the invite was fetched through {@link GuildInviteManager#fetch} + * or created through {@link GuildInviteManager#create}.</info> + * @type {?number} + */ + this.maxAge = data.max_age; + } else { + this.maxAge ??= null; + } + + if ('uses' in data) { + /** + * How many times this invite has been used + * <info>This is only available when the invite was fetched through {@link GuildInviteManager#fetch} + * or created through {@link GuildInviteManager#create}.</info> + * @type {?number} + */ + this.uses = data.uses; + } else { + this.uses ??= null; + } + + if ('max_uses' in data) { + /** + * The maximum uses of this invite + * <info>This is only available when the invite was fetched through {@link GuildInviteManager#fetch} + * or created through {@link GuildInviteManager#create}.</info> + * @type {?number} + */ + this.maxUses = data.max_uses; + } else { + this.maxUses ??= null; + } + + if ('inviter_id' in data) { + /** + * The user's id who created this invite + * @type {?Snowflake} + */ + this.inviterId = data.inviter_id; + } else { + this.inviterId ??= null; + } + + if ('inviter' in data) { + this.client.users._add(data.inviter); + this.inviterId = data.inviter.id; + } + + if ('target_user' in data) { + /** + * The user whose stream to display for this voice channel stream invite + * @type {?User} + */ + this.targetUser = this.client.users._add(data.target_user); + } else { + this.targetUser ??= null; + } + + if ('target_application' in data) { + /** + * The embedded application to open for this voice channel embedded application invite + * @type {?IntegrationApplication} + */ + this.targetApplication = new IntegrationApplication(this.client, data.target_application); + } else { + this.targetApplication ??= null; + } + + if ('target_type' in data) { + /** + * The target type + * @type {?InviteTargetType} + */ + this.targetType = data.target_type; + } else { + this.targetType ??= null; + } + + if ('channel_id' in data) { + /** + * The id of the channel this invite is for + * @type {?Snowflake} + */ + this.channelId = data.channel_id; + } + + if ('channel' in data) { + /** + * The channel this invite is for + * @type {?BaseChannel} + */ + this.channel = + this.client.channels._add(data.channel, this.guild, { cache: false }) ?? + this.client.channels.resolve(this.channelId); + + this.channelId ??= data.channel.id; + } + + if ('created_at' in data) { + /** + * The timestamp this invite was created at + * @type {?number} + */ + this.createdTimestamp = Date.parse(data.created_at); + } else { + this.createdTimestamp ??= null; + } + + if ('expires_at' in data) { + this._expiresTimestamp = data.expires_at && Date.parse(data.expires_at); + } else { + this._expiresTimestamp ??= null; + } + + if ('stage_instance' in data) { + /** + * The stage instance data if there is a public {@link StageInstance} in the stage channel this invite is for + * @type {?InviteStageInstance} + * @deprecated + */ + this.stageInstance = new InviteStageInstance(this.client, data.stage_instance, this.channel.id, this.guild.id); + } else { + this.stageInstance ??= null; + } + + if ('guild_scheduled_event' in data) { + /** + * The guild scheduled event data if there is a {@link GuildScheduledEvent} in the channel this invite is for + * @type {?GuildScheduledEvent} + */ + this.guildScheduledEvent = new GuildScheduledEvent(this.client, data.guild_scheduled_event); + } else { + this.guildScheduledEvent ??= null; + } + } + + /** + * The time the invite was created at + * @type {?Date} + * @readonly + */ + get createdAt() { + return this.createdTimestamp && new Date(this.createdTimestamp); + } + + /** + * Whether the invite is deletable by the client user + * @type {boolean} + * @readonly + */ + get deletable() { + const guild = this.guild; + if (!guild || !this.client.guilds.cache.has(guild.id)) return false; + if (!guild.members.me) throw new DiscordjsError(ErrorCodes.GuildUncachedMe); + return Boolean( + this.channel?.permissionsFor(this.client.user).has(PermissionFlagsBits.ManageChannels, false) || + guild.members.me.permissions.has(PermissionFlagsBits.ManageGuild), + ); + } + + /** + * The timestamp the invite will expire at + * @type {?number} + * @readonly + */ + get expiresTimestamp() { + return ( + this._expiresTimestamp ?? + (this.createdTimestamp && this.maxAge ? this.createdTimestamp + this.maxAge * 1_000 : null) + ); + } + + /** + * The time the invite will expire at + * @type {?Date} + * @readonly + */ + get expiresAt() { + return this.expiresTimestamp && new Date(this.expiresTimestamp); + } + + /** + * The user who created this invite + * @type {?User} + * @readonly + */ + get inviter() { + return this.inviterId && this.client.users.resolve(this.inviterId); + } + + /** + * The URL to the invite + * @type {string} + * @readonly + */ + get url() { + return `${RouteBases.invite}/${this.code}`; + } + + /** + * Deletes this invite. + * @param {string} [reason] Reason for deleting this invite + * @returns {Promise<Invite>} + */ + async delete(reason) { + await this.client.rest.delete(Routes.invite(this.code), { reason }); + return this; + } + + /** + * When concatenated with a string, this automatically concatenates the invite's URL instead of the object. + * @returns {string} + * @example + * // Logs: Invite: https://discord.gg/A1b2C3 + * console.log(`Invite: ${invite}`); + */ + toString() { + return this.url; + } + + toJSON() { + return super.toJSON({ + url: true, + expiresTimestamp: true, + presenceCount: false, + memberCount: false, + uses: false, + channel: 'channelId', + inviter: 'inviterId', + guild: 'guildId', + }); + } + + valueOf() { + return this.code; + } +} + +module.exports = Invite; diff --git a/node_modules/discord.js/src/structures/InviteGuild.js b/node_modules/discord.js/src/structures/InviteGuild.js new file mode 100644 index 0000000..8efd980 --- /dev/null +++ b/node_modules/discord.js/src/structures/InviteGuild.js @@ -0,0 +1,22 @@ +'use strict'; + +const AnonymousGuild = require('./AnonymousGuild'); +const WelcomeScreen = require('./WelcomeScreen'); + +/** + * Represents a guild received from an invite, includes welcome screen data if available. + * @extends {AnonymousGuild} + */ +class InviteGuild extends AnonymousGuild { + constructor(client, data) { + super(client, data); + + /** + * The welcome screen for this invite guild + * @type {?WelcomeScreen} + */ + this.welcomeScreen = data.welcome_screen !== undefined ? new WelcomeScreen(this, data.welcome_screen) : null; + } +} + +module.exports = InviteGuild; diff --git a/node_modules/discord.js/src/structures/InviteStageInstance.js b/node_modules/discord.js/src/structures/InviteStageInstance.js new file mode 100644 index 0000000..21ede43 --- /dev/null +++ b/node_modules/discord.js/src/structures/InviteStageInstance.js @@ -0,0 +1,87 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Base = require('./Base'); + +/** + * Represents the data about a public {@link StageInstance} in an {@link Invite}. + * @extends {Base} + * @deprecated + */ +class InviteStageInstance extends Base { + constructor(client, data, channelId, guildId) { + super(client); + + /** + * The id of the stage channel this invite is for + * @type {Snowflake} + */ + this.channelId = channelId; + + /** + * The stage channel's guild id + * @type {Snowflake} + */ + this.guildId = guildId; + + /** + * The members speaking in the stage channel + * @type {Collection<Snowflake, GuildMember>} + */ + this.members = new Collection(); + + this._patch(data); + } + + _patch(data) { + if ('topic' in data) { + /** + * The topic of the stage instance + * @type {string} + */ + this.topic = data.topic; + } + + if ('participant_count' in data) { + /** + * The number of users in the stage channel + * @type {number} + */ + this.participantCount = data.participant_count; + } + + if ('speaker_count' in data) { + /** + * The number of users speaking in the stage channel + * @type {number} + */ + this.speakerCount = data.speaker_count; + } + + this.members.clear(); + for (const rawMember of data.members) { + const member = this.guild.members._add(rawMember); + this.members.set(member.id, member); + } + } + + /** + * The stage channel this invite is for + * @type {?StageChannel} + * @readonly + */ + get channel() { + return this.client.channels.resolve(this.channelId); + } + + /** + * The guild of the stage channel this invite is for + * @type {?Guild} + * @readonly + */ + get guild() { + return this.client.guilds.resolve(this.guildId); + } +} + +module.exports = InviteStageInstance; diff --git a/node_modules/discord.js/src/structures/MentionableSelectMenuBuilder.js b/node_modules/discord.js/src/structures/MentionableSelectMenuBuilder.js new file mode 100644 index 0000000..b22f600 --- /dev/null +++ b/node_modules/discord.js/src/structures/MentionableSelectMenuBuilder.js @@ -0,0 +1,32 @@ +'use strict'; + +const { MentionableSelectMenuBuilder: BuildersMentionableSelectMenu } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersMentionableSelectMenu} + */ +class MentionableSelectMenuBuilder extends BuildersMentionableSelectMenu { + constructor(data = {}) { + super(toSnakeCase(data)); + } + + /** + * Creates a new select menu builder from JSON data + * @param {MentionableSelectMenuBuilder|MentionableSelectMenuComponent|APIMentionableSelectComponent} other + * The other data + * @returns {MentionableSelectMenuBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } +} + +module.exports = MentionableSelectMenuBuilder; + +/** + * @external BuildersMentionableSelectMenu + * @see {@link https://discord.js.org/docs/packages/builders/stable/MentionableSelectMenuBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/MentionableSelectMenuComponent.js b/node_modules/discord.js/src/structures/MentionableSelectMenuComponent.js new file mode 100644 index 0000000..d0f75c3 --- /dev/null +++ b/node_modules/discord.js/src/structures/MentionableSelectMenuComponent.js @@ -0,0 +1,11 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a mentionable select menu component + * @extends {BaseSelectMenuComponent} + */ +class MentionableSelectMenuComponent extends BaseSelectMenuComponent {} + +module.exports = MentionableSelectMenuComponent; diff --git a/node_modules/discord.js/src/structures/MentionableSelectMenuInteraction.js b/node_modules/discord.js/src/structures/MentionableSelectMenuInteraction.js new file mode 100644 index 0000000..416d5ce --- /dev/null +++ b/node_modules/discord.js/src/structures/MentionableSelectMenuInteraction.js @@ -0,0 +1,71 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const MessageComponentInteraction = require('./MessageComponentInteraction'); +const Events = require('../util/Events'); + +/** + * Represents a {@link ComponentType.MentionableSelect} select menu interaction. + * @extends {MessageComponentInteraction} + */ +class MentionableSelectMenuInteraction extends MessageComponentInteraction { + constructor(client, data) { + super(client, data); + const { resolved, values } = data.data; + const { members, users, roles } = resolved ?? {}; + + /** + * An array of the selected user and role ids + * @type {Snowflake[]} + */ + this.values = values ?? []; + + /** + * Collection of the selected users + * @type {Collection<Snowflake, User>} + */ + this.users = new Collection(); + + /** + * Collection of the selected users + * @type {Collection<Snowflake, GuildMember|APIGuildMember>} + */ + this.members = new Collection(); + + /** + * Collection of the selected roles + * @type {Collection<Snowflake, Role|APIRole>} + */ + this.roles = new Collection(); + + if (members) { + for (const [id, member] of Object.entries(members)) { + const user = users[id]; + if (!user) { + this.client.emit( + Events.Debug, + `[MentionableSelectMenuInteraction] Received a member without a user, skipping ${id}`, + ); + + continue; + } + + this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member }); + } + } + + if (users) { + for (const user of Object.values(users)) { + this.users.set(user.id, this.client.users._add(user)); + } + } + + if (roles) { + for (const role of Object.values(roles)) { + this.roles.set(role.id, this.guild?.roles._add(role) ?? role); + } + } + } +} + +module.exports = MentionableSelectMenuInteraction; diff --git a/node_modules/discord.js/src/structures/Message.js b/node_modules/discord.js/src/structures/Message.js new file mode 100644 index 0000000..c82c177 --- /dev/null +++ b/node_modules/discord.js/src/structures/Message.js @@ -0,0 +1,997 @@ +'use strict'; + +const { messageLink } = require('@discordjs/builders'); +const { Collection } = require('@discordjs/collection'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { + InteractionType, + ChannelType, + MessageType, + MessageFlags, + PermissionFlagsBits, +} = require('discord-api-types/v10'); +const Attachment = require('./Attachment'); +const Base = require('./Base'); +const ClientApplication = require('./ClientApplication'); +const Embed = require('./Embed'); +const InteractionCollector = require('./InteractionCollector'); +const Mentions = require('./MessageMentions'); +const MessagePayload = require('./MessagePayload'); +const ReactionCollector = require('./ReactionCollector'); +const { Sticker } = require('./Sticker'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const ReactionManager = require('../managers/ReactionManager'); +const { createComponent } = require('../util/Components'); +const { NonSystemMessageTypes, MaxBulkDeletableMessageAge, DeletableMessageTypes } = require('../util/Constants'); +const MessageFlagsBitField = require('../util/MessageFlagsBitField'); +const PermissionsBitField = require('../util/PermissionsBitField'); +const { cleanContent, resolvePartialEmoji } = require('../util/Util'); + +/** + * Represents a message on Discord. + * @extends {Base} + */ +class Message extends Base { + constructor(client, data) { + super(client); + + /** + * The id of the channel the message was sent in + * @type {Snowflake} + */ + this.channelId = data.channel_id; + + /** + * The id of the guild the message was sent in, if any + * @type {?Snowflake} + */ + this.guildId = data.guild_id ?? this.channel?.guild?.id ?? null; + + this._patch(data); + } + + _patch(data) { + /** + * The message's id + * @type {Snowflake} + */ + this.id = data.id; + + /** + * The timestamp the message was sent at + * @type {number} + */ + this.createdTimestamp = DiscordSnowflake.timestampFrom(this.id); + + if ('type' in data) { + /** + * The type of the message + * @type {?MessageType} + */ + this.type = data.type; + + /** + * Whether or not this message was sent by Discord, not actually a user (e.g. pin notifications) + * @type {?boolean} + */ + this.system = !NonSystemMessageTypes.includes(this.type); + } else { + this.system ??= null; + this.type ??= null; + } + + if ('content' in data) { + /** + * The content of the message. + * <info>This property requires the {@link GatewayIntentBits.MessageContent} privileged intent + * in a guild for messages that do not mention the client.</info> + * @type {?string} + */ + this.content = data.content; + } else { + this.content ??= null; + } + + if ('author' in data) { + /** + * The author of the message + * @type {?User} + */ + this.author = this.client.users._add(data.author, !data.webhook_id); + } else { + this.author ??= null; + } + + if ('pinned' in data) { + /** + * Whether or not this message is pinned + * @type {?boolean} + */ + this.pinned = Boolean(data.pinned); + } else { + this.pinned ??= null; + } + + if ('tts' in data) { + /** + * Whether or not the message was Text-To-Speech + * @type {?boolean} + */ + this.tts = data.tts; + } else { + this.tts ??= null; + } + + if ('nonce' in data) { + /** + * A random number or string used for checking message delivery + * <warn>This is only received after the message was sent successfully, and + * lost if re-fetched</warn> + * @type {?string} + */ + this.nonce = data.nonce; + } else { + this.nonce ??= null; + } + + if ('embeds' in data) { + /** + * An array of embeds in the message - e.g. YouTube Player. + * <info>This property requires the {@link GatewayIntentBits.MessageContent} privileged intent + * in a guild for messages that do not mention the client.</info> + * @type {Embed[]} + */ + this.embeds = data.embeds.map(e => new Embed(e)); + } else { + this.embeds = this.embeds?.slice() ?? []; + } + + if ('components' in data) { + /** + * An array of of action rows in the message. + * <info>This property requires the {@link GatewayIntentBits.MessageContent} privileged intent + * in a guild for messages that do not mention the client.</info> + * @type {ActionRow[]} + */ + this.components = data.components.map(c => createComponent(c)); + } else { + this.components = this.components?.slice() ?? []; + } + + if ('attachments' in data) { + /** + * A collection of attachments in the message - e.g. Pictures - mapped by their ids. + * <info>This property requires the {@link GatewayIntentBits.MessageContent} privileged intent + * in a guild for messages that do not mention the client.</info> + * @type {Collection<Snowflake, Attachment>} + */ + this.attachments = new Collection(); + if (data.attachments) { + for (const attachment of data.attachments) { + this.attachments.set(attachment.id, new Attachment(attachment)); + } + } + } else { + this.attachments = new Collection(this.attachments); + } + + if ('sticker_items' in data || 'stickers' in data) { + /** + * A collection of stickers in the message + * @type {Collection<Snowflake, Sticker>} + */ + this.stickers = new Collection( + (data.sticker_items ?? data.stickers)?.map(s => [s.id, new Sticker(this.client, s)]), + ); + } else { + this.stickers = new Collection(this.stickers); + } + + if ('position' in data) { + /** + * A generally increasing integer (there may be gaps or duplicates) that represents + * the approximate position of the message in a thread. + * @type {?number} + */ + this.position = data.position; + } else { + this.position ??= null; + } + + if ('role_subscription_data' in data) { + /** + * Role subscription data found on {@link MessageType.RoleSubscriptionPurchase} messages. + * @typedef {Object} RoleSubscriptionData + * @property {Snowflake} roleSubscriptionListingId The id of the SKU and listing the user is subscribed to + * @property {string} tierName The name of the tier the user is subscribed to + * @property {number} totalMonthsSubscribed The total number of months the user has been subscribed for + * @property {boolean} isRenewal Whether this notification is a renewal + */ + + /** + * The data of the role subscription purchase or renewal. + * <info>This is present on {@link MessageType.RoleSubscriptionPurchase} messages.</info> + * @type {?RoleSubscriptionData} + */ + this.roleSubscriptionData = { + roleSubscriptionListingId: data.role_subscription_data.role_subscription_listing_id, + tierName: data.role_subscription_data.tier_name, + totalMonthsSubscribed: data.role_subscription_data.total_months_subscribed, + isRenewal: data.role_subscription_data.is_renewal, + }; + } else { + this.roleSubscriptionData ??= null; + } + + // Discord sends null if the message has not been edited + if (data.edited_timestamp) { + /** + * The timestamp the message was last edited at (if applicable) + * @type {?number} + */ + this.editedTimestamp = Date.parse(data.edited_timestamp); + } else { + this.editedTimestamp ??= null; + } + + if ('reactions' in data) { + /** + * A manager of the reactions belonging to this message + * @type {ReactionManager} + */ + this.reactions = new ReactionManager(this); + if (data.reactions?.length > 0) { + for (const reaction of data.reactions) { + this.reactions._add(reaction); + } + } + } else { + this.reactions ??= new ReactionManager(this); + } + + if (!this.mentions) { + /** + * All valid mentions that the message contains + * @type {MessageMentions} + */ + this.mentions = new Mentions( + this, + data.mentions, + data.mention_roles, + data.mention_everyone, + data.mention_channels, + data.referenced_message?.author, + ); + } else { + this.mentions = new Mentions( + this, + data.mentions ?? this.mentions.users, + data.mention_roles ?? this.mentions.roles, + data.mention_everyone ?? this.mentions.everyone, + data.mention_channels ?? this.mentions.crosspostedChannels, + data.referenced_message?.author ?? this.mentions.repliedUser, + ); + } + + if ('webhook_id' in data) { + /** + * The id of the webhook that sent the message, if applicable + * @type {?Snowflake} + */ + this.webhookId = data.webhook_id; + } else { + this.webhookId ??= null; + } + + if ('application' in data) { + /** + * Supplemental application information for group activities + * @type {?ClientApplication} + */ + this.groupActivityApplication = new ClientApplication(this.client, data.application); + } else { + this.groupActivityApplication ??= null; + } + + if ('application_id' in data) { + /** + * The id of the application of the interaction that sent this message, if any + * @type {?Snowflake} + */ + this.applicationId = data.application_id; + } else { + this.applicationId ??= null; + } + + if ('activity' in data) { + /** + * Group activity + * @type {?MessageActivity} + */ + this.activity = { + partyId: data.activity.party_id, + type: data.activity.type, + }; + } else { + this.activity ??= null; + } + + if ('thread' in data) { + this.client.channels._add(data.thread, this.guild); + } + + if (this.member && data.member) { + this.member._patch(data.member); + } else if (data.member && this.guild && this.author) { + this.guild.members._add(Object.assign(data.member, { user: this.author })); + } + + if ('flags' in data) { + /** + * Flags that are applied to the message + * @type {Readonly<MessageFlagsBitField>} + */ + this.flags = new MessageFlagsBitField(data.flags).freeze(); + } else { + this.flags = new MessageFlagsBitField(this.flags).freeze(); + } + + /** + * Reference data sent in a message that contains ids identifying the referenced message. + * This can be present in the following types of message: + * * Crossposted messages (`MessageFlags.Crossposted`) + * * {@link MessageType.ChannelFollowAdd} + * * {@link MessageType.ChannelPinnedMessage} + * * {@link MessageType.Reply} + * * {@link MessageType.ThreadStarterMessage} + * @see {@link https://discord.com/developers/docs/resources/channel#message-types} + * @typedef {Object} MessageReference + * @property {Snowflake} channelId The channel's id the message was referenced + * @property {?Snowflake} guildId The guild's id the message was referenced + * @property {?Snowflake} messageId The message's id that was referenced + */ + + if ('message_reference' in data) { + /** + * Message reference data + * @type {?MessageReference} + */ + this.reference = { + channelId: data.message_reference.channel_id, + guildId: data.message_reference.guild_id, + messageId: data.message_reference.message_id, + }; + } else { + this.reference ??= null; + } + + if (data.referenced_message) { + this.channel?.messages._add({ guild_id: data.message_reference?.guild_id, ...data.referenced_message }); + } + + /** + * Partial data of the interaction that a message is a reply to + * @typedef {Object} MessageInteraction + * @property {Snowflake} id The interaction's id + * @property {InteractionType} type The type of the interaction + * @property {string} commandName The name of the interaction's application command, + * as well as the subcommand and subcommand group, where applicable + * @property {User} user The user that invoked the interaction + */ + + if (data.interaction) { + /** + * Partial data of the interaction that this message is a reply to + * @type {?MessageInteraction} + */ + this.interaction = { + id: data.interaction.id, + type: data.interaction.type, + commandName: data.interaction.name, + user: this.client.users._add(data.interaction.user), + }; + } else { + this.interaction ??= null; + } + } + + /** + * The channel that the message was sent in + * @type {TextBasedChannels} + * @readonly + */ + get channel() { + return this.client.channels.resolve(this.channelId); + } + + /** + * Whether or not this message is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return typeof this.content !== 'string' || !this.author; + } + + /** + * Represents the author of the message as a guild member. + * Only available if the message comes from a guild where the author is still a member + * @type {?GuildMember} + * @readonly + */ + get member() { + return this.guild?.members.resolve(this.author) ?? null; + } + + /** + * The time the message was sent at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The time the message was last edited at (if applicable) + * @type {?Date} + * @readonly + */ + get editedAt() { + return this.editedTimestamp && new Date(this.editedTimestamp); + } + + /** + * The guild the message was sent in (if in a guild channel) + * @type {?Guild} + * @readonly + */ + get guild() { + return this.client.guilds.resolve(this.guildId) ?? this.channel?.guild ?? null; + } + + /** + * Whether this message has a thread associated with it + * @type {boolean} + * @readonly + */ + get hasThread() { + return this.flags.has(MessageFlags.HasThread); + } + + /** + * The thread started by this message + * <info>This property is not suitable for checking whether a message has a thread, + * use {@link Message#hasThread} instead.</info> + * @type {?ThreadChannel} + * @readonly + */ + get thread() { + return this.channel?.threads?.resolve(this.id) ?? null; + } + + /** + * The URL to jump to this message + * @type {string} + * @readonly + */ + get url() { + return this.inGuild() ? messageLink(this.channelId, this.id, this.guildId) : messageLink(this.channelId, this.id); + } + + /** + * The message contents with all mentions replaced by the equivalent text. + * If mentions cannot be resolved to a name, the relevant mention in the message content will not be converted. + * @type {?string} + * @readonly + */ + get cleanContent() { + // eslint-disable-next-line eqeqeq + return this.content != null ? cleanContent(this.content, this.channel) : null; + } + + /** + * Creates a reaction collector. + * @param {ReactionCollectorOptions} [options={}] Options to send to the collector + * @returns {ReactionCollector} + * @example + * // Create a reaction collector + * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someId'; + * const collector = message.createReactionCollector({ filter, time: 15_000 }); + * collector.on('collect', r => console.log(`Collected ${r.emoji.name}`)); + * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); + */ + createReactionCollector(options = {}) { + return new ReactionCollector(this, options); + } + + /** + * An object containing the same properties as CollectorOptions, but a few more: + * @typedef {ReactionCollectorOptions} AwaitReactionsOptions + * @property {string[]} [errors] Stop/end reasons that cause the promise to reject + */ + + /** + * Similar to createReactionCollector but in promise form. + * Resolves with a collection of reactions that pass the specified filter. + * @param {AwaitReactionsOptions} [options={}] Optional options to pass to the internal collector + * @returns {Promise<Collection<string | Snowflake, MessageReaction>>} + * @example + * // Create a reaction collector + * const filter = (reaction, user) => reaction.emoji.name === '👌' && user.id === 'someId' + * message.awaitReactions({ filter, time: 15_000 }) + * .then(collected => console.log(`Collected ${collected.size} reactions`)) + * .catch(console.error); + */ + awaitReactions(options = {}) { + return new Promise((resolve, reject) => { + const collector = this.createReactionCollector(options); + collector.once('end', (reactions, reason) => { + if (options.errors?.includes(reason)) reject(reactions); + else resolve(reactions); + }); + }); + } + + /** + * @typedef {CollectorOptions} MessageComponentCollectorOptions + * @property {ComponentType} [componentType] The type of component to listen for + * @property {number} [max] The maximum total amount of interactions to collect + * @property {number} [maxComponents] The maximum number of components to collect + * @property {number} [maxUsers] The maximum number of users to interact + */ + + /** + * Creates a message component interaction collector. + * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector + * @returns {InteractionCollector} + * @example + * // Create a message component interaction collector + * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; + * const collector = message.createMessageComponentCollector({ filter, time: 15_000 }); + * collector.on('collect', i => console.log(`Collected ${i.customId}`)); + * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); + */ + createMessageComponentCollector(options = {}) { + return new InteractionCollector(this.client, { + ...options, + interactionType: InteractionType.MessageComponent, + message: this, + }); + } + + /** + * An object containing the same properties as CollectorOptions, but a few more: + * @typedef {Object} AwaitMessageComponentOptions + * @property {CollectorFilter} [filter] The filter applied to this collector + * @property {number} [time] Time to wait for an interaction before rejecting + * @property {ComponentType} [componentType] The type of component interaction to collect + * @property {number} [idle] Time to wait without another message component interaction before ending the collector + * @property {boolean} [dispose] Whether to remove the message component interaction after collecting + * @property {InteractionResponse} [interactionResponse] The interaction response to collect interactions from + */ + + /** + * Collects a single component interaction that passes the filter. + * The Promise will reject if the time expires. + * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector + * @returns {Promise<MessageComponentInteraction>} + * @example + * // Collect a message component interaction + * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; + * message.awaitMessageComponent({ filter, time: 15_000 }) + * .then(interaction => console.log(`${interaction.customId} was clicked!`)) + * .catch(console.error); + */ + awaitMessageComponent(options = {}) { + const _options = { ...options, max: 1 }; + return new Promise((resolve, reject) => { + const collector = this.createMessageComponentCollector(_options); + collector.once('end', (interactions, reason) => { + const interaction = interactions.first(); + if (interaction) resolve(interaction); + else reject(new DiscordjsError(ErrorCodes.InteractionCollectorError, reason)); + }); + }); + } + + /** + * Whether the message is editable by the client user + * @type {boolean} + * @readonly + */ + get editable() { + const precheck = Boolean(this.author.id === this.client.user.id && (!this.guild || this.channel?.viewable)); + + // Regardless of permissions thread messages cannot be edited if + // the thread is archived or the thread is locked and the bot does not have permission to manage threads. + if (this.channel?.isThread()) { + if (this.channel.archived) return false; + if (this.channel.locked) { + const permissions = this.channel.permissionsFor(this.client.user); + if (!permissions?.has(PermissionFlagsBits.ManageThreads, true)) return false; + } + } + + return precheck; + } + + /** + * Whether the message is deletable by the client user + * @type {boolean} + * @readonly + */ + get deletable() { + if (!DeletableMessageTypes.includes(this.type)) return false; + + if (!this.guild) { + return this.author.id === this.client.user.id; + } + // DMChannel does not have viewable property, so check viewable after proved that message is on a guild. + if (!this.channel?.viewable) { + return false; + } + + const permissions = this.channel?.permissionsFor(this.client.user); + if (!permissions) return false; + // This flag allows deleting even if timed out + if (permissions.has(PermissionFlagsBits.Administrator, false)) return true; + + // The auto moderation action message author is the reference message author + return ( + (this.type !== MessageType.AutoModerationAction && this.author.id === this.client.user.id) || + (permissions.has(PermissionFlagsBits.ManageMessages, false) && !this.guild.members.me.isCommunicationDisabled()) + ); + } + + /** + * Whether the message is bulk deletable by the client user + * @type {boolean} + * @readonly + * @example + * // Filter for bulk deletable messages + * channel.bulkDelete(messages.filter(message => message.bulkDeletable)); + */ + get bulkDeletable() { + return ( + (this.inGuild() && + Date.now() - this.createdTimestamp < MaxBulkDeletableMessageAge && + this.deletable && + this.channel?.permissionsFor(this.client.user).has(PermissionFlagsBits.ManageMessages, false)) ?? + false + ); + } + + /** + * Whether the message is pinnable by the client user + * @type {boolean} + * @readonly + */ + get pinnable() { + const { channel } = this; + return Boolean( + !this.system && + (!this.guild || + (channel?.viewable && + channel?.permissionsFor(this.client.user)?.has(PermissionFlagsBits.ManageMessages, false))), + ); + } + + /** + * Fetches the Message this crosspost/reply/pin-add references, if available to the client + * @returns {Promise<Message>} + */ + async fetchReference() { + if (!this.reference) throw new DiscordjsError(ErrorCodes.MessageReferenceMissing); + const { channelId, messageId } = this.reference; + const channel = this.client.channels.resolve(channelId); + if (!channel) throw new DiscordjsError(ErrorCodes.GuildChannelResolve); + const message = await channel.messages.fetch(messageId); + return message; + } + + /** + * Whether the message is crosspostable by the client user + * @type {boolean} + * @readonly + */ + get crosspostable() { + const bitfield = + PermissionFlagsBits.SendMessages | + (this.author.id === this.client.user.id ? PermissionsBitField.DefaultBit : PermissionFlagsBits.ManageMessages); + const { channel } = this; + return Boolean( + channel?.type === ChannelType.GuildAnnouncement && + !this.flags.has(MessageFlags.Crossposted) && + this.type === MessageType.Default && + channel.viewable && + channel.permissionsFor(this.client.user)?.has(bitfield, false), + ); + } + + /** + * Edits the content of the message. + * @param {string|MessagePayload|MessageEditOptions} options The options to provide + * @returns {Promise<Message>} + * @example + * // Update the content of a message + * message.edit('This is my new content!') + * .then(msg => console.log(`Updated the content of a message to ${msg.content}`)) + * .catch(console.error); + */ + edit(options) { + if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached)); + return this.channel.messages.edit(this, options); + } + + /** + * Publishes a message in an announcement channel to all channels following it. + * @returns {Promise<Message>} + * @example + * // Crosspost a message + * if (message.channel.type === ChannelType.GuildAnnouncement) { + * message.crosspost() + * .then(() => console.log('Crossposted message')) + * .catch(console.error); + * } + */ + crosspost() { + if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached)); + return this.channel.messages.crosspost(this.id); + } + + /** + * Pins this message to the channel's pinned messages. + * @param {string} [reason] Reason for pinning + * @returns {Promise<Message>} + * @example + * // Pin a message + * message.pin() + * .then(console.log) + * .catch(console.error) + */ + async pin(reason) { + if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached); + await this.channel.messages.pin(this.id, reason); + return this; + } + + /** + * Unpins this message from the channel's pinned messages. + * @param {string} [reason] Reason for unpinning + * @returns {Promise<Message>} + * @example + * // Unpin a message + * message.unpin() + * .then(console.log) + * .catch(console.error) + */ + async unpin(reason) { + if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached); + await this.channel.messages.unpin(this.id, reason); + return this; + } + + /** + * Adds a reaction to the message. + * @param {EmojiIdentifierResolvable} emoji The emoji to react with + * @returns {Promise<MessageReaction>} + * @example + * // React to a message with a unicode emoji + * message.react('🤔') + * .then(console.log) + * .catch(console.error); + * @example + * // React to a message with a custom emoji + * message.react(message.guild.emojis.cache.get('123456789012345678')) + * .then(console.log) + * .catch(console.error); + */ + async react(emoji) { + if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached); + await this.channel.messages.react(this.id, emoji); + + return this.client.actions.MessageReactionAdd.handle( + { + [this.client.actions.injectedUser]: this.client.user, + [this.client.actions.injectedChannel]: this.channel, + [this.client.actions.injectedMessage]: this, + emoji: resolvePartialEmoji(emoji), + }, + true, + ).reaction; + } + + /** + * Deletes the message. + * @returns {Promise<Message>} + * @example + * // Delete a message + * message.delete() + * .then(msg => console.log(`Deleted message from ${msg.author.username}`)) + * .catch(console.error); + */ + async delete() { + if (!this.channel) throw new DiscordjsError(ErrorCodes.ChannelNotCached); + await this.channel.messages.delete(this.id); + return this; + } + + /** + * Options provided when sending a message as an inline reply. + * @typedef {BaseMessageCreateOptions} MessageReplyOptions + * @property {boolean} [failIfNotExists=this.client.options.failIfNotExists] Whether to error if the referenced + * message does not exist (creates a standard message in this case when false) + */ + + /** + * Send an inline reply to this message. + * @param {string|MessagePayload|MessageReplyOptions} options The options to provide + * @returns {Promise<Message>} + * @example + * // Reply to a message + * message.reply('This is a reply!') + * .then(() => console.log(`Replied to message "${message.content}"`)) + * .catch(console.error); + */ + reply(options) { + if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached)); + let data; + + if (options instanceof MessagePayload) { + data = options; + } else { + data = MessagePayload.create(this, options, { + reply: { + messageReference: this, + failIfNotExists: options?.failIfNotExists ?? this.client.options.failIfNotExists, + }, + }); + } + return this.channel.send(data); + } + + /** + * Options for starting a thread on a message. + * @typedef {Object} StartThreadOptions + * @property {string} name The name of the new thread + * @property {ThreadAutoArchiveDuration} [autoArchiveDuration=this.channel.defaultAutoArchiveDuration] The amount of + * time after which the thread should automatically archive in case of no recent activity + * @property {string} [reason] Reason for creating the thread + * @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the thread in seconds + */ + + /** + * Create a new public thread from this message + * @see GuildTextThreadManager#create + * @param {StartThreadOptions} [options] Options for starting a thread on this message + * @returns {Promise<ThreadChannel>} + */ + startThread(options = {}) { + if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached)); + if (![ChannelType.GuildText, ChannelType.GuildAnnouncement].includes(this.channel.type)) { + return Promise.reject(new DiscordjsError(ErrorCodes.MessageThreadParent)); + } + if (this.hasThread) return Promise.reject(new DiscordjsError(ErrorCodes.MessageExistingThread)); + return this.channel.threads.create({ ...options, startMessage: this }); + } + + /** + * Fetch this message. + * @param {boolean} [force=true] Whether to skip the cache check and request the API + * @returns {Promise<Message>} + */ + fetch(force = true) { + if (!this.channel) return Promise.reject(new DiscordjsError(ErrorCodes.ChannelNotCached)); + return this.channel.messages.fetch({ message: this.id, force }); + } + + /** + * Fetches the webhook used to create this message. + * @returns {Promise<?Webhook>} + */ + fetchWebhook() { + if (!this.webhookId) return Promise.reject(new DiscordjsError(ErrorCodes.WebhookMessage)); + if (this.webhookId === this.applicationId) return Promise.reject(new DiscordjsError(ErrorCodes.WebhookApplication)); + return this.client.fetchWebhook(this.webhookId); + } + + /** + * Suppresses or unsuppresses embeds on a message. + * @param {boolean} [suppress=true] If the embeds should be suppressed or not + * @returns {Promise<Message>} + */ + suppressEmbeds(suppress = true) { + const flags = new MessageFlagsBitField(this.flags.bitfield); + + if (suppress) { + flags.add(MessageFlags.SuppressEmbeds); + } else { + flags.remove(MessageFlags.SuppressEmbeds); + } + + return this.edit({ flags }); + } + + /** + * Removes the attachments from this message. + * @returns {Promise<Message>} + */ + removeAttachments() { + return this.edit({ attachments: [] }); + } + + /** + * Resolves a component by a custom id. + * @param {string} customId The custom id to resolve against + * @returns {?MessageActionRowComponent} + */ + resolveComponent(customId) { + return this.components.flatMap(row => row.components).find(component => component.customId === customId) ?? null; + } + + /** + * Used mainly internally. Whether two messages are identical in properties. If you want to compare messages + * without checking all the properties, use `message.id === message2.id`, which is much more efficient. This + * method allows you to see if there are differences in content, embeds, attachments, nonce and tts properties. + * @param {Message} message The message to compare it to + * @param {APIMessage} rawData Raw data passed through the WebSocket about this message + * @returns {boolean} + */ + equals(message, rawData) { + if (!message) return false; + const embedUpdate = !message.author && !message.attachments; + if (embedUpdate) return this.id === message.id && this.embeds.length === message.embeds.length; + + let equal = + this.id === message.id && + this.author.id === message.author.id && + this.content === message.content && + this.tts === message.tts && + this.nonce === message.nonce && + this.embeds.length === message.embeds.length && + this.attachments.length === message.attachments.length; + + if (equal && rawData) { + equal = + this.mentions.everyone === message.mentions.everyone && + this.createdTimestamp === Date.parse(rawData.timestamp) && + this.editedTimestamp === Date.parse(rawData.edited_timestamp); + } + + return equal; + } + + /** + * Whether this message is from a guild. + * @returns {boolean} + */ + inGuild() { + return Boolean(this.guildId); + } + + /** + * When concatenated with a string, this automatically concatenates the message's content instead of the object. + * @returns {string} + * @example + * // Logs: Message: This is a message! + * console.log(`Message: ${message}`); + */ + toString() { + return this.content; + } + + toJSON() { + return super.toJSON({ + channel: 'channelId', + author: 'authorId', + groupActivityApplication: 'groupActivityApplicationId', + guild: 'guildId', + cleanContent: true, + member: false, + reactions: false, + }); + } +} + +exports.Message = Message; diff --git a/node_modules/discord.js/src/structures/MessageCollector.js b/node_modules/discord.js/src/structures/MessageCollector.js new file mode 100644 index 0000000..7101965 --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageCollector.js @@ -0,0 +1,146 @@ +'use strict'; + +const Collector = require('./interfaces/Collector'); +const Events = require('../util/Events'); + +/** + * @typedef {CollectorOptions} MessageCollectorOptions + * @property {number} max The maximum amount of messages to collect + * @property {number} maxProcessed The maximum amount of messages to process + */ + +/** + * Collects messages on a channel. + * Will automatically stop if the channel ({@link Client#event:channelDelete channelDelete}), + * thread ({@link Client#event:threadDelete threadDelete}), or + * guild ({@link Client#event:guildDelete guildDelete}) is deleted. + * @extends {Collector} + */ +class MessageCollector extends Collector { + /** + * @param {TextBasedChannels} channel The channel + * @param {MessageCollectorOptions} options The options to be applied to this collector + * @emits MessageCollector#message + */ + constructor(channel, options = {}) { + super(channel.client, options); + + /** + * The channel + * @type {TextBasedChannels} + */ + this.channel = channel; + + /** + * Total number of messages that were received in the channel during message collection + * @type {number} + */ + this.received = 0; + + const bulkDeleteListener = messages => { + for (const message of messages.values()) this.handleDispose(message); + }; + + this._handleChannelDeletion = this._handleChannelDeletion.bind(this); + this._handleThreadDeletion = this._handleThreadDeletion.bind(this); + this._handleGuildDeletion = this._handleGuildDeletion.bind(this); + + this.client.incrementMaxListeners(); + this.client.on(Events.MessageCreate, this.handleCollect); + this.client.on(Events.MessageDelete, this.handleDispose); + this.client.on(Events.MessageBulkDelete, bulkDeleteListener); + this.client.on(Events.ChannelDelete, this._handleChannelDeletion); + this.client.on(Events.ThreadDelete, this._handleThreadDeletion); + this.client.on(Events.GuildDelete, this._handleGuildDeletion); + + this.once('end', () => { + this.client.removeListener(Events.MessageCreate, this.handleCollect); + this.client.removeListener(Events.MessageDelete, this.handleDispose); + this.client.removeListener(Events.MessageBulkDelete, bulkDeleteListener); + this.client.removeListener(Events.ChannelDelete, this._handleChannelDeletion); + this.client.removeListener(Events.ThreadDelete, this._handleThreadDeletion); + this.client.removeListener(Events.GuildDelete, this._handleGuildDeletion); + this.client.decrementMaxListeners(); + }); + } + + /** + * Handles a message for possible collection. + * @param {Message} message The message that could be collected + * @returns {?Snowflake} + * @private + */ + collect(message) { + /** + * Emitted whenever a message is collected. + * @event MessageCollector#collect + * @param {Message} message The message that was collected + */ + if (message.channelId !== this.channel.id) return null; + this.received++; + return message.id; + } + + /** + * Handles a message for possible disposal. + * @param {Message} message The message that could be disposed of + * @returns {?Snowflake} + */ + dispose(message) { + /** + * Emitted whenever a message is disposed of. + * @event MessageCollector#dispose + * @param {Message} message The message that was disposed of + */ + return message.channelId === this.channel.id ? message.id : null; + } + + /** + * The reason this collector has ended with, or null if it hasn't ended yet + * @type {?string} + * @readonly + */ + get endReason() { + if (this.options.max && this.collected.size >= this.options.max) return 'limit'; + if (this.options.maxProcessed && this.received === this.options.maxProcessed) return 'processedLimit'; + return super.endReason; + } + + /** + * Handles checking if the channel has been deleted, and if so, stops the collector with the reason 'channelDelete'. + * @private + * @param {GuildChannel} channel The channel that was deleted + * @returns {void} + */ + _handleChannelDeletion(channel) { + if (channel.id === this.channel.id || channel.id === this.channel.parentId) { + this.stop('channelDelete'); + } + } + + /** + * Handles checking if the thread has been deleted, and if so, stops the collector with the reason 'threadDelete'. + * @private + * @param {ThreadChannel} thread The thread that was deleted + * @returns {void} + */ + _handleThreadDeletion(thread) { + if (thread.id === this.channel.id) { + this.stop('threadDelete'); + } + } + + /** + * Handles checking if the guild has been deleted, and if so, stops the collector with the reason 'guildDelete'. + * @private + * @param {Guild} guild The guild that was deleted + * @returns {void} + */ + _handleGuildDeletion(guild) { + if (guild.id === this.channel.guild?.id) { + this.stop('guildDelete'); + } + } +} + +module.exports = MessageCollector; diff --git a/node_modules/discord.js/src/structures/MessageComponentInteraction.js b/node_modules/discord.js/src/structures/MessageComponentInteraction.js new file mode 100644 index 0000000..47b31e0 --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageComponentInteraction.js @@ -0,0 +1,107 @@ +'use strict'; + +const { lazy } = require('@discordjs/util'); +const BaseInteraction = require('./BaseInteraction'); +const InteractionWebhook = require('./InteractionWebhook'); +const InteractionResponses = require('./interfaces/InteractionResponses'); + +const getMessage = lazy(() => require('./Message').Message); + +/** + * Represents a message component interaction. + * @extends {BaseInteraction} + * @implements {InteractionResponses} + */ +class MessageComponentInteraction extends BaseInteraction { + constructor(client, data) { + super(client, data); + + /** + * The id of the channel this interaction was sent in + * @type {Snowflake} + * @name MessageComponentInteraction#channelId + */ + + /** + * The message to which the component was attached + * @type {Message} + */ + this.message = this.channel?.messages._add(data.message) ?? new (getMessage())(client, data.message); + + /** + * The custom id of the component which was interacted with + * @type {string} + */ + this.customId = data.data.custom_id; + + /** + * The type of component which was interacted with + * @type {ComponentType} + */ + this.componentType = data.data.component_type; + + /** + * Whether the reply to this interaction has been deferred + * @type {boolean} + */ + this.deferred = false; + + /** + * Whether the reply to this interaction is ephemeral + * @type {?boolean} + */ + this.ephemeral = null; + + /** + * Whether this interaction has already been replied to + * @type {boolean} + */ + this.replied = false; + + /** + * An associated interaction webhook, can be used to further interact with this interaction + * @type {InteractionWebhook} + */ + this.webhook = new InteractionWebhook(this.client, this.applicationId, this.token); + } + + /** + * Components that can be placed in an action row for messages. + * * ButtonComponent + * * StringSelectMenuComponent + * * UserSelectMenuComponent + * * RoleSelectMenuComponent + * * MentionableSelectMenuComponent + * * ChannelSelectMenuComponent + * @typedef {ButtonComponent|StringSelectMenuComponent|UserSelectMenuComponent| + * RoleSelectMenuComponent|MentionableSelectMenuComponent|ChannelSelectMenuComponent} MessageActionRowComponent + */ + + /** + * The component which was interacted with + * @type {MessageActionRowComponent|APIMessageActionRowComponent} + * @readonly + */ + get component() { + return this.message.components + .flatMap(row => row.components) + .find(component => (component.customId ?? component.custom_id) === this.customId); + } + + // These are here only for documentation purposes - they are implemented by InteractionResponses + /* eslint-disable no-empty-function */ + deferReply() {} + reply() {} + fetchReply() {} + editReply() {} + deleteReply() {} + followUp() {} + deferUpdate() {} + update() {} + showModal() {} + awaitModalSubmit() {} +} + +InteractionResponses.applyToClass(MessageComponentInteraction); + +module.exports = MessageComponentInteraction; diff --git a/node_modules/discord.js/src/structures/MessageContextMenuCommandInteraction.js b/node_modules/discord.js/src/structures/MessageContextMenuCommandInteraction.js new file mode 100644 index 0000000..1100591 --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageContextMenuCommandInteraction.js @@ -0,0 +1,20 @@ +'use strict'; + +const ContextMenuCommandInteraction = require('./ContextMenuCommandInteraction'); + +/** + * Represents a message context menu interaction. + * @extends {ContextMenuCommandInteraction} + */ +class MessageContextMenuCommandInteraction extends ContextMenuCommandInteraction { + /** + * The message this interaction was sent from + * @type {Message|APIMessage} + * @readonly + */ + get targetMessage() { + return this.options.getMessage('message'); + } +} + +module.exports = MessageContextMenuCommandInteraction; diff --git a/node_modules/discord.js/src/structures/MessageMentions.js b/node_modules/discord.js/src/structures/MessageMentions.js new file mode 100644 index 0000000..a07e77f --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageMentions.js @@ -0,0 +1,297 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { FormattingPatterns } = require('discord-api-types/v10'); +const { flatten } = require('../util/Util'); + +/** + * Keeps track of mentions in a {@link Message}. + */ +class MessageMentions { + /** + * A regular expression that matches `@everyone` and `@here`. + * The `mention` group property is present on the `exec` result of this expression. + * @type {RegExp} + * @memberof MessageMentions + */ + static EveryonePattern = /@(?<mention>everyone|here)/; + + /** + * A regular expression that matches user mentions like `<@81440962496172032>`. + * The `id` group property is present on the `exec` result of this expression. + * @type {RegExp} + * @memberof MessageMentions + */ + static UsersPattern = FormattingPatterns.UserWithOptionalNickname; + + /** + * A regular expression that matches role mentions like `<@&297577916114403338>`. + * The `id` group property is present on the `exec` result of this expression. + * @type {RegExp} + * @memberof MessageMentions + */ + static RolesPattern = FormattingPatterns.Role; + + /** + * A regular expression that matches channel mentions like `<#222079895583457280>`. + * The `id` group property is present on the `exec` result of this expression. + * @type {RegExp} + * @memberof MessageMentions + */ + static ChannelsPattern = FormattingPatterns.Channel; + + /** + * A global regular expression variant of {@link MessageMentions.ChannelsPattern}. + * @type {RegExp} + * @memberof MessageMentions + * @private + */ + static GlobalChannelsPattern = new RegExp(this.ChannelsPattern.source, 'g'); + + /** + * A global regular expression variant of {@link MessageMentions.UsersPattern}. + * @type {RegExp} + * @memberof MessageMentions + * @private + */ + static GlobalUsersPattern = new RegExp(this.UsersPattern.source, 'g'); + + constructor(message, users, roles, everyone, crosspostedChannels, repliedUser) { + /** + * The client the message is from + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: message.client }); + + /** + * The guild the message is in + * @type {?Guild} + * @readonly + */ + Object.defineProperty(this, 'guild', { value: message.guild }); + + /** + * The initial message content + * @type {string} + * @readonly + * @private + */ + Object.defineProperty(this, '_content', { value: message.content }); + + /** + * Whether `@everyone` or `@here` were mentioned + * @type {boolean} + */ + this.everyone = Boolean(everyone); + + if (users) { + if (users instanceof Collection) { + /** + * Any users that were mentioned + * <info>Order as received from the API, not as they appear in the message content</info> + * @type {Collection<Snowflake, User>} + */ + this.users = new Collection(users); + } else { + this.users = new Collection(); + for (const mention of users) { + if (mention.member && message.guild) { + message.guild.members._add(Object.assign(mention.member, { user: mention })); + } + const user = message.client.users._add(mention); + this.users.set(user.id, user); + } + } + } else { + this.users = new Collection(); + } + + if (roles instanceof Collection) { + /** + * Any roles that were mentioned + * <info>Order as received from the API, not as they appear in the message content</info> + * @type {Collection<Snowflake, Role>} + */ + this.roles = new Collection(roles); + } else if (roles) { + this.roles = new Collection(); + const guild = message.guild; + if (guild) { + for (const mention of roles) { + const role = guild.roles.cache.get(mention); + if (role) this.roles.set(role.id, role); + } + } + } else { + this.roles = new Collection(); + } + + /** + * Cached members for {@link MessageMentions#members} + * @type {?Collection<Snowflake, GuildMember>} + * @private + */ + this._members = null; + + /** + * Cached channels for {@link MessageMentions#channels} + * @type {?Collection<Snowflake, BaseChannel>} + * @private + */ + this._channels = null; + + /** + * Cached users for {@link MessageMentions#parsedUsers} + * @type {?Collection<Snowflake, User>} + * @private + */ + this._parsedUsers = null; + + /** + * Crossposted channel data. + * @typedef {Object} CrosspostedChannel + * @property {Snowflake} channelId The mentioned channel's id + * @property {Snowflake} guildId The id of the guild that has the channel + * @property {ChannelType} type The channel's type + * @property {string} name The channel's name + */ + + if (crosspostedChannels) { + if (crosspostedChannels instanceof Collection) { + /** + * A collection of crossposted channels + * <info>Order as received from the API, not as they appear in the message content</info> + * @type {Collection<Snowflake, CrosspostedChannel>} + */ + this.crosspostedChannels = new Collection(crosspostedChannels); + } else { + this.crosspostedChannels = new Collection(); + for (const d of crosspostedChannels) { + this.crosspostedChannels.set(d.id, { + channelId: d.id, + guildId: d.guild_id, + type: d.type, + name: d.name, + }); + } + } + } else { + this.crosspostedChannels = new Collection(); + } + + /** + * The author of the message that this message is a reply to + * @type {?User} + */ + this.repliedUser = repliedUser ? this.client.users._add(repliedUser) : null; + } + + /** + * 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> + * @type {?Collection<Snowflake, GuildMember>} + * @readonly + */ + get members() { + if (this._members) return this._members; + if (!this.guild) return null; + this._members = new Collection(); + this.users.forEach(user => { + const member = this.guild.members.resolve(user); + if (member) this._members.set(member.user.id, member); + }); + return this._members; + } + + /** + * Any channels that were mentioned + * <info>Order as they appear first in the message content</info> + * @type {Collection<Snowflake, BaseChannel>} + * @readonly + */ + get channels() { + if (this._channels) return this._channels; + this._channels = new Collection(); + let matches; + + while ((matches = this.constructor.GlobalChannelsPattern.exec(this._content)) !== null) { + const channel = this.client.channels.cache.get(matches.groups.id); + if (channel) this._channels.set(channel.id, channel); + } + + return this._channels; + } + + /** + * Any user mentions that were included in the message content + * <info>Order as they appear first in the message content</info> + * @type {Collection<Snowflake, User>} + * @readonly + */ + get parsedUsers() { + if (this._parsedUsers) return this._parsedUsers; + this._parsedUsers = new Collection(); + let matches; + while ((matches = this.constructor.GlobalUsersPattern.exec(this._content)) !== null) { + const user = this.client.users.cache.get(matches[1]); + if (user) this._parsedUsers.set(user.id, user); + } + return this._parsedUsers; + } + + /** + * Options used to check for a mention. + * @typedef {Object} MessageMentionsHasOptions + * @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} [ignoreRepliedUser=false] Whether to ignore replied user mention to an user + * @property {boolean} [ignoreEveryone=false] Whether to ignore `@everyone`/`@here` mentions + */ + + /** + * Checks if a user, guild member, thread member, role, or channel is mentioned. + * Takes into account user mentions, role mentions, channel mentions, + * replied user mention, and `@everyone`/`@here` mentions. + * @param {UserResolvable|RoleResolvable|ChannelResolvable} data The User/Role/Channel to check for + * @param {MessageMentionsHasOptions} [options] The options for the check + * @returns {boolean} + */ + has(data, { ignoreDirect = false, ignoreRoles = false, ignoreRepliedUser = false, ignoreEveryone = false } = {}) { + const user = this.client.users.resolve(data); + + if (!ignoreEveryone && user && this.everyone) return true; + + const userWasRepliedTo = user && this.repliedUser?.id === user.id; + + if (!ignoreRepliedUser && userWasRepliedTo && this.users.has(user.id)) return true; + + if (!ignoreDirect) { + if (user && (!ignoreRepliedUser || this.parsedUsers.has(user.id)) && this.users.has(user.id)) return true; + + const role = this.guild?.roles.resolve(data); + if (role && this.roles.has(role.id)) return true; + + const channel = this.client.channels.resolve(data); + if (channel && this.channels.has(channel.id)) return true; + } + + if (!ignoreRoles) { + const member = this.guild?.members.resolve(data); + if (member) { + for (const mentionedRole of this.roles.values()) if (member.roles.cache.has(mentionedRole.id)) return true; + } + } + + return false; + } + + toJSON() { + return flatten(this, { + members: true, + channels: true, + }); + } +} + +module.exports = MessageMentions; diff --git a/node_modules/discord.js/src/structures/MessagePayload.js b/node_modules/discord.js/src/structures/MessagePayload.js new file mode 100644 index 0000000..e237309 --- /dev/null +++ b/node_modules/discord.js/src/structures/MessagePayload.js @@ -0,0 +1,299 @@ +'use strict'; + +const { Buffer } = require('node:buffer'); +const { lazy, isJSONEncodable } = require('@discordjs/util'); +const { MessageFlags } = require('discord-api-types/v10'); +const ActionRowBuilder = require('./ActionRowBuilder'); +const { DiscordjsRangeError, ErrorCodes } = require('../errors'); +const DataResolver = require('../util/DataResolver'); +const MessageFlagsBitField = require('../util/MessageFlagsBitField'); +const { basename, verifyString } = require('../util/Util'); + +const getBaseInteraction = lazy(() => require('./BaseInteraction')); + +/** + * Represents a message to be sent to the API. + */ +class MessagePayload { + /** + * @param {MessageTarget} target The target for this message to be sent to + * @param {MessagePayloadOption} options The payload of this message + */ + constructor(target, options) { + /** + * The target for this message to be sent to + * @type {MessageTarget} + */ + this.target = target; + + /** + * The payload of this message. + * @type {MessagePayloadOption} + */ + this.options = options; + + /** + * Body sendable to the API + * @type {?APIMessage} + */ + this.body = null; + + /** + * Files sendable to the API + * @type {?RawFile[]} + */ + this.files = null; + } + + /** + * Whether or not the target is a {@link Webhook} or a {@link WebhookClient} + * @type {boolean} + * @readonly + */ + get isWebhook() { + const Webhook = require('./Webhook'); + const WebhookClient = require('../client/WebhookClient'); + return this.target instanceof Webhook || this.target instanceof WebhookClient; + } + + /** + * Whether or not the target is a {@link User} + * @type {boolean} + * @readonly + */ + get isUser() { + const User = require('./User'); + const { GuildMember } = require('./GuildMember'); + return this.target instanceof User || this.target instanceof GuildMember; + } + + /** + * Whether or not the target is a {@link Message} + * @type {boolean} + * @readonly + */ + get isMessage() { + const { Message } = require('./Message'); + return this.target instanceof Message; + } + + /** + * Whether or not the target is a {@link MessageManager} + * @type {boolean} + * @readonly + */ + get isMessageManager() { + const MessageManager = require('../managers/MessageManager'); + return this.target instanceof MessageManager; + } + + /** + * Whether or not the target is an {@link BaseInteraction} or an {@link InteractionWebhook} + * @type {boolean} + * @readonly + */ + get isInteraction() { + const BaseInteraction = getBaseInteraction(); + const InteractionWebhook = require('./InteractionWebhook'); + return this.target instanceof BaseInteraction || this.target instanceof InteractionWebhook; + } + + /** + * Makes the content of this message. + * @returns {?string} + */ + makeContent() { + let content; + if (this.options.content === null) { + content = ''; + } else if (this.options.content !== undefined) { + content = verifyString(this.options.content, DiscordjsRangeError, ErrorCodes.MessageContentType, true); + } + + return content; + } + + /** + * Resolves the body. + * @returns {MessagePayload} + */ + resolveBody() { + if (this.body) return this; + const isInteraction = this.isInteraction; + const isWebhook = this.isWebhook; + + const content = this.makeContent(); + const tts = Boolean(this.options.tts); + + let nonce; + if (this.options.nonce !== undefined) { + nonce = this.options.nonce; + if (typeof nonce === 'number' ? !Number.isInteger(nonce) : typeof nonce !== 'string') { + throw new DiscordjsRangeError(ErrorCodes.MessageNonceType); + } + } + + const components = this.options.components?.map(c => (isJSONEncodable(c) ? c : new ActionRowBuilder(c)).toJSON()); + + let username; + let avatarURL; + let threadName; + if (isWebhook) { + username = this.options.username ?? this.target.name; + if (this.options.avatarURL) avatarURL = this.options.avatarURL; + if (this.options.threadName) threadName = this.options.threadName; + } + + let flags; + if ( + this.options.flags !== undefined || + (this.isMessage && this.options.reply === undefined) || + this.isMessageManager + ) { + flags = + // eslint-disable-next-line eqeqeq + this.options.flags != null + ? new MessageFlagsBitField(this.options.flags).bitfield + : this.target.flags?.bitfield; + } + + if (isInteraction && this.options.ephemeral) { + flags |= MessageFlags.Ephemeral; + } + + let allowedMentions = + this.options.allowedMentions === undefined + ? this.target.client.options.allowedMentions + : this.options.allowedMentions; + + if (allowedMentions?.repliedUser !== undefined) { + allowedMentions = { ...allowedMentions, replied_user: allowedMentions.repliedUser }; + delete allowedMentions.repliedUser; + } + + let message_reference; + if (typeof this.options.reply === 'object') { + const reference = this.options.reply.messageReference; + const message_id = this.isMessage ? reference.id ?? reference : this.target.messages.resolveId(reference); + if (message_id) { + message_reference = { + message_id, + fail_if_not_exists: this.options.reply.failIfNotExists ?? this.target.client.options.failIfNotExists, + }; + } + } + + const attachments = this.options.files?.map((file, index) => ({ + id: index.toString(), + description: file.description, + })); + if (Array.isArray(this.options.attachments)) { + this.options.attachments.push(...(attachments ?? [])); + } else { + this.options.attachments = attachments; + } + + this.body = { + content, + tts, + nonce, + embeds: this.options.embeds?.map(embed => + isJSONEncodable(embed) ? embed.toJSON() : this.target.client.options.jsonTransformer(embed), + ), + components, + username, + avatar_url: avatarURL, + allowed_mentions: content === undefined && message_reference === undefined ? undefined : allowedMentions, + flags, + message_reference, + attachments: this.options.attachments, + sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker), + thread_name: threadName, + }; + return this; + } + + /** + * Resolves files. + * @returns {Promise<MessagePayload>} + */ + async resolveFiles() { + if (this.files) return this; + + this.files = await Promise.all(this.options.files?.map(file => this.constructor.resolveFile(file)) ?? []); + return this; + } + + /** + * Resolves a single file into an object sendable to the API. + * @param {AttachmentPayload|BufferResolvable|Stream} fileLike Something that could be resolved to a file + * @returns {Promise<RawFile>} + */ + static async resolveFile(fileLike) { + let attachment; + let name; + + const findName = thing => { + if (typeof thing === 'string') { + return basename(thing); + } + + if (thing.path) { + return basename(thing.path); + } + + return 'file.jpg'; + }; + + const ownAttachment = + typeof fileLike === 'string' || fileLike instanceof Buffer || typeof fileLike.pipe === 'function'; + if (ownAttachment) { + attachment = fileLike; + name = findName(attachment); + } else { + attachment = fileLike.attachment; + name = fileLike.name ?? findName(attachment); + } + + const { data, contentType } = await DataResolver.resolveFile(attachment); + return { data, name, contentType }; + } + + /** + * Creates a {@link MessagePayload} from user-level arguments. + * @param {MessageTarget} target Target to send to + * @param {string|MessagePayloadOption} options Options or content to use + * @param {MessagePayloadOption} [extra={}] Extra options to add onto specified options + * @returns {MessagePayload} + */ + static create(target, options, extra = {}) { + return new this( + target, + typeof options !== 'object' || options === null ? { content: options, ...extra } : { ...options, ...extra }, + ); + } +} + +module.exports = MessagePayload; + +/** + * A target for a message. + * @typedef {TextBasedChannels|User|GuildMember|Webhook|WebhookClient|BaseInteraction|InteractionWebhook| + * Message|MessageManager} MessageTarget + */ + +/** + * A possible payload option. + * @typedef {MessageCreateOptions|MessageEditOptions|WebhookMessageCreateOptions|WebhookMessageEditOptions| + * InteractionReplyOptions|InteractionUpdateOptions} MessagePayloadOption + */ + +/** + * @external APIMessage + * @see {@link https://discord.com/developers/docs/resources/channel#message-object} + */ + +/** + * @external RawFile + * @see {@link https://discord.js.org/docs/packages/rest/stable/RawFile:Interface} + */ diff --git a/node_modules/discord.js/src/structures/MessageReaction.js b/node_modules/discord.js/src/structures/MessageReaction.js new file mode 100644 index 0000000..43f05e3 --- /dev/null +++ b/node_modules/discord.js/src/structures/MessageReaction.js @@ -0,0 +1,142 @@ +'use strict'; + +const { Routes } = require('discord-api-types/v10'); +const GuildEmoji = require('./GuildEmoji'); +const ReactionEmoji = require('./ReactionEmoji'); +const ReactionUserManager = require('../managers/ReactionUserManager'); +const { flatten } = require('../util/Util'); + +/** + * Represents a reaction to a message. + */ +class MessageReaction { + constructor(client, data, message) { + /** + * The client that instantiated this message reaction + * @name MessageReaction#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * The message that this reaction refers to + * @type {Message} + */ + this.message = message; + + /** + * Whether the client has given this reaction + * @type {boolean} + */ + this.me = data.me; + + /** + * A manager of the users that have given this reaction + * @type {ReactionUserManager} + */ + this.users = new ReactionUserManager(this, this.me ? [client.user] : []); + + this._emoji = new ReactionEmoji(this, data.emoji); + + this._patch(data); + } + + _patch(data) { + if ('count' in data) { + /** + * The number of people that have given the same reaction + * @type {?number} + */ + this.count ??= data.count; + } + } + + /** + * Makes the client user react with this reaction + * @returns {Promise<MessageReaction>} + */ + react() { + return this.message.react(this.emoji); + } + + /** + * Removes all users from this reaction. + * @returns {Promise<MessageReaction>} + */ + async remove() { + await this.client.rest.delete( + Routes.channelMessageReaction(this.message.channelId, this.message.id, this._emoji.identifier), + ); + return this; + } + + /** + * The emoji of this reaction. Either a {@link GuildEmoji} object for known custom emojis, or a {@link ReactionEmoji} + * object which has fewer properties. Whatever the prototype of the emoji, it will still have + * `name`, `id`, `identifier` and `toString()` + * @type {GuildEmoji|ReactionEmoji} + * @readonly + */ + get emoji() { + if (this._emoji instanceof GuildEmoji) return this._emoji; + // Check to see if the emoji has become known to the client + if (this._emoji.id) { + const emojis = this.message.client.emojis.cache; + if (emojis.has(this._emoji.id)) { + const emoji = emojis.get(this._emoji.id); + this._emoji = emoji; + return emoji; + } + } + return this._emoji; + } + + /** + * Whether or not this reaction is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return this.count === null; + } + + /** + * Fetch this reaction. + * @returns {Promise<MessageReaction>} + */ + async fetch() { + const message = await this.message.fetch(); + const existing = message.reactions.cache.get(this.emoji.id ?? this.emoji.name); + // The reaction won't get set when it has been completely removed + this._patch(existing ?? { count: 0 }); + return this; + } + + toJSON() { + return flatten(this, { emoji: 'emojiId', message: 'messageId' }); + } + + valueOf() { + return this._emoji.id ?? this._emoji.name; + } + + _add(user) { + if (this.partial) return; + this.users.cache.set(user.id, user); + if (!this.me || user.id !== this.message.client.user.id || this.count === 0) this.count++; + this.me ||= user.id === this.message.client.user.id; + } + + _remove(user) { + if (this.partial) return; + this.users.cache.delete(user.id); + if (!this.me || user.id !== this.message.client.user.id) this.count--; + if (user.id === this.message.client.user.id) this.me = false; + if (this.count <= 0 && this.users.cache.size === 0) { + this.message.reactions.cache.delete(this.emoji.id ?? this.emoji.name); + } + } +} + +module.exports = MessageReaction; diff --git a/node_modules/discord.js/src/structures/ModalBuilder.js b/node_modules/discord.js/src/structures/ModalBuilder.js new file mode 100644 index 0000000..535b4a5 --- /dev/null +++ b/node_modules/discord.js/src/structures/ModalBuilder.js @@ -0,0 +1,34 @@ +'use strict'; + +const { ModalBuilder: BuildersModal, ComponentBuilder } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Represents a modal builder. + * @extends {BuildersModal} + */ +class ModalBuilder extends BuildersModal { + constructor({ components, ...data } = {}) { + super({ + ...toSnakeCase(data), + components: components?.map(c => (c instanceof ComponentBuilder ? c : toSnakeCase(c))), + }); + } + + /** + * Creates a new modal builder from JSON data + * @param {ModalBuilder|APIModalComponent} other The other data + * @returns {ModalBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } +} + +module.exports = ModalBuilder; + +/** + * @external BuildersModal + * @see {@link https://discord.js.org/docs/packages/builders/stable/ModalBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/ModalSubmitFields.js b/node_modules/discord.js/src/structures/ModalSubmitFields.js new file mode 100644 index 0000000..8e67b21 --- /dev/null +++ b/node_modules/discord.js/src/structures/ModalSubmitFields.js @@ -0,0 +1,55 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { ComponentType } = require('discord-api-types/v10'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); + +/** + * Represents the serialized fields from a modal submit interaction + */ +class ModalSubmitFields { + constructor(components) { + /** + * The components within the modal + * @type {ActionRowModalData[]} + */ + this.components = components; + + /** + * The extracted fields from the modal + * @type {Collection<string, ModalData>} + */ + this.fields = components.reduce((accumulator, next) => { + next.components.forEach(c => accumulator.set(c.customId, c)); + return accumulator; + }, new Collection()); + } + + /** + * Gets a field given a custom id from a component + * @param {string} customId The custom id of the component + * @param {ComponentType} [type] The type of the component + * @returns {ModalData} + */ + getField(customId, type) { + const field = this.fields.get(customId); + if (!field) throw new DiscordjsTypeError(ErrorCodes.ModalSubmitInteractionFieldNotFound, customId); + + if (type !== undefined && type !== field.type) { + throw new DiscordjsTypeError(ErrorCodes.ModalSubmitInteractionFieldType, customId, field.type, type); + } + + return field; + } + + /** + * Gets the value of a text input component given a custom id + * @param {string} customId The custom id of the text input component + * @returns {string} + */ + getTextInputValue(customId) { + return this.getField(customId, ComponentType.TextInput).value; + } +} + +module.exports = ModalSubmitFields; diff --git a/node_modules/discord.js/src/structures/ModalSubmitInteraction.js b/node_modules/discord.js/src/structures/ModalSubmitInteraction.js new file mode 100644 index 0000000..8f0ccf1 --- /dev/null +++ b/node_modules/discord.js/src/structures/ModalSubmitInteraction.js @@ -0,0 +1,122 @@ +'use strict'; + +const { lazy } = require('@discordjs/util'); +const BaseInteraction = require('./BaseInteraction'); +const InteractionWebhook = require('./InteractionWebhook'); +const ModalSubmitFields = require('./ModalSubmitFields'); +const InteractionResponses = require('./interfaces/InteractionResponses'); + +const getMessage = lazy(() => require('./Message').Message); + +/** + * @typedef {Object} ModalData + * @property {string} value The value of the field + * @property {ComponentType} type The component type of the field + * @property {string} customId The custom id of the field + */ + +/** + * @typedef {Object} ActionRowModalData + * @property {ModalData[]} components The components of this action row + * @property {ComponentType} type The component type of the action row + */ + +/** + * Represents a modal interaction + * @extends {BaseInteraction} + * @implements {InteractionResponses} + */ +class ModalSubmitInteraction extends BaseInteraction { + constructor(client, data) { + super(client, data); + /** + * The custom id of the modal. + * @type {string} + */ + this.customId = data.data.custom_id; + + if ('message' in data) { + /** + * The message associated with this interaction + * @type {?Message} + */ + this.message = this.channel?.messages._add(data.message) ?? new (getMessage())(this.client, data.message); + } else { + this.message = null; + } + + /** + * The components within the modal + * @type {ActionRowModalData[]} + */ + this.components = data.data.components?.map(c => ModalSubmitInteraction.transformComponent(c)); + + /** + * The fields within the modal + * @type {ModalSubmitFields} + */ + this.fields = new ModalSubmitFields(this.components); + + /** + * Whether the reply to this interaction has been deferred + * @type {boolean} + */ + this.deferred = false; + + /** + * Whether this interaction has already been replied to + * @type {boolean} + */ + this.replied = false; + + /** + * Whether the reply to this interaction is ephemeral + * @type {?boolean} + */ + this.ephemeral = null; + + /** + * An associated interaction webhook, can be used to further interact with this interaction + * @type {InteractionWebhook} + */ + this.webhook = new InteractionWebhook(this.client, this.applicationId, this.token); + } + + /** + * Transforms component data to discord.js-compatible data + * @param {*} rawComponent The data to transform + * @returns {ModalData[]} + */ + static transformComponent(rawComponent) { + return rawComponent.components + ? { type: rawComponent.type, components: rawComponent.components.map(c => this.transformComponent(c)) } + : { + value: rawComponent.value, + type: rawComponent.type, + customId: rawComponent.custom_id, + }; + } + + /** + * Whether this is from a {@link MessageComponentInteraction}. + * @returns {boolean} + */ + isFromMessage() { + return Boolean(this.message); + } + + // These are here only for documentation purposes - they are implemented by InteractionResponses + /* eslint-disable no-empty-function */ + deferReply() {} + reply() {} + fetchReply() {} + editReply() {} + deleteReply() {} + followUp() {} + deferUpdate() {} + update() {} +} + +InteractionResponses.applyToClass(ModalSubmitInteraction, 'showModal'); + +module.exports = ModalSubmitInteraction; diff --git a/node_modules/discord.js/src/structures/NewsChannel.js b/node_modules/discord.js/src/structures/NewsChannel.js new file mode 100644 index 0000000..3f5aff3 --- /dev/null +++ b/node_modules/discord.js/src/structures/NewsChannel.js @@ -0,0 +1,32 @@ +'use strict'; + +const { Routes } = require('discord-api-types/v10'); +const BaseGuildTextChannel = require('./BaseGuildTextChannel'); +const { DiscordjsError, ErrorCodes } = require('../errors'); + +/** + * Represents a guild news channel on Discord. + * @extends {BaseGuildTextChannel} + */ +class NewsChannel extends BaseGuildTextChannel { + /** + * Adds the target to this channel's followers. + * @param {TextChannelResolvable} channel The channel where the webhook should be created + * @param {string} [reason] Reason for creating the webhook + * @returns {Promise<NewsChannel>} + * @example + * if (channel.type === ChannelType.GuildAnnouncement) { + * channel.addFollower('222197033908436994', 'Important announcements') + * .then(() => console.log('Added follower')) + * .catch(console.error); + * } + */ + async addFollower(channel, reason) { + const channelId = this.guild.channels.resolveId(channel); + if (!channelId) throw new DiscordjsError(ErrorCodes.GuildChannelResolve); + await this.client.rest.post(Routes.channelFollowers(this.id), { body: { webhook_channel_id: channelId }, reason }); + return this; + } +} + +module.exports = NewsChannel; diff --git a/node_modules/discord.js/src/structures/OAuth2Guild.js b/node_modules/discord.js/src/structures/OAuth2Guild.js new file mode 100644 index 0000000..d5104ac --- /dev/null +++ b/node_modules/discord.js/src/structures/OAuth2Guild.js @@ -0,0 +1,28 @@ +'use strict'; + +const BaseGuild = require('./BaseGuild'); +const PermissionsBitField = require('../util/PermissionsBitField'); + +/** + * A partial guild received when using {@link GuildManager#fetch} to fetch multiple guilds. + * @extends {BaseGuild} + */ +class OAuth2Guild extends BaseGuild { + constructor(client, data) { + super(client, data); + + /** + * Whether the client user is the owner of the guild + * @type {boolean} + */ + this.owner = data.owner; + + /** + * The permissions that the client user has in this guild + * @type {Readonly<PermissionsBitField>} + */ + this.permissions = new PermissionsBitField(BigInt(data.permissions)).freeze(); + } +} + +module.exports = OAuth2Guild; diff --git a/node_modules/discord.js/src/structures/PartialGroupDMChannel.js b/node_modules/discord.js/src/structures/PartialGroupDMChannel.js new file mode 100644 index 0000000..ecbb878 --- /dev/null +++ b/node_modules/discord.js/src/structures/PartialGroupDMChannel.js @@ -0,0 +1,60 @@ +'use strict'; + +const { BaseChannel } = require('./BaseChannel'); +const { DiscordjsError, ErrorCodes } = require('../errors'); + +/** + * Represents a Partial Group DM Channel on Discord. + * @extends {BaseChannel} + */ +class PartialGroupDMChannel extends BaseChannel { + constructor(client, data) { + super(client, data); + + // No flags are present when fetching partial group DM channels. + this.flags = null; + + /** + * The name of this Group DM Channel + * @type {?string} + */ + this.name = data.name; + + /** + * The hash of the channel icon + * @type {?string} + */ + this.icon = data.icon; + + /** + * Recipient data received in a {@link PartialGroupDMChannel}. + * @typedef {Object} PartialRecipient + * @property {string} username The username of the recipient + */ + + /** + * The recipients of this Group DM Channel. + * @type {PartialRecipient[]} + */ + this.recipients = data.recipients; + } + + /** + * The URL to this channel's icon. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + iconURL(options = {}) { + return this.icon && this.client.rest.cdn.channelIcon(this.id, this.icon, options); + } + + delete() { + return Promise.reject(new DiscordjsError(ErrorCodes.DeleteGroupDMChannel)); + } + + fetch() { + return Promise.reject(new DiscordjsError(ErrorCodes.FetchGroupDMChannel)); + } +} + +module.exports = PartialGroupDMChannel; diff --git a/node_modules/discord.js/src/structures/PermissionOverwrites.js b/node_modules/discord.js/src/structures/PermissionOverwrites.js new file mode 100644 index 0000000..2cdc827 --- /dev/null +++ b/node_modules/discord.js/src/structures/PermissionOverwrites.js @@ -0,0 +1,196 @@ +'use strict'; + +const { OverwriteType } = require('discord-api-types/v10'); +const Base = require('./Base'); +const { Role } = require('./Role'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const PermissionsBitField = require('../util/PermissionsBitField'); + +/** + * Represents a permission overwrite for a role or member in a guild channel. + * @extends {Base} + */ +class PermissionOverwrites extends Base { + constructor(client, data, channel) { + super(client); + + /** + * The GuildChannel this overwrite is for + * @name PermissionOverwrites#channel + * @type {GuildChannel} + * @readonly + */ + Object.defineProperty(this, 'channel', { value: channel }); + + if (data) this._patch(data); + } + + _patch(data) { + /** + * The overwrite's id, either a {@link User} or a {@link Role} id + * @type {Snowflake} + */ + this.id = data.id; + + if ('type' in data) { + /** + * The type of this overwrite + * @type {OverwriteType} + */ + this.type = data.type; + } + + if ('deny' in data) { + /** + * The permissions that are denied for the user or role. + * @type {Readonly<PermissionsBitField>} + */ + this.deny = new PermissionsBitField(BigInt(data.deny)).freeze(); + } + + if ('allow' in data) { + /** + * The permissions that are allowed for the user or role. + * @type {Readonly<PermissionsBitField>} + */ + this.allow = new PermissionsBitField(BigInt(data.allow)).freeze(); + } + } + + /** + * Edits this Permission Overwrite. + * @param {PermissionOverwriteOptions} options The options for the update + * @param {string} [reason] Reason for creating/editing this overwrite + * @returns {Promise<PermissionOverwrites>} + * @example + * // Update permission overwrites + * permissionOverwrites.edit({ + * SendMessages: false + * }) + * .then(channel => console.log(channel.permissionOverwrites.get(message.author.id))) + * .catch(console.error); + */ + async edit(options, reason) { + await this.channel.permissionOverwrites.upsert(this.id, options, { type: this.type, reason }, this); + return this; + } + + /** + * Deletes this Permission Overwrite. + * @param {string} [reason] Reason for deleting this overwrite + * @returns {Promise<PermissionOverwrites>} + */ + async delete(reason) { + await this.channel.permissionOverwrites.delete(this.id, reason); + return this; + } + + toJSON() { + return { + id: this.id, + type: this.type, + allow: this.allow, + deny: this.deny, + }; + } + + /** + * An object mapping permission flags to `true` (enabled), `null` (unset) or `false` (disabled). + * ```js + * { + * 'SendMessages': true, + * 'EmbedLinks': null, + * 'AttachFiles': false, + * } + * ``` + * @typedef {Object} PermissionOverwriteOptions + */ + + /** + * @typedef {Object} ResolvedOverwriteOptions + * @property {PermissionsBitField} allow The allowed permissions + * @property {PermissionsBitField} deny The denied permissions + */ + + /** + * Resolves bitfield permissions overwrites from an object. + * @param {PermissionOverwriteOptions} options The options for the update + * @param {ResolvedOverwriteOptions} initialPermissions The initial permissions + * @returns {ResolvedOverwriteOptions} + */ + static resolveOverwriteOptions(options, { allow, deny } = {}) { + allow = new PermissionsBitField(allow); + deny = new PermissionsBitField(deny); + + for (const [perm, value] of Object.entries(options)) { + if (value === true) { + allow.add(perm); + deny.remove(perm); + } else if (value === false) { + allow.remove(perm); + deny.add(perm); + } else if (value === null) { + allow.remove(perm); + deny.remove(perm); + } + } + + return { allow, deny }; + } + + /** + * The raw data for a permission overwrite + * @typedef {Object} RawOverwriteData + * @property {Snowflake} id The id of the {@link Role} or {@link User} this overwrite belongs to + * @property {string} allow The permissions to allow + * @property {string} deny The permissions to deny + * @property {number} type The type of this OverwriteData + */ + + /** + * Data that can be resolved into {@link RawOverwriteData}. This can be: + * * PermissionOverwrites + * * OverwriteData + * @typedef {PermissionOverwrites|OverwriteData} OverwriteResolvable + */ + + /** + * Data that can be used for a permission overwrite + * @typedef {Object} OverwriteData + * @property {GuildMemberResolvable|RoleResolvable} id Member or role this overwrite is for + * @property {PermissionResolvable} [allow] The permissions to allow + * @property {PermissionResolvable} [deny] The permissions to deny + * @property {OverwriteType} [type] The type of this OverwriteData + */ + + /** + * Resolves an overwrite into {@link RawOverwriteData}. + * @param {OverwriteResolvable} overwrite The overwrite-like data to resolve + * @param {Guild} [guild] The guild to resolve from + * @returns {RawOverwriteData} + */ + static resolve(overwrite, guild) { + if (overwrite instanceof this) return overwrite.toJSON(); + if (typeof overwrite.id === 'string' && overwrite.type in OverwriteType) { + return { + id: overwrite.id, + type: overwrite.type, + allow: PermissionsBitField.resolve(overwrite.allow ?? PermissionsBitField.DefaultBit).toString(), + deny: PermissionsBitField.resolve(overwrite.deny ?? PermissionsBitField.DefaultBit).toString(), + }; + } + + const userOrRole = guild.roles.resolve(overwrite.id) ?? guild.client.users.resolve(overwrite.id); + if (!userOrRole) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'parameter', 'User nor a Role'); + const type = userOrRole instanceof Role ? OverwriteType.Role : OverwriteType.Member; + + return { + id: userOrRole.id, + type, + allow: PermissionsBitField.resolve(overwrite.allow ?? PermissionsBitField.DefaultBit).toString(), + deny: PermissionsBitField.resolve(overwrite.deny ?? PermissionsBitField.DefaultBit).toString(), + }; + } +} + +module.exports = PermissionOverwrites; diff --git a/node_modules/discord.js/src/structures/Presence.js b/node_modules/discord.js/src/structures/Presence.js new file mode 100644 index 0000000..79ecb29 --- /dev/null +++ b/node_modules/discord.js/src/structures/Presence.js @@ -0,0 +1,378 @@ +'use strict'; + +const Base = require('./Base'); +const { Emoji } = require('./Emoji'); +const ActivityFlagsBitField = require('../util/ActivityFlagsBitField'); +const { flatten } = require('../util/Util'); + +/** + * Activity sent in a message. + * @typedef {Object} MessageActivity + * @property {string} [partyId] Id of the party represented in activity + * @property {MessageActivityType} type Type of activity sent + */ + +/** + * The status of this presence: + * * **`online`** - user is online + * * **`idle`** - user is AFK + * * **`offline`** - user is offline or invisible + * * **`dnd`** - user is in Do Not Disturb + * @typedef {string} PresenceStatus + */ + +/** + * The status of this presence: + * * **`online`** - user is online + * * **`idle`** - user is AFK + * * **`dnd`** - user is in Do Not Disturb + * @typedef {string} ClientPresenceStatus + */ + +/** + * Represents a user's presence. + * @extends {Base} + */ +class Presence extends Base { + constructor(client, data = {}) { + super(client); + + /** + * The presence's user id + * @type {Snowflake} + */ + this.userId = data.user.id; + + /** + * The guild this presence is in + * @type {?Guild} + */ + this.guild = data.guild ?? null; + + this._patch(data); + } + + /** + * The user of this presence + * @type {?User} + * @readonly + */ + get user() { + return this.client.users.resolve(this.userId); + } + + /** + * The member of this presence + * @type {?GuildMember} + * @readonly + */ + get member() { + return this.guild.members.resolve(this.userId); + } + + _patch(data) { + if ('status' in data) { + /** + * The status of this presence + * @type {PresenceStatus} + */ + this.status = data.status; + } else { + this.status ??= 'offline'; + } + + if ('activities' in data) { + /** + * The activities of this presence + * @type {Activity[]} + */ + this.activities = data.activities.map(activity => new Activity(this, activity)); + } else { + this.activities ??= []; + } + + if ('client_status' in data) { + /** + * The devices this presence is on + * @type {?Object} + * @property {?ClientPresenceStatus} web The current presence in the web application + * @property {?ClientPresenceStatus} mobile The current presence in the mobile application + * @property {?ClientPresenceStatus} desktop The current presence in the desktop application + */ + this.clientStatus = data.client_status; + } else { + this.clientStatus ??= null; + } + + return this; + } + + _clone() { + const clone = Object.assign(Object.create(this), this); + clone.activities = this.activities.map(activity => activity._clone()); + return clone; + } + + /** + * Whether this presence is equal to another. + * @param {Presence} presence The presence to compare with + * @returns {boolean} + */ + equals(presence) { + return ( + this === presence || + (presence && + this.status === presence.status && + this.activities.length === presence.activities.length && + this.activities.every((activity, index) => activity.equals(presence.activities[index])) && + this.clientStatus?.web === presence.clientStatus?.web && + this.clientStatus?.mobile === presence.clientStatus?.mobile && + this.clientStatus?.desktop === presence.clientStatus?.desktop) + ); + } + + toJSON() { + return flatten(this); + } +} + +/** + * Represents an activity that is part of a user's presence. + */ +class Activity { + constructor(presence, data) { + /** + * The presence of the Activity + * @type {Presence} + * @readonly + * @name Activity#presence + */ + Object.defineProperty(this, 'presence', { value: presence }); + + /** + * The activity's name + * @type {string} + */ + this.name = data.name; + + /** + * The activity status's type + * @type {ActivityType} + */ + this.type = data.type; + + /** + * If the activity is being streamed, a link to the stream + * @type {?string} + */ + this.url = data.url ?? null; + + /** + * Details about the activity + * @type {?string} + */ + this.details = data.details ?? null; + + /** + * State of the activity + * @type {?string} + */ + this.state = data.state ?? null; + + /** + * The id of the application associated with this activity + * @type {?Snowflake} + */ + this.applicationId = data.application_id ?? null; + + /** + * Represents timestamps of an activity + * @typedef {Object} ActivityTimestamps + * @property {?Date} start When the activity started + * @property {?Date} end When the activity will end + */ + + /** + * Timestamps for the activity + * @type {?ActivityTimestamps} + */ + this.timestamps = data.timestamps + ? { + start: data.timestamps.start ? new Date(Number(data.timestamps.start)) : null, + end: data.timestamps.end ? new Date(Number(data.timestamps.end)) : null, + } + : null; + + /** + * Represents a party of an activity + * @typedef {Object} ActivityParty + * @property {?string} id The party's id + * @property {number[]} size Size of the party as `[current, max]` + */ + + /** + * Party of the activity + * @type {?ActivityParty} + */ + this.party = data.party ?? null; + + /** + * Assets for rich presence + * @type {?RichPresenceAssets} + */ + this.assets = data.assets ? new RichPresenceAssets(this, data.assets) : null; + + /** + * Flags that describe the activity + * @type {Readonly<ActivityFlagsBitField>} + */ + this.flags = new ActivityFlagsBitField(data.flags).freeze(); + + /** + * Emoji for a custom activity + * @type {?Emoji} + */ + this.emoji = data.emoji ? new Emoji(presence.client, data.emoji) : null; + + /** + * The labels of the buttons of this rich presence + * @type {string[]} + */ + this.buttons = data.buttons ?? []; + + /** + * Creation date of the activity + * @type {number} + */ + this.createdTimestamp = data.created_at; + } + + /** + * Whether this activity is equal to another activity. + * @param {Activity} activity The activity to compare with + * @returns {boolean} + */ + equals(activity) { + return ( + this === activity || + (activity && + this.name === activity.name && + this.type === activity.type && + this.url === activity.url && + this.state === activity.state && + this.details === activity.details && + this.emoji?.id === activity.emoji?.id && + this.emoji?.name === activity.emoji?.name) + ); + } + + /** + * The time the activity was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * When concatenated with a string, this automatically returns the activity's name instead of the Activity object. + * @returns {string} + */ + toString() { + return this.name; + } + + _clone() { + return Object.assign(Object.create(this), this); + } +} + +/** + * Assets for a rich presence + */ +class RichPresenceAssets { + constructor(activity, assets) { + /** + * The activity of the RichPresenceAssets + * @type {Activity} + * @readonly + * @name RichPresenceAssets#activity + */ + Object.defineProperty(this, 'activity', { value: activity }); + + /** + * Hover text for the large image + * @type {?string} + */ + this.largeText = assets.large_text ?? null; + + /** + * Hover text for the small image + * @type {?string} + */ + this.smallText = assets.small_text ?? null; + + /** + * The large image asset's id + * @type {?Snowflake} + */ + this.largeImage = assets.large_image ?? null; + + /** + * The small image asset's id + * @type {?Snowflake} + */ + this.smallImage = assets.small_image ?? null; + } + + /** + * Gets the URL of the small image asset + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + smallImageURL(options = {}) { + if (!this.smallImage) return null; + if (this.smallImage.includes(':')) { + const [platform, id] = this.smallImage.split(':'); + switch (platform) { + case 'mp': + return `https://media.discordapp.net/${id}`; + default: + return null; + } + } + + return this.activity.presence.client.rest.cdn.appAsset(this.activity.applicationId, this.smallImage, options); + } + + /** + * Gets the URL of the large image asset + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + largeImageURL(options = {}) { + if (!this.largeImage) return null; + if (this.largeImage.includes(':')) { + const [platform, id] = this.largeImage.split(':'); + switch (platform) { + case 'mp': + return `https://media.discordapp.net/${id}`; + case 'spotify': + return `https://i.scdn.co/image/${id}`; + case 'youtube': + return `https://i.ytimg.com/vi/${id}/hqdefault_live.jpg`; + case 'twitch': + return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${id}.png`; + default: + return null; + } + } + + return this.activity.presence.client.rest.cdn.appAsset(this.activity.applicationId, this.largeImage, options); + } +} + +exports.Presence = Presence; +exports.Activity = Activity; +exports.RichPresenceAssets = RichPresenceAssets; diff --git a/node_modules/discord.js/src/structures/ReactionCollector.js b/node_modules/discord.js/src/structures/ReactionCollector.js new file mode 100644 index 0000000..924b33b --- /dev/null +++ b/node_modules/discord.js/src/structures/ReactionCollector.js @@ -0,0 +1,229 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const Collector = require('./interfaces/Collector'); +const Events = require('../util/Events'); + +/** + * @typedef {CollectorOptions} ReactionCollectorOptions + * @property {number} max The maximum total amount of reactions to collect + * @property {number} maxEmojis The maximum number of emojis to collect + * @property {number} maxUsers The maximum number of users to react + */ + +/** + * Collects reactions on messages. + * Will automatically stop if the message ({@link Client#event:messageDelete messageDelete} or + * {@link Client#event:messageDeleteBulk messageDeleteBulk}), + * channel ({@link Client#event:channelDelete channelDelete}), + * thread ({@link Client#event:threadDelete threadDelete}), or + * guild ({@link Client#event:guildDelete guildDelete}) is deleted. + * @extends {Collector} + */ +class ReactionCollector extends Collector { + /** + * @param {Message} message The message upon which to collect reactions + * @param {ReactionCollectorOptions} [options={}] The options to apply to this collector + */ + constructor(message, options = {}) { + super(message.client, options); + + /** + * The message upon which to collect reactions + * @type {Message} + */ + this.message = message; + + /** + * The users that have reacted to this message + * @type {Collection} + */ + this.users = new Collection(); + + /** + * The total number of reactions collected + * @type {number} + */ + this.total = 0; + + this.empty = this.empty.bind(this); + this._handleChannelDeletion = this._handleChannelDeletion.bind(this); + this._handleThreadDeletion = this._handleThreadDeletion.bind(this); + this._handleGuildDeletion = this._handleGuildDeletion.bind(this); + this._handleMessageDeletion = this._handleMessageDeletion.bind(this); + + const bulkDeleteListener = messages => { + if (messages.has(this.message.id)) this.stop('messageDelete'); + }; + + this.client.incrementMaxListeners(); + this.client.on(Events.MessageReactionAdd, this.handleCollect); + this.client.on(Events.MessageReactionRemove, this.handleDispose); + this.client.on(Events.MessageReactionRemoveAll, this.empty); + this.client.on(Events.MessageDelete, this._handleMessageDeletion); + this.client.on(Events.MessageBulkDelete, bulkDeleteListener); + this.client.on(Events.ChannelDelete, this._handleChannelDeletion); + this.client.on(Events.ThreadDelete, this._handleThreadDeletion); + this.client.on(Events.GuildDelete, this._handleGuildDeletion); + + this.once('end', () => { + this.client.removeListener(Events.MessageReactionAdd, this.handleCollect); + this.client.removeListener(Events.MessageReactionRemove, this.handleDispose); + this.client.removeListener(Events.MessageReactionRemoveAll, this.empty); + this.client.removeListener(Events.MessageDelete, this._handleMessageDeletion); + this.client.removeListener(Events.MessageBulkDelete, bulkDeleteListener); + this.client.removeListener(Events.ChannelDelete, this._handleChannelDeletion); + this.client.removeListener(Events.ThreadDelete, this._handleThreadDeletion); + this.client.removeListener(Events.GuildDelete, this._handleGuildDeletion); + this.client.decrementMaxListeners(); + }); + + this.on('collect', (reaction, user) => { + /** + * Emitted whenever a reaction is newly created on a message. Will emit only when a new reaction is + * added to the message, as opposed to {@link Collector#collect} which will + * be emitted even when a reaction has already been added to the message. + * @event ReactionCollector#create + * @param {MessageReaction} reaction The reaction that was added + * @param {User} user The user that added the reaction + */ + if (reaction.count === 1) { + this.emit('create', reaction, user); + } + this.total++; + this.users.set(user.id, user); + }); + + this.on('remove', (reaction, user) => { + this.total--; + if (!this.collected.some(r => r.users.cache.has(user.id))) this.users.delete(user.id); + }); + } + + /** + * Handles an incoming reaction for possible collection. + * @param {MessageReaction} reaction The reaction to possibly collect + * @param {User} user The user that added the reaction + * @returns {?(Snowflake|string)} + * @private + */ + collect(reaction) { + /** + * Emitted whenever a reaction is collected. + * @event ReactionCollector#collect + * @param {MessageReaction} reaction The reaction that was collected + * @param {User} user The user that added the reaction + */ + if (reaction.message.id !== this.message.id) return null; + + return ReactionCollector.key(reaction); + } + + /** + * Handles a reaction deletion for possible disposal. + * @param {MessageReaction} reaction The reaction to possibly dispose of + * @param {User} user The user that removed the reaction + * @returns {?(Snowflake|string)} + */ + dispose(reaction, user) { + /** + * Emitted when the reaction had all the users removed and the `dispose` option is set to true. + * @event ReactionCollector#dispose + * @param {MessageReaction} reaction The reaction that was disposed of + * @param {User} user The user that removed the reaction + */ + if (reaction.message.id !== this.message.id) return null; + + /** + * Emitted when the reaction had one user removed and the `dispose` option is set to true. + * @event ReactionCollector#remove + * @param {MessageReaction} reaction The reaction that was removed + * @param {User} user The user that removed the reaction + */ + if (this.collected.has(ReactionCollector.key(reaction)) && this.users.has(user.id)) { + this.emit('remove', reaction, user); + } + return reaction.count ? null : ReactionCollector.key(reaction); + } + + /** + * Empties this reaction collector. + */ + empty() { + this.total = 0; + this.collected.clear(); + this.users.clear(); + this.checkEnd(); + } + + /** + * The reason this collector has ended with, or null if it hasn't ended yet + * @type {?string} + * @readonly + */ + get endReason() { + if (this.options.max && this.total >= this.options.max) return 'limit'; + if (this.options.maxEmojis && this.collected.size >= this.options.maxEmojis) return 'emojiLimit'; + if (this.options.maxUsers && this.users.size >= this.options.maxUsers) return 'userLimit'; + return super.endReason; + } + + /** + * Handles checking if the message has been deleted, and if so, stops the collector with the reason 'messageDelete'. + * @private + * @param {Message} message The message that was deleted + * @returns {void} + */ + _handleMessageDeletion(message) { + if (message.id === this.message.id) { + this.stop('messageDelete'); + } + } + + /** + * Handles checking if the channel has been deleted, and if so, stops the collector with the reason 'channelDelete'. + * @private + * @param {GuildChannel} channel The channel that was deleted + * @returns {void} + */ + _handleChannelDeletion(channel) { + if (channel.id === this.message.channelId || channel.threads?.cache.has(this.message.channelId)) { + this.stop('channelDelete'); + } + } + + /** + * Handles checking if the thread has been deleted, and if so, stops the collector with the reason 'threadDelete'. + * @private + * @param {ThreadChannel} thread The thread that was deleted + * @returns {void} + */ + _handleThreadDeletion(thread) { + if (thread.id === this.message.channelId) { + this.stop('threadDelete'); + } + } + + /** + * Handles checking if the guild has been deleted, and if so, stops the collector with the reason 'guildDelete'. + * @private + * @param {Guild} guild The guild that was deleted + * @returns {void} + */ + _handleGuildDeletion(guild) { + if (guild.id === this.message.guild?.id) { + this.stop('guildDelete'); + } + } + + /** + * Gets the collector key for a reaction. + * @param {MessageReaction} reaction The message reaction to get the key for + * @returns {Snowflake|string} + */ + static key(reaction) { + return reaction.emoji.id ?? reaction.emoji.name; + } +} + +module.exports = ReactionCollector; diff --git a/node_modules/discord.js/src/structures/ReactionEmoji.js b/node_modules/discord.js/src/structures/ReactionEmoji.js new file mode 100644 index 0000000..08e2ea0 --- /dev/null +++ b/node_modules/discord.js/src/structures/ReactionEmoji.js @@ -0,0 +1,31 @@ +'use strict'; + +const { Emoji } = require('./Emoji'); +const { flatten } = require('../util/Util'); + +/** + * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis + * will use this class opposed to the Emoji class when the client doesn't know enough + * information about them. + * @extends {Emoji} + */ +class ReactionEmoji extends Emoji { + constructor(reaction, emoji) { + super(reaction.message.client, emoji); + /** + * The message reaction this emoji refers to + * @type {MessageReaction} + */ + this.reaction = reaction; + } + + toJSON() { + return flatten(this, { identifier: true }); + } + + valueOf() { + return this.id; + } +} + +module.exports = ReactionEmoji; diff --git a/node_modules/discord.js/src/structures/Role.js b/node_modules/discord.js/src/structures/Role.js new file mode 100644 index 0000000..09a2a52 --- /dev/null +++ b/node_modules/discord.js/src/structures/Role.js @@ -0,0 +1,471 @@ +'use strict'; + +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { PermissionFlagsBits } = require('discord-api-types/v10'); +const Base = require('./Base'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const PermissionsBitField = require('../util/PermissionsBitField'); +const RoleFlagsBitField = require('../util/RoleFlagsBitField'); + +/** + * Represents a role on Discord. + * @extends {Base} + */ +class Role extends Base { + constructor(client, data, guild) { + super(client); + + /** + * The guild that the role belongs to + * @type {Guild} + */ + this.guild = guild; + + /** + * The icon hash of the role + * @type {?string} + */ + this.icon = null; + + /** + * The unicode emoji for the role + * @type {?string} + */ + this.unicodeEmoji = null; + + if (data) this._patch(data); + } + + _patch(data) { + /** + * The role's id (unique to the guild it is part of) + * @type {Snowflake} + */ + this.id = data.id; + if ('name' in data) { + /** + * The name of the role + * @type {string} + */ + this.name = data.name; + } + + if ('color' in data) { + /** + * The base 10 color of the role + * @type {number} + */ + this.color = data.color; + } + + if ('hoist' in data) { + /** + * If true, users that are part of this role will appear in a separate category in the users list + * @type {boolean} + */ + this.hoist = data.hoist; + } + + if ('position' in data) { + /** + * The raw position of the role from the API + * @type {number} + */ + this.rawPosition = data.position; + } + + if ('permissions' in data) { + /** + * The permissions of the role + * @type {Readonly<PermissionsBitField>} + */ + this.permissions = new PermissionsBitField(BigInt(data.permissions)).freeze(); + } + + if ('managed' in data) { + /** + * Whether or not the role is managed by an external service + * @type {boolean} + */ + this.managed = data.managed; + } + + if ('mentionable' in data) { + /** + * Whether or not the role can be mentioned by anyone + * @type {boolean} + */ + this.mentionable = data.mentionable; + } + + if ('icon' in data) this.icon = data.icon; + + if ('unicode_emoji' in data) this.unicodeEmoji = data.unicode_emoji; + + if ('flags' in data) { + /** + * The flags of this role + * @type {Readonly<RoleFlagsBitField>} + */ + this.flags = new RoleFlagsBitField(data.flags).freeze(); + } else { + this.flags ??= new RoleFlagsBitField().freeze(); + } + + /** + * The tags this role has + * @type {?Object} + * @property {Snowflake} [botId] The id of the bot this role belongs to + * @property {Snowflake|string} [integrationId] The id of the integration this role belongs to + * @property {true} [premiumSubscriberRole] Whether this is the guild's premium subscription role + * @property {Snowflake} [subscriptionListingId] The id of this role's subscription SKU and listing + * @property {true} [availableForPurchase] Whether this role is available for purchase + * @property {true} [guildConnections] Whether this role is a guild's linked role + */ + this.tags = data.tags ? {} : null; + if (data.tags) { + if ('bot_id' in data.tags) { + this.tags.botId = data.tags.bot_id; + } + if ('integration_id' in data.tags) { + this.tags.integrationId = data.tags.integration_id; + } + if ('premium_subscriber' in data.tags) { + this.tags.premiumSubscriberRole = true; + } + if ('subscription_listing_id' in data.tags) { + this.tags.subscriptionListingId = data.tags.subscription_listing_id; + } + if ('available_for_purchase' in data.tags) { + this.tags.availableForPurchase = true; + } + if ('guild_connections' in data.tags) { + this.tags.guildConnections = true; + } + } + } + + /** + * The timestamp the role was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the role was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The hexadecimal version of the role color, with a leading hashtag + * @type {string} + * @readonly + */ + get hexColor() { + return `#${this.color.toString(16).padStart(6, '0')}`; + } + + /** + * The cached guild members that have this role + * @type {Collection<Snowflake, GuildMember>} + * @readonly + */ + get members() { + return this.id === this.guild.id + ? this.guild.members.cache.clone() + : this.guild.members.cache.filter(m => m._roles.includes(this.id)); + } + + /** + * Whether the role is editable by the client user + * @type {boolean} + * @readonly + */ + get editable() { + if (this.managed) return false; + const clientMember = this.guild.members.resolve(this.client.user); + if (!clientMember.permissions.has(PermissionFlagsBits.ManageRoles)) return false; + return clientMember.roles.highest.comparePositionTo(this) > 0; + } + + /** + * The position of the role in the role manager + * @type {number} + * @readonly + */ + get position() { + return this.guild.roles.cache.reduce( + (acc, role) => + acc + + (this.rawPosition === role.rawPosition + ? BigInt(this.id) > BigInt(role.id) + : this.rawPosition > role.rawPosition), + 0, + ); + } + + /** + * Compares this role's position to another role's. + * @param {RoleResolvable} role Role to compare to this one + * @returns {number} Negative number if this role's position is lower (other role's is higher), + * positive number if this one is higher (other's is lower), 0 if equal + * @example + * // Compare the position of a role to another + * const roleCompare = role.comparePositionTo(otherRole); + * if (roleCompare >= 1) console.log(`${role.name} is higher than ${otherRole.name}`); + */ + comparePositionTo(role) { + return this.guild.roles.comparePositions(this, role); + } + + /** + * The data for a role. + * @typedef {Object} RoleData + * @property {string} [name] The name of the role + * @property {ColorResolvable} [color] The color of the role, either a hex string or a base 10 number + * @property {boolean} [hoist] Whether or not the role should be hoisted + * @property {number} [position] The position of the role + * @property {PermissionResolvable} [permissions] The permissions of the role + * @property {boolean} [mentionable] Whether or not the role should be mentionable + * @property {?(BufferResolvable|Base64Resolvable|EmojiResolvable)} [icon] The icon for the role + * <warn>The `EmojiResolvable` should belong to the same guild as the role. + * If not, pass the emoji's URL directly</warn> + * @property {?string} [unicodeEmoji] The unicode emoji for the role + */ + + /** + * Edits the role. + * @param {RoleEditOptions} options The options to provide + * @returns {Promise<Role>} + * @example + * // Edit a role + * role.edit({ name: 'new role' }) + * .then(updated => console.log(`Edited role name to ${updated.name}`)) + * .catch(console.error); + */ + edit(options) { + return this.guild.roles.edit(this, options); + } + + /** + * Returns `channel.permissionsFor(role)`. Returns permissions for a role in a guild channel, + * taking into account permission overwrites. + * @param {GuildChannel|Snowflake} channel The guild channel to use as context + * @param {boolean} [checkAdmin=true] Whether having the {@link PermissionFlagsBits.Administrator} permission + * will return all permissions + * @returns {Readonly<PermissionsBitField>} + */ + permissionsIn(channel, checkAdmin = true) { + channel = this.guild.channels.resolve(channel); + if (!channel) throw new DiscordjsError(ErrorCodes.GuildChannelResolve); + return channel.rolePermissions(this, checkAdmin); + } + + /** + * Sets a new name for the role. + * @param {string} name The new name of the role + * @param {string} [reason] Reason for changing the role's name + * @returns {Promise<Role>} + * @example + * // Set the name of the role + * role.setName('new role') + * .then(updated => console.log(`Updated role name to ${updated.name}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name, reason }); + } + + /** + * Sets a new color for the role. + * @param {ColorResolvable} color The color of the role + * @param {string} [reason] Reason for changing the role's color + * @returns {Promise<Role>} + * @example + * // Set the color of a role + * role.setColor('#FF0000') + * .then(updated => console.log(`Set color of role to ${updated.color}`)) + * .catch(console.error); + */ + setColor(color, reason) { + return this.edit({ color, reason }); + } + + /** + * Sets whether or not the role should be hoisted. + * @param {boolean} [hoist=true] Whether or not to hoist the role + * @param {string} [reason] Reason for setting whether or not the role should be hoisted + * @returns {Promise<Role>} + * @example + * // Set the hoist of the role + * role.setHoist(true) + * .then(updated => console.log(`Role hoisted: ${updated.hoist}`)) + * .catch(console.error); + */ + setHoist(hoist = true, reason) { + return this.edit({ hoist, reason }); + } + + /** + * Sets the permissions of the role. + * @param {PermissionResolvable} permissions The permissions of the role + * @param {string} [reason] Reason for changing the role's permissions + * @returns {Promise<Role>} + * @example + * // Set the permissions of the role + * role.setPermissions([PermissionFlagsBits.KickMembers, PermissionFlagsBits.BanMembers]) + * .then(updated => console.log(`Updated permissions to ${updated.permissions.bitfield}`)) + * .catch(console.error); + * @example + * // Remove all permissions from a role + * role.setPermissions(0n) + * .then(updated => console.log(`Updated permissions to ${updated.permissions.bitfield}`)) + * .catch(console.error); + */ + setPermissions(permissions, reason) { + return this.edit({ permissions, reason }); + } + + /** + * Sets whether this role is mentionable. + * @param {boolean} [mentionable=true] Whether this role should be mentionable + * @param {string} [reason] Reason for setting whether or not this role should be mentionable + * @returns {Promise<Role>} + * @example + * // Make the role mentionable + * role.setMentionable(true) + * .then(updated => console.log(`Role updated ${updated.name}`)) + * .catch(console.error); + */ + setMentionable(mentionable = true, reason) { + return this.edit({ mentionable, reason }); + } + + /** + * Sets a new icon for the role. + * @param {?(BufferResolvable|Base64Resolvable|EmojiResolvable)} icon The icon for the role + * <warn>The `EmojiResolvable` should belong to the same guild as the role. + * If not, pass the emoji's URL directly</warn> + * @param {string} [reason] Reason for changing the role's icon + * @returns {Promise<Role>} + */ + setIcon(icon, reason) { + return this.edit({ icon, reason }); + } + + /** + * Sets a new unicode emoji for the role. + * @param {?string} unicodeEmoji The new unicode emoji for the role + * @param {string} [reason] Reason for changing the role's unicode emoji + * @returns {Promise<Role>} + * @example + * // Set a new unicode emoji for the role + * role.setUnicodeEmoji('🤖') + * .then(updated => console.log(`Set unicode emoji for the role to ${updated.unicodeEmoji}`)) + * .catch(console.error); + */ + setUnicodeEmoji(unicodeEmoji, reason) { + return this.edit({ unicodeEmoji, reason }); + } + + /** + * Options used to set the position of a role. + * @typedef {Object} SetRolePositionOptions + * @property {boolean} [relative=false] Whether to change the position relative to its current value or not + * @property {string} [reason] The reason for changing the position + */ + + /** + * Sets the new position of the role. + * @param {number} position The new position for the role + * @param {SetRolePositionOptions} [options] Options for setting the position + * @returns {Promise<Role>} + * @example + * // Set the position of the role + * role.setPosition(1) + * .then(updated => console.log(`Role position: ${updated.position}`)) + * .catch(console.error); + */ + setPosition(position, options = {}) { + return this.guild.roles.setPosition(this, position, options); + } + + /** + * Deletes the role. + * @param {string} [reason] Reason for deleting this role + * @returns {Promise<Role>} + * @example + * // Delete a role + * role.delete('The role needed to go') + * .then(deleted => console.log(`Deleted role ${deleted.name}`)) + * .catch(console.error); + */ + async delete(reason) { + await this.guild.roles.delete(this.id, reason); + return this; + } + + /** + * A link to the role's icon + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + iconURL(options = {}) { + return this.icon && this.client.rest.cdn.roleIcon(this.id, this.icon, options); + } + + /** + * Whether this role equals another role. It compares all properties, so for most operations + * it is advisable to just compare `role.id === role2.id` as it is much faster and is often + * what most users need. + * @param {Role} role Role to compare with + * @returns {boolean} + */ + equals(role) { + return ( + role && + this.id === role.id && + this.name === role.name && + this.color === role.color && + this.hoist === role.hoist && + this.position === role.position && + this.permissions.bitfield === role.permissions.bitfield && + this.managed === role.managed && + this.icon === role.icon && + this.unicodeEmoji === role.unicodeEmoji + ); + } + + /** + * When concatenated with a string, this automatically returns the role's mention instead of the Role object. + * @returns {string} + * @example + * // Logs: Role: <@&123456789012345678> + * console.log(`Role: ${role}`); + */ + toString() { + if (this.id === this.guild.id) return '@everyone'; + return `<@&${this.id}>`; + } + + toJSON() { + return { + ...super.toJSON({ createdTimestamp: true }), + permissions: this.permissions.toJSON(), + }; + } +} + +exports.Role = Role; + +/** + * @external APIRole + * @see {@link https://discord.com/developers/docs/topics/permissions#role-object} + */ diff --git a/node_modules/discord.js/src/structures/RoleSelectMenuBuilder.js b/node_modules/discord.js/src/structures/RoleSelectMenuBuilder.js new file mode 100644 index 0000000..0d80de5 --- /dev/null +++ b/node_modules/discord.js/src/structures/RoleSelectMenuBuilder.js @@ -0,0 +1,31 @@ +'use strict'; + +const { RoleSelectMenuBuilder: BuildersRoleSelectMenu } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersRoleSelectMenu} + */ +class RoleSelectMenuBuilder extends BuildersRoleSelectMenu { + constructor(data = {}) { + super(toSnakeCase(data)); + } + + /** + * Creates a new select menu builder from JSON data + * @param {RoleSelectMenuBuilder|RoleSelectMenuComponent|APIRoleSelectComponent} other The other data + * @returns {RoleSelectMenuBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } +} + +module.exports = RoleSelectMenuBuilder; + +/** + * @external BuildersRoleSelectMenu + * @see {@link https://discord.js.org/docs/packages/builders/stable/RoleSelectMenuBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/RoleSelectMenuComponent.js b/node_modules/discord.js/src/structures/RoleSelectMenuComponent.js new file mode 100644 index 0000000..1b27942 --- /dev/null +++ b/node_modules/discord.js/src/structures/RoleSelectMenuComponent.js @@ -0,0 +1,11 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a role select menu component + * @extends {BaseSelectMenuComponent} + */ +class RoleSelectMenuComponent extends BaseSelectMenuComponent {} + +module.exports = RoleSelectMenuComponent; diff --git a/node_modules/discord.js/src/structures/RoleSelectMenuInteraction.js b/node_modules/discord.js/src/structures/RoleSelectMenuInteraction.js new file mode 100644 index 0000000..eb42eff --- /dev/null +++ b/node_modules/discord.js/src/structures/RoleSelectMenuInteraction.js @@ -0,0 +1,33 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const MessageComponentInteraction = require('./MessageComponentInteraction'); + +/** + * Represents a {@link ComponentType.RoleSelect} select menu interaction. + * @extends {MessageComponentInteraction} + */ +class RoleSelectMenuInteraction extends MessageComponentInteraction { + constructor(client, data) { + super(client, data); + const { resolved, values } = data.data; + + /** + * An array of the selected role ids + * @type {Snowflake[]} + */ + this.values = values ?? []; + + /** + * Collection of the selected roles + * @type {Collection<Snowflake, Role|APIRole>} + */ + this.roles = new Collection(); + + for (const role of Object.values(resolved?.roles ?? {})) { + this.roles.set(role.id, this.guild?.roles._add(role) ?? role); + } + } +} + +module.exports = RoleSelectMenuInteraction; diff --git a/node_modules/discord.js/src/structures/SelectMenuBuilder.js b/node_modules/discord.js/src/structures/SelectMenuBuilder.js new file mode 100644 index 0000000..a779370 --- /dev/null +++ b/node_modules/discord.js/src/structures/SelectMenuBuilder.js @@ -0,0 +1,26 @@ +'use strict'; + +const process = require('node:process'); +const StringSelectMenuBuilder = require('./StringSelectMenuBuilder'); + +let deprecationEmitted = false; + +/** + * @deprecated Use {@link StringSelectMenuBuilder} instead. + * @extends {StringSelectMenuBuilder} + */ +class SelectMenuBuilder extends StringSelectMenuBuilder { + constructor(...params) { + super(...params); + + if (!deprecationEmitted) { + process.emitWarning( + 'The SelectMenuBuilder class is deprecated. Use StringSelectMenuBuilder instead.', + 'DeprecationWarning', + ); + deprecationEmitted = true; + } + } +} + +module.exports = SelectMenuBuilder; diff --git a/node_modules/discord.js/src/structures/SelectMenuComponent.js b/node_modules/discord.js/src/structures/SelectMenuComponent.js new file mode 100644 index 0000000..2cd8097 --- /dev/null +++ b/node_modules/discord.js/src/structures/SelectMenuComponent.js @@ -0,0 +1,26 @@ +'use strict'; + +const process = require('node:process'); +const StringSelectMenuComponent = require('./StringSelectMenuComponent'); + +let deprecationEmitted = false; + +/** + * @deprecated Use {@link StringSelectMenuComponent} instead. + * @extends {StringSelectMenuComponent} + */ +class SelectMenuComponent extends StringSelectMenuComponent { + constructor(...params) { + super(...params); + + if (!deprecationEmitted) { + process.emitWarning( + 'The SelectMenuComponent class is deprecated. Use StringSelectMenuComponent instead.', + 'DeprecationWarning', + ); + deprecationEmitted = true; + } + } +} + +module.exports = SelectMenuComponent; diff --git a/node_modules/discord.js/src/structures/SelectMenuInteraction.js b/node_modules/discord.js/src/structures/SelectMenuInteraction.js new file mode 100644 index 0000000..a096559 --- /dev/null +++ b/node_modules/discord.js/src/structures/SelectMenuInteraction.js @@ -0,0 +1,26 @@ +'use strict'; + +const process = require('node:process'); +const StringSelectMenuInteraction = require('./StringSelectMenuInteraction'); + +let deprecationEmitted = false; + +/** + * @deprecated Use {@link StringSelectMenuInteraction} instead. + * @extends {StringSelectMenuInteraction} + */ +class SelectMenuInteraction extends StringSelectMenuInteraction { + constructor(...params) { + super(...params); + + if (!deprecationEmitted) { + process.emitWarning( + 'The SelectMenuInteraction class is deprecated. Use StringSelectMenuInteraction instead.', + 'DeprecationWarning', + ); + deprecationEmitted = true; + } + } +} + +module.exports = SelectMenuInteraction; diff --git a/node_modules/discord.js/src/structures/SelectMenuOptionBuilder.js b/node_modules/discord.js/src/structures/SelectMenuOptionBuilder.js new file mode 100644 index 0000000..85309d1 --- /dev/null +++ b/node_modules/discord.js/src/structures/SelectMenuOptionBuilder.js @@ -0,0 +1,26 @@ +'use strict'; + +const process = require('node:process'); +const StringSelectMenuOptionBuilder = require('./StringSelectMenuOptionBuilder'); + +let deprecationEmitted = false; + +/** + * @deprecated Use {@link StringSelectMenuOptionBuilder} instead. + * @extends {StringSelectMenuOptionBuilder} + */ +class SelectMenuOptionBuilder extends StringSelectMenuOptionBuilder { + constructor(...params) { + super(...params); + + if (!deprecationEmitted) { + process.emitWarning( + 'The SelectMenuOptionBuilder class is deprecated. Use StringSelectMenuOptionBuilder instead.', + 'DeprecationWarning', + ); + deprecationEmitted = true; + } + } +} + +module.exports = SelectMenuOptionBuilder; diff --git a/node_modules/discord.js/src/structures/StageChannel.js b/node_modules/discord.js/src/structures/StageChannel.js new file mode 100644 index 0000000..2661489 --- /dev/null +++ b/node_modules/discord.js/src/structures/StageChannel.js @@ -0,0 +1,112 @@ +'use strict'; + +const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel'); + +/** + * Represents a guild stage channel on Discord. + * @extends {BaseGuildVoiceChannel} + */ +class StageChannel extends BaseGuildVoiceChannel { + _patch(data) { + super._patch(data); + + if ('topic' in data) { + /** + * The topic of the stage channel + * @type {?string} + */ + this.topic = data.topic; + } + } + + /** + * The stage instance of this stage channel, if it exists + * @type {?StageInstance} + * @readonly + */ + get stageInstance() { + return this.guild.stageInstances.cache.find(stageInstance => stageInstance.channelId === this.id) ?? null; + } + + /** + * Creates a stage instance associated with this stage channel. + * @param {StageInstanceCreateOptions} options The options to create the stage instance + * @returns {Promise<StageInstance>} + */ + createStageInstance(options) { + return this.guild.stageInstances.create(this.id, options); + } + + /** + * Sets a new topic for the guild channel. + * @param {?string} topic The new topic for the guild channel + * @param {string} [reason] Reason for changing the guild channel's topic + * @returns {Promise<StageChannel>} + * @example + * // Set a new channel topic + * stageChannel.setTopic('needs more rate limiting') + * .then(channel => console.log(`Channel's new topic is ${channel.topic}`)) + * .catch(console.error); + */ + setTopic(topic, reason) { + return this.edit({ topic, reason }); + } +} + +/** + * Sets the bitrate of the channel. + * @method setBitrate + * @memberof StageChannel + * @instance + * @param {number} bitrate The new bitrate + * @param {string} [reason] Reason for changing the channel's bitrate + * @returns {Promise<StageChannel>} + * @example + * // Set the bitrate of a voice channel + * stageChannel.setBitrate(48_000) + * .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`)) + * .catch(console.error); + */ + +/** + * Sets the RTC region of the channel. + * @method setRTCRegion + * @memberof StageChannel + * @instance + * @param {?string} rtcRegion The new region of the channel. Set to `null` to remove a specific region for the channel + * @param {string} [reason] The reason for modifying this region. + * @returns {Promise<StageChannel>} + * @example + * // Set the RTC region to sydney + * stageChannel.setRTCRegion('sydney'); + * @example + * // Remove a fixed region for this channel - let Discord decide automatically + * stageChannel.setRTCRegion(null, 'We want to let Discord decide.'); + */ + +/** + * Sets the user limit of the channel. + * @method setUserLimit + * @memberof StageChannel + * @instance + * @param {number} userLimit The new user limit + * @param {string} [reason] Reason for changing the user limit + * @returns {Promise<StageChannel>} + * @example + * // Set the user limit of a voice channel + * stageChannel.setUserLimit(42) + * .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`)) + * .catch(console.error); + */ + +/** + * Sets the camera video quality mode of the channel. + * @method setVideoQualityMode + * @memberof StageChannel + * @instance + * @param {VideoQualityMode} videoQualityMode The new camera video quality mode. + * @param {string} [reason] Reason for changing the camera video quality mode. + * @returns {Promise<StageChannel>} + */ + +module.exports = StageChannel; diff --git a/node_modules/discord.js/src/structures/StageInstance.js b/node_modules/discord.js/src/structures/StageInstance.js new file mode 100644 index 0000000..97f65df --- /dev/null +++ b/node_modules/discord.js/src/structures/StageInstance.js @@ -0,0 +1,167 @@ +'use strict'; + +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const Base = require('./Base'); + +/** + * Represents a stage instance. + * @extends {Base} + */ +class StageInstance extends Base { + constructor(client, data) { + super(client); + + /** + * The stage instance's id + * @type {Snowflake} + */ + this.id = data.id; + + this._patch(data); + } + + _patch(data) { + if ('guild_id' in data) { + /** + * The id of the guild associated with the stage channel + * @type {Snowflake} + */ + this.guildId = data.guild_id; + } + + if ('channel_id' in data) { + /** + * The id of the channel associated with the stage channel + * @type {Snowflake} + */ + this.channelId = data.channel_id; + } + + if ('topic' in data) { + /** + * The topic of the stage instance + * @type {string} + */ + this.topic = data.topic; + } + + if ('privacy_level' in data) { + /** + * The privacy level of the stage instance + * @type {StageInstancePrivacyLevel} + */ + this.privacyLevel = data.privacy_level; + } + + if ('discoverable_disabled' in data) { + /** + * Whether or not stage discovery is disabled + * @type {?boolean} + * @deprecated See https://github.com/discord/discord-api-docs/pull/4296 for more information + */ + this.discoverableDisabled = data.discoverable_disabled; + } else { + this.discoverableDisabled ??= null; + } + + if ('guild_scheduled_event_id' in data) { + /** + * The associated guild scheduled event id of this stage instance + * @type {?Snowflake} + */ + this.guildScheduledEventId = data.guild_scheduled_event_id; + } else { + this.guildScheduledEventId ??= null; + } + } + + /** + * The stage channel associated with this stage instance + * @type {?StageChannel} + * @readonly + */ + get channel() { + return this.client.channels.resolve(this.channelId); + } + + /** + * The guild this stage instance belongs to + * @type {?Guild} + * @readonly + */ + get guild() { + return this.client.guilds.resolve(this.guildId); + } + + /** + * The associated guild scheduled event of this stage instance + * @type {?GuildScheduledEvent} + * @readonly + */ + get guildScheduledEvent() { + return this.guild?.scheduledEvents.resolve(this.guildScheduledEventId) ?? null; + } + + /** + * Edits this stage instance. + * @param {StageInstanceEditOptions} options The options to edit the stage instance + * @returns {Promise<StageInstance>} + * @example + * // Edit a stage instance + * stageInstance.edit({ topic: 'new topic' }) + * .then(stageInstance => console.log(stageInstance)) + * .catch(console.error) + */ + edit(options) { + return this.guild.stageInstances.edit(this.channelId, options); + } + + /** + * Deletes this stage instance. + * @returns {Promise<StageInstance>} + * @example + * // Delete a stage instance + * stageInstance.delete() + * .then(stageInstance => console.log(stageInstance)) + * .catch(console.error); + */ + async delete() { + await this.guild.stageInstances.delete(this.channelId); + const clone = this._clone(); + return clone; + } + + /** + * Sets the topic of this stage instance. + * @param {string} topic The topic for the stage instance + * @returns {Promise<StageInstance>} + * @example + * // Set topic of a stage instance + * stageInstance.setTopic('new topic') + * .then(stageInstance => console.log(`Set the topic to: ${stageInstance.topic}`)) + * .catch(console.error); + */ + setTopic(topic) { + return this.guild.stageInstances.edit(this.channelId, { topic }); + } + + /** + * The timestamp this stage instances was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time this stage instance was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } +} + +exports.StageInstance = StageInstance; diff --git a/node_modules/discord.js/src/structures/Sticker.js b/node_modules/discord.js/src/structures/Sticker.js new file mode 100644 index 0000000..b0f2ef6 --- /dev/null +++ b/node_modules/discord.js/src/structures/Sticker.js @@ -0,0 +1,272 @@ +'use strict'; + +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { Routes } = require('discord-api-types/v10'); +const Base = require('./Base'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const { StickerFormatExtensionMap } = require('../util/Constants'); + +/** + * Represents a Sticker. + * @extends {Base} + */ +class Sticker extends Base { + constructor(client, sticker) { + super(client); + + this._patch(sticker); + } + + _patch(sticker) { + /** + * The sticker's id + * @type {Snowflake} + */ + this.id = sticker.id; + + if ('description' in sticker) { + /** + * The description of the sticker + * @type {?string} + */ + this.description = sticker.description; + } else { + this.description ??= null; + } + + if ('type' in sticker) { + /** + * The type of the sticker + * @type {?StickerType} + */ + this.type = sticker.type; + } else { + this.type ??= null; + } + + if ('format_type' in sticker) { + /** + * The format of the sticker + * @type {StickerFormatType} + */ + this.format = sticker.format_type; + } + + if ('name' in sticker) { + /** + * The name of the sticker + * @type {string} + */ + this.name = sticker.name; + } + + if ('pack_id' in sticker) { + /** + * The id of the pack the sticker is from, for standard stickers + * @type {?Snowflake} + */ + this.packId = sticker.pack_id; + } else { + this.packId ??= null; + } + + if ('tags' in sticker) { + /** + * Autocomplete/suggestions for the sticker + * @type {?string} + */ + this.tags = sticker.tags; + } else { + this.tags ??= null; + } + + if ('available' in sticker) { + /** + * Whether or not the guild sticker is available + * @type {?boolean} + */ + this.available = sticker.available; + } else { + this.available ??= null; + } + + if ('guild_id' in sticker) { + /** + * The id of the guild that owns this sticker + * @type {?Snowflake} + */ + this.guildId = sticker.guild_id; + } else { + this.guildId ??= null; + } + + if ('user' in sticker) { + /** + * The user that uploaded the guild sticker + * @type {?User} + */ + this.user = this.client.users._add(sticker.user); + } else { + this.user ??= null; + } + + if ('sort_value' in sticker) { + /** + * The standard sticker's sort order within its pack + * @type {?number} + */ + this.sortValue = sticker.sort_value; + } else { + this.sortValue ??= null; + } + } + + /** + * The timestamp the sticker was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the sticker was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * Whether this sticker is partial + * @type {boolean} + * @readonly + */ + get partial() { + return !this.type; + } + + /** + * The guild that owns this sticker + * @type {?Guild} + * @readonly + */ + get guild() { + return this.client.guilds.resolve(this.guildId); + } + + /** + * A link to the sticker + * <info>If the sticker's format is {@link StickerFormatType.Lottie}, it returns + * the URL of the Lottie JSON file.</info> + * @type {string} + * @readonly + */ + get url() { + return this.client.rest.cdn.sticker(this.id, StickerFormatExtensionMap[this.format]); + } + + /** + * Fetches this sticker. + * @returns {Promise<Sticker>} + */ + async fetch() { + const data = await this.client.rest.get(Routes.sticker(this.id)); + this._patch(data); + return this; + } + + /** + * Fetches the pack this sticker is part of from Discord, if this is a Nitro sticker. + * @returns {Promise<?StickerPack>} + */ + async fetchPack() { + return (this.packId && (await this.client.fetchPremiumStickerPacks()).get(this.packId)) ?? null; + } + + /** + * Fetches the user who uploaded this sticker, if this is a guild sticker. + * @returns {Promise<?User>} + */ + async fetchUser() { + if (this.partial) await this.fetch(); + if (!this.guildId) throw new DiscordjsError(ErrorCodes.NotGuildSticker); + return this.guild.stickers.fetchUser(this); + } + + /** + * Data for editing a sticker. + * @typedef {Object} GuildStickerEditOptions + * @property {string} [name] The name of the sticker + * @property {?string} [description] The description of the sticker + * @property {string} [tags] The Discord name of a unicode emoji representing the sticker's expression + * @property {string} [reason] Reason for editing this sticker + */ + + /** + * Edits the sticker. + * @param {GuildStickerEditOptions} options The options to provide + * @returns {Promise<Sticker>} + * @example + * // Update the name of a sticker + * sticker.edit({ name: 'new name' }) + * .then(s => console.log(`Updated the name of the sticker to ${s.name}`)) + * .catch(console.error); + */ + edit(options) { + return this.guild.stickers.edit(this, options); + } + + /** + * Deletes the sticker. + * @returns {Promise<Sticker>} + * @param {string} [reason] Reason for deleting this sticker + * @example + * // Delete a message + * sticker.delete() + * .then(s => console.log(`Deleted sticker ${s.name}`)) + * .catch(console.error); + */ + async delete(reason) { + await this.guild.stickers.delete(this, reason); + return this; + } + + /** + * Whether this sticker is the same as another one. + * @param {Sticker|APISticker} other The sticker to compare it to + * @returns {boolean} + */ + equals(other) { + if (other instanceof Sticker) { + return ( + other.id === this.id && + other.description === this.description && + other.type === this.type && + other.format === this.format && + other.name === this.name && + other.packId === this.packId && + other.tags === this.tags && + other.available === this.available && + other.guildId === this.guildId && + other.sortValue === this.sortValue + ); + } else { + return ( + other.id === this.id && + other.description === this.description && + other.name === this.name && + other.tags === this.tags + ); + } + } +} + +exports.Sticker = Sticker; + +/** + * @external APISticker + * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-object} + */ diff --git a/node_modules/discord.js/src/structures/StickerPack.js b/node_modules/discord.js/src/structures/StickerPack.js new file mode 100644 index 0000000..7e599b7 --- /dev/null +++ b/node_modules/discord.js/src/structures/StickerPack.js @@ -0,0 +1,95 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const Base = require('./Base'); +const { Sticker } = require('./Sticker'); + +/** + * Represents a pack of standard stickers. + * @extends {Base} + */ +class StickerPack extends Base { + constructor(client, pack) { + super(client); + /** + * The Sticker pack's id + * @type {Snowflake} + */ + this.id = pack.id; + + /** + * The stickers in the pack + * @type {Collection<Snowflake, Sticker>} + */ + this.stickers = new Collection(pack.stickers.map(s => [s.id, new Sticker(client, s)])); + + /** + * The name of the sticker pack + * @type {string} + */ + this.name = pack.name; + + /** + * The id of the pack's SKU + * @type {Snowflake} + */ + this.skuId = pack.sku_id; + + /** + * The id of a sticker in the pack which is shown as the pack's icon + * @type {?Snowflake} + */ + this.coverStickerId = pack.cover_sticker_id ?? null; + + /** + * The description of the sticker pack + * @type {string} + */ + this.description = pack.description; + + /** + * The id of the sticker pack's banner image + * @type {?Snowflake} + */ + this.bannerId = pack.banner_asset_id ?? null; + } + + /** + * The timestamp the sticker was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the sticker was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The sticker which is shown as the pack's icon + * @type {?Sticker} + * @readonly + */ + get coverSticker() { + return this.coverStickerId && this.stickers.get(this.coverStickerId); + } + + /** + * The URL to this sticker pack's banner. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + bannerURL(options = {}) { + return this.bannerId && this.client.rest.cdn.stickerPackBanner(this.bannerId, options); + } +} + +module.exports = StickerPack; diff --git a/node_modules/discord.js/src/structures/StringSelectMenuBuilder.js b/node_modules/discord.js/src/structures/StringSelectMenuBuilder.js new file mode 100644 index 0000000..ac555e7 --- /dev/null +++ b/node_modules/discord.js/src/structures/StringSelectMenuBuilder.js @@ -0,0 +1,79 @@ +'use strict'; + +const { SelectMenuBuilder: BuildersSelectMenu, normalizeArray } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); +const { resolvePartialEmoji } = require('../util/Util'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersSelectMenu} + */ +class StringSelectMenuBuilder extends BuildersSelectMenu { + constructor({ options, ...data } = {}) { + super( + toSnakeCase({ + ...data, + options: options?.map(({ emoji, ...option }) => ({ + ...option, + emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji, + })), + }), + ); + } + + /** + * Normalizes a select menu option emoji + * @param {SelectMenuOptionData|APISelectMenuOption} selectMenuOption The option to normalize + * @returns {SelectMenuOptionBuilder|APISelectMenuOption} + * @private + */ + static normalizeEmoji(selectMenuOption) { + if (isJSONEncodable(selectMenuOption)) { + return selectMenuOption; + } + + const { emoji, ...option } = selectMenuOption; + return { + ...option, + emoji: typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji, + }; + } + + /** + * Adds options to this select menu + * @param {RestOrArray<APISelectMenuOption>} options The options to add to this select menu + * @returns {StringSelectMenuBuilder} + */ + addOptions(...options) { + return super.addOptions(normalizeArray(options).map(option => StringSelectMenuBuilder.normalizeEmoji(option))); + } + + /** + * Sets the options on this select menu + * @param {RestOrArray<APISelectMenuOption>} options The options to set on this select menu + * @returns {StringSelectMenuBuilder} + */ + setOptions(...options) { + return super.setOptions(normalizeArray(options).map(option => StringSelectMenuBuilder.normalizeEmoji(option))); + } + + /** + * Creates a new select menu builder from json data + * @param {StringSelectMenuBuilder|StringSelectMenuComponent|APIStringSelectComponent} other The other data + * @returns {StringSelectMenuBuilder} + */ + static from(other) { + if (isJSONEncodable(other)) { + return new this(other.toJSON()); + } + return new this(other); + } +} + +module.exports = StringSelectMenuBuilder; + +/** + * @external BuildersSelectMenu + * @see {@link https://discord.js.org/docs/packages/builders/stable/StringSelectMenuBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/StringSelectMenuComponent.js b/node_modules/discord.js/src/structures/StringSelectMenuComponent.js new file mode 100644 index 0000000..e008ae5 --- /dev/null +++ b/node_modules/discord.js/src/structures/StringSelectMenuComponent.js @@ -0,0 +1,20 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a string select menu component + * @extends {BaseSelectMenuComponent} + */ +class StringSelectMenuComponent extends BaseSelectMenuComponent { + /** + * The options in this select menu + * @type {APISelectMenuOption[]} + * @readonly + */ + get options() { + return this.data.options; + } +} + +module.exports = StringSelectMenuComponent; diff --git a/node_modules/discord.js/src/structures/StringSelectMenuInteraction.js b/node_modules/discord.js/src/structures/StringSelectMenuInteraction.js new file mode 100644 index 0000000..1db8c28 --- /dev/null +++ b/node_modules/discord.js/src/structures/StringSelectMenuInteraction.js @@ -0,0 +1,21 @@ +'use strict'; + +const MessageComponentInteraction = require('./MessageComponentInteraction'); + +/** + * Represents a {@link ComponentType.StringSelect} select menu interaction. + * @extends {MessageComponentInteraction} + */ +class StringSelectMenuInteraction extends MessageComponentInteraction { + constructor(client, data) { + super(client, data); + + /** + * The values selected + * @type {string[]} + */ + this.values = data.data.values ?? []; + } +} + +module.exports = StringSelectMenuInteraction; diff --git a/node_modules/discord.js/src/structures/StringSelectMenuOptionBuilder.js b/node_modules/discord.js/src/structures/StringSelectMenuOptionBuilder.js new file mode 100644 index 0000000..cc85750 --- /dev/null +++ b/node_modules/discord.js/src/structures/StringSelectMenuOptionBuilder.js @@ -0,0 +1,49 @@ +'use strict'; + +const { SelectMenuOptionBuilder: BuildersSelectMenuOption } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); +const { resolvePartialEmoji } = require('../util/Util'); + +/** + * Represents a select menu option builder. + * @extends {BuildersSelectMenuOption} + */ +class StringSelectMenuOptionBuilder extends BuildersSelectMenuOption { + constructor({ emoji, ...data } = {}) { + super( + toSnakeCase({ + ...data, + emoji: emoji && typeof emoji === 'string' ? resolvePartialEmoji(emoji) : emoji, + }), + ); + } + + /** + * Sets the emoji to display on this option + * @param {ComponentEmojiResolvable} emoji The emoji to display on this option + * @returns {StringSelectMenuOptionBuilder} + */ + setEmoji(emoji) { + if (typeof emoji === 'string') { + return super.setEmoji(resolvePartialEmoji(emoji)); + } + return super.setEmoji(emoji); + } + + /** + * Creates a new select menu option builder from JSON data + * @param {StringSelectMenuOptionBuilder|APISelectMenuOption} other The other data + * @returns {StringSelectMenuOptionBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } +} + +module.exports = StringSelectMenuOptionBuilder; + +/** + * @external BuildersSelectMenuOption + * @see {@link https://discord.js.org/docs/packages/builders/stable/StringSelectMenuOptionBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/Team.js b/node_modules/discord.js/src/structures/Team.js new file mode 100644 index 0000000..98eb199 --- /dev/null +++ b/node_modules/discord.js/src/structures/Team.js @@ -0,0 +1,117 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const Base = require('./Base'); +const TeamMember = require('./TeamMember'); + +/** + * Represents a Client OAuth2 Application Team. + * @extends {Base} + */ +class Team extends Base { + constructor(client, data) { + super(client); + this._patch(data); + } + + _patch(data) { + /** + * The Team's id + * @type {Snowflake} + */ + this.id = data.id; + + if ('name' in data) { + /** + * The name of the Team + * @type {string} + */ + this.name = data.name; + } + + if ('icon' in data) { + /** + * The Team's icon hash + * @type {?string} + */ + this.icon = data.icon; + } else { + this.icon ??= null; + } + + if ('owner_user_id' in data) { + /** + * The Team's owner id + * @type {?Snowflake} + */ + this.ownerId = data.owner_user_id; + } else { + this.ownerId ??= null; + } + /** + * The Team's members + * @type {Collection<Snowflake, TeamMember>} + */ + this.members = new Collection(); + + for (const memberData of data.members) { + const member = new TeamMember(this, memberData); + this.members.set(member.id, member); + } + } + + /** + * The owner of this team + * @type {?TeamMember} + * @readonly + */ + get owner() { + return this.members.get(this.ownerId) ?? null; + } + + /** + * The timestamp the team was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the team was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * A link to the team's icon. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + iconURL(options = {}) { + return this.icon && this.client.rest.cdn.teamIcon(this.id, this.icon, options); + } + + /** + * When concatenated with a string, this automatically returns the Team's name instead of the + * Team object. + * @returns {string} + * @example + * // Logs: Team name: My Team + * console.log(`Team name: ${team}`); + */ + toString() { + return this.name; + } + + toJSON() { + return super.toJSON({ createdTimestamp: true }); + } +} + +module.exports = Team; diff --git a/node_modules/discord.js/src/structures/TeamMember.js b/node_modules/discord.js/src/structures/TeamMember.js new file mode 100644 index 0000000..9270418 --- /dev/null +++ b/node_modules/discord.js/src/structures/TeamMember.js @@ -0,0 +1,70 @@ +'use strict'; + +const Base = require('./Base'); + +/** + * Represents a Client OAuth2 Application Team Member. + * @extends {Base} + */ +class TeamMember extends Base { + constructor(team, data) { + super(team.client); + + /** + * The Team this member is part of + * @type {Team} + */ + this.team = team; + + this._patch(data); + } + + _patch(data) { + if ('permissions' in data) { + /** + * The permissions this Team Member has with regard to the team + * @type {string[]} + */ + this.permissions = data.permissions; + } + + if ('membership_state' in data) { + /** + * The permissions this Team Member has with regard to the team + * @type {TeamMemberMembershipState} + */ + this.membershipState = data.membership_state; + } + + if ('user' in data) { + /** + * The user for this Team Member + * @type {User} + */ + this.user = this.client.users._add(data.user); + } + } + + /** + * The Team Member's id + * @type {Snowflake} + * @readonly + */ + get id() { + return this.user.id; + } + + /** + * When concatenated with a string, this automatically returns the team member's mention instead of the + * TeamMember object. + * @returns {string} + * @example + * // Logs: Team Member's mention: <@123456789012345678> + * console.log(`Team Member's mention: ${teamMember}`); + */ + toString() { + return this.user.toString(); + } +} + +module.exports = TeamMember; diff --git a/node_modules/discord.js/src/structures/TextChannel.js b/node_modules/discord.js/src/structures/TextChannel.js new file mode 100644 index 0000000..66cc8c4 --- /dev/null +++ b/node_modules/discord.js/src/structures/TextChannel.js @@ -0,0 +1,33 @@ +'use strict'; + +const BaseGuildTextChannel = require('./BaseGuildTextChannel'); + +/** + * Represents a guild text channel on Discord. + * @extends {BaseGuildTextChannel} + */ +class TextChannel extends BaseGuildTextChannel { + _patch(data) { + super._patch(data); + + if ('rate_limit_per_user' in data) { + /** + * The rate limit per user (slowmode) for this channel in seconds + * @type {number} + */ + this.rateLimitPerUser = data.rate_limit_per_user; + } + } + + /** + * Sets the rate limit per user (slowmode) for this channel. + * @param {number} rateLimitPerUser The new rate limit in seconds + * @param {string} [reason] Reason for changing the channel's rate limit + * @returns {Promise<TextChannel>} + */ + setRateLimitPerUser(rateLimitPerUser, reason) { + return this.edit({ rateLimitPerUser, reason }); + } +} + +module.exports = TextChannel; diff --git a/node_modules/discord.js/src/structures/TextInputBuilder.js b/node_modules/discord.js/src/structures/TextInputBuilder.js new file mode 100644 index 0000000..9382154 --- /dev/null +++ b/node_modules/discord.js/src/structures/TextInputBuilder.js @@ -0,0 +1,31 @@ +'use strict'; + +const { TextInputBuilder: BuildersTextInput } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Represents a text input builder. + * @extends {BuildersTextInput} + */ +class TextInputBuilder extends BuildersTextInput { + constructor(data) { + super(toSnakeCase(data)); + } + + /** + * Creates a new text input builder from JSON data + * @param {TextInputBuilder|TextInputComponent|APITextInputComponent} other The other data + * @returns {TextInputBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } +} + +module.exports = TextInputBuilder; + +/** + * @external BuildersTextInput + * @see {@link https://discord.js.org/docs/packages/builders/stable/TextInputBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/TextInputComponent.js b/node_modules/discord.js/src/structures/TextInputComponent.js new file mode 100644 index 0000000..3cc3115 --- /dev/null +++ b/node_modules/discord.js/src/structures/TextInputComponent.js @@ -0,0 +1,29 @@ +'use strict'; + +const Component = require('./Component'); + +/** + * Represents a text input component. + * @extends {Component} + */ +class TextInputComponent extends Component { + /** + * The custom id of this text input + * @type {string} + * @readonly + */ + get customId() { + return this.data.custom_id; + } + + /** + * The value for this text input + * @type {string} + * @readonly + */ + get value() { + return this.data.value; + } +} + +module.exports = TextInputComponent; diff --git a/node_modules/discord.js/src/structures/ThreadChannel.js b/node_modules/discord.js/src/structures/ThreadChannel.js new file mode 100644 index 0000000..96b4087 --- /dev/null +++ b/node_modules/discord.js/src/structures/ThreadChannel.js @@ -0,0 +1,606 @@ +'use strict'; + +const { ChannelType, PermissionFlagsBits, Routes, ChannelFlags } = require('discord-api-types/v10'); +const { BaseChannel } = require('./BaseChannel'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const { DiscordjsRangeError, ErrorCodes } = require('../errors'); +const GuildMessageManager = require('../managers/GuildMessageManager'); +const ThreadMemberManager = require('../managers/ThreadMemberManager'); +const ChannelFlagsBitField = require('../util/ChannelFlagsBitField'); + +/** + * Represents a thread channel on Discord. + * @extends {BaseChannel} + * @implements {TextBasedChannel} + */ +class ThreadChannel extends BaseChannel { + constructor(guild, data, client) { + super(guild?.client ?? client, data, false); + + /** + * The guild the thread is in + * @type {Guild} + */ + this.guild = guild; + + /** + * The id of the guild the channel is in + * @type {Snowflake} + */ + this.guildId = guild?.id ?? data.guild_id; + + /** + * A manager of the messages sent to this thread + * @type {GuildMessageManager} + */ + this.messages = new GuildMessageManager(this); + + /** + * A manager of the members that are part of this thread + * @type {ThreadMemberManager} + */ + this.members = new ThreadMemberManager(this); + if (data) this._patch(data); + } + + _patch(data) { + super._patch(data); + + if ('message' in data) this.messages._add(data.message); + + if ('name' in data) { + /** + * The name of the thread + * @type {string} + */ + this.name = data.name; + } + + if ('guild_id' in data) { + this.guildId = data.guild_id; + } + + if ('parent_id' in data) { + /** + * The id of the parent channel of this thread + * @type {?Snowflake} + */ + this.parentId = data.parent_id; + } else { + this.parentId ??= null; + } + + if ('thread_metadata' in data) { + /** + * Whether the thread is locked + * @type {?boolean} + */ + this.locked = data.thread_metadata.locked ?? false; + + /** + * Whether members without the {@link PermissionFlagsBits.ManageThreads} permission + * can invite other members to this thread. + * <info>This property is always `null` in public threads.</info> + * @type {?boolean} + */ + this.invitable = this.type === ChannelType.PrivateThread ? data.thread_metadata.invitable ?? false : null; + + /** + * Whether the thread is archived + * @type {?boolean} + */ + this.archived = data.thread_metadata.archived; + + /** + * The amount of time (in minutes) after which the thread will automatically archive in case of no recent activity + * @type {?ThreadAutoArchiveDuration} + */ + this.autoArchiveDuration = data.thread_metadata.auto_archive_duration; + + /** + * The timestamp when the thread's archive status was last changed + * <info>If the thread was never archived or unarchived, this is the timestamp at which the thread was + * created</info> + * @type {?number} + */ + this.archiveTimestamp = Date.parse(data.thread_metadata.archive_timestamp); + + if ('create_timestamp' in data.thread_metadata) { + // Note: this is needed because we can't assign directly to getters + this._createdTimestamp = Date.parse(data.thread_metadata.create_timestamp); + } + } else { + this.locked ??= null; + this.archived ??= null; + this.autoArchiveDuration ??= null; + this.archiveTimestamp ??= null; + this.invitable ??= null; + } + + this._createdTimestamp ??= this.type === ChannelType.PrivateThread ? super.createdTimestamp : null; + + if ('owner_id' in data) { + /** + * The id of the member who created this thread + * @type {?Snowflake} + */ + this.ownerId = data.owner_id; + } else { + this.ownerId ??= null; + } + + if ('last_message_id' in data) { + /** + * The last message id sent in this thread, if one was sent + * @type {?Snowflake} + */ + this.lastMessageId = data.last_message_id; + } else { + this.lastMessageId ??= null; + } + + if ('last_pin_timestamp' in data) { + /** + * The timestamp when the last pinned message was pinned, if there was one + * @type {?number} + */ + this.lastPinTimestamp = data.last_pin_timestamp ? Date.parse(data.last_pin_timestamp) : null; + } else { + this.lastPinTimestamp ??= null; + } + + if ('rate_limit_per_user' in data) { + /** + * The rate limit per user (slowmode) for this thread in seconds + * @type {?number} + */ + this.rateLimitPerUser = data.rate_limit_per_user ?? 0; + } else { + this.rateLimitPerUser ??= null; + } + + if ('message_count' in data) { + /** + * The approximate count of messages in this thread + * <info>Threads created before July 1, 2022 may have an inaccurate count. + * If you need an approximate value higher than that, use `ThreadChannel#messages.cache.size`</info> + * @type {?number} + */ + this.messageCount = data.message_count; + } else { + this.messageCount ??= null; + } + + if ('member_count' in data) { + /** + * The approximate count of users in this thread + * <info>This stops counting at 50. If you need an approximate value higher than that, use + * `ThreadChannel#members.cache.size`</info> + * @type {?number} + */ + this.memberCount = data.member_count; + } else { + this.memberCount ??= null; + } + + if ('total_message_sent' in data) { + /** + * The number of messages ever sent in a thread, similar to {@link ThreadChannel#messageCount} except it + * will not decrement whenever a message is deleted + * @type {?number} + */ + this.totalMessageSent = data.total_message_sent; + } else { + this.totalMessageSent ??= null; + } + + if (data.member && this.client.user) this.members._add({ user_id: this.client.user.id, ...data.member }); + if (data.messages) for (const message of data.messages) this.messages._add(message); + + if ('applied_tags' in data) { + /** + * The tags applied to this thread + * @type {Snowflake[]} + */ + this.appliedTags = data.applied_tags; + } else { + this.appliedTags ??= []; + } + } + + /** + * The timestamp when this thread was created. This isn't available for threads + * created before 2022-01-09 + * @type {?number} + * @readonly + */ + get createdTimestamp() { + return this._createdTimestamp; + } + + /** + * A collection of associated guild member objects of this thread's members + * @type {Collection<Snowflake, GuildMember>} + * @readonly + */ + get guildMembers() { + return this.members.cache.mapValues(member => member.guildMember); + } + + /** + * The time at which this thread's archive status was last changed + * <info>If the thread was never archived or unarchived, this is the time at which the thread was created</info> + * @type {?Date} + * @readonly + */ + get archivedAt() { + return this.archiveTimestamp && new Date(this.archiveTimestamp); + } + + /** + * The time the thread was created at + * @type {?Date} + * @readonly + */ + get createdAt() { + return this.createdTimestamp && new Date(this.createdTimestamp); + } + + /** + * The parent channel of this thread + * @type {?(NewsChannel|TextChannel|ForumChannel)} + * @readonly + */ + get parent() { + return this.guild.channels.resolve(this.parentId); + } + + /** + * Makes the client user join the thread. + * @returns {Promise<ThreadChannel>} + */ + async join() { + await this.members.add('@me'); + return this; + } + + /** + * Makes the client user leave the thread. + * @returns {Promise<ThreadChannel>} + */ + async leave() { + await this.members.remove('@me'); + return this; + } + + /** + * Gets the overall set of permissions for a member or role in this thread's parent channel, taking overwrites into + * account. + * @param {GuildMemberResolvable|RoleResolvable} memberOrRole The member or role to obtain the overall permissions for + * @param {boolean} [checkAdmin=true] Whether having the {@link PermissionFlagsBits.Administrator} permission + * will return all permissions + * @returns {?Readonly<PermissionsBitField>} + */ + permissionsFor(memberOrRole, checkAdmin) { + return this.parent?.permissionsFor(memberOrRole, checkAdmin) ?? null; + } + + /** + * Fetches the owner of this thread. If the thread member object isn't needed, + * use {@link ThreadChannel#ownerId} instead. + * @param {BaseFetchOptions} [options] The options for fetching the member + * @returns {Promise<?ThreadMember>} + */ + async fetchOwner({ cache = true, force = false } = {}) { + if (!force) { + const existing = this.members.cache.get(this.ownerId); + if (existing) return existing; + } + + // We cannot fetch a single thread member, as of this commit's date, Discord API responds with 405 + const members = await this.members.fetch({ cache }); + return members.get(this.ownerId) ?? null; + } + + /** + * Fetches the message that started this thread, if any. + * <info>The `Promise` will reject if the original message in a forum post is deleted + * or when the original message in the parent channel is deleted. + * If you just need the id of that message, use {@link ThreadChannel#id} instead.</info> + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise<Message<true>|null>} + */ + // eslint-disable-next-line require-await + async fetchStarterMessage(options) { + const channel = this.parent?.type === ChannelType.GuildForum ? this : this.parent; + return channel?.messages.fetch({ message: this.id, ...options }) ?? null; + } + + /** + * The options used to edit a thread channel + * @typedef {Object} ThreadEditOptions + * @property {string} [name] The new name for the thread + * @property {boolean} [archived] Whether the thread is archived + * @property {ThreadAutoArchiveDuration} [autoArchiveDuration] The amount of time after which the thread + * should automatically archive in case of no recent activity + * @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the thread in seconds + * @property {boolean} [locked] Whether the thread is locked + * @property {boolean} [invitable] Whether non-moderators can add other non-moderators to a thread + * <info>Can only be edited on {@link ChannelType.PrivateThread}</info> + * @property {Snowflake[]} [appliedTags] The tags to apply to the thread + * @property {ChannelFlagsResolvable} [flags] The flags to set on the channel + * @property {string} [reason] Reason for editing the thread + */ + + /** + * Edits this thread. + * @param {ThreadEditOptions} options The options to provide + * @returns {Promise<ThreadChannel>} + * @example + * // Edit a thread + * thread.edit({ name: 'new-thread' }) + * .then(editedThread => console.log(editedThread)) + * .catch(console.error); + */ + async edit(options) { + const newData = await this.client.rest.patch(Routes.channel(this.id), { + body: { + name: (options.name ?? this.name).trim(), + archived: options.archived, + auto_archive_duration: options.autoArchiveDuration, + rate_limit_per_user: options.rateLimitPerUser, + locked: options.locked, + invitable: this.type === ChannelType.PrivateThread ? options.invitable : undefined, + applied_tags: options.appliedTags, + flags: 'flags' in options ? ChannelFlagsBitField.resolve(options.flags) : undefined, + }, + reason: options.reason, + }); + + return this.client.actions.ChannelUpdate.handle(newData).updated; + } + + /** + * Sets whether the thread is archived. + * @param {boolean} [archived=true] Whether the thread is archived + * @param {string} [reason] Reason for archiving or unarchiving + * @returns {Promise<ThreadChannel>} + * @example + * // Archive the thread + * thread.setArchived(true) + * .then(newThread => console.log(`Thread is now ${newThread.archived ? 'archived' : 'active'}`)) + * .catch(console.error); + */ + setArchived(archived = true, reason) { + return this.edit({ archived, reason }); + } + + /** + * Sets the duration after which the thread will automatically archive in case of no recent activity. + * @param {ThreadAutoArchiveDuration} autoArchiveDuration The amount of time after which the thread + * should automatically archive in case of no recent activity + * @param {string} [reason] Reason for changing the auto archive duration + * @returns {Promise<ThreadChannel>} + * @example + * // Set the thread's auto archive time to 1 hour + * thread.setAutoArchiveDuration(ThreadAutoArchiveDuration.OneHour) + * .then(newThread => { + * console.log(`Thread will now archive after ${newThread.autoArchiveDuration} minutes of inactivity`); + * }); + * .catch(console.error); + */ + setAutoArchiveDuration(autoArchiveDuration, reason) { + return this.edit({ autoArchiveDuration, reason }); + } + + /** + * Sets whether members without the {@link PermissionFlagsBits.ManageThreads} permission + * can invite other members to this thread. + * @param {boolean} [invitable=true] Whether non-moderators can invite non-moderators to this thread + * @param {string} [reason] Reason for changing invite + * @returns {Promise<ThreadChannel>} + */ + setInvitable(invitable = true, reason) { + if (this.type !== ChannelType.PrivateThread) { + return Promise.reject(new DiscordjsRangeError(ErrorCodes.ThreadInvitableType, this.type)); + } + return this.edit({ invitable, reason }); + } + + /** + * Sets whether the thread can be **unarchived** by anyone with the + * {@link PermissionFlagsBits.SendMessages} permission. When a thread is locked, only members with the + * {@link PermissionFlagsBits.ManageThreads} permission can unarchive it. + * @param {boolean} [locked=true] Whether the thread is locked + * @param {string} [reason] Reason for locking or unlocking the thread + * @returns {Promise<ThreadChannel>} + * @example + * // Set the thread to locked + * thread.setLocked(true) + * .then(newThread => console.log(`Thread is now ${newThread.locked ? 'locked' : 'unlocked'}`)) + * .catch(console.error); + */ + setLocked(locked = true, reason) { + return this.edit({ locked, reason }); + } + + /** + * Sets a new name for this thread. + * @param {string} name The new name for the thread + * @param {string} [reason] Reason for changing the thread's name + * @returns {Promise<ThreadChannel>} + * @example + * // Change the thread's name + * thread.setName('not_general') + * .then(newThread => console.log(`Thread's new name is ${newThread.name}`)) + * .catch(console.error); + */ + setName(name, reason) { + return this.edit({ name, reason }); + } + + /** + * Sets the rate limit per user (slowmode) for this thread. + * @param {number} rateLimitPerUser The new rate limit in seconds + * @param {string} [reason] Reason for changing the thread's rate limit + * @returns {Promise<ThreadChannel>} + */ + setRateLimitPerUser(rateLimitPerUser, reason) { + return this.edit({ rateLimitPerUser, reason }); + } + + /** + * Set the applied tags for this channel (only applicable to forum threads) + * @param {Snowflake[]} appliedTags The tags to set for this channel + * @param {string} [reason] Reason for changing the thread's applied tags + * @returns {Promise<ThreadChannel>} + */ + setAppliedTags(appliedTags, reason) { + return this.edit({ appliedTags, reason }); + } + + /** + * Pins this thread from the forum channel (only applicable to forum threads). + * @param {string} [reason] Reason for pinning + * @returns {Promise<ThreadChannel>} + */ + pin(reason) { + return this.edit({ flags: this.flags.add(ChannelFlags.Pinned), reason }); + } + + /** + * Unpins this thread from the forum channel (only applicable to forum threads). + * @param {string} [reason] Reason for unpinning + * @returns {Promise<ThreadChannel>} + */ + unpin(reason) { + return this.edit({ flags: this.flags.remove(ChannelFlags.Pinned), reason }); + } + + /** + * Whether the client user is a member of the thread. + * @type {boolean} + * @readonly + */ + get joined() { + return this.members.cache.has(this.client.user?.id); + } + + /** + * Whether the thread is editable by the client user (name, archived, autoArchiveDuration) + * @type {boolean} + * @readonly + */ + get editable() { + return ( + (this.ownerId === this.client.user.id && (this.type !== ChannelType.PrivateThread || this.joined)) || + this.manageable + ); + } + + /** + * Whether the thread is joinable by the client user + * @type {boolean} + * @readonly + */ + get joinable() { + return ( + !this.archived && + !this.joined && + this.permissionsFor(this.client.user)?.has( + this.type === ChannelType.PrivateThread ? PermissionFlagsBits.ManageThreads : PermissionFlagsBits.ViewChannel, + false, + ) + ); + } + + /** + * Whether the thread is manageable by the client user, for deleting or editing rateLimitPerUser or locked. + * @type {boolean} + * @readonly + */ + get manageable() { + const permissions = this.permissionsFor(this.client.user); + if (!permissions) return false; + // This flag allows managing even if timed out + if (permissions.has(PermissionFlagsBits.Administrator, false)) return true; + + return ( + this.guild.members.me.communicationDisabledUntilTimestamp < Date.now() && + permissions.has(PermissionFlagsBits.ManageThreads, false) + ); + } + + /** + * Whether the thread is viewable by the client user + * @type {boolean} + * @readonly + */ + get viewable() { + if (this.client.user.id === this.guild.ownerId) return true; + const permissions = this.permissionsFor(this.client.user); + if (!permissions) return false; + return permissions.has(PermissionFlagsBits.ViewChannel, false); + } + + /** + * Whether the client user can send messages in this thread + * @type {boolean} + * @readonly + */ + get sendable() { + const permissions = this.permissionsFor(this.client.user); + if (!permissions) return false; + // This flag allows sending even if timed out + if (permissions.has(PermissionFlagsBits.Administrator, false)) return true; + + return ( + !(this.archived && this.locked && !this.manageable) && + (this.type !== ChannelType.PrivateThread || this.joined || this.manageable) && + permissions.has(PermissionFlagsBits.SendMessagesInThreads, false) && + this.guild.members.me.communicationDisabledUntilTimestamp < Date.now() + ); + } + + /** + * Whether the thread is unarchivable by the client user + * @type {boolean} + * @readonly + */ + get unarchivable() { + return this.archived && this.sendable && (!this.locked || this.manageable); + } + + /** + * Deletes this thread. + * @param {string} [reason] Reason for deleting this thread + * @returns {Promise<ThreadChannel>} + * @example + * // Delete the thread + * thread.delete('cleaning out old threads') + * .then(deletedThread => console.log(deletedThread)) + * .catch(console.error); + */ + async delete(reason) { + await this.guild.channels.delete(this.id, reason); + return this; + } + + // These are here only for documentation purposes - they are implemented by TextBasedChannel + /* eslint-disable no-empty-function */ + get lastMessage() {} + get lastPinAt() {} + send() {} + sendTyping() {} + createMessageCollector() {} + awaitMessages() {} + createMessageComponentCollector() {} + awaitMessageComponent() {} + bulkDelete() {} + // Doesn't work on Thread channels; setRateLimitPerUser() {} + // Doesn't work on Thread channels; setNSFW() {} +} + +TextBasedChannel.applyToClass(ThreadChannel, true, ['fetchWebhooks', 'setRateLimitPerUser', 'setNSFW']); + +module.exports = ThreadChannel; diff --git a/node_modules/discord.js/src/structures/ThreadMember.js b/node_modules/discord.js/src/structures/ThreadMember.js new file mode 100644 index 0000000..fc79dd0 --- /dev/null +++ b/node_modules/discord.js/src/structures/ThreadMember.js @@ -0,0 +1,113 @@ +'use strict'; + +const Base = require('./Base'); +const ThreadMemberFlagsBitField = require('../util/ThreadMemberFlagsBitField'); + +/** + * Represents a Member for a Thread. + * @extends {Base} + */ +class ThreadMember extends Base { + constructor(thread, data, extra = {}) { + super(thread.client); + + /** + * The thread that this member is a part of + * @type {ThreadChannel} + */ + this.thread = thread; + + /** + * The timestamp the member last joined the thread at + * @type {?number} + */ + this.joinedTimestamp = null; + + /** + * The flags for this thread member. This will be `null` if partial. + * @type {?ThreadMemberFlagsBitField} + */ + this.flags = null; + + /** + * The id of the thread member + * @type {Snowflake} + */ + this.id = data.user_id; + + this._patch(data, extra); + } + + _patch(data, extra = {}) { + if ('join_timestamp' in data) this.joinedTimestamp = Date.parse(data.join_timestamp); + if ('flags' in data) this.flags = new ThreadMemberFlagsBitField(data.flags).freeze(); + + if ('member' in data) { + /** + * The guild member associated with this thread member. + * @type {?GuildMember} + * @private + */ + this.member = this.thread.guild.members._add(data.member, extra.cache); + } else { + this.member ??= null; + } + } + + /** + * Whether this thread member is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return this.flags === null; + } + + /** + * The guild member associated with this thread member + * @type {?GuildMember} + * @readonly + */ + get guildMember() { + return this.member ?? this.thread.guild.members.resolve(this.id); + } + + /** + * The last time this member joined the thread + * @type {?Date} + * @readonly + */ + get joinedAt() { + return this.joinedTimestamp && new Date(this.joinedTimestamp); + } + + /** + * The user associated with this thread member + * @type {?User} + * @readonly + */ + get user() { + return this.client.users.resolve(this.id); + } + + /** + * Whether the client user can manage this thread member + * @type {boolean} + * @readonly + */ + get manageable() { + return !this.thread.archived && this.thread.editable; + } + + /** + * Removes this member from the thread. + * @param {string} [reason] Reason for removing the member + * @returns {ThreadMember} + */ + async remove(reason) { + await this.thread.members.remove(this.id, reason); + return this; + } +} + +module.exports = ThreadMember; diff --git a/node_modules/discord.js/src/structures/Typing.js b/node_modules/discord.js/src/structures/Typing.js new file mode 100644 index 0000000..341d7ca --- /dev/null +++ b/node_modules/discord.js/src/structures/Typing.js @@ -0,0 +1,74 @@ +'use strict'; + +const Base = require('./Base'); + +/** + * Represents a typing state for a user in a channel. + * @extends {Base} + */ +class Typing extends Base { + constructor(channel, user, data) { + super(channel.client); + + /** + * The channel the status is from + * @type {TextBasedChannels} + */ + this.channel = channel; + + /** + * The user who is typing + * @type {User} + */ + this.user = user; + + this._patch(data); + } + + _patch(data) { + if ('timestamp' in data) { + /** + * The UNIX timestamp in milliseconds the user started typing at + * @type {number} + */ + this.startedTimestamp = data.timestamp * 1_000; + } + } + + /** + * Indicates whether the status is received from a guild. + * @returns {boolean} + */ + inGuild() { + return this.guild !== null; + } + + /** + * The time the user started typing at + * @type {Date} + * @readonly + */ + get startedAt() { + return new Date(this.startedTimestamp); + } + + /** + * The guild the status is from + * @type {?Guild} + * @readonly + */ + get guild() { + return this.channel.guild ?? null; + } + + /** + * The member who is typing + * @type {?GuildMember} + * @readonly + */ + get member() { + return this.guild?.members.resolve(this.user) ?? null; + } +} + +module.exports = Typing; diff --git a/node_modules/discord.js/src/structures/User.js b/node_modules/discord.js/src/structures/User.js new file mode 100644 index 0000000..4e38d2d --- /dev/null +++ b/node_modules/discord.js/src/structures/User.js @@ -0,0 +1,380 @@ +'use strict'; + +const { userMention } = require('@discordjs/builders'); +const { calculateUserDefaultAvatarIndex } = require('@discordjs/rest'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const Base = require('./Base'); +const TextBasedChannel = require('./interfaces/TextBasedChannel'); +const UserFlagsBitField = require('../util/UserFlagsBitField'); + +/** + * Represents a user on Discord. + * @implements {TextBasedChannel} + * @extends {Base} + */ +class User extends Base { + constructor(client, data) { + super(client); + + /** + * The user's id + * @type {Snowflake} + */ + this.id = data.id; + + this.bot = null; + + this.system = null; + + this.flags = null; + + this._patch(data); + } + + _patch(data) { + if ('username' in data) { + /** + * The username of the user + * @type {?string} + */ + this.username = data.username; + } else { + this.username ??= null; + } + + if ('global_name' in data) { + /** + * The global name of this user + * @type {?string} + */ + this.globalName = data.global_name; + } else { + this.globalName ??= null; + } + + if ('bot' in data) { + /** + * Whether or not the user is a bot + * @type {?boolean} + */ + this.bot = Boolean(data.bot); + } else if (!this.partial && typeof this.bot !== 'boolean') { + this.bot = false; + } + + if ('discriminator' in data) { + /** + * The discriminator of this user + * <info>`'0'`, or a 4-digit stringified number if they're using the legacy username system</info> + * @type {?string} + */ + this.discriminator = data.discriminator; + } else { + this.discriminator ??= null; + } + + if ('avatar' in data) { + /** + * The user avatar's hash + * @type {?string} + */ + this.avatar = data.avatar; + } else { + this.avatar ??= null; + } + + if ('banner' in data) { + /** + * The user banner's hash + * <info>The user must be force fetched for this property to be present or be updated</info> + * @type {?string} + */ + this.banner = data.banner; + } else if (this.banner !== null) { + this.banner ??= undefined; + } + + if ('accent_color' in data) { + /** + * The base 10 accent color of the user's banner + * <info>The user must be force fetched for this property to be present or be updated</info> + * @type {?number} + */ + this.accentColor = data.accent_color; + } else if (this.accentColor !== null) { + this.accentColor ??= undefined; + } + + if ('system' in data) { + /** + * Whether the user is an Official Discord System user (part of the urgent message system) + * @type {?boolean} + */ + this.system = Boolean(data.system); + } else if (!this.partial && typeof this.system !== 'boolean') { + this.system = false; + } + + if ('public_flags' in data) { + /** + * The flags for this user + * @type {?UserFlagsBitField} + */ + this.flags = new UserFlagsBitField(data.public_flags); + } + + if ('avatar_decoration' in data) { + /** + * The user avatar decoration's hash + * @type {?string} + */ + this.avatarDecoration = data.avatar_decoration; + } else { + this.avatarDecoration ??= null; + } + } + + /** + * Whether this User is a partial + * @type {boolean} + * @readonly + */ + get partial() { + return typeof this.username !== 'string'; + } + + /** + * The timestamp the user was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the user was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * A link to the user's avatar. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + avatarURL(options = {}) { + return this.avatar && this.client.rest.cdn.avatar(this.id, this.avatar, options); + } + + /** + * A link to the user's avatar decoration. + * @param {BaseImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + avatarDecorationURL(options = {}) { + return this.avatarDecoration && this.client.rest.cdn.avatarDecoration(this.id, this.avatarDecoration, options); + } + + /** + * A link to the user's default avatar + * @type {string} + * @readonly + */ + get defaultAvatarURL() { + const index = this.discriminator === '0' ? calculateUserDefaultAvatarIndex(this.id) : this.discriminator % 5; + return this.client.rest.cdn.defaultAvatar(index); + } + + /** + * A link to the user's avatar if they have one. + * Otherwise a link to their default avatar will be returned. + * @param {ImageURLOptions} [options={}] Options for the Image URL + * @returns {string} + */ + displayAvatarURL(options) { + return this.avatarURL(options) ?? this.defaultAvatarURL; + } + + /** + * The hexadecimal version of the user accent color, with a leading hash + * <info>The user must be force fetched for this property to be present</info> + * @type {?string} + * @readonly + */ + get hexAccentColor() { + if (typeof this.accentColor !== 'number') return this.accentColor; + return `#${this.accentColor.toString(16).padStart(6, '0')}`; + } + + /** + * A link to the user's banner. See {@link User#banner} for more info + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + bannerURL(options = {}) { + return this.banner && this.client.rest.cdn.banner(this.id, this.banner, options); + } + + /** + * The tag of this user + * <info>This user's username, or their legacy tag (e.g. `hydrabolt#0001`) + * if they're using the legacy username system</info> + * @type {?string} + * @readonly + */ + get tag() { + return typeof this.username === 'string' + ? this.discriminator === '0' + ? this.username + : `${this.username}#${this.discriminator}` + : null; + } + + /** + * The global name of this user, or their username if they don't have one + * @type {?string} + * @readonly + */ + get displayName() { + return this.globalName ?? this.username; + } + + /** + * The DM between the client's user and this user + * @type {?DMChannel} + * @readonly + */ + get dmChannel() { + return this.client.users.dmChannel(this.id); + } + + /** + * Creates a DM channel between the client and the user. + * @param {boolean} [force=false] Whether to skip the cache check and request the API + * @returns {Promise<DMChannel>} + */ + createDM(force = false) { + return this.client.users.createDM(this.id, { force }); + } + + /** + * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful. + * @returns {Promise<DMChannel>} + */ + deleteDM() { + return this.client.users.deleteDM(this.id); + } + + /** + * Checks if the user is equal to another. + * It compares id, username, discriminator, avatar, banner, accent color, and bot flags. + * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties. + * @param {User} user User to compare with + * @returns {boolean} + */ + equals(user) { + return ( + user && + this.id === user.id && + this.username === user.username && + this.discriminator === user.discriminator && + this.globalName === user.globalName && + this.avatar === user.avatar && + this.flags?.bitfield === user.flags?.bitfield && + this.banner === user.banner && + this.accentColor === user.accentColor + ); + } + + /** + * Compares the user with an API user object + * @param {APIUser} user The API user object to compare + * @returns {boolean} + * @private + */ + _equals(user) { + return ( + user && + this.id === user.id && + this.username === user.username && + this.discriminator === user.discriminator && + this.globalName === user.global_name && + this.avatar === user.avatar && + this.flags?.bitfield === user.public_flags && + ('banner' in user ? this.banner === user.banner : true) && + ('accent_color' in user ? this.accentColor === user.accent_color : true) + ); + } + + /** + * Fetches this user's flags. + * @param {boolean} [force=false] Whether to skip the cache check and request the API + * @returns {Promise<UserFlagsBitField>} + */ + fetchFlags(force = false) { + return this.client.users.fetchFlags(this.id, { force }); + } + + /** + * Fetches this user. + * @param {boolean} [force=true] Whether to skip the cache check and request the API + * @returns {Promise<User>} + */ + fetch(force = true) { + return this.client.users.fetch(this.id, { force }); + } + + /** + * When concatenated with a string, this automatically returns the user's mention instead of the User object. + * @returns {string} + * @example + * // Logs: Hello from <@123456789012345678>! + * console.log(`Hello from ${user}!`); + */ + toString() { + return userMention(this.id); + } + + toJSON(...props) { + const json = super.toJSON( + { + createdTimestamp: true, + defaultAvatarURL: true, + hexAccentColor: true, + tag: true, + }, + ...props, + ); + json.avatarURL = this.avatarURL(); + json.displayAvatarURL = this.displayAvatarURL(); + json.bannerURL = this.banner ? this.bannerURL() : this.banner; + return json; + } +} + +/** + * Sends a message to this user. + * @method send + * @memberof User + * @instance + * @param {string|MessagePayload|MessageCreateOptions} options The options to provide + * @returns {Promise<Message>} + * @example + * // Send a direct message + * user.send('Hello!') + * .then(message => console.log(`Sent message: ${message.content} to ${user.tag}`)) + * .catch(console.error); + */ + +TextBasedChannel.applyToClass(User); + +module.exports = User; + +/** + * @external APIUser + * @see {@link https://discord.com/developers/docs/resources/user#user-object} + */ diff --git a/node_modules/discord.js/src/structures/UserContextMenuCommandInteraction.js b/node_modules/discord.js/src/structures/UserContextMenuCommandInteraction.js new file mode 100644 index 0000000..2e9dc7c --- /dev/null +++ b/node_modules/discord.js/src/structures/UserContextMenuCommandInteraction.js @@ -0,0 +1,29 @@ +'use strict'; + +const ContextMenuCommandInteraction = require('./ContextMenuCommandInteraction'); + +/** + * Represents a user context menu interaction. + * @extends {ContextMenuCommandInteraction} + */ +class UserContextMenuCommandInteraction extends ContextMenuCommandInteraction { + /** + * The target user from this interaction + * @type {User} + * @readonly + */ + get targetUser() { + return this.options.getUser('user'); + } + + /** + * The target member from this interaction + * @type {?(GuildMember|APIGuildMember)} + * @readonly + */ + get targetMember() { + return this.options.getMember('user'); + } +} + +module.exports = UserContextMenuCommandInteraction; diff --git a/node_modules/discord.js/src/structures/UserSelectMenuBuilder.js b/node_modules/discord.js/src/structures/UserSelectMenuBuilder.js new file mode 100644 index 0000000..61bdbb8 --- /dev/null +++ b/node_modules/discord.js/src/structures/UserSelectMenuBuilder.js @@ -0,0 +1,31 @@ +'use strict'; + +const { UserSelectMenuBuilder: BuildersUserSelectMenu } = require('@discordjs/builders'); +const { isJSONEncodable } = require('@discordjs/util'); +const { toSnakeCase } = require('../util/Transformers'); + +/** + * Class used to build select menu components to be sent through the API + * @extends {BuildersUserSelectMenu} + */ +class UserSelectMenuBuilder extends BuildersUserSelectMenu { + constructor(data = {}) { + super(toSnakeCase(data)); + } + + /** + * Creates a new select menu builder from JSON data + * @param {UserSelectMenuBuilder|UserSelectMenuComponent|APIUserSelectComponent} other The other data + * @returns {UserSelectMenuBuilder} + */ + static from(other) { + return new this(isJSONEncodable(other) ? other.toJSON() : other); + } +} + +module.exports = UserSelectMenuBuilder; + +/** + * @external BuildersUserSelectMenu + * @see {@link https://discord.js.org/docs/packages/builders/stable/UserSelectMenuBuilder:Class} + */ diff --git a/node_modules/discord.js/src/structures/UserSelectMenuComponent.js b/node_modules/discord.js/src/structures/UserSelectMenuComponent.js new file mode 100644 index 0000000..0acacdf --- /dev/null +++ b/node_modules/discord.js/src/structures/UserSelectMenuComponent.js @@ -0,0 +1,11 @@ +'use strict'; + +const BaseSelectMenuComponent = require('./BaseSelectMenuComponent'); + +/** + * Represents a user select menu component + * @extends {BaseSelectMenuComponent} + */ +class UserSelectMenuComponent extends BaseSelectMenuComponent {} + +module.exports = UserSelectMenuComponent; diff --git a/node_modules/discord.js/src/structures/UserSelectMenuInteraction.js b/node_modules/discord.js/src/structures/UserSelectMenuInteraction.js new file mode 100644 index 0000000..5e23239 --- /dev/null +++ b/node_modules/discord.js/src/structures/UserSelectMenuInteraction.js @@ -0,0 +1,51 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const MessageComponentInteraction = require('./MessageComponentInteraction'); +const Events = require('../util/Events'); + +/** + * Represents a {@link ComponentType.UserSelect} select menu interaction. + * @extends {MessageComponentInteraction} + */ +class UserSelectMenuInteraction extends MessageComponentInteraction { + constructor(client, data) { + super(client, data); + const { resolved, values } = data.data; + + /** + * An array of the selected user ids + * @type {Snowflake[]} + */ + this.values = values ?? []; + + /** + * Collection of the selected users + * @type {Collection<Snowflake, User>} + */ + this.users = new Collection(); + + /** + * Collection of the selected members + * @type {Collection<Snowflake, GuildMember|APIGuildMember>} + */ + this.members = new Collection(); + + for (const user of Object.values(resolved?.users ?? {})) { + this.users.set(user.id, this.client.users._add(user)); + } + + for (const [id, member] of Object.entries(resolved?.members ?? {})) { + const user = resolved.users[id]; + + if (!user) { + this.client.emit(Events.Debug, `[UserSelectMenuInteraction] Received a member without a user, skipping ${id}`); + continue; + } + + this.members.set(id, this.guild?.members._add({ user, ...member }) ?? { user, ...member }); + } + } +} + +module.exports = UserSelectMenuInteraction; diff --git a/node_modules/discord.js/src/structures/VoiceChannel.js b/node_modules/discord.js/src/structures/VoiceChannel.js new file mode 100644 index 0000000..d4f33ca --- /dev/null +++ b/node_modules/discord.js/src/structures/VoiceChannel.js @@ -0,0 +1,96 @@ +'use strict'; + +const { PermissionFlagsBits } = require('discord-api-types/v10'); +const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel'); + +/** + * Represents a guild voice channel on Discord. + * @extends {BaseGuildVoiceChannel} + */ +class VoiceChannel extends BaseGuildVoiceChannel { + /** + * Whether the channel is joinable by the client user + * @type {boolean} + * @readonly + */ + get joinable() { + if (!super.joinable) return false; + if (this.full && !this.permissionsFor(this.client.user).has(PermissionFlagsBits.MoveMembers, false)) return false; + return true; + } + + /** + * Checks if the client has permission to send audio to the voice channel + * @type {boolean} + * @readonly + */ + get speakable() { + const permissions = this.permissionsFor(this.client.user); + if (!permissions) return false; + // This flag allows speaking even if timed out + if (permissions.has(PermissionFlagsBits.Administrator, false)) return true; + + return ( + this.guild.members.me.communicationDisabledUntilTimestamp < Date.now() && + permissions.has(PermissionFlagsBits.Speak, false) + ); + } +} + +/** + * Sets the bitrate of the channel. + * @method setBitrate + * @memberof VoiceChannel + * @instance + * @param {number} bitrate The new bitrate + * @param {string} [reason] Reason for changing the channel's bitrate + * @returns {Promise<VoiceChannel>} + * @example + * // Set the bitrate of a voice channel + * voiceChannel.setBitrate(48_000) + * .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`)) + * .catch(console.error); + */ + +/** + * Sets the RTC region of the channel. + * @method setRTCRegion + * @memberof VoiceChannel + * @instance + * @param {?string} rtcRegion The new region of the channel. Set to `null` to remove a specific region for the channel + * @param {string} [reason] The reason for modifying this region. + * @returns {Promise<VoiceChannel>} + * @example + * // Set the RTC region to sydney + * voiceChannel.setRTCRegion('sydney'); + * @example + * // Remove a fixed region for this channel - let Discord decide automatically + * voiceChannel.setRTCRegion(null, 'We want to let Discord decide.'); + */ + +/** + * Sets the user limit of the channel. + * @method setUserLimit + * @memberof VoiceChannel + * @instance + * @param {number} userLimit The new user limit + * @param {string} [reason] Reason for changing the user limit + * @returns {Promise<VoiceChannel>} + * @example + * // Set the user limit of a voice channel + * voiceChannel.setUserLimit(42) + * .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`)) + * .catch(console.error); + */ + +/** + * Sets the camera video quality mode of the channel. + * @method setVideoQualityMode + * @memberof VoiceChannel + * @instance + * @param {VideoQualityMode} videoQualityMode The new camera video quality mode. + * @param {string} [reason] Reason for changing the camera video quality mode. + * @returns {Promise<VoiceChannel>} + */ + +module.exports = VoiceChannel; diff --git a/node_modules/discord.js/src/structures/VoiceRegion.js b/node_modules/discord.js/src/structures/VoiceRegion.js new file mode 100644 index 0000000..1f5652a --- /dev/null +++ b/node_modules/discord.js/src/structures/VoiceRegion.js @@ -0,0 +1,46 @@ +'use strict'; + +const { flatten } = require('../util/Util'); + +/** + * Represents a Discord voice region for guilds. + */ +class VoiceRegion { + constructor(data) { + /** + * The region's id + * @type {string} + */ + this.id = data.id; + + /** + * Name of the region + * @type {string} + */ + this.name = data.name; + + /** + * Whether the region is deprecated + * @type {boolean} + */ + this.deprecated = data.deprecated; + + /** + * Whether the region is optimal + * @type {boolean} + */ + this.optimal = data.optimal; + + /** + * Whether the region is custom + * @type {boolean} + */ + this.custom = data.custom; + } + + toJSON() { + return flatten(this); + } +} + +module.exports = VoiceRegion; diff --git a/node_modules/discord.js/src/structures/VoiceState.js b/node_modules/discord.js/src/structures/VoiceState.js new file mode 100644 index 0000000..ae510f2 --- /dev/null +++ b/node_modules/discord.js/src/structures/VoiceState.js @@ -0,0 +1,303 @@ +'use strict'; + +const { ChannelType, Routes } = require('discord-api-types/v10'); +const Base = require('./Base'); +const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); + +/** + * Represents the voice state for a Guild Member. + * @extends {Base} + */ +class VoiceState extends Base { + constructor(guild, data) { + super(guild.client); + /** + * The guild of this voice state + * @type {Guild} + */ + this.guild = guild; + /** + * The id of the member of this voice state + * @type {Snowflake} + */ + this.id = data.user_id; + this._patch(data); + } + + _patch(data) { + if ('deaf' in data) { + /** + * Whether this member is deafened server-wide + * @type {?boolean} + */ + this.serverDeaf = data.deaf; + } else { + this.serverDeaf ??= null; + } + + if ('mute' in data) { + /** + * Whether this member is muted server-wide + * @type {?boolean} + */ + this.serverMute = data.mute; + } else { + this.serverMute ??= null; + } + + if ('self_deaf' in data) { + /** + * Whether this member is self-deafened + * @type {?boolean} + */ + this.selfDeaf = data.self_deaf; + } else { + this.selfDeaf ??= null; + } + + if ('self_mute' in data) { + /** + * Whether this member is self-muted + * @type {?boolean} + */ + this.selfMute = data.self_mute; + } else { + this.selfMute ??= null; + } + + if ('self_video' in data) { + /** + * Whether this member's camera is enabled + * @type {?boolean} + */ + this.selfVideo = data.self_video; + } else { + this.selfVideo ??= null; + } + + if ('session_id' in data) { + /** + * The session id for this member's connection + * @type {?string} + */ + this.sessionId = data.session_id; + } else { + this.sessionId ??= null; + } + + // The self_stream is property is omitted if false, check for another property + // here to avoid incorrectly clearing this when partial data is specified + if ('self_video' in data) { + /** + * Whether this member is streaming using "Screen Share" + * @type {?boolean} + */ + this.streaming = data.self_stream ?? false; + } else { + this.streaming ??= null; + } + + if ('channel_id' in data) { + /** + * The {@link VoiceChannel} or {@link StageChannel} id the member is in + * @type {?Snowflake} + */ + this.channelId = data.channel_id; + } else { + this.channelId ??= null; + } + + if ('suppress' in data) { + /** + * Whether this member is suppressed from speaking. This property is specific to stage channels only. + * @type {?boolean} + */ + this.suppress = data.suppress; + } else { + this.suppress ??= null; + } + + if ('request_to_speak_timestamp' in data) { + /** + * The time at which the member requested to speak. This property is specific to stage channels only. + * @type {?number} + */ + this.requestToSpeakTimestamp = data.request_to_speak_timestamp && Date.parse(data.request_to_speak_timestamp); + } else { + this.requestToSpeakTimestamp ??= null; + } + + return this; + } + + /** + * The member that this voice state belongs to + * @type {?GuildMember} + * @readonly + */ + get member() { + return this.guild.members.cache.get(this.id) ?? null; + } + + /** + * The channel that the member is connected to + * @type {?(VoiceChannel|StageChannel)} + * @readonly + */ + get channel() { + return this.guild.channels.cache.get(this.channelId) ?? null; + } + + /** + * Whether this member is either self-deafened or server-deafened + * @type {?boolean} + * @readonly + */ + get deaf() { + return this.serverDeaf || this.selfDeaf; + } + + /** + * Whether this member is either self-muted or server-muted + * @type {?boolean} + * @readonly + */ + get mute() { + return this.serverMute || this.selfMute; + } + + /** + * Mutes/unmutes the member of this voice state. + * @param {boolean} [mute=true] Whether or not the member should be muted + * @param {string} [reason] Reason for muting or unmuting + * @returns {Promise<GuildMember>} + */ + setMute(mute = true, reason) { + return this.guild.members.edit(this.id, { mute, reason }); + } + + /** + * Deafens/undeafens the member of this voice state. + * @param {boolean} [deaf=true] Whether or not the member should be deafened + * @param {string} [reason] Reason for deafening or undeafening + * @returns {Promise<GuildMember>} + */ + setDeaf(deaf = true, reason) { + return this.guild.members.edit(this.id, { deaf, reason }); + } + + /** + * Disconnects the member from the channel. + * @param {string} [reason] Reason for disconnecting the member from the channel + * @returns {Promise<GuildMember>} + */ + disconnect(reason) { + return this.setChannel(null, reason); + } + + /** + * Moves the member to a different channel, or disconnects them from the one they're in. + * @param {GuildVoiceChannelResolvable|null} channel Channel to move the member to, or `null` if you want to + * disconnect them from voice. + * @param {string} [reason] Reason for moving member to another channel or disconnecting + * @returns {Promise<GuildMember>} + */ + setChannel(channel, reason) { + return this.guild.members.edit(this.id, { channel, reason }); + } + + /** + * Data to edit the logged in user's own voice state with, when in a stage channel + * @typedef {Object} VoiceStateEditOptions + * @property {boolean} [requestToSpeak] Whether or not the client is requesting to become a speaker. + * <info>Only available to the logged in user's own voice state.</info> + * @property {boolean} [suppressed] Whether or not the user should be suppressed. + */ + + /** + * Edits this voice state. Currently only available when in a stage channel + * @param {VoiceStateEditOptions} options The options to provide + * @returns {Promise<VoiceState>} + */ + async edit(options) { + if (this.channel?.type !== ChannelType.GuildStageVoice) throw new DiscordjsError(ErrorCodes.VoiceNotStageChannel); + + const target = this.client.user.id === this.id ? '@me' : this.id; + + if (target !== '@me' && options.requestToSpeak !== undefined) { + throw new DiscordjsError(ErrorCodes.VoiceStateNotOwn); + } + + if (!['boolean', 'undefined'].includes(typeof options.requestToSpeak)) { + throw new DiscordjsTypeError(ErrorCodes.VoiceStateInvalidType, 'requestToSpeak'); + } + + if (!['boolean', 'undefined'].includes(typeof options.suppressed)) { + throw new DiscordjsTypeError(ErrorCodes.VoiceStateInvalidType, 'suppressed'); + } + + await this.client.rest.patch(Routes.guildVoiceState(this.guild.id, target), { + body: { + channel_id: this.channelId, + request_to_speak_timestamp: options.requestToSpeak + ? new Date().toISOString() + : options.requestToSpeak === false + ? null + : undefined, + suppress: options.suppressed, + }, + }); + return this; + } + + /** + * Toggles the request to speak in the channel. + * Only applicable for stage channels and for the client's own voice state. + * @param {boolean} [requestToSpeak=true] Whether or not the client is requesting to become a speaker. + * @example + * // Making the client request to speak in a stage channel (raise its hand) + * guild.members.me.voice.setRequestToSpeak(true); + * @example + * // Making the client cancel a request to speak + * guild.members.me.voice.setRequestToSpeak(false); + * @returns {Promise<VoiceState>} + */ + setRequestToSpeak(requestToSpeak = true) { + return this.edit({ requestToSpeak }); + } + + /** + * Suppress/unsuppress the user. Only applicable for stage channels. + * @param {boolean} [suppressed=true] Whether or not the user should be suppressed. + * @example + * // Making the client a speaker + * guild.members.me.voice.setSuppressed(false); + * @example + * // Making the client an audience member + * guild.members.me.voice.setSuppressed(true); + * @example + * // Inviting another user to speak + * voiceState.setSuppressed(false); + * @example + * // Moving another user to the audience, or cancelling their invite to speak + * voiceState.setSuppressed(true); + * @returns {Promise<VoiceState>} + */ + setSuppressed(suppressed = true) { + return this.edit({ suppressed }); + } + + toJSON() { + return super.toJSON({ + id: true, + serverDeaf: true, + serverMute: true, + selfDeaf: true, + selfMute: true, + sessionId: true, + channelId: 'channel', + }); + } +} + +module.exports = VoiceState; diff --git a/node_modules/discord.js/src/structures/Webhook.js b/node_modules/discord.js/src/structures/Webhook.js new file mode 100644 index 0000000..738d9e7 --- /dev/null +++ b/node_modules/discord.js/src/structures/Webhook.js @@ -0,0 +1,479 @@ +'use strict'; + +const { makeURLSearchParams } = require('@discordjs/rest'); +const { lazy } = require('@discordjs/util'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { Routes, WebhookType } = require('discord-api-types/v10'); +const MessagePayload = require('./MessagePayload'); +const { DiscordjsError, ErrorCodes } = require('../errors'); +const DataResolver = require('../util/DataResolver'); + +const getMessage = lazy(() => require('./Message').Message); + +/** + * Represents a webhook. + */ +class Webhook { + constructor(client, data) { + /** + * The client that instantiated the webhook + * @name Webhook#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + if (data) this._patch(data); + } + + _patch(data) { + if ('name' in data) { + /** + * The name of the webhook + * @type {string} + */ + this.name = data.name; + } + + /** + * The token for the webhook, unavailable for follower webhooks and webhooks owned by another application. + * @name Webhook#token + * @type {?string} + */ + Object.defineProperty(this, 'token', { + value: data.token ?? null, + writable: true, + configurable: true, + }); + + if ('avatar' in data) { + /** + * The avatar for the webhook + * @type {?string} + */ + this.avatar = data.avatar; + } + + /** + * The webhook's id + * @type {Snowflake} + */ + this.id = data.id; + + if ('type' in data) { + /** + * The type of the webhook + * @type {WebhookType} + */ + this.type = data.type; + } + + if ('guild_id' in data) { + /** + * The guild the webhook belongs to + * @type {Snowflake} + */ + this.guildId = data.guild_id; + } + + if ('channel_id' in data) { + /** + * The id of the channel the webhook belongs to + * @type {Snowflake} + */ + this.channelId = data.channel_id; + } + + if ('user' in data) { + /** + * The owner of the webhook + * @type {?(User|APIUser)} + */ + this.owner = this.client.users?._add(data.user) ?? data.user; + } else { + this.owner ??= null; + } + + if ('application_id' in data) { + /** + * The application that created this webhook + * @type {?Snowflake} + */ + this.applicationId = data.application_id; + } else { + this.applicationId ??= null; + } + + if ('source_guild' in data) { + /** + * The source guild of the webhook + * @type {?(Guild|APIGuild)} + */ + this.sourceGuild = this.client.guilds?.resolve(data.source_guild.id) ?? data.source_guild; + } else { + this.sourceGuild ??= null; + } + + if ('source_channel' in data) { + /** + * The source channel of the webhook + * @type {?(NewsChannel|APIChannel)} + */ + this.sourceChannel = this.client.channels?.resolve(data.source_channel?.id) ?? data.source_channel; + } else { + this.sourceChannel ??= null; + } + } + + /** + * Options that can be passed into send. + * @typedef {BaseMessageOptions} WebhookMessageCreateOptions + * @property {boolean} [tts=false] Whether the message should be spoken aloud + * @property {MessageFlags} [flags] Which flags to set for the message. + * <info>Only the {@link MessageFlags.SuppressEmbeds} flag can be set.</info> + * @property {string} [username=this.name] Username override for the message + * @property {string} [avatarURL] Avatar URL override for the message + * @property {Snowflake} [threadId] The id of the thread in the channel to send to. + * <info>For interaction webhooks, this property is ignored</info> + * @property {string} [threadName] Name of the thread to create (only available if webhook is in a forum channel) + */ + + /** + * Options that can be passed into editMessage. + * @typedef {BaseMessageOptions} WebhookMessageEditOptions + * @property {Attachment[]} [attachments] Attachments to send with the message + * @property {Snowflake} [threadId] The id of the thread this message belongs to + * <info>For interaction webhooks, this property is ignored</info> + */ + + /** + * The channel the webhook belongs to + * @type {?(TextChannel|VoiceChannel|StageChannel|NewsChannel|ForumChannel)} + * @readonly + */ + get channel() { + return this.client.channels.resolve(this.channelId); + } + + /** + * Sends a message with this webhook. + * @param {string|MessagePayload|WebhookMessageCreateOptions} options The options to provide + * @returns {Promise<Message>} + * @example + * // Send a basic message + * webhook.send('hello!') + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); + * @example + * // Send a basic message in a thread + * webhook.send({ content: 'hello!', threadId: '836856309672348295' }) + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); + * @example + * // Send a remote file + * webhook.send({ + * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send a local file + * webhook.send({ + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg' + * }] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send an embed with a local image inside + * webhook.send({ + * content: 'This is an embed', + * embeds: [{ + * thumbnail: { + * url: 'attachment://file.jpg' + * } + * }], + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg' + * }] + * }) + * .then(console.log) + * .catch(console.error); + */ + async send(options) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + let messagePayload; + + if (options instanceof MessagePayload) { + messagePayload = options.resolveBody(); + } else { + messagePayload = MessagePayload.create(this, options).resolveBody(); + } + + const query = makeURLSearchParams({ + wait: true, + thread_id: messagePayload.options.threadId, + }); + + const { body, files } = await messagePayload.resolveFiles(); + const d = await this.client.rest.post(Routes.webhook(this.id, this.token), { + body, + files, + query, + auth: false, + }); + + if (!this.client.channels) return d; + return this.client.channels.cache.get(d.channel_id)?.messages._add(d, false) ?? new (getMessage())(this.client, d); + } + + /** + * Sends a raw slack message with this webhook. + * @param {Object} body The raw body to send + * @returns {Promise<boolean>} + * @example + * // Send a slack message + * webhook.sendSlackMessage({ + * 'username': 'Wumpus', + * 'attachments': [{ + * 'pretext': 'this looks pretty cool', + * 'color': '#F0F', + * 'footer_icon': 'http://snek.s3.amazonaws.com/topSnek.png', + * 'footer': 'Powered by sneks', + * 'ts': Date.now() / 1_000 + * }] + * }).catch(console.error); + * @see {@link https://api.slack.com/messaging/webhooks} + */ + async sendSlackMessage(body) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + const data = await this.client.rest.post(Routes.webhookPlatform(this.id, this.token, 'slack'), { + query: makeURLSearchParams({ wait: true }), + auth: false, + body, + }); + return data.toString() === 'ok'; + } + + /** + * Options used to edit a {@link Webhook}. + * @typedef {Object} WebhookEditOptions + * @property {string} [name=this.name] The new name for the webhook + * @property {?(BufferResolvable)} [avatar] The new avatar for the webhook + * @property {GuildTextChannelResolvable|VoiceChannel|StageChannel|ForumChannel} [channel] + * The new channel for the webhook + * @property {string} [reason] Reason for editing the webhook + */ + + /** + * Edits this webhook. + * @param {WebhookEditOptions} options Options for editing the webhook + * @returns {Promise<Webhook>} + */ + async edit({ name = this.name, avatar, channel, reason }) { + if (avatar && !(typeof avatar === 'string' && avatar.startsWith('data:'))) { + avatar = await DataResolver.resolveImage(avatar); + } + channel &&= channel.id ?? channel; + const data = await this.client.rest.patch(Routes.webhook(this.id, channel ? undefined : this.token), { + body: { name, avatar, channel_id: channel }, + reason, + auth: !this.token || Boolean(channel), + }); + + this.name = data.name; + this.avatar = data.avatar; + this.channelId = data.channel_id; + return this; + } + + /** + * Options that can be passed into fetchMessage. + * @typedef {options} WebhookFetchMessageOptions + * @property {boolean} [cache=true] Whether to cache the message. + * @property {Snowflake} [threadId] The id of the thread this message belongs to. + * <info>For interaction webhooks, this property is ignored</info> + */ + + /** + * Gets a message that was sent by this webhook. + * @param {Snowflake|'@original'} message The id of the message to fetch + * @param {WebhookFetchMessageOptions} [options={}] The options to provide to fetch the message. + * @returns {Promise<Message>} Returns the message sent by this webhook + */ + async fetchMessage(message, { threadId } = {}) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + const data = await this.client.rest.get(Routes.webhookMessage(this.id, this.token, message), { + query: threadId ? makeURLSearchParams({ thread_id: threadId }) : undefined, + auth: false, + }); + + if (!this.client.channels) return data; + return ( + this.client.channels.cache.get(data.channel_id)?.messages._add(data, false) ?? + new (getMessage())(this.client, data) + ); + } + + /** + * Edits a message that was sent by this webhook. + * @param {MessageResolvable|'@original'} message The message to edit + * @param {string|MessagePayload|WebhookMessageEditOptions} options The options to provide + * @returns {Promise<Message>} Returns the message edited by this webhook + */ + async editMessage(message, options) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + let messagePayload; + + if (options instanceof MessagePayload) messagePayload = options; + else messagePayload = MessagePayload.create(this, options); + + const { body, files } = await messagePayload.resolveBody().resolveFiles(); + + const d = await this.client.rest.patch( + Routes.webhookMessage(this.id, this.token, typeof message === 'string' ? message : message.id), + { + body, + files, + query: messagePayload.options.threadId + ? makeURLSearchParams({ thread_id: messagePayload.options.threadId }) + : undefined, + auth: false, + }, + ); + + const channelManager = this.client.channels; + if (!channelManager) return d; + + const messageManager = channelManager.cache.get(d.channel_id)?.messages; + if (!messageManager) return new (getMessage())(this.client, d); + + const existing = messageManager.cache.get(d.id); + if (!existing) return messageManager._add(d); + + const clone = existing._clone(); + clone._patch(d); + return clone; + } + + /** + * Deletes the webhook. + * @param {string} [reason] Reason for deleting this webhook + * @returns {Promise<void>} + */ + delete(reason) { + return this.client.deleteWebhook(this.id, { token: this.token, reason }); + } + + /** + * Delete a message that was sent by this webhook. + * @param {MessageResolvable|'@original'} message The message to delete + * @param {Snowflake} [threadId] The id of the thread this message belongs to + * @returns {Promise<void>} + */ + async deleteMessage(message, threadId) { + if (!this.token) throw new DiscordjsError(ErrorCodes.WebhookTokenUnavailable); + + await this.client.rest.delete( + Routes.webhookMessage(this.id, this.token, typeof message === 'string' ? message : message.id), + { + query: threadId ? makeURLSearchParams({ thread_id: threadId }) : undefined, + auth: false, + }, + ); + } + + /** + * The timestamp the webhook was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the webhook was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The URL of this webhook + * @type {string} + * @readonly + */ + get url() { + return this.client.options.rest.api + Routes.webhook(this.id, this.token); + } + + /** + * A link to the webhook's avatar. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + avatarURL(options = {}) { + return this.avatar && this.client.rest.cdn.avatar(this.id, this.avatar, options); + } + + /** + * Whether this webhook is created by a user. + * @returns {boolean} + */ + isUserCreated() { + return Boolean(this.type === WebhookType.Incoming && this.owner && !this.owner.bot); + } + + /** + * Whether this webhook is created by an application. + * @returns {boolean} + */ + isApplicationCreated() { + return this.type === WebhookType.Application; + } + + /** + * Whether or not this webhook is a channel follower webhook. + * @returns {boolean} + */ + isChannelFollower() { + return this.type === WebhookType.ChannelFollower; + } + + /** + * Whether or not this webhook is an incoming webhook. + * @returns {boolean} + */ + isIncoming() { + return this.type === WebhookType.Incoming; + } + + static applyToClass(structure, ignore = []) { + for (const prop of [ + 'send', + 'sendSlackMessage', + 'fetchMessage', + 'edit', + 'editMessage', + 'delete', + 'deleteMessage', + 'createdTimestamp', + 'createdAt', + 'url', + ]) { + if (ignore.includes(prop)) continue; + Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(Webhook.prototype, prop)); + } + } +} + +module.exports = Webhook; diff --git a/node_modules/discord.js/src/structures/WelcomeChannel.js b/node_modules/discord.js/src/structures/WelcomeChannel.js new file mode 100644 index 0000000..d783e06 --- /dev/null +++ b/node_modules/discord.js/src/structures/WelcomeChannel.js @@ -0,0 +1,60 @@ +'use strict'; + +const Base = require('./Base'); +const { Emoji } = require('./Emoji'); + +/** + * Represents a channel link in a guild's welcome screen. + * @extends {Base} + */ +class WelcomeChannel extends Base { + constructor(guild, data) { + super(guild.client); + + /** + * The guild for this welcome channel + * @type {Guild|InviteGuild} + */ + this.guild = guild; + + /** + * The description of this welcome channel + * @type {string} + */ + this.description = data.description; + + /** + * The raw emoji data + * @type {Object} + * @private + */ + this._emoji = { + name: data.emoji_name, + id: data.emoji_id, + }; + + /** + * The id of this welcome channel + * @type {Snowflake} + */ + this.channelId = data.channel_id; + } + + /** + * The channel of this welcome channel + * @type {?(TextChannel|NewsChannel|ForumChannel)} + */ + get channel() { + return this.client.channels.resolve(this.channelId); + } + + /** + * The emoji of this welcome channel + * @type {GuildEmoji|Emoji} + */ + get emoji() { + return this.client.emojis.resolve(this._emoji.id) ?? new Emoji(this.client, this._emoji); + } +} + +module.exports = WelcomeChannel; diff --git a/node_modules/discord.js/src/structures/WelcomeScreen.js b/node_modules/discord.js/src/structures/WelcomeScreen.js new file mode 100644 index 0000000..9ff79bc --- /dev/null +++ b/node_modules/discord.js/src/structures/WelcomeScreen.js @@ -0,0 +1,49 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { GuildFeature } = require('discord-api-types/v10'); +const Base = require('./Base'); +const WelcomeChannel = require('./WelcomeChannel'); + +/** + * Represents a welcome screen. + * @extends {Base} + */ +class WelcomeScreen extends Base { + constructor(guild, data) { + super(guild.client); + + /** + * The guild for this welcome screen + * @type {Guild} + */ + this.guild = guild; + + /** + * The description of this welcome screen + * @type {?string} + */ + this.description = data.description ?? null; + + /** + * Collection of welcome channels belonging to this welcome screen + * @type {Collection<Snowflake, WelcomeChannel>} + */ + this.welcomeChannels = new Collection(); + + for (const channel of data.welcome_channels) { + const welcomeChannel = new WelcomeChannel(this.guild, channel); + this.welcomeChannels.set(welcomeChannel.channelId, welcomeChannel); + } + } + + /** + * Whether the welcome screen is enabled on the guild + * @type {boolean} + */ + get enabled() { + return this.guild.features.includes(GuildFeature.WelcomeScreenEnabled); + } +} + +module.exports = WelcomeScreen; diff --git a/node_modules/discord.js/src/structures/Widget.js b/node_modules/discord.js/src/structures/Widget.js new file mode 100644 index 0000000..344c81a --- /dev/null +++ b/node_modules/discord.js/src/structures/Widget.js @@ -0,0 +1,88 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { Routes } = require('discord-api-types/v10'); +const Base = require('./Base'); +const WidgetMember = require('./WidgetMember'); + +/** + * Represents a Widget. + * @extends {Base} + */ +class Widget extends Base { + constructor(client, data) { + super(client); + this._patch(data); + } + + /** + * Represents a channel in a Widget + * @typedef {Object} WidgetChannel + * @property {Snowflake} id Id of the channel + * @property {string} name Name of the channel + * @property {number} position Position of the channel + */ + + _patch(data) { + /** + * The id of the guild. + * @type {Snowflake} + */ + this.id = data.id; + + if ('name' in data) { + /** + * The name of the guild. + * @type {string} + */ + this.name = data.name; + } + + if ('instant_invite' in data) { + /** + * The invite of the guild. + * @type {?string} + */ + this.instantInvite = data.instant_invite; + } + + /** + * The list of channels in the guild. + * @type {Collection<Snowflake, WidgetChannel>} + */ + this.channels = new Collection(); + for (const channel of data.channels) { + this.channels.set(channel.id, channel); + } + + /** + * The list of members in the guild. + * These strings are just arbitrary numbers, they aren't Snowflakes. + * @type {Collection<string, WidgetMember>} + */ + this.members = new Collection(); + for (const member of data.members) { + this.members.set(member.id, new WidgetMember(this.client, member)); + } + + if ('presence_count' in data) { + /** + * The number of members online. + * @type {number} + */ + this.presenceCount = data.presence_count; + } + } + + /** + * Update the Widget. + * @returns {Promise<Widget>} + */ + async fetch() { + const data = await this.client.rest.get(Routes.guildWidgetJSON(this.id)); + this._patch(data); + return this; + } +} + +module.exports = Widget; diff --git a/node_modules/discord.js/src/structures/WidgetMember.js b/node_modules/discord.js/src/structures/WidgetMember.js new file mode 100644 index 0000000..d7aca21 --- /dev/null +++ b/node_modules/discord.js/src/structures/WidgetMember.js @@ -0,0 +1,99 @@ +'use strict'; + +const Base = require('./Base'); + +/** + * Represents a WidgetMember. + * @extends {Base} + */ +class WidgetMember extends Base { + /** + * Activity sent in a {@link WidgetMember}. + * @typedef {Object} WidgetActivity + * @property {string} name The name of the activity + */ + + constructor(client, data) { + super(client); + + /** + * The id of the user. It's an arbitrary number. + * @type {string} + */ + this.id = data.id; + + /** + * The username of the member. + * @type {string} + */ + this.username = data.username; + + /** + * The discriminator of the member. + * @type {string} + */ + this.discriminator = data.discriminator; + + /** + * The avatar of the member. + * @type {?string} + */ + this.avatar = data.avatar; + + /** + * The status of the member. + * @type {PresenceStatus} + */ + this.status = data.status; + + /** + * If the member is server deafened + * @type {?boolean} + */ + this.deaf = data.deaf ?? null; + + /** + * If the member is server muted + * @type {?boolean} + */ + this.mute = data.mute ?? null; + + /** + * If the member is self deafened + * @type {?boolean} + */ + this.selfDeaf = data.self_deaf ?? null; + + /** + * If the member is self muted + * @type {?boolean} + */ + this.selfMute = data.self_mute ?? null; + + /** + * If the member is suppressed + * @type {?boolean} + */ + this.suppress = data.suppress ?? null; + + /** + * The id of the voice channel the member is in, if any + * @type {?Snowflake} + */ + this.channelId = data.channel_id ?? null; + + /** + * The avatar URL of the member. + * @type {string} + */ + this.avatarURL = data.avatar_url; + + /** + * The activity of the member. + * @type {?WidgetActivity} + */ + this.activity = data.activity ?? null; + } +} + +module.exports = WidgetMember; diff --git a/node_modules/discord.js/src/structures/interfaces/Application.js b/node_modules/discord.js/src/structures/interfaces/Application.js new file mode 100644 index 0000000..5e81465 --- /dev/null +++ b/node_modules/discord.js/src/structures/interfaces/Application.js @@ -0,0 +1,108 @@ +'use strict'; + +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const Base = require('../Base'); + +/** + * Represents an OAuth2 Application. + * @extends {Base} + * @abstract + */ +class Application extends Base { + constructor(client, data) { + super(client); + this._patch(data); + } + + _patch(data) { + /** + * The application's id + * @type {Snowflake} + */ + this.id = data.id; + + if ('name' in data) { + /** + * The name of the application + * @type {?string} + */ + this.name = data.name; + } else { + this.name ??= null; + } + + if ('description' in data) { + /** + * The application's description + * @type {?string} + */ + this.description = data.description; + } else { + this.description ??= null; + } + + if ('icon' in data) { + /** + * The application's icon hash + * @type {?string} + */ + this.icon = data.icon; + } else { + this.icon ??= null; + } + } + + /** + * The timestamp the application was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return DiscordSnowflake.timestampFrom(this.id); + } + + /** + * The time the application was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * A link to the application's icon. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + iconURL(options = {}) { + return this.icon && this.client.rest.cdn.appIcon(this.id, this.icon, options); + } + + /** + * A link to this application's cover image. + * @param {ImageURLOptions} [options={}] Options for the image URL + * @returns {?string} + */ + coverURL(options = {}) { + return this.cover && this.client.rest.cdn.appIcon(this.id, this.cover, options); + } + + /** + * When concatenated with a string, this automatically returns the application's name instead of the + * Application object. + * @returns {?string} + * @example + * // Logs: Application name: My App + * console.log(`Application name: ${application}`); + */ + toString() { + return this.name; + } + + toJSON() { + return super.toJSON({ createdTimestamp: true }); + } +} + +module.exports = Application; diff --git a/node_modules/discord.js/src/structures/interfaces/Collector.js b/node_modules/discord.js/src/structures/interfaces/Collector.js new file mode 100644 index 0000000..65f4117 --- /dev/null +++ b/node_modules/discord.js/src/structures/interfaces/Collector.js @@ -0,0 +1,335 @@ +'use strict'; + +const EventEmitter = require('node:events'); +const { setTimeout, clearTimeout } = require('node:timers'); +const { Collection } = require('@discordjs/collection'); +const { DiscordjsTypeError, ErrorCodes } = require('../../errors'); +const { flatten } = require('../../util/Util'); + +/** + * Filter to be applied to the collector. + * @typedef {Function} CollectorFilter + * @param {...*} args Any arguments received by the listener + * @param {Collection} collection The items collected by this collector + * @returns {boolean|Promise<boolean>} + */ + +/** + * Options to be applied to the collector. + * @typedef {Object} CollectorOptions + * @property {CollectorFilter} [filter] The filter applied to this collector + * @property {number} [time] How long to run the collector for in milliseconds + * @property {number} [idle] How long to stop the collector after inactivity in milliseconds + * @property {boolean} [dispose=false] Whether to dispose data when it's deleted + */ + +/** + * Abstract class for defining a new Collector. + * @extends {EventEmitter} + * @abstract + */ +class Collector extends EventEmitter { + constructor(client, options = {}) { + super(); + + /** + * The client that instantiated this Collector + * @name Collector#client + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * The filter applied to this collector + * @type {CollectorFilter} + * @returns {boolean|Promise<boolean>} + */ + this.filter = options.filter ?? (() => true); + + /** + * The options of this collector + * @type {CollectorOptions} + */ + this.options = options; + + /** + * The items collected by this collector + * @type {Collection} + */ + this.collected = new Collection(); + + /** + * Whether this collector has finished collecting + * @type {boolean} + */ + this.ended = false; + + /** + * Timeout for cleanup + * @type {?Timeout} + * @private + */ + this._timeout = null; + + /** + * Timeout for cleanup due to inactivity + * @type {?Timeout} + * @private + */ + this._idletimeout = null; + + /** + * The reason the collector ended + * @type {string|null} + * @private + */ + this._endReason = null; + + if (typeof this.filter !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options.filter', 'function'); + } + + this.handleCollect = this.handleCollect.bind(this); + this.handleDispose = this.handleDispose.bind(this); + + if (options.time) this._timeout = setTimeout(() => this.stop('time'), options.time).unref(); + if (options.idle) this._idletimeout = setTimeout(() => this.stop('idle'), options.idle).unref(); + + /** + * The timestamp at which this collector last collected an item + * @type {?number} + */ + this.lastCollectedTimestamp = null; + } + + /** + * The Date at which this collector last collected an item + * @type {?Date} + */ + get lastCollectedAt() { + return this.lastCollectedTimestamp && new Date(this.lastCollectedTimestamp); + } + + /** + * Call this to handle an event as a collectable element. Accepts any event data as parameters. + * @param {...*} args The arguments emitted by the listener + * @returns {Promise<void>} + * @emits Collector#collect + */ + async handleCollect(...args) { + const collectedId = await this.collect(...args); + + if (collectedId) { + const filterResult = await this.filter(...args, this.collected); + if (filterResult) { + this.collected.set(collectedId, args[0]); + + /** + * Emitted whenever an element is collected. + * @event Collector#collect + * @param {...*} args The arguments emitted by the listener + */ + this.emit('collect', ...args); + + this.lastCollectedTimestamp = Date.now(); + if (this._idletimeout) { + clearTimeout(this._idletimeout); + this._idletimeout = setTimeout(() => this.stop('idle'), this.options.idle).unref(); + } + } else { + /** + * Emitted whenever an element is not collected by the collector. + * @event Collector#ignore + * @param {...*} args The arguments emitted by the listener + */ + this.emit('ignore', ...args); + } + } + this.checkEnd(); + } + + /** + * Call this to remove an element from the collection. Accepts any event data as parameters. + * @param {...*} args The arguments emitted by the listener + * @returns {Promise<void>} + * @emits Collector#dispose + */ + async handleDispose(...args) { + if (!this.options.dispose) return; + + const dispose = this.dispose(...args); + if (!dispose || !(await this.filter(...args)) || !this.collected.has(dispose)) return; + this.collected.delete(dispose); + + /** + * Emitted whenever an element is disposed of. + * @event Collector#dispose + * @param {...*} args The arguments emitted by the listener + */ + this.emit('dispose', ...args); + this.checkEnd(); + } + + /** + * Returns a promise that resolves with the next collected element; + * rejects with collected elements if the collector finishes without receiving a next element + * @type {Promise} + * @readonly + */ + get next() { + return new Promise((resolve, reject) => { + if (this.ended) { + reject(this.collected); + return; + } + + const cleanup = () => { + this.removeListener('collect', onCollect); + this.removeListener('end', onEnd); + }; + + const onCollect = item => { + cleanup(); + resolve(item); + }; + + const onEnd = () => { + cleanup(); + reject(this.collected); + }; + + this.on('collect', onCollect); + this.on('end', onEnd); + }); + } + + /** + * Stops this collector and emits the `end` event. + * @param {string} [reason='user'] The reason this collector is ending + * @emits Collector#end + */ + stop(reason = 'user') { + if (this.ended) return; + + if (this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if (this._idletimeout) { + clearTimeout(this._idletimeout); + this._idletimeout = null; + } + + this._endReason = reason; + this.ended = true; + + /** + * Emitted when the collector is finished collecting. + * @event Collector#end + * @param {Collection} collected The elements collected by the collector + * @param {string} reason The reason the collector ended + */ + this.emit('end', this.collected, reason); + } + + /** + * Options used to reset the timeout and idle timer of a {@link Collector}. + * @typedef {Object} CollectorResetTimerOptions + * @property {number} [time] How long to run the collector for (in milliseconds) + * @property {number} [idle] How long to wait to stop the collector after inactivity (in milliseconds) + */ + + /** + * Resets the collector's timeout and idle timer. + * @param {CollectorResetTimerOptions} [options] Options for resetting + */ + resetTimer({ time, idle } = {}) { + if (this._timeout) { + clearTimeout(this._timeout); + this._timeout = setTimeout(() => this.stop('time'), time ?? this.options.time).unref(); + } + if (this._idletimeout) { + clearTimeout(this._idletimeout); + this._idletimeout = setTimeout(() => this.stop('idle'), idle ?? this.options.idle).unref(); + } + } + + /** + * Checks whether the collector should end, and if so, ends it. + * @returns {boolean} Whether the collector ended or not + */ + checkEnd() { + const reason = this.endReason; + if (reason) this.stop(reason); + return Boolean(reason); + } + + /** + * Allows collectors to be consumed with for-await-of loops + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of} + */ + async *[Symbol.asyncIterator]() { + const queue = []; + const onCollect = (...item) => queue.push(item); + this.on('collect', onCollect); + + try { + while (queue.length || !this.ended) { + if (queue.length) { + yield queue.shift(); + } else { + // eslint-disable-next-line no-await-in-loop + await new Promise(resolve => { + const tick = () => { + this.removeListener('collect', tick); + this.removeListener('end', tick); + return resolve(); + }; + this.on('collect', tick); + this.on('end', tick); + }); + } + } + } finally { + this.removeListener('collect', onCollect); + } + } + + toJSON() { + return flatten(this); + } + + /* eslint-disable no-empty-function */ + /** + * The reason this collector has ended with, or null if it hasn't ended yet + * @type {?string} + * @readonly + */ + get endReason() { + return this._endReason; + } + + /** + * Handles incoming events from the `handleCollect` function. Returns null if the event should not + * be collected, or returns an object describing the data that should be stored. + * @see Collector#handleCollect + * @param {...*} args Any args the event listener emits + * @returns {?(*|Promise<?*>)} Data to insert into collection, if any + * @abstract + */ + collect() {} + + /** + * Handles incoming events from the `handleDispose`. Returns null if the event should not + * be disposed, or returns the key that should be removed. + * @see Collector#handleDispose + * @param {...*} args Any args the event listener emits + * @returns {?*} Key to remove from the collection, if any + * @abstract + */ + dispose() {} + /* eslint-enable no-empty-function */ +} + +module.exports = Collector; diff --git a/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js b/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js new file mode 100644 index 0000000..15256e3 --- /dev/null +++ b/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js @@ -0,0 +1,320 @@ +'use strict'; + +const { isJSONEncodable } = require('@discordjs/util'); +const { InteractionResponseType, MessageFlags, Routes, InteractionType } = require('discord-api-types/v10'); +const { DiscordjsError, ErrorCodes } = require('../../errors'); +const InteractionCollector = require('../InteractionCollector'); +const InteractionResponse = require('../InteractionResponse'); +const MessagePayload = require('../MessagePayload'); + +/** + * @typedef {Object} ModalComponentData + * @property {string} title The title of the modal + * @property {string} customId The custom id of the modal + * @property {ActionRow[]} components The components within this modal + */ + +/** + * Interface for classes that support shared interaction response types. + * @interface + */ +class InteractionResponses { + /** + * Options for deferring the reply to an {@link BaseInteraction}. + * @typedef {Object} InteractionDeferReplyOptions + * @property {boolean} [ephemeral] Whether the reply should be ephemeral + * @property {boolean} [fetchReply] Whether to fetch the reply + */ + + /** + * Options for deferring and updating the reply to a {@link MessageComponentInteraction}. + * @typedef {Object} InteractionDeferUpdateOptions + * @property {boolean} [fetchReply] Whether to fetch the reply + */ + + /** + * Options for a reply to a {@link BaseInteraction}. + * @typedef {BaseMessageOptions} InteractionReplyOptions + * @property {boolean} [tts=false] Whether the message should be spoken aloud + * @property {boolean} [ephemeral] Whether the reply should be ephemeral + * @property {boolean} [fetchReply] Whether to fetch the reply + * @property {MessageFlags} [flags] Which flags to set for the message. + * <info>Only `MessageFlags.SuppressEmbeds` and `MessageFlags.Ephemeral` can be set.</info> + */ + + /** + * Options for updating the message received from a {@link MessageComponentInteraction}. + * @typedef {MessageEditOptions} InteractionUpdateOptions + * @property {boolean} [fetchReply] Whether to fetch the reply + */ + + /** + * Defers the reply to this interaction. + * @param {InteractionDeferReplyOptions} [options] Options for deferring the reply to this interaction + * @returns {Promise<Message|InteractionResponse>} + * @example + * // Defer the reply to this interaction + * interaction.deferReply() + * .then(console.log) + * .catch(console.error) + * @example + * // Defer to send an ephemeral reply later + * interaction.deferReply({ ephemeral: true }) + * .then(console.log) + * .catch(console.error); + */ + async deferReply(options = {}) { + if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied); + this.ephemeral = options.ephemeral ?? false; + await this.client.rest.post(Routes.interactionCallback(this.id, this.token), { + body: { + type: InteractionResponseType.DeferredChannelMessageWithSource, + data: { + flags: options.ephemeral ? MessageFlags.Ephemeral : undefined, + }, + }, + auth: false, + }); + this.deferred = true; + + return options.fetchReply ? this.fetchReply() : new InteractionResponse(this); + } + + /** + * Creates a reply to this interaction. + * <info>Use the `fetchReply` option to get the bot's reply message.</info> + * @param {string|MessagePayload|InteractionReplyOptions} options The options for the reply + * @returns {Promise<Message|InteractionResponse>} + * @example + * // Reply to the interaction and fetch the response + * interaction.reply({ content: 'Pong!', fetchReply: true }) + * .then((message) => console.log(`Reply sent with content ${message.content}`)) + * .catch(console.error); + * @example + * // Create an ephemeral reply with an embed + * const embed = new EmbedBuilder().setDescription('Pong!'); + * + * interaction.reply({ embeds: [embed], ephemeral: true }) + * .then(() => console.log('Reply sent.')) + * .catch(console.error); + */ + async reply(options) { + if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied); + this.ephemeral = options.ephemeral ?? false; + + let messagePayload; + if (options instanceof MessagePayload) messagePayload = options; + else messagePayload = MessagePayload.create(this, options); + + const { body: data, files } = await messagePayload.resolveBody().resolveFiles(); + + await this.client.rest.post(Routes.interactionCallback(this.id, this.token), { + body: { + type: InteractionResponseType.ChannelMessageWithSource, + data, + }, + files, + auth: false, + }); + this.replied = true; + + return options.fetchReply ? this.fetchReply() : new InteractionResponse(this); + } + + /** + * Fetches a reply to this interaction. + * @see Webhook#fetchMessage + * @param {Snowflake|'@original'} [message='@original'] The response to fetch + * @returns {Promise<Message>} + * @example + * // Fetch the initial reply to this interaction + * interaction.fetchReply() + * .then(reply => console.log(`Replied with ${reply.content}`)) + * .catch(console.error); + */ + fetchReply(message = '@original') { + return this.webhook.fetchMessage(message); + } + + /** + * Options that can be passed into {@link InteractionResponses#editReply}. + * @typedef {WebhookMessageEditOptions} InteractionEditReplyOptions + * @property {MessageResolvable|'@original'} [message='@original'] The response to edit + */ + + /** + * Edits a reply to this interaction. + * @see Webhook#editMessage + * @param {string|MessagePayload|InteractionEditReplyOptions} options The new options for the message + * @returns {Promise<Message>} + * @example + * // Edit the initial reply to this interaction + * interaction.editReply('New content') + * .then(console.log) + * .catch(console.error); + */ + async editReply(options) { + if (!this.deferred && !this.replied) throw new DiscordjsError(ErrorCodes.InteractionNotReplied); + const msg = await this.webhook.editMessage(options.message ?? '@original', options); + this.replied = true; + return msg; + } + + /** + * Deletes a reply to this interaction. + * @see Webhook#deleteMessage + * @param {MessageResolvable|'@original'} [message='@original'] The response to delete + * @returns {Promise<void>} + * @example + * // Delete the initial reply to this interaction + * interaction.deleteReply() + * .then(console.log) + * .catch(console.error); + */ + async deleteReply(message = '@original') { + await this.webhook.deleteMessage(message); + } + + /** + * Send a follow-up message to this interaction. + * @param {string|MessagePayload|InteractionReplyOptions} options The options for the reply + * @returns {Promise<Message>} + */ + followUp(options) { + if (!this.deferred && !this.replied) return Promise.reject(new DiscordjsError(ErrorCodes.InteractionNotReplied)); + return this.webhook.send(options); + } + + /** + * Defers an update to the message to which the component was attached. + * @param {InteractionDeferUpdateOptions} [options] Options for deferring the update to this interaction + * @returns {Promise<Message|InteractionResponse>} + * @example + * // Defer updating and reset the component's loading state + * interaction.deferUpdate() + * .then(console.log) + * .catch(console.error); + */ + async deferUpdate(options = {}) { + if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied); + await this.client.rest.post(Routes.interactionCallback(this.id, this.token), { + body: { + type: InteractionResponseType.DeferredMessageUpdate, + }, + auth: false, + }); + this.deferred = true; + + return options.fetchReply ? this.fetchReply() : new InteractionResponse(this, this.message?.interaction?.id); + } + + /** + * Updates the original message of the component on which the interaction was received on. + * @param {string|MessagePayload|InteractionUpdateOptions} options The options for the updated message + * @returns {Promise<Message|void>} + * @example + * // Remove the components from the message + * interaction.update({ + * content: "A component interaction was received", + * components: [] + * }) + * .then(console.log) + * .catch(console.error); + */ + async update(options) { + if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied); + + let messagePayload; + if (options instanceof MessagePayload) messagePayload = options; + else messagePayload = MessagePayload.create(this, options); + + const { body: data, files } = await messagePayload.resolveBody().resolveFiles(); + + await this.client.rest.post(Routes.interactionCallback(this.id, this.token), { + body: { + type: InteractionResponseType.UpdateMessage, + data, + }, + files, + auth: false, + }); + this.replied = true; + + return options.fetchReply ? this.fetchReply() : new InteractionResponse(this, this.message.interaction?.id); + } + + /** + * Shows a modal component + * @param {ModalBuilder|ModalComponentData|APIModalInteractionResponseCallbackData} modal The modal to show + * @returns {Promise<void>} + */ + async showModal(modal) { + if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied); + await this.client.rest.post(Routes.interactionCallback(this.id, this.token), { + body: { + type: InteractionResponseType.Modal, + data: isJSONEncodable(modal) ? modal.toJSON() : this.client.options.jsonTransformer(modal), + }, + auth: false, + }); + this.replied = true; + } + + /** + * An object containing the same properties as {@link CollectorOptions}, but a few less: + * @typedef {Object} AwaitModalSubmitOptions + * @property {CollectorFilter} [filter] The filter applied to this collector + * @property {number} time Time in milliseconds to wait for an interaction before rejecting + */ + + /** + * Collects a single modal submit interaction that passes the filter. + * The Promise will reject if the time expires. + * @param {AwaitModalSubmitOptions} options Options to pass to the internal collector + * @returns {Promise<ModalSubmitInteraction>} + * @example + * // Collect a modal submit interaction + * const filter = (interaction) => interaction.customId === 'modal'; + * interaction.awaitModalSubmit({ filter, time: 15_000 }) + * .then(interaction => console.log(`${interaction.customId} was submitted!`)) + * .catch(console.error); + */ + awaitModalSubmit(options) { + if (typeof options.time !== 'number') throw new DiscordjsError(ErrorCodes.InvalidType, 'time', 'number'); + const _options = { ...options, max: 1, interactionType: InteractionType.ModalSubmit }; + return new Promise((resolve, reject) => { + const collector = new InteractionCollector(this.client, _options); + collector.once('end', (interactions, reason) => { + const interaction = interactions.first(); + if (interaction) resolve(interaction); + else reject(new DiscordjsError(ErrorCodes.InteractionCollectorError, reason)); + }); + }); + } + + static applyToClass(structure, ignore = []) { + const props = [ + 'deferReply', + 'reply', + 'fetchReply', + 'editReply', + 'deleteReply', + 'followUp', + 'deferUpdate', + 'update', + 'showModal', + 'awaitModalSubmit', + ]; + + for (const prop of props) { + if (ignore.includes(prop)) continue; + Object.defineProperty( + structure.prototype, + prop, + Object.getOwnPropertyDescriptor(InteractionResponses.prototype, prop), + ); + } + } +} + +module.exports = InteractionResponses; diff --git a/node_modules/discord.js/src/structures/interfaces/TextBasedChannel.js b/node_modules/discord.js/src/structures/interfaces/TextBasedChannel.js new file mode 100644 index 0000000..cf455b9 --- /dev/null +++ b/node_modules/discord.js/src/structures/interfaces/TextBasedChannel.js @@ -0,0 +1,413 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { DiscordSnowflake } = require('@sapphire/snowflake'); +const { InteractionType, Routes } = require('discord-api-types/v10'); +const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../../errors'); +const { MaxBulkDeletableMessageAge } = require('../../util/Constants'); +const InteractionCollector = require('../InteractionCollector'); +const MessageCollector = require('../MessageCollector'); +const MessagePayload = require('../MessagePayload'); + +/** + * Interface for classes that have text-channel-like features. + * @interface + */ +class TextBasedChannel { + constructor() { + /** + * A manager of the messages sent to this channel + * @type {GuildMessageManager} + */ + this.messages = new GuildMessageManager(this); + + /** + * The channel's last message id, if one was sent + * @type {?Snowflake} + */ + this.lastMessageId = null; + + /** + * The timestamp when the last pinned message was pinned, if there was one + * @type {?number} + */ + this.lastPinTimestamp = null; + } + + /** + * The Message object of the last message in the channel, if one was sent + * @type {?Message} + * @readonly + */ + get lastMessage() { + return this.messages.resolve(this.lastMessageId); + } + + /** + * The date when the last pinned message was pinned, if there was one + * @type {?Date} + * @readonly + */ + get lastPinAt() { + return this.lastPinTimestamp && new Date(this.lastPinTimestamp); + } + + /** + * The base message options for messages. + * @typedef {Object} BaseMessageOptions + * @property {string|null} [content=''] The content for the message. This can only be `null` when editing a message. + * @property {Array<(EmbedBuilder|Embed|APIEmbed)>} [embeds] The embeds for the message + * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content + * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details) + * @property {Array<(AttachmentBuilder|Attachment|AttachmentPayload|BufferResolvable)>} [files] + * The files to send with the message. + * @property {Array<(ActionRowBuilder|ActionRow|APIActionRowComponent)>} [components] + * Action rows containing interactive components for the message (buttons, select menus) + */ + + /** + * Options for sending a message with a reply. + * @typedef {Object} ReplyOptions + * @property {MessageResolvable} messageReference The message to reply to (must be in the same channel and not system) + * @property {boolean} [failIfNotExists=this.client.options.failIfNotExists] Whether to error if the referenced + * message does not exist (creates a standard message in this case when false) + */ + + /** + * The options for sending a message. + * @typedef {BaseMessageOptions} BaseMessageCreateOptions + * @property {boolean} [tts=false] Whether the message should be spoken aloud + * @property {string} [nonce=''] The nonce for the message + * @property {StickerResolvable[]} [stickers=[]] The stickers to send in the message + * @property {MessageFlags} [flags] Which flags to set for the message. + * <info>Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set.</info> + */ + + /** + * The options for sending a message. + * @typedef {BaseMessageCreateOptions} MessageCreateOptions + * @property {ReplyOptions} [reply] The options for replying to a message + */ + + /** + * Options provided to control parsing of mentions by Discord + * @typedef {Object} MessageMentionOptions + * @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed + * @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions + * @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions + * @property {boolean} [repliedUser=true] Whether the author of the Message being replied to should be pinged + */ + + /** + * Types of mentions to enable in MessageMentionOptions. + * - `roles` + * - `users` + * - `everyone` + * @typedef {string} MessageMentionTypes + */ + + /** + * @typedef {Object} FileOptions + * @property {BufferResolvable} attachment File to attach + * @property {string} [name='file.jpg'] Filename of the attachment + * @property {string} description The description of the file + */ + + /** + * Sends a message to this channel. + * @param {string|MessagePayload|MessageCreateOptions} options The options to provide + * @returns {Promise<Message>} + * @example + * // Send a basic message + * channel.send('hello!') + * .then(message => console.log(`Sent message: ${message.content}`)) + * .catch(console.error); + * @example + * // Send a remote file + * channel.send({ + * files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048'] + * }) + * .then(console.log) + * .catch(console.error); + * @example + * // Send a local file + * channel.send({ + * files: [{ + * attachment: 'entire/path/to/file.jpg', + * name: 'file.jpg', + * description: 'A description of the file' + * }] + * }) + * .then(console.log) + * .catch(console.error); + */ + async send(options) { + const User = require('../User'); + const { GuildMember } = require('../GuildMember'); + + if (this instanceof User || this instanceof GuildMember) { + const dm = await this.createDM(); + return dm.send(options); + } + + let messagePayload; + + if (options instanceof MessagePayload) { + messagePayload = options.resolveBody(); + } else { + messagePayload = MessagePayload.create(this, options).resolveBody(); + } + + const { body, files } = await messagePayload.resolveFiles(); + const d = await this.client.rest.post(Routes.channelMessages(this.id), { body, files }); + + return this.messages.cache.get(d.id) ?? this.messages._add(d); + } + + /** + * Sends a typing indicator in the channel. + * @returns {Promise<void>} Resolves upon the typing status being sent + * @example + * // Start typing in a channel + * channel.sendTyping(); + */ + async sendTyping() { + await this.client.rest.post(Routes.channelTyping(this.id)); + } + + /** + * Creates a Message Collector. + * @param {MessageCollectorOptions} [options={}] The options to pass to the collector + * @returns {MessageCollector} + * @example + * // Create a message collector + * const filter = m => m.content.includes('discord'); + * const collector = channel.createMessageCollector({ filter, time: 15_000 }); + * collector.on('collect', m => console.log(`Collected ${m.content}`)); + * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); + */ + createMessageCollector(options = {}) { + return new MessageCollector(this, options); + } + + /** + * An object containing the same properties as CollectorOptions, but a few more: + * @typedef {MessageCollectorOptions} AwaitMessagesOptions + * @property {string[]} [errors] Stop/end reasons that cause the promise to reject + */ + + /** + * Similar to createMessageCollector but in promise form. + * Resolves with a collection of messages that pass the specified filter. + * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector + * @returns {Promise<Collection<Snowflake, Message>>} + * @example + * // Await !vote messages + * const filter = m => m.content.startsWith('!vote'); + * // Errors: ['time'] treats ending because of the time limit as an error + * channel.awaitMessages({ filter, max: 4, time: 60_000, errors: ['time'] }) + * .then(collected => console.log(collected.size)) + * .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`)); + */ + awaitMessages(options = {}) { + return new Promise((resolve, reject) => { + const collector = this.createMessageCollector(options); + collector.once('end', (collection, reason) => { + if (options.errors?.includes(reason)) { + reject(collection); + } else { + resolve(collection); + } + }); + }); + } + + /** + * Creates a component interaction collector. + * @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector + * @returns {InteractionCollector} + * @example + * // Create a button interaction collector + * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; + * const collector = channel.createMessageComponentCollector({ filter, time: 15_000 }); + * collector.on('collect', i => console.log(`Collected ${i.customId}`)); + * collector.on('end', collected => console.log(`Collected ${collected.size} items`)); + */ + createMessageComponentCollector(options = {}) { + return new InteractionCollector(this.client, { + ...options, + interactionType: InteractionType.MessageComponent, + channel: this, + }); + } + + /** + * Collects a single component interaction that passes the filter. + * The Promise will reject if the time expires. + * @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector + * @returns {Promise<MessageComponentInteraction>} + * @example + * // Collect a message component interaction + * const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId'; + * channel.awaitMessageComponent({ filter, time: 15_000 }) + * .then(interaction => console.log(`${interaction.customId} was clicked!`)) + * .catch(console.error); + */ + awaitMessageComponent(options = {}) { + const _options = { ...options, max: 1 }; + return new Promise((resolve, reject) => { + const collector = this.createMessageComponentCollector(_options); + collector.once('end', (interactions, reason) => { + const interaction = interactions.first(); + if (interaction) resolve(interaction); + else reject(new DiscordjsError(ErrorCodes.InteractionCollectorError, reason)); + }); + }); + } + + /** + * Bulk deletes given messages that are newer than two weeks. + * @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages + * Messages or number of messages to delete + * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically + * @returns {Promise<Collection<Snowflake, Message|undefined>>} Returns the deleted messages + * @example + * // Bulk delete messages + * channel.bulkDelete(5) + * .then(messages => console.log(`Bulk deleted ${messages.size} messages`)) + * .catch(console.error); + */ + async bulkDelete(messages, filterOld = false) { + if (Array.isArray(messages) || messages instanceof Collection) { + let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m); + if (filterOld) { + messageIds = messageIds.filter( + id => Date.now() - DiscordSnowflake.timestampFrom(id) < MaxBulkDeletableMessageAge, + ); + } + if (messageIds.length === 0) return new Collection(); + if (messageIds.length === 1) { + const message = this.client.actions.MessageDelete.getMessage( + { + message_id: messageIds[0], + }, + this, + ); + await this.client.rest.delete(Routes.channelMessage(this.id, messageIds[0])); + return message ? new Collection([[message.id, message]]) : new Collection(); + } + await this.client.rest.post(Routes.channelBulkDelete(this.id), { body: { messages: messageIds } }); + return messageIds.reduce( + (col, id) => + col.set( + id, + this.client.actions.MessageDeleteBulk.getMessage( + { + message_id: id, + }, + this, + ), + ), + new Collection(), + ); + } + if (!isNaN(messages)) { + const msgs = await this.messages.fetch({ limit: messages }); + return this.bulkDelete(msgs, filterOld); + } + throw new DiscordjsTypeError(ErrorCodes.MessageBulkDeleteType); + } + + /** + * Fetches all webhooks for the channel. + * @returns {Promise<Collection<Snowflake, Webhook>>} + * @example + * // Fetch webhooks + * channel.fetchWebhooks() + * .then(hooks => console.log(`This channel has ${hooks.size} hooks`)) + * .catch(console.error); + */ + fetchWebhooks() { + return this.guild.channels.fetchWebhooks(this.id); + } + + /** + * Options used to create a {@link Webhook}. + * @typedef {Object} ChannelWebhookCreateOptions + * @property {string} name The name of the webhook + * @property {?(BufferResolvable|Base64Resolvable)} [avatar] Avatar for the webhook + * @property {string} [reason] Reason for creating the webhook + */ + + /** + * Creates a webhook for the channel. + * @param {ChannelWebhookCreateOptions} [options] Options for creating the webhook + * @returns {Promise<Webhook>} Returns the created Webhook + * @example + * // Create a webhook for the current channel + * channel.createWebhook({ + * name: 'Snek', + * avatar: 'https://i.imgur.com/mI8XcpG.jpg', + * reason: 'Needed a cool new Webhook' + * }) + * .then(console.log) + * .catch(console.error) + */ + createWebhook(options) { + return this.guild.channels.createWebhook({ channel: this.id, ...options }); + } + + /** + * Sets the rate limit per user (slowmode) for this channel. + * @param {number} rateLimitPerUser The new rate limit in seconds + * @param {string} [reason] Reason for changing the channel's rate limit + * @returns {Promise<this>} + */ + setRateLimitPerUser(rateLimitPerUser, reason) { + return this.edit({ rateLimitPerUser, reason }); + } + + /** + * Sets whether this channel is flagged as NSFW. + * @param {boolean} [nsfw=true] Whether the channel should be considered NSFW + * @param {string} [reason] Reason for changing the channel's NSFW flag + * @returns {Promise<this>} + */ + setNSFW(nsfw = true, reason) { + return this.edit({ nsfw, reason }); + } + + static applyToClass(structure, full = false, ignore = []) { + const props = ['send']; + if (full) { + props.push( + 'lastMessage', + 'lastPinAt', + 'bulkDelete', + 'sendTyping', + 'createMessageCollector', + 'awaitMessages', + 'createMessageComponentCollector', + 'awaitMessageComponent', + 'fetchWebhooks', + 'createWebhook', + 'setRateLimitPerUser', + 'setNSFW', + ); + } + for (const prop of props) { + if (ignore.includes(prop)) continue; + Object.defineProperty( + structure.prototype, + prop, + Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop), + ); + } + } +} + +module.exports = TextBasedChannel; + +// Fixes Circular +// eslint-disable-next-line import/order +const GuildMessageManager = require('../../managers/GuildMessageManager'); diff --git a/node_modules/discord.js/src/util/APITypes.js b/node_modules/discord.js/src/util/APITypes.js new file mode 100644 index 0000000..e8ab25a --- /dev/null +++ b/node_modules/discord.js/src/util/APITypes.js @@ -0,0 +1,476 @@ +/* eslint-disable max-len */ + +/** + * @external ActivityFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityFlags} + */ + +/** + * @external ActivityType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityType} + */ + +/** + * @external APIActionRowComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIActionRowComponent} + */ + +/** + * @external APIApplication + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIApplication} + */ + +/** + * @external APIApplicationCommand + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIApplicationCommand} + */ + +/** + * @external APIApplicationCommandOption + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIApplicationCommandOption} + */ + +/** + * @external APIAutoModerationAction + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIAutoModerationAction} + */ + +/** + * @external APIButtonComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIButtonComponent} + */ + +/** + * @external APIChannel + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIChannel} + */ + +/** + * @external APIChannelSelectComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIChannelSelectComponent} + */ + +/** + * @external APIEmbed + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIEmbed} + */ + +/** + * @external APIEmbedField + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIEmbedField} + */ + +/** + * @external APIEmbedProvider + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIEmbedProvider} + */ + +/** + * @external APIEmoji + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIEmoji} + */ + +/** + * @external APIGuild + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuild} + */ + +/** + * @external APIGuildForumDefaultReactionEmoji + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildForumDefaultReactionEmoji} + */ + +/** + * @external APIGuildForumTag + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIGuildForumTag} + */ + +/** + * @external APIInteraction + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIInteraction} + */ + +/** + * @external APIInteractionDataResolvedChannel + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIInteractionDataResolvedChannel} + */ + +/** + * @external APIInteractionDataResolvedGuildMember + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIInteractionDataResolvedGuildMember} + */ + +/** + * @external APIInteractionGuildMember + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIInteractionGuildMember} + */ + +/** + * @external APIMentionableSelectComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIMentionableSelectComponent} + */ + +/** + * @external APIMessage + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIMessage} + */ + +/** + * @external APIMessageActionRowComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIMessageActionRowComponent} + */ + +/** + * @external APIMessageComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIMessageComponent} + */ + +/** + * @external APIMessageComponentEmoji + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIMessageComponentEmoji} + */ + +/** + * @external APIModalInteractionResponse + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIModalInteractionResponse} + */ + +/** + * @external APIModalInteractionResponseCallbackData + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIModalInteractionResponseCallbackData} + */ + +/** + * @external APIModalComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIModalComponent} + */ + +/** + * @external APIModalSubmission + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIModalSubmission} + */ + +/** + * @external APIPresence + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIPresence} + */ + +/** + * @external APIRoleSelectComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIRoleSelectComponent} + */ + +/** + * @external APISelectMenuOption + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APISelectMenuOption} + */ + +/** + * @external APIStringSelectComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIStringSelectComponent} + */ + +/** + * @external APITextInputComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APITextInputComponent} + */ + +/** + * @external APIUser + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIUser} + */ + +/** + * @external APIUserSelectComponent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIUserSelectComponent} + */ + +/** + * @external ApplicationCommandType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationCommandType} + */ + +/** + * @external ApplicationCommandOptionType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationCommandOptionType} + */ + +/** + * @external ApplicationCommandPermissionType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationCommandPermissionType} + */ + +/** + * @external ApplicationFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationFlags} + */ + +/** + * @external ApplicationRoleConnectionMetadataType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationRoleConnectionMetadataType} + */ + +/** + * @external AttachmentFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AttachmentFlags} + */ + +/** + * @external AutoModerationActionType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AutoModerationActionType} + */ + +/** + * @external AutoModerationRuleEventType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AutoModerationRuleEventType} + */ + +/** + * @external AutoModerationRuleTriggerType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AutoModerationRuleTriggerType} + */ + +/** + * @external AutoModerationRuleKeywordPresetType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AutoModerationRuleKeywordPresetType} + */ + +/** + * @external AuditLogEvent + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/AuditLogEvent} + */ + +/** + * @external ButtonStyle + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ButtonStyle} + */ + +/** + * @external ChannelFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ChannelFlags} + */ + +/** + * @external ChannelType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ChannelType} + */ + +/** + * @external ComponentType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ComponentType} + */ + +/** + * @external ForumLayoutType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ForumLayoutType} + */ + +/** + * @external GatewayCloseCodes + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GatewayCloseCodes} + */ + +/** + * @external GatewayDispatchEvents + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GatewayDispatchEvents} + */ + +/** + * @external GatewayIntentBits + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GatewayIntentBits} + */ + +/** + * @external GatewayOpcodes + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GatewayOpcodes} + */ + +/** + * @external GuildDefaultMessageNotifications + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildDefaultMessageNotifications} + */ + +/** + * @external GuildExplicitContentFilter + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildExplicitContentFilter} + */ + +/** + * @external GuildFeature + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildFeature} + */ + +/** + * @external GuildMFALevel + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildMFALevel} + */ + +/** + * @external GuildMemberFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildMemberFlags} + */ + +/** + * @external GuildNSFWLevel + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildNSFWLevel} + */ + +/** + * @external GuildOnboardingPromptType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildOnboardingPromptType} + */ + +/** + * @external GuildPremiumTier + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildPremiumTier} + */ + +/** + * @external GuildScheduledEventEntityType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventEntityType} + */ + +/** + * @external GuildScheduledEventPrivacyLevel + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventPrivacyLevel} + */ + +/** + * @external GuildScheduledEventStatus + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildScheduledEventStatus} + */ + +/** + * @external GuildSystemChannelFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildSystemChannelFlags} + */ + +/** + * @external GuildVerificationLevel + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/GuildVerificationLevel} + */ + +/** + * @external IntegrationExpireBehavior + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/IntegrationExpireBehavior} + */ + +/** + * @external InteractionType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/InteractionType} + */ + +/** + * @external InteractionResponseType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/InteractionResponseType} + */ + +/** + * @external InviteTargetType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/InviteTargetType} + */ + +/** + * @external Locale + * @see {@link https://discord-api-types.dev/api/discord-api-types-rest/common/enum/Locale} + */ + +/** + * @external LocaleString + * @see {@link https://discord-api-types.dev/api/discord-api-types-rest/common#LocaleString} + */ + +/** + * @external MessageActivityType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/MessageActivityType} + */ + +/** + * @external MessageType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/MessageType} + */ + +/** + * @external MessageFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/MessageFlags} + */ + +/** + * @external OAuth2Scopes + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/OAuth2Scopes} + */ + +/** + * @external OverwriteType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/OverwriteType} + */ + +/** + * @external PermissionFlagsBits + * @see {@link https://discord-api-types.dev/api/discord-api-types-payloads/common#PermissionFlagsBits} + */ + +/** + * @external RoleFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/RoleFlags} + */ + +/** + * @external RESTGetAPIGuildThreadsResult + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10#RESTGetAPIGuildThreadsResult} + */ + +/** + * @external RESTJSONErrorCodes + * @see {@link https://discord-api-types.dev/api/discord-api-types-rest/common/enum/RESTJSONErrorCodes} + */ + +/** + * @external SortOrderType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/SortOrderType} + */ + +/** + * @external StageInstancePrivacyLevel + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/StageInstancePrivacyLevel} + */ + +/** + * @external StickerType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/StickerType} + */ + +/** + * @external StickerFormatType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/StickerFormatType} + */ + +/** + * @external TeamMemberMembershipState + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/TeamMemberMembershipState} + */ + +/** + * @external TextInputStyle + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/TextInputStyle} + */ + +/** + * @external ThreadAutoArchiveDuration + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ThreadAutoArchiveDuration} + */ + +/** + * @external UserFlags + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/UserFlags} + */ + +/** + * @external VideoQualityMode + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/VideoQualityMode} + */ + +/** + * @external WebhookType + * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/WebhookType} + */ diff --git a/node_modules/discord.js/src/util/ActivityFlagsBitField.js b/node_modules/discord.js/src/util/ActivityFlagsBitField.js new file mode 100644 index 0000000..7c0ef76 --- /dev/null +++ b/node_modules/discord.js/src/util/ActivityFlagsBitField.js @@ -0,0 +1,26 @@ +'use strict'; + +const { ActivityFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with an {@link Activity#flags} bitfield. + * @extends {BitField} + */ +class ActivityFlagsBitField extends BitField { + /** + * Numeric activity flags. + * @type {ActivityFlags} + * @memberof ActivityFlagsBitField + */ + static Flags = ActivityFlags; +} + +/** + * @name ActivityFlagsBitField + * @kind constructor + * @memberof ActivityFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +module.exports = ActivityFlagsBitField; diff --git a/node_modules/discord.js/src/util/ApplicationFlagsBitField.js b/node_modules/discord.js/src/util/ApplicationFlagsBitField.js new file mode 100644 index 0000000..6a5a9fe --- /dev/null +++ b/node_modules/discord.js/src/util/ApplicationFlagsBitField.js @@ -0,0 +1,26 @@ +'use strict'; + +const { ApplicationFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link ClientApplication#flags} bitfield. + * @extends {BitField} + */ +class ApplicationFlagsBitField extends BitField { + /** + * Numeric application flags. All available properties: + * @type {ApplicationFlags} + * @memberof ApplicationFlagsBitField + */ + static Flags = ApplicationFlags; +} + +/** + * @name ApplicationFlagsBitField + * @kind constructor + * @memberof ApplicationFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +module.exports = ApplicationFlagsBitField; diff --git a/node_modules/discord.js/src/util/AttachmentFlagsBitField.js b/node_modules/discord.js/src/util/AttachmentFlagsBitField.js new file mode 100644 index 0000000..f7f2bd2 --- /dev/null +++ b/node_modules/discord.js/src/util/AttachmentFlagsBitField.js @@ -0,0 +1,26 @@ +'use strict'; + +const { AttachmentFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with an {@link Attachment#flags} bitfield. + * @extends {BitField} + */ +class AttachmentFlagsBitField extends BitField { + /** + * Numeric attachment flags. + * @type {AttachmentFlags} + * @memberof AttachmentFlagsBitField + */ + static Flags = AttachmentFlags; +} + +/** + * @name AttachmentFlagsBitField + * @kind constructor + * @memberof AttachmentFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +module.exports = AttachmentFlagsBitField; diff --git a/node_modules/discord.js/src/util/BitField.js b/node_modules/discord.js/src/util/BitField.js new file mode 100644 index 0000000..f0778f6 --- /dev/null +++ b/node_modules/discord.js/src/util/BitField.js @@ -0,0 +1,176 @@ +'use strict'; + +const { DiscordjsRangeError, ErrorCodes } = require('../errors'); + +/** + * Data structure that makes it easy to interact with a bitfield. + */ +class BitField { + /** + * Numeric bitfield flags. + * <info>Defined in extension classes</info> + * @type {Object} + * @memberof BitField + * @abstract + */ + static Flags = {}; + + /** + * @type {number|bigint} + * @memberof BitField + * @private + */ + static DefaultBit = 0; + + /** + * @param {BitFieldResolvable} [bits=this.constructor.DefaultBit] Bit(s) to read from + */ + constructor(bits = this.constructor.DefaultBit) { + /** + * Bitfield of the packed bits + * @type {number|bigint} + */ + this.bitfield = this.constructor.resolve(bits); + } + + /** + * Checks whether the bitfield has a bit, or any of multiple bits. + * @param {BitFieldResolvable} bit Bit(s) to check for + * @returns {boolean} + */ + any(bit) { + return (this.bitfield & this.constructor.resolve(bit)) !== this.constructor.DefaultBit; + } + + /** + * Checks if this bitfield equals another + * @param {BitFieldResolvable} bit Bit(s) to check for + * @returns {boolean} + */ + equals(bit) { + return this.bitfield === this.constructor.resolve(bit); + } + + /** + * Checks whether the bitfield has a bit, or multiple bits. + * @param {BitFieldResolvable} bit Bit(s) to check for + * @returns {boolean} + */ + has(bit) { + bit = this.constructor.resolve(bit); + return (this.bitfield & bit) === bit; + } + + /** + * Gets all given bits that are missing from the bitfield. + * @param {BitFieldResolvable} bits Bit(s) to check for + * @param {...*} hasParams Additional parameters for the has method, if any + * @returns {string[]} + */ + missing(bits, ...hasParams) { + return new this.constructor(bits).remove(this).toArray(...hasParams); + } + + /** + * Freezes these bits, making them immutable. + * @returns {Readonly<BitField>} + */ + freeze() { + return Object.freeze(this); + } + + /** + * Adds bits to these ones. + * @param {...BitFieldResolvable} [bits] Bits to add + * @returns {BitField} These bits or new BitField if the instance is frozen. + */ + add(...bits) { + let total = this.constructor.DefaultBit; + for (const bit of bits) { + total |= this.constructor.resolve(bit); + } + if (Object.isFrozen(this)) return new this.constructor(this.bitfield | total); + this.bitfield |= total; + return this; + } + + /** + * Removes bits from these. + * @param {...BitFieldResolvable} [bits] Bits to remove + * @returns {BitField} These bits or new BitField if the instance is frozen. + */ + remove(...bits) { + let total = this.constructor.DefaultBit; + for (const bit of bits) { + total |= this.constructor.resolve(bit); + } + if (Object.isFrozen(this)) return new this.constructor(this.bitfield & ~total); + this.bitfield &= ~total; + return this; + } + + /** + * Gets an object mapping field names to a {@link boolean} indicating whether the + * bit is available. + * @param {...*} hasParams Additional parameters for the has method, if any + * @returns {Object} + */ + serialize(...hasParams) { + const serialized = {}; + for (const [flag, bit] of Object.entries(this.constructor.Flags)) { + if (isNaN(flag)) serialized[flag] = this.has(bit, ...hasParams); + } + return serialized; + } + + /** + * Gets an {@link Array} of bitfield names based on the bits available. + * @param {...*} hasParams Additional parameters for the has method, if any + * @returns {string[]} + */ + toArray(...hasParams) { + return [...this[Symbol.iterator](...hasParams)]; + } + + toJSON() { + return typeof this.bitfield === 'number' ? this.bitfield : this.bitfield.toString(); + } + + valueOf() { + return this.bitfield; + } + + *[Symbol.iterator](...hasParams) { + for (const bitName of Object.keys(this.constructor.Flags)) { + if (isNaN(bitName) && this.has(bitName, ...hasParams)) yield bitName; + } + } + + /** + * Data that can be resolved to give a bitfield. This can be: + * * A bit number (this can be a number literal or a value taken from {@link BitField.Flags}) + * * A string bit number + * * An instance of BitField + * * An Array of BitFieldResolvable + * @typedef {number|string|bigint|BitField|BitFieldResolvable[]} BitFieldResolvable + */ + + /** + * Resolves bitfields to their numeric form. + * @param {BitFieldResolvable} [bit] bit(s) to resolve + * @returns {number|bigint} + */ + static resolve(bit) { + const { DefaultBit } = this; + if (typeof DefaultBit === typeof bit && bit >= DefaultBit) return bit; + if (bit instanceof BitField) return bit.bitfield; + if (Array.isArray(bit)) return bit.map(p => this.resolve(p)).reduce((prev, p) => prev | p, DefaultBit); + if (typeof bit === 'string') { + if (!isNaN(bit)) return typeof DefaultBit === 'bigint' ? BigInt(bit) : Number(bit); + if (this.Flags[bit] !== undefined) return this.Flags[bit]; + } + throw new DiscordjsRangeError(ErrorCodes.BitFieldInvalid, bit); + } +} + +module.exports = BitField; diff --git a/node_modules/discord.js/src/util/ChannelFlagsBitField.js b/node_modules/discord.js/src/util/ChannelFlagsBitField.js new file mode 100644 index 0000000..ad987e1 --- /dev/null +++ b/node_modules/discord.js/src/util/ChannelFlagsBitField.js @@ -0,0 +1,41 @@ +'use strict'; + +const { ChannelFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link BaseChannel#flags} bitfield. + * @extends {BitField} + */ +class ChannelFlagsBitField extends BitField { + /** + * Numeric guild channel flags. + * @type {ChannelFlags} + * @memberof ChannelFlagsBitField + */ + static Flags = ChannelFlags; +} + +/** + * @name ChannelFlagsBitField + * @kind constructor + * @memberof ChannelFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Bitfield of the packed bits + * @type {number} + * @name ChannelFlagsBitField#bitfield + */ + +/** + * Data that can be resolved to give a channel flag bitfield. This can be: + * * A string (see {@link ChannelFlagsBitField.Flags}) + * * A channel flag + * * An instance of ChannelFlagsBitField + * * An Array of ChannelFlagsResolvable + * @typedef {string|number|ChannelFlagsBitField|ChannelFlagsResolvable[]} ChannelFlagsResolvable + */ + +module.exports = ChannelFlagsBitField; diff --git a/node_modules/discord.js/src/util/Channels.js b/node_modules/discord.js/src/util/Channels.js new file mode 100644 index 0000000..8d070bd --- /dev/null +++ b/node_modules/discord.js/src/util/Channels.js @@ -0,0 +1,150 @@ +'use strict'; + +const { lazy } = require('@discordjs/util'); +const { ChannelType } = require('discord-api-types/v10'); + +const getCategoryChannel = lazy(() => require('../structures/CategoryChannel')); +const getDMChannel = lazy(() => require('../structures/DMChannel')); +const getNewsChannel = lazy(() => require('../structures/NewsChannel')); +const getStageChannel = lazy(() => require('../structures/StageChannel')); +const getTextChannel = lazy(() => require('../structures/TextChannel')); +const getThreadChannel = lazy(() => require('../structures/ThreadChannel')); +const getVoiceChannel = lazy(() => require('../structures/VoiceChannel')); +const getDirectoryChannel = lazy(() => require('../structures/DirectoryChannel')); +const getPartialGroupDMChannel = lazy(() => require('../structures/PartialGroupDMChannel')); +const getForumChannel = lazy(() => require('../structures/ForumChannel')); + +/** + * Creates a discord.js channel from data received from the API. + * @param {Client} client The client + * @param {APIChannel} data The data of the channel to create + * @param {Guild} [guild] The guild where this channel belongs + * @param {Object} [extras] Extra information to supply for creating this channel + * @returns {BaseChannel} Any kind of channel. + * @ignore + */ +function createChannel(client, data, guild, { allowUnknownGuild } = {}) { + let channel; + if (!data.guild_id && !guild) { + if ((data.recipients && data.type !== ChannelType.GroupDM) || data.type === ChannelType.DM) { + channel = new (getDMChannel())(client, data); + } else if (data.type === ChannelType.GroupDM) { + channel = new (getPartialGroupDMChannel())(client, data); + } + } else { + guild ??= client.guilds.cache.get(data.guild_id); + + if (guild || allowUnknownGuild) { + switch (data.type) { + case ChannelType.GuildText: { + channel = new (getTextChannel())(guild, data, client); + break; + } + case ChannelType.GuildVoice: { + channel = new (getVoiceChannel())(guild, data, client); + break; + } + case ChannelType.GuildCategory: { + channel = new (getCategoryChannel())(guild, data, client); + break; + } + case ChannelType.GuildAnnouncement: { + channel = new (getNewsChannel())(guild, data, client); + break; + } + case ChannelType.GuildStageVoice: { + channel = new (getStageChannel())(guild, data, client); + break; + } + case ChannelType.AnnouncementThread: + case ChannelType.PublicThread: + case ChannelType.PrivateThread: { + channel = new (getThreadChannel())(guild, data, client); + if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel); + break; + } + case ChannelType.GuildDirectory: + channel = new (getDirectoryChannel())(guild, data, client); + break; + case ChannelType.GuildForum: + channel = new (getForumChannel())(guild, data, client); + break; + } + if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel); + } + } + return channel; +} + +/** + * Transforms an API guild forum tag to camel-cased guild forum tag. + * @param {APIGuildForumTag} tag The tag to transform + * @returns {GuildForumTag} + * @ignore + */ +function transformAPIGuildForumTag(tag) { + return { + id: tag.id, + name: tag.name, + moderated: tag.moderated, + emoji: + tag.emoji_id ?? tag.emoji_name + ? { + id: tag.emoji_id, + name: tag.emoji_name, + } + : null, + }; +} + +/** + * Transforms a camel-cased guild forum tag to an API guild forum tag. + * @param {GuildForumTag} tag The tag to transform + * @returns {APIGuildForumTag} + * @ignore + */ +function transformGuildForumTag(tag) { + return { + id: tag.id, + name: tag.name, + moderated: tag.moderated, + emoji_id: tag.emoji?.id ?? null, + emoji_name: tag.emoji?.name ?? null, + }; +} + +/** + * Transforms an API guild forum default reaction object to a + * camel-cased guild forum default reaction object. + * @param {APIGuildForumDefaultReactionEmoji} defaultReaction The default reaction to transform + * @returns {DefaultReactionEmoji} + * @ignore + */ +function transformAPIGuildDefaultReaction(defaultReaction) { + return { + id: defaultReaction.emoji_id, + name: defaultReaction.emoji_name, + }; +} + +/** + * Transforms a camel-cased guild forum default reaction object to an + * API guild forum default reaction object. + * @param {DefaultReactionEmoji} defaultReaction The default reaction to transform + * @returns {APIGuildForumDefaultReactionEmoji} + * @ignore + */ +function transformGuildDefaultReaction(defaultReaction) { + return { + emoji_id: defaultReaction.id, + emoji_name: defaultReaction.name, + }; +} + +module.exports = { + createChannel, + transformAPIGuildForumTag, + transformGuildForumTag, + transformAPIGuildDefaultReaction, + transformGuildDefaultReaction, +}; diff --git a/node_modules/discord.js/src/util/Colors.js b/node_modules/discord.js/src/util/Colors.js new file mode 100644 index 0000000..a2d35e5 --- /dev/null +++ b/node_modules/discord.js/src/util/Colors.js @@ -0,0 +1,73 @@ +'use strict'; + +/** + * @typedef {Object} Colors + * @property {number} Default 0x000000 | rgb(0,0,0) + * @property {number} White 0xFFFFFF | rgb(255,255,255) + * @property {number} Aqua 0x1ABC9C | rgb(26,188,156) + * @property {number} Green 0x57F287 | rgb(87,242,135) + * @property {number} Blue 0x3498DB | rgb(52,152,219) + * @property {number} Yellow 0xFEE75C | rgb(254,231,92) + * @property {number} Purple 0x9B59B6 | rgb(155,89,182) + * @property {number} LuminousVividPink 0xE91E63 | rgb(233,30,99) + * @property {number} Fuchsia 0xEB459E | rgb(235,69,158) + * @property {number} Gold 0xF1C40F | rgb(241,196,15) + * @property {number} Orange 0xE67E22 | rgb(230,126,34) + * @property {number} Red 0xED4245 | rgb(237,66,69) + * @property {number} Grey 0x95A5A6 | rgb(149,165,166) + * @property {number} Navy 0x34495E | rgb(52,73,94) + * @property {number} DarkAqua 0x11806A | rgb(17,128,106) + * @property {number} DarkGreen 0x1F8B4C | rgb(31,139,76) + * @property {number} DarkBlue 0x206694 | rgb(32,102,148) + * @property {number} DarkPurple 0x71368A | rgb(113,54,138) + * @property {number} DarkVividPink 0xAD1457 | rgb(173,20,87) + * @property {number} DarkGold 0xC27C0E | rgb(194,124,14) + * @property {number} DarkOrange 0xA84300 | rgb(168,67,0) + * @property {number} DarkRed 0x992D22 | rgb(153,45,34) + * @property {number} DarkGrey 0x979C9F | rgb(151,156,159) + * @property {number} DarkerGrey 0x7F8C8D | rgb(127,140,141) + * @property {number} LightGrey 0xBCC0C0 | rgb(188,192,192) + * @property {number} DarkNavy 0x2C3E50 | rgb(44,62,80) + * @property {number} Blurple 0x5865F2 | rgb(88,101,242) + * @property {number} Greyple 0x99AAb5 | rgb(153,170,181) + * @property {number} DarkButNotBlack 0x2C2F33 | rgb(44,47,51) + * @property {number} NotQuiteBlack 0x23272A | rgb(35,39,42) + */ + +// JSDoc for IntelliSense purposes +/** + * @type {Colors} + * @ignore + */ +module.exports = { + Default: 0x000000, + White: 0xffffff, + Aqua: 0x1abc9c, + Green: 0x57f287, + Blue: 0x3498db, + Yellow: 0xfee75c, + Purple: 0x9b59b6, + LuminousVividPink: 0xe91e63, + Fuchsia: 0xeb459e, + Gold: 0xf1c40f, + Orange: 0xe67e22, + Red: 0xed4245, + Grey: 0x95a5a6, + Navy: 0x34495e, + DarkAqua: 0x11806a, + DarkGreen: 0x1f8b4c, + DarkBlue: 0x206694, + DarkPurple: 0x71368a, + DarkVividPink: 0xad1457, + DarkGold: 0xc27c0e, + DarkOrange: 0xa84300, + DarkRed: 0x992d22, + DarkGrey: 0x979c9f, + DarkerGrey: 0x7f8c8d, + LightGrey: 0xbcc0c0, + DarkNavy: 0x2c3e50, + Blurple: 0x5865f2, + Greyple: 0x99aab5, + DarkButNotBlack: 0x2c2f33, + NotQuiteBlack: 0x23272a, +}; diff --git a/node_modules/discord.js/src/util/Components.js b/node_modules/discord.js/src/util/Components.js new file mode 100644 index 0000000..1a2ccbc --- /dev/null +++ b/node_modules/discord.js/src/util/Components.js @@ -0,0 +1,152 @@ +'use strict'; + +// This file contains the typedefs for camel-cased JSON data +const { ComponentBuilder } = require('@discordjs/builders'); +const { ComponentType } = require('discord-api-types/v10'); +/** + * @typedef {Object} BaseComponentData + * @property {ComponentType} type The type of component + */ + +/** + * @typedef {BaseComponentData} ActionRowData + * @property {ComponentData[]} components The components in this action row + */ + +/** + * @typedef {BaseComponentData} ButtonComponentData + * @property {ButtonStyle} style The style of the button + * @property {?boolean} disabled Whether this button is disabled + * @property {string} label The label of this button + * @property {?APIMessageComponentEmoji} emoji The emoji on this button + * @property {?string} customId The custom id of the button + * @property {?string} url The URL of the button + */ + +/** + * @typedef {object} SelectMenuComponentOptionData + * @property {string} label The label of the option + * @property {string} value The value of the option + * @property {?string} description The description of the option + * @property {?APIMessageComponentEmoji} emoji The emoji on the option + * @property {?boolean} default Whether this option is selected by default + */ + +/** + * @typedef {BaseComponentData} SelectMenuComponentData + * @property {string} customId The custom id of the select menu + * @property {?boolean} disabled Whether the select menu is disabled or not + * @property {?number} maxValues The maximum amount of options that can be selected + * @property {?number} minValues The minimum amount of options that can be selected + * @property {?SelectMenuComponentOptionData[]} options The options in this select menu + * @property {?string} placeholder The placeholder of the select menu + */ + +/** + * @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData} MessageComponentData + */ + +/** + * @typedef {BaseComponentData} TextInputComponentData + * @property {string} customId The custom id of the text input + * @property {TextInputStyle} style The style of the text input + * @property {string} label The text that appears on top of the text input field + * @property {?number} minLength The minimum number of characters that can be entered in the text input + * @property {?number} maxLength The maximum number of characters that can be entered in the text input + * @property {?boolean} required Whether or not the text input is required or not + * @property {?string} value The pre-filled text in the text input + * @property {?string} placeholder Placeholder for the text input + */ + +/** + * @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData|TextInputComponentData} ComponentData + */ + +/** + * Any emoji data that can be used within a button + * @typedef {APIMessageComponentEmoji|string} ComponentEmojiResolvable + */ + +/** + * Transforms API data into a component + * @param {APIMessageComponent|Component} data The data to create the component from + * @returns {Component} + */ +function createComponent(data) { + if (data instanceof Component) { + return data; + } + + switch (data.type) { + case ComponentType.ActionRow: + return new ActionRow(data); + case ComponentType.Button: + return new ButtonComponent(data); + case ComponentType.StringSelect: + return new StringSelectMenuComponent(data); + case ComponentType.TextInput: + return new TextInputComponent(data); + case ComponentType.UserSelect: + return new UserSelectMenuComponent(data); + case ComponentType.RoleSelect: + return new RoleSelectMenuComponent(data); + case ComponentType.MentionableSelect: + return new MentionableSelectMenuComponent(data); + case ComponentType.ChannelSelect: + return new ChannelSelectMenuComponent(data); + default: + return new Component(data); + } +} + +/** + * Transforms API data into a component builder + * @param {APIMessageComponent|ComponentBuilder} data The data to create the component from + * @returns {ComponentBuilder} + */ +function createComponentBuilder(data) { + if (data instanceof ComponentBuilder) { + return data; + } + + switch (data.type) { + case ComponentType.ActionRow: + return new ActionRowBuilder(data); + case ComponentType.Button: + return new ButtonBuilder(data); + case ComponentType.StringSelect: + return new StringSelectMenuBuilder(data); + case ComponentType.TextInput: + return new TextInputBuilder(data); + case ComponentType.UserSelect: + return new UserSelectMenuBuilder(data); + case ComponentType.RoleSelect: + return new RoleSelectMenuBuilder(data); + case ComponentType.MentionableSelect: + return new MentionableSelectMenuBuilder(data); + case ComponentType.ChannelSelect: + return new ChannelSelectMenuBuilder(data); + default: + return new ComponentBuilder(data); + } +} + +module.exports = { createComponent, createComponentBuilder }; + +const ActionRow = require('../structures/ActionRow'); +const ActionRowBuilder = require('../structures/ActionRowBuilder'); +const ButtonBuilder = require('../structures/ButtonBuilder'); +const ButtonComponent = require('../structures/ButtonComponent'); +const ChannelSelectMenuBuilder = require('../structures/ChannelSelectMenuBuilder'); +const ChannelSelectMenuComponent = require('../structures/ChannelSelectMenuComponent'); +const Component = require('../structures/Component'); +const MentionableSelectMenuBuilder = require('../structures/MentionableSelectMenuBuilder'); +const MentionableSelectMenuComponent = require('../structures/MentionableSelectMenuComponent'); +const RoleSelectMenuBuilder = require('../structures/RoleSelectMenuBuilder'); +const RoleSelectMenuComponent = require('../structures/RoleSelectMenuComponent'); +const StringSelectMenuBuilder = require('../structures/StringSelectMenuBuilder'); +const StringSelectMenuComponent = require('../structures/StringSelectMenuComponent'); +const TextInputBuilder = require('../structures/TextInputBuilder'); +const TextInputComponent = require('../structures/TextInputComponent'); +const UserSelectMenuBuilder = require('../structures/UserSelectMenuBuilder'); +const UserSelectMenuComponent = require('../structures/UserSelectMenuComponent'); diff --git a/node_modules/discord.js/src/util/Constants.js b/node_modules/discord.js/src/util/Constants.js new file mode 100644 index 0000000..c2d3da9 --- /dev/null +++ b/node_modules/discord.js/src/util/Constants.js @@ -0,0 +1,230 @@ +'use strict'; + +const { ChannelType, MessageType, ComponentType, ImageFormat, StickerFormatType } = require('discord-api-types/v10'); + +/** + * Max bulk deletable message age + * @typedef {number} MaxBulkDeletableMessageAge + */ +exports.MaxBulkDeletableMessageAge = 1_209_600_000; + +/** + * The name of an item to be swept in Sweepers + * * `autoModerationRules` + * * `applicationCommands` - both global and guild commands + * * `bans` + * * `emojis` + * * `invites` - accepts the `lifetime` property, using it will sweep based on expires timestamp + * * `guildMembers` + * * `messages` - accepts the `lifetime` property, using it will sweep based on edited or created timestamp + * * `presences` + * * `reactions` + * * `stageInstances` + * * `stickers` + * * `threadMembers` + * * `threads` - accepts the `lifetime` property, using it will sweep archived threads based on archived timestamp + * * `users` + * * `voiceStates` + * @typedef {string} SweeperKey + */ +exports.SweeperKeys = [ + 'autoModerationRules', + 'applicationCommands', + 'bans', + 'emojis', + 'invites', + 'guildMembers', + 'messages', + 'presences', + 'reactions', + 'stageInstances', + 'stickers', + 'threadMembers', + 'threads', + 'users', + 'voiceStates', +]; + +/** + * The types of messages that are not `System`. The available types are: + * * {@link MessageType.Default} + * * {@link MessageType.Reply} + * * {@link MessageType.ChatInputCommand} + * * {@link MessageType.ContextMenuCommand} + * @typedef {MessageType[]} NonSystemMessageTypes + */ +exports.NonSystemMessageTypes = [ + MessageType.Default, + MessageType.Reply, + MessageType.ChatInputCommand, + MessageType.ContextMenuCommand, +]; + +/** + * The guild channels that are text-based. + * * TextChannel + * * NewsChannel + * * ThreadChannel + * * VoiceChannel + * * StageChannel + * @typedef {TextChannel|NewsChannel|ThreadChannel|VoiceChannel|StageChannel} GuildTextBasedChannel + */ + +/** + * The types of guild channels that are text-based. The available types are: + * * {@link ChannelType.GuildText} + * * {@link ChannelType.GuildAnnouncement} + * * {@link ChannelType.AnnouncementThread} + * * {@link ChannelType.PublicThread} + * * {@link ChannelType.PrivateThread} + * * {@link ChannelType.GuildVoice} + * * {@link ChannelType.GuildStageVoice} + * @typedef {ChannelType[]} GuildTextBasedChannelTypes + */ +exports.GuildTextBasedChannelTypes = [ + ChannelType.GuildText, + ChannelType.GuildAnnouncement, + ChannelType.AnnouncementThread, + ChannelType.PublicThread, + ChannelType.PrivateThread, + ChannelType.GuildVoice, + ChannelType.GuildStageVoice, +]; + +/** + * The channels that are text-based. + * * DMChannel + * * GuildTextBasedChannel + * @typedef {DMChannel|GuildTextBasedChannel} TextBasedChannels + */ + +/** + * Data that resolves to give a text-based channel. This can be: + * * A text-based channel + * * A snowflake + * @typedef {TextBasedChannels|Snowflake} TextBasedChannelsResolvable + */ + +/** + * The types of channels that are text-based. The available types are: + * * {@link ChannelType.DM} + * * {@link ChannelType.GuildText} + * * {@link ChannelType.GuildAnnouncement} + * * {@link ChannelType.AnnouncementThread} + * * {@link ChannelType.PublicThread} + * * {@link ChannelType.PrivateThread} + * * {@link ChannelType.GuildVoice} + * * {@link ChannelType.GuildStageVoice} + * @typedef {ChannelType[]} TextBasedChannelTypes + */ +exports.TextBasedChannelTypes = [...exports.GuildTextBasedChannelTypes, ChannelType.DM]; + +/** + * The types of channels that are threads. The available types are: + * * {@link ChannelType.AnnouncementThread} + * * {@link ChannelType.PublicThread} + * * {@link ChannelType.PrivateThread} + * @typedef {ChannelType[]} ThreadChannelTypes + */ +exports.ThreadChannelTypes = [ChannelType.AnnouncementThread, ChannelType.PublicThread, ChannelType.PrivateThread]; + +/** + * The types of channels that are voice-based. The available types are: + * * {@link ChannelType.GuildVoice} + * * {@link ChannelType.GuildStageVoice} + * @typedef {ChannelType[]} VoiceBasedChannelTypes + */ +exports.VoiceBasedChannelTypes = [ChannelType.GuildVoice, ChannelType.GuildStageVoice]; + +/** + * The types of select menus. The available types are: + * * {@link ComponentType.StringSelect} + * * {@link ComponentType.UserSelect} + * * {@link ComponentType.RoleSelect} + * * {@link ComponentType.MentionableSelect} + * * {@link ComponentType.ChannelSelect} + * @typedef {ComponentType[]} SelectMenuTypes + */ +exports.SelectMenuTypes = [ + ComponentType.StringSelect, + ComponentType.UserSelect, + ComponentType.RoleSelect, + ComponentType.MentionableSelect, + ComponentType.ChannelSelect, +]; + +/** + * The types of messages that can be deleted. The available types are: + * * {@link MessageType.AutoModerationAction} + * * {@link MessageType.ChannelFollowAdd} + * * {@link MessageType.ChannelPinnedMessage} + * * {@link MessageType.ChatInputCommand} + * * {@link MessageType.ContextMenuCommand} + * * {@link MessageType.Default} + * * {@link MessageType.GuildBoost} + * * {@link MessageType.GuildBoostTier1} + * * {@link MessageType.GuildBoostTier2} + * * {@link MessageType.GuildBoostTier3} + * * {@link MessageType.GuildInviteReminder} + * * {@link MessageType.InteractionPremiumUpsell} + * * {@link MessageType.Reply} + * * {@link MessageType.RoleSubscriptionPurchase} + * * {@link MessageType.StageEnd} + * * {@link MessageType.StageRaiseHand} + * * {@link MessageType.StageSpeaker} + * * {@link MessageType.StageStart} + * * {@link MessageType.StageTopic} + * * {@link MessageType.ThreadCreated} + * * {@link MessageType.UserJoin} + * @typedef {MessageType[]} DeletableMessageTypes + */ +exports.DeletableMessageTypes = [ + MessageType.AutoModerationAction, + MessageType.ChannelFollowAdd, + MessageType.ChannelPinnedMessage, + MessageType.ChatInputCommand, + MessageType.ContextMenuCommand, + MessageType.Default, + MessageType.GuildBoost, + MessageType.GuildBoostTier1, + MessageType.GuildBoostTier2, + MessageType.GuildBoostTier3, + MessageType.GuildInviteReminder, + MessageType.InteractionPremiumUpsell, + MessageType.Reply, + MessageType.RoleSubscriptionPurchase, + MessageType.StageEnd, + MessageType.StageRaiseHand, + MessageType.StageSpeaker, + MessageType.StageStart, + MessageType.StageTopic, + MessageType.ThreadCreated, + MessageType.UserJoin, +]; + +/** + * A mapping between sticker formats and their respective image formats. + * * {@link StickerFormatType.PNG} -> {@link ImageFormat.PNG} + * * {@link StickerFormatType.APNG} -> {@link ImageFormat.PNG} + * * {@link StickerFormatType.Lottie} -> {@link ImageFormat.Lottie} + * * {@link StickerFormatType.GIF} -> {@link ImageFormat.GIF} + * @typedef {Object} StickerFormatExtensionMap + */ +exports.StickerFormatExtensionMap = { + [StickerFormatType.PNG]: ImageFormat.PNG, + [StickerFormatType.APNG]: ImageFormat.PNG, + [StickerFormatType.Lottie]: ImageFormat.Lottie, + [StickerFormatType.GIF]: ImageFormat.GIF, +}; + +/** + * @typedef {Object} Constants Constants that can be used in an enum or object-like way. + * @property {number} MaxBulkDeletableMessageAge Max bulk deletable message age + * @property {SweeperKey[]} SweeperKeys The possible names of items that can be swept in sweepers + * @property {NonSystemMessageTypes} NonSystemMessageTypes The types of messages that are not deemed a system type + * @property {TextBasedChannelTypes} TextBasedChannelTypes The types of channels that are text-based + * @property {ThreadChannelTypes} ThreadChannelTypes The types of channels that are threads + * @property {VoiceBasedChannelTypes} VoiceBasedChannelTypes The types of channels that are voice-based + * @property {SelectMenuTypes} SelectMenuTypes The types of components that are select menus. + * @property {Object} StickerFormatExtensionMap A mapping between sticker formats and their respective image formats. + */ diff --git a/node_modules/discord.js/src/util/DataResolver.js b/node_modules/discord.js/src/util/DataResolver.js new file mode 100644 index 0000000..6b8a64c --- /dev/null +++ b/node_modules/discord.js/src/util/DataResolver.js @@ -0,0 +1,140 @@ +'use strict'; + +const { Buffer } = require('node:buffer'); +const fs = require('node:fs/promises'); +const path = require('node:path'); +const { fetch } = require('undici'); +const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); +const Invite = require('../structures/Invite'); + +/** + * The DataResolver identifies different objects and tries to resolve a specific piece of information from them. + * @private + */ +class DataResolver extends null { + /** + * Data that can be resolved to give an invite code. This can be: + * * An invite code + * * An invite URL + * @typedef {string} InviteResolvable + */ + + /** + * Data that can be resolved to give a template code. This can be: + * * A template code + * * A template URL + * @typedef {string} GuildTemplateResolvable + */ + + /** + * Resolves the string to a code based on the passed regex. + * @param {string} data The string to resolve + * @param {RegExp} regex The RegExp used to extract the code + * @returns {string} + */ + static resolveCode(data, regex) { + return regex.exec(data)?.[1] ?? data; + } + + /** + * Resolves InviteResolvable to an invite code. + * @param {InviteResolvable} data The invite resolvable to resolve + * @returns {string} + */ + static resolveInviteCode(data) { + return this.resolveCode(data, Invite.InvitesPattern); + } + + /** + * Resolves GuildTemplateResolvable to a template code. + * @param {GuildTemplateResolvable} data The template resolvable to resolve + * @returns {string} + */ + static resolveGuildTemplateCode(data) { + const GuildTemplate = require('../structures/GuildTemplate'); + return this.resolveCode(data, GuildTemplate.GuildTemplatesPattern); + } + + /** + * Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image. + * @param {BufferResolvable|Base64Resolvable} image The image to be resolved + * @returns {Promise<?string>} + */ + static async resolveImage(image) { + if (!image) return null; + if (typeof image === 'string' && image.startsWith('data:')) { + return image; + } + const file = await this.resolveFile(image); + return this.resolveBase64(file.data); + } + + /** + * Data that resolves to give a Base64 string, typically for image uploading. This can be: + * * A Buffer + * * A base64 string + * @typedef {Buffer|string} Base64Resolvable + */ + + /** + * Resolves a Base64Resolvable to a Base 64 image. + * @param {Base64Resolvable} data The base 64 resolvable you want to resolve + * @returns {?string} + */ + static resolveBase64(data) { + if (Buffer.isBuffer(data)) return `data:image/jpg;base64,${data.toString('base64')}`; + return data; + } + + /** + * Data that can be resolved to give a Buffer. This can be: + * * A Buffer + * * The path to a local file + * * A URL <warn>When provided a URL, discord.js will fetch the URL internally in order to create a Buffer. + * This can pose a security risk when the URL has not been sanitized</warn> + * @typedef {string|Buffer} BufferResolvable + */ + + /** + * @external Stream + * @see {@link https://nodejs.org/api/stream.html} + */ + + /** + * @typedef {Object} ResolvedFile + * @property {Buffer} data Buffer containing the file data + * @property {string} [contentType] Content type of the file + */ + + /** + * Resolves a BufferResolvable to a Buffer. + * @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve + * @returns {Promise<ResolvedFile>} + */ + static async resolveFile(resource) { + if (Buffer.isBuffer(resource)) return { data: resource }; + + if (typeof resource[Symbol.asyncIterator] === 'function') { + const buffers = []; + for await (const data of resource) buffers.push(Buffer.from(data)); + return { data: Buffer.concat(buffers) }; + } + + if (typeof resource === 'string') { + if (/^https?:\/\//.test(resource)) { + const res = await fetch(resource); + return { data: Buffer.from(await res.arrayBuffer()), contentType: res.headers.get('content-type') }; + } + + const file = path.resolve(resource); + + const stats = await fs.stat(file); + if (!stats.isFile()) throw new DiscordjsError(ErrorCodes.FileNotFound, file); + return { data: await fs.readFile(file) }; + } + + throw new DiscordjsTypeError(ErrorCodes.ReqResourceType); + } +} + +module.exports = DataResolver; diff --git a/node_modules/discord.js/src/util/Enums.js b/node_modules/discord.js/src/util/Enums.js new file mode 100644 index 0000000..e3e5cac --- /dev/null +++ b/node_modules/discord.js/src/util/Enums.js @@ -0,0 +1,13 @@ +'use strict'; + +function createEnum(keys) { + const obj = {}; + for (const [index, key] of keys.entries()) { + if (key === null) continue; + obj[key] = index; + obj[index] = key; + } + return obj; +} + +module.exports = { createEnum }; diff --git a/node_modules/discord.js/src/util/Events.js b/node_modules/discord.js/src/util/Events.js new file mode 100644 index 0000000..1a83ec8 --- /dev/null +++ b/node_modules/discord.js/src/util/Events.js @@ -0,0 +1,162 @@ +'use strict'; + +/** + * @typedef {Object} Events + * @property {string} ApplicationCommandPermissionsUpdate applicationCommandPermissionsUpdate + * @property {string} AutoModerationActionExecution autoModerationActionExecution + * @property {string} AutoModerationRuleCreate autoModerationRuleCreate + * @property {string} AutoModerationRuleDelete autoModerationRuleDelete + * @property {string} AutoModerationRuleUpdate autoModerationRuleUpdate + * @property {string} CacheSweep cacheSweep + * @property {string} ChannelCreate channelCreate + * @property {string} ChannelDelete channelDelete + * @property {string} ChannelPinsUpdate channelPinsUpdate + * @property {string} ChannelUpdate channelUpdate + * @property {string} ClientReady ready + * @property {string} Debug debug + * @property {string} Error error + * @property {string} GuildAuditLogEntryCreate guildAuditLogEntryCreate + * @property {string} GuildAvailable guildAvailable + * @property {string} GuildBanAdd guildBanAdd + * @property {string} GuildBanRemove guildBanRemove + * @property {string} GuildCreate guildCreate + * @property {string} GuildDelete guildDelete + * @property {string} GuildEmojiCreate emojiCreate + * @property {string} GuildEmojiDelete emojiDelete + * @property {string} GuildEmojiUpdate emojiUpdate + * @property {string} GuildIntegrationsUpdate guildIntegrationsUpdate + * @property {string} GuildMemberAdd guildMemberAdd + * @property {string} GuildMemberAvailable guildMemberAvailable + * @property {string} GuildMemberRemove guildMemberRemove + * @property {string} GuildMembersChunk guildMembersChunk + * @property {string} GuildMemberUpdate guildMemberUpdate + * @property {string} GuildRoleCreate roleCreate + * @property {string} GuildRoleDelete roleDelete + * @property {string} GuildRoleUpdate roleUpdate + * @property {string} GuildScheduledEventCreate guildScheduledEventCreate + * @property {string} GuildScheduledEventDelete guildScheduledEventDelete + * @property {string} GuildScheduledEventUpdate guildScheduledEventUpdate + * @property {string} GuildScheduledEventUserAdd guildScheduledEventUserAdd + * @property {string} GuildScheduledEventUserRemove guildScheduledEventUserRemove + * @property {string} GuildStickerCreate stickerCreate + * @property {string} GuildStickerDelete stickerDelete + * @property {string} GuildStickerUpdate stickerUpdate + * @property {string} GuildUnavailable guildUnavailable + * @property {string} GuildUpdate guildUpdate + * @property {string} InteractionCreate interactionCreate + * @property {string} Invalidated invalidated + * @property {string} InviteCreate inviteCreate + * @property {string} InviteDelete inviteDelete + * @property {string} MessageBulkDelete messageDeleteBulk + * @property {string} MessageCreate messageCreate + * @property {string} MessageDelete messageDelete + * @property {string} MessageReactionAdd messageReactionAdd + * @property {string} MessageReactionRemove messageReactionRemove + * @property {string} MessageReactionRemoveAll messageReactionRemoveAll + * @property {string} MessageReactionRemoveEmoji messageReactionRemoveEmoji + * @property {string} MessageUpdate messageUpdate + * @property {string} PresenceUpdate presenceUpdate + * @property {string} ShardDisconnect shardDisconnect + * @property {string} ShardError shardError + * @property {string} ShardReady shardReady + * @property {string} ShardReconnecting shardReconnecting + * @property {string} ShardResume shardResume + * @property {string} StageInstanceCreate stageInstanceCreate + * @property {string} StageInstanceDelete stageInstanceDelete + * @property {string} StageInstanceUpdate stageInstanceUpdate + * @property {string} ThreadCreate threadCreate + * @property {string} ThreadDelete threadDelete + * @property {string} ThreadListSync threadListSync + * @property {string} ThreadMembersUpdate threadMembersUpdate + * @property {string} ThreadMemberUpdate threadMemberUpdate + * @property {string} ThreadUpdate threadUpdate + * @property {string} TypingStart typingStart + * @property {string} UserUpdate userUpdate + * @property {string} VoiceServerUpdate voiceServerUpdate + * @property {string} VoiceStateUpdate voiceStateUpdate + * @property {string} Warn warn + * @property {string} WebhooksUpdate webhookUpdate + */ + +// JSDoc for IntelliSense purposes +/** + * @type {Events} + * @ignore + */ +module.exports = { + ApplicationCommandPermissionsUpdate: 'applicationCommandPermissionsUpdate', + AutoModerationActionExecution: 'autoModerationActionExecution', + AutoModerationRuleCreate: 'autoModerationRuleCreate', + AutoModerationRuleDelete: 'autoModerationRuleDelete', + AutoModerationRuleUpdate: 'autoModerationRuleUpdate', + CacheSweep: 'cacheSweep', + ChannelCreate: 'channelCreate', + ChannelDelete: 'channelDelete', + ChannelPinsUpdate: 'channelPinsUpdate', + ChannelUpdate: 'channelUpdate', + ClientReady: 'ready', + Debug: 'debug', + Error: 'error', + GuildAuditLogEntryCreate: 'guildAuditLogEntryCreate', + GuildAvailable: 'guildAvailable', + GuildBanAdd: 'guildBanAdd', + GuildBanRemove: 'guildBanRemove', + GuildCreate: 'guildCreate', + GuildDelete: 'guildDelete', + GuildEmojiCreate: 'emojiCreate', + GuildEmojiDelete: 'emojiDelete', + GuildEmojiUpdate: 'emojiUpdate', + GuildIntegrationsUpdate: 'guildIntegrationsUpdate', + GuildMemberAdd: 'guildMemberAdd', + GuildMemberAvailable: 'guildMemberAvailable', + GuildMemberRemove: 'guildMemberRemove', + GuildMembersChunk: 'guildMembersChunk', + GuildMemberUpdate: 'guildMemberUpdate', + GuildRoleCreate: 'roleCreate', + GuildRoleDelete: 'roleDelete', + GuildRoleUpdate: 'roleUpdate', + GuildScheduledEventCreate: 'guildScheduledEventCreate', + GuildScheduledEventDelete: 'guildScheduledEventDelete', + GuildScheduledEventUpdate: 'guildScheduledEventUpdate', + GuildScheduledEventUserAdd: 'guildScheduledEventUserAdd', + GuildScheduledEventUserRemove: 'guildScheduledEventUserRemove', + GuildStickerCreate: 'stickerCreate', + GuildStickerDelete: 'stickerDelete', + GuildStickerUpdate: 'stickerUpdate', + GuildUnavailable: 'guildUnavailable', + GuildUpdate: 'guildUpdate', + InteractionCreate: 'interactionCreate', + Invalidated: 'invalidated', + InviteCreate: 'inviteCreate', + InviteDelete: 'inviteDelete', + MessageBulkDelete: 'messageDeleteBulk', + MessageCreate: 'messageCreate', + MessageDelete: 'messageDelete', + MessageReactionAdd: 'messageReactionAdd', + MessageReactionRemove: 'messageReactionRemove', + MessageReactionRemoveAll: 'messageReactionRemoveAll', + MessageReactionRemoveEmoji: 'messageReactionRemoveEmoji', + MessageUpdate: 'messageUpdate', + PresenceUpdate: 'presenceUpdate', + Raw: 'raw', + ShardDisconnect: 'shardDisconnect', + ShardError: 'shardError', + ShardReady: 'shardReady', + ShardReconnecting: 'shardReconnecting', + ShardResume: 'shardResume', + StageInstanceCreate: 'stageInstanceCreate', + StageInstanceDelete: 'stageInstanceDelete', + StageInstanceUpdate: 'stageInstanceUpdate', + ThreadCreate: 'threadCreate', + ThreadDelete: 'threadDelete', + ThreadListSync: 'threadListSync', + ThreadMembersUpdate: 'threadMembersUpdate', + ThreadMemberUpdate: 'threadMemberUpdate', + ThreadUpdate: 'threadUpdate', + TypingStart: 'typingStart', + UserUpdate: 'userUpdate', + VoiceServerUpdate: 'voiceServerUpdate', + VoiceStateUpdate: 'voiceStateUpdate', + Warn: 'warn', + WebhooksUpdate: 'webhookUpdate', +}; diff --git a/node_modules/discord.js/src/util/Formatters.js b/node_modules/discord.js/src/util/Formatters.js new file mode 100644 index 0000000..bd93a36 --- /dev/null +++ b/node_modules/discord.js/src/util/Formatters.js @@ -0,0 +1,413 @@ +'use strict'; + +const { deprecate } = require('node:util'); +const { + blockQuote, + bold, + channelMention, + codeBlock, + formatEmoji, + hideLinkEmbed, + hyperlink, + inlineCode, + italic, + quote, + roleMention, + spoiler, + strikethrough, + time, + TimestampStyles, + underscore, + userMention, +} = require('@discordjs/builders'); + +/** + * Formats an application command name and id into an application command mention. + * @method chatInputApplicationCommandMention + * @param {string} commandName The name of the application command + * @param {string|Snowflake} subcommandGroupOrSubOrId + * The subcommand group name, subcommand name, or application command id + * @param {string|Snowflake} [subcommandNameOrId] The subcommand name or application command id + * @param {string} [commandId] The id of the application command + * @returns {string} + */ + +/** + * Wraps the content inside a code block with an optional language. + * @method codeBlock + * @param {string} contentOrLanguage The language to use or content if a second parameter isn't provided + * @param {string} [content] The content to wrap + * @returns {string} + */ + +/** + * Wraps the content inside \`backticks\`, which formats it as inline code. + * @method inlineCode + * @param {string} content The content to wrap + * @returns {string} + */ + +/** + * Formats the content into italic text. + * @method italic + * @param {string} content The content to wrap + * @returns {string} + */ + +/** + * Formats the content into bold text. + * @method bold + * @param {string} content The content to wrap + * @returns {string} + */ + +/** + * Formats the content into underscored text. + * @method underscore + * @param {string} content The content to wrap + * @returns {string} + */ + +/** + * Formats the content into strike-through text. + * @method strikethrough + * @param {string} content The content to wrap + * @returns {string} + */ + +/** + * Formats the content into a quote. + * <info>This needs to be at the start of the line for Discord to format it.</info> + * @method quote + * @param {string} content The content to wrap + * @returns {string} + */ + +/** + * Formats the content into a block quote. + * <info>This needs to be at the start of the line for Discord to format it.</info> + * @method blockQuote + * @param {string} content The content to wrap + * @returns {string} + */ + +/** + * Wraps the URL into `<>`, which stops it from embedding. + * @method hideLinkEmbed + * @param {string} content The content to wrap + * @returns {string} + */ + +/** + * Formats the content and the URL into a masked URL with an optional title. + * @method hyperlink + * @param {string} content The content to display + * @param {string} url The URL the content links to + * @param {string} [title] The title shown when hovering on the masked link + * @returns {string} + */ + +/** + * Formats the content into spoiler text. + * @method spoiler + * @param {string} content The content to spoiler + * @returns {string} + */ + +/** + * Formats a user id into a user mention. + * @method userMention + * @param {Snowflake} userId The user id to format + * @returns {string} + */ + +/** + * Formats a channel id into a channel mention. + * @method channelMention + * @param {Snowflake} channelId The channel id to format + * @returns {string} + */ + +/** + * Formats a role id into a role mention. + * @method roleMention + * @param {Snowflake} roleId The role id to format + * @returns {string} + */ + +/** + * Formats an emoji id into a fully qualified emoji identifier. + * @method formatEmoji + * @param {Snowflake} emojiId The emoji id to format + * @param {boolean} [animated=false] Whether the emoji is animated + * @returns {string} + */ + +/** + * Formats a channel link for a channel. + * @method channelLink + * @param {Snowflake} channelId The id of the channel + * @param {Snowflake} [guildId] The id of the guild + * @returns {string} + */ + +/** + * Formats a message link for a channel. + * @method messageLink + * @param {Snowflake} channelId The id of the channel + * @param {Snowflake} messageId The id of the message + * @param {Snowflake} [guildId] The id of the guild + * @returns {string} + */ + +/** + * A message formatting timestamp style, as defined in + * [here](https://discord.com/developers/docs/reference#message-formatting-timestamp-styles). + * * `t` Short time format, consisting of hours and minutes, e.g. 16:20. + * * `T` Long time format, consisting of hours, minutes, and seconds, e.g. 16:20:30. + * * `d` Short date format, consisting of day, month, and year, e.g. 20/04/2021. + * * `D` Long date format, consisting of day, month, and year, e.g. 20 April 2021. + * * `f` Short date-time format, consisting of short date and short time formats, e.g. 20 April 2021 16:20. + * * `F` Long date-time format, consisting of long date and short time formats, e.g. Tuesday, 20 April 2021 16:20. + * * `R` Relative time format, consisting of a relative duration format, e.g. 2 months ago. + * @typedef {string} TimestampStylesString + */ + +/** + * Formats a date into a short date-time string. + * @method time + * @param {number|Date} [date] The date to format + * @param {TimestampStylesString} [style] The style to use + * @returns {string} + */ + +/** + * Contains various Discord-specific functions for formatting messages. + * @deprecated This class is redundant as all methods of the class can be imported from discord.js directly. + */ +class Formatters extends null { + /** + * Formats the content into a block quote. + * <info>This needs to be at the start of the line for Discord to format it.</info> + * @method blockQuote + * @memberof Formatters + * @param {string} content The content to wrap + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static blockQuote = deprecate( + blockQuote, + 'Formatters.blockQuote() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats the content into bold text. + * @method bold + * @memberof Formatters + * @param {string} content The content to wrap + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static bold = deprecate( + bold, + 'Formatters.bold() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats a channel id into a channel mention. + * @method channelMention + * @memberof Formatters + * @param {Snowflake} channelId The channel id to format + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static channelMention = deprecate( + channelMention, + 'Formatters.channelMention() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Wraps the content inside a code block with an optional language. + * @method codeBlock + * @memberof Formatters + * @param {string} contentOrLanguage The language to use or content if a second parameter isn't provided + * @param {string} [content] The content to wrap + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static codeBlock = deprecate( + codeBlock, + 'Formatters.codeBlock() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats an emoji id into a fully qualified emoji identifier. + * @method formatEmoji + * @memberof Formatters + * @param {string} emojiId The emoji id to format + * @param {boolean} [animated=false] Whether the emoji is animated + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static formatEmoji = deprecate( + formatEmoji, + 'Formatters.formatEmoji() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Wraps the URL into `<>`, which stops it from embedding. + * @method hideLinkEmbed + * @memberof Formatters + * @param {string} content The content to wrap + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static hideLinkEmbed = deprecate( + hideLinkEmbed, + 'Formatters.hideLinkEmbed() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats the content and the URL into a masked URL with an optional title. + * @method hyperlink + * @memberof Formatters + * @param {string} content The content to display + * @param {string} url The URL the content links to + * @param {string} [title] The title shown when hovering on the masked link + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static hyperlink = deprecate( + hyperlink, + 'Formatters.hyperlink() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Wraps the content inside \`backticks\`, which formats it as inline code. + * @method inlineCode + * @memberof Formatters + * @param {string} content The content to wrap + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static inlineCode = deprecate( + inlineCode, + 'Formatters.inlineCode() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats the content into italic text. + * @method italic + * @memberof Formatters + * @param {string} content The content to wrap + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static italic = deprecate( + italic, + 'Formatters.italic() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats the content into a quote. This needs to be at the start of the line for Discord to format it. + * @method quote + * @memberof Formatters + * @param {string} content The content to wrap + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static quote = deprecate( + quote, + 'Formatters.quote() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats a role id into a role mention. + * @method roleMention + * @memberof Formatters + * @param {Snowflake} roleId The role id to format + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static roleMention = deprecate( + roleMention, + 'Formatters.roleMention() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats the content into spoiler text. + * @method spoiler + * @memberof Formatters + * @param {string} content The content to spoiler + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static spoiler = deprecate( + spoiler, + 'Formatters.spoiler() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats the content into strike-through text. + * @method strikethrough + * @memberof Formatters + * @param {string} content The content to wrap + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static strikethrough = deprecate( + strikethrough, + 'Formatters.strikethrough() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats a date into a short date-time string. + * @method time + * @memberof Formatters + * @param {number|Date} [date] The date to format + * @param {TimestampStylesString} [style] The style to use + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static time = deprecate( + time, + 'Formatters.time() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * The message formatting timestamp + * [styles](https://discord.com/developers/docs/reference#message-formatting-timestamp-styles) supported by Discord. + * @type {Object<string, TimestampStylesString>} + * @memberof Formatters + * @deprecated Import this property directly from discord.js instead. + */ + static TimestampStyles = TimestampStyles; + + /** + * Formats the content into underscored text. + * @method underscore + * @memberof Formatters + * @param {string} content The content to wrap + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static underscore = deprecate( + underscore, + 'Formatters.underscore() is deprecated. Import this method directly from discord.js instead.', + ); + + /** + * Formats a user id into a user mention. + * @method userMention + * @memberof Formatters + * @param {Snowflake} userId The user id to format + * @returns {string} + * @deprecated Import this method directly from discord.js instead. + */ + static userMention = deprecate( + userMention, + 'Formatters.userMention() is deprecated. Import this method directly from discord.js instead.', + ); +} + +module.exports = Formatters; diff --git a/node_modules/discord.js/src/util/GuildMemberFlagsBitField.js b/node_modules/discord.js/src/util/GuildMemberFlagsBitField.js new file mode 100644 index 0000000..84ce6b9 --- /dev/null +++ b/node_modules/discord.js/src/util/GuildMemberFlagsBitField.js @@ -0,0 +1,41 @@ +'use strict'; + +const { GuildMemberFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link GuildMember#flags} bitfield. + * @extends {BitField} + */ +class GuildMemberFlagsBitField extends BitField { + /** + * Numeric guild guild member flags. + * @type {GuildMemberFlags} + * @memberof GuildMemberFlagsBitField + */ + static Flags = GuildMemberFlags; +} + +/** + * @name GuildMemberFlagsBitField + * @kind constructor + * @memberof GuildMemberFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Bitfield of the packed bits + * @type {number} + * @name GuildMemberFlagsBitField#bitfield + */ + +/** + * Data that can be resolved to give a guild member flag bitfield. This can be: + * * A string (see {@link GuildMemberFlagsBitField.Flags}) + * * A guild member flag + * * An instance of GuildMemberFlagsBitField + * * An Array of GuildMemberFlagsResolvable + * @typedef {string|number|GuildMemberFlagsBitField|GuildMemberFlagsResolvable[]} GuildMemberFlagsResolvable + */ + +exports.GuildMemberFlagsBitField = GuildMemberFlagsBitField; diff --git a/node_modules/discord.js/src/util/IntentsBitField.js b/node_modules/discord.js/src/util/IntentsBitField.js new file mode 100644 index 0000000..ea908b6 --- /dev/null +++ b/node_modules/discord.js/src/util/IntentsBitField.js @@ -0,0 +1,34 @@ +'use strict'; +const { GatewayIntentBits } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to calculate intents. + * @extends {BitField} + */ +class IntentsBitField extends BitField { + /** + * Numeric WebSocket intents + * @type {GatewayIntentBits} + * @memberof IntentsBitField + */ + static Flags = GatewayIntentBits; +} + +/** + * @name IntentsBitField + * @kind constructor + * @memberof IntentsBitField + * @param {IntentsResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Data that can be resolved to give a permission number. This can be: + * * A string (see {@link IntentsBitField.Flags}) + * * An intents flag + * * An instance of {@link IntentsBitField} + * * An array of IntentsResolvable + * @typedef {string|number|IntentsBitField|IntentsResolvable[]} IntentsResolvable + */ + +module.exports = IntentsBitField; diff --git a/node_modules/discord.js/src/util/LimitedCollection.js b/node_modules/discord.js/src/util/LimitedCollection.js new file mode 100644 index 0000000..12a8360 --- /dev/null +++ b/node_modules/discord.js/src/util/LimitedCollection.js @@ -0,0 +1,68 @@ +'use strict'; + +const { Collection } = require('@discordjs/collection'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); + +/** + * Options for defining the behavior of a LimitedCollection + * @typedef {Object} LimitedCollectionOptions + * @property {?number} [maxSize=Infinity] The maximum size of the Collection + * @property {?Function} [keepOverLimit=null] A function, which is passed the value and key of an entry, ran to decide + * to keep an entry past the maximum size + */ + +/** + * A Collection which holds a max amount of entries. + * @extends {Collection} + * @param {LimitedCollectionOptions} [options={}] Options for constructing the Collection. + * @param {Iterable} [iterable=null] Optional entries passed to the Map constructor. + */ +class LimitedCollection extends Collection { + constructor(options = {}, iterable) { + if (typeof options !== 'object' || options === null) { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); + } + const { maxSize = Infinity, keepOverLimit = null } = options; + + if (typeof maxSize !== 'number') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'maxSize', 'number'); + } + if (keepOverLimit !== null && typeof keepOverLimit !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'keepOverLimit', 'function'); + } + + super(iterable); + + /** + * The max size of the Collection. + * @type {number} + */ + this.maxSize = maxSize; + + /** + * A function called to check if an entry should be kept when the Collection is at max size. + * @type {?Function} + */ + this.keepOverLimit = keepOverLimit; + } + + set(key, value) { + if (this.maxSize === 0 && !this.keepOverLimit?.(value, key, this)) return this; + if (this.size >= this.maxSize && !this.has(key)) { + for (const [k, v] of this.entries()) { + const keep = this.keepOverLimit?.(v, k, this) ?? false; + if (!keep) { + this.delete(k); + break; + } + } + } + return super.set(key, value); + } + + static get [Symbol.species]() { + return Collection; + } +} + +module.exports = LimitedCollection; diff --git a/node_modules/discord.js/src/util/MessageFlagsBitField.js b/node_modules/discord.js/src/util/MessageFlagsBitField.js new file mode 100644 index 0000000..71f1fd6 --- /dev/null +++ b/node_modules/discord.js/src/util/MessageFlagsBitField.js @@ -0,0 +1,32 @@ +'use strict'; + +const { MessageFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link Message#flags} bitfield. + * @extends {BitField} + */ +class MessageFlagsBitField extends BitField { + /** + * Numeric message flags. + * @type {MessageFlags} + * @memberof MessageFlagsBitField + */ + static Flags = MessageFlags; +} + +/** + * @name MessageFlagsBitField + * @kind constructor + * @memberof MessageFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Bitfield of the packed bits + * @type {number} + * @name MessageFlagsBitField#bitfield + */ + +module.exports = MessageFlagsBitField; diff --git a/node_modules/discord.js/src/util/Options.js b/node_modules/discord.js/src/util/Options.js new file mode 100644 index 0000000..9d1ce50 --- /dev/null +++ b/node_modules/discord.js/src/util/Options.js @@ -0,0 +1,237 @@ +'use strict'; + +const { DefaultRestOptions, DefaultUserAgentAppendix } = require('@discordjs/rest'); +const { toSnakeCase } = require('./Transformers'); +const { version } = require('../../package.json'); + +// TODO(ckohen): switch order of params so full manager is first and "type" is optional +/** + * @typedef {Function} CacheFactory + * @param {Function} managerType The base manager class the cache is being requested from. + * @param {Function} holds The class that the cache will hold. + * @param {Function} manager The fully extended manager class the cache is being requested from. + * @returns {Collection} A Collection used to store the cache of the manager. + */ + +/** + * Options for a client. + * @typedef {Object} ClientOptions + * @property {number|number[]|string} [shards] The shard's id to run, or an array of shard ids. If not specified, + * the client will spawn {@link ClientOptions#shardCount} shards. If set to `auto`, it will fetch the + * recommended amount of shards from Discord and spawn that amount + * @property {number} [closeTimeout=5_000] The amount of time in milliseconds to wait for the close frame to be received + * from the WebSocket. Don't have this too high/low. Its best to have it between 2_000-6_000 ms. + * @property {number} [shardCount=1] The total amount of shards used by all processes of this bot + * (e.g. recommended shard count, shard count of the ShardingManager) + * @property {CacheFactory} [makeCache] Function to create a cache. + * You can use your own function, or the {@link Options} class to customize the Collection used for the cache. + * <warn>Overriding the cache used in `GuildManager`, `ChannelManager`, `GuildChannelManager`, `RoleManager`, + * and `PermissionOverwriteManager` is unsupported and **will** break functionality</warn> + * @property {MessageMentionOptions} [allowedMentions] The default value for {@link BaseMessageOptions#allowedMentions} + * @property {Partials[]} [partials] Structures allowed to be partial. This means events can be emitted even when + * they're missing all the data for a particular structure. See the "Partial Structures" topic on the + * [guide](https://discordjs.guide/popular-topics/partials.html) for some + * important usage information, as partials require you to put checks in place when handling data. + * @property {boolean} [failIfNotExists=true] The default value for {@link MessageReplyOptions#failIfNotExists} + * @property {PresenceData} [presence={}] Presence data to use upon login + * @property {IntentsResolvable} intents Intents to enable for this connection + * @property {number} [waitGuildTimeout=15_000] Time in milliseconds that clients with the + * {@link GatewayIntentBits.Guilds} gateway intent should wait for missing guilds to be received before being ready. + * @property {SweeperOptions} [sweepers=this.DefaultSweeperSettings] Options for cache sweeping + * @property {WebsocketOptions} [ws] Options for the WebSocket + * @property {RESTOptions} [rest] Options for the REST manager + * @property {Function} [jsonTransformer] A function used to transform outgoing json data + */ + +/** + * Options for {@link Sweepers} defining the behavior of cache sweeping + * @typedef {Object<SweeperKey, SweepOptions>} SweeperOptions + */ + +/** + * Options for sweeping a single type of item from cache + * @typedef {Object} SweepOptions + * @property {number} interval The interval (in seconds) at which to perform sweeping of the item + * @property {number} [lifetime] How long an item should stay in cache until it is considered sweepable. + * <warn>This property is only valid for the `invites`, `messages`, and `threads` keys. The `filter` property + * is mutually exclusive to this property and takes priority</warn> + * @property {GlobalSweepFilter} filter The function used to determine the function passed to the sweep method + * <info>This property is optional when the key is `invites`, `messages`, or `threads` and `lifetime` is set</info> + */ + +/** + * A function to determine what strategy to use for sharding internally. + * ```js + * (manager) => new WorkerShardingStrategy(manager, { shardsPerWorker: 2 }) + * ``` + * @typedef {Function} BuildStrategyFunction + * @param {WSWebSocketManager} manager The WebSocketManager that is going to initiate the sharding + * @returns {IShardingStrategy} The strategy to use for sharding + */ + +/** + * A function to change the concurrency handling for shard identifies of this manager + * ```js + * async (manager) => { + * const gateway = await manager.fetchGatewayInformation(); + * return new SimpleIdentifyThrottler(gateway.session_start_limit.max_concurrency); + * } + * ``` + * @typedef {Function} IdentifyThrottlerFunction + * @param {WSWebSocketManager} manager The WebSocketManager that is going to initiate the sharding + * @returns {Awaitable<IIdentifyThrottler>} The identify throttler that this ws manager will use + */ + +/** + * WebSocket options (these are left as snake_case to match the API) + * @typedef {Object} WebsocketOptions + * @property {number} [large_threshold=50] Number of members in a guild after which offline users will no longer be + * sent in the initial guild member list, must be between 50 and 250 + * @property {number} [version=10] The Discord gateway version to use <warn>Changing this can break the library; + * only set this if you know what you are doing</warn> + * @property {BuildStrategyFunction} [buildStrategy] Builds the strategy to use for sharding + * @property {IdentifyThrottlerFunction} [buildIdentifyThrottler] Builds the identify throttler to use for sharding + */ + +/** + * Contains various utilities for client options. + */ +class Options extends null { + /** + * The default user agent appendix. + * @type {string} + * @memberof Options + * @private + */ + static userAgentAppendix = `discord.js/${version} ${DefaultUserAgentAppendix}`.trimEnd(); + + /** + * The default client options. + * @returns {ClientOptions} + */ + static createDefault() { + return { + closeTimeout: 5_000, + waitGuildTimeout: 15_000, + shardCount: 1, + makeCache: this.cacheWithLimits(this.DefaultMakeCacheSettings), + partials: [], + failIfNotExists: true, + presence: {}, + sweepers: this.DefaultSweeperSettings, + ws: { + large_threshold: 50, + version: 10, + }, + rest: { + ...DefaultRestOptions, + userAgentAppendix: this.userAgentAppendix, + }, + jsonTransformer: toSnakeCase, + }; + } + + /** + * Create a cache factory using predefined settings to sweep or limit. + * @param {Object<string, LimitedCollectionOptions|number>} [settings={}] Settings passed to the relevant constructor. + * If no setting is provided for a manager, it uses Collection. + * If a number is provided for a manager, it uses that number as the max size for a LimitedCollection. + * If LimitedCollectionOptions are provided for a manager, it uses those settings to form a LimitedCollection. + * @returns {CacheFactory} + * @example + * // Store up to 200 messages per channel and 200 members per guild, always keeping the client member. + * Options.cacheWithLimits({ + * MessageManager: 200, + * GuildMemberManager: { + * maxSize: 200, + * keepOverLimit: (member) => member.id === client.user.id, + * }, + * }); + */ + static cacheWithLimits(settings = {}) { + const { Collection } = require('@discordjs/collection'); + const LimitedCollection = require('./LimitedCollection'); + + return (managerType, _, manager) => { + const setting = settings[manager.name] ?? settings[managerType.name]; + /* eslint-disable-next-line eqeqeq */ + if (setting == null) { + return new Collection(); + } + if (typeof setting === 'number') { + if (setting === Infinity) { + return new Collection(); + } + return new LimitedCollection({ maxSize: setting }); + } + /* eslint-disable-next-line eqeqeq */ + const noLimit = setting.maxSize == null || setting.maxSize === Infinity; + if (noLimit) { + return new Collection(); + } + return new LimitedCollection(setting); + }; + } + + /** + * Create a cache factory that always caches everything. + * @returns {CacheFactory} + */ + static cacheEverything() { + const { Collection } = require('@discordjs/collection'); + return () => new Collection(); + } + + /** + * The default settings passed to {@link ClientOptions.makeCache}. + * The caches that this changes are: + * * `MessageManager` - Limit to 200 messages + * <info>If you want to keep default behavior and add on top of it you can use this object and add on to it, e.g. + * `makeCache: Options.cacheWithLimits({ ...Options.DefaultMakeCacheSettings, ReactionManager: 0 })`</info> + * @type {Object<string, LimitedCollectionOptions|number>} + */ + static get DefaultMakeCacheSettings() { + return { + MessageManager: 200, + }; + } + + /** + * The default settings passed to {@link ClientOptions.sweepers}. + * The sweepers that this changes are: + * * `threads` - Sweep archived threads every hour, removing those archived more than 4 hours ago + * <info>If you want to keep default behavior and add on top of it you can use this object and add on to it, e.g. + * `sweepers: { ...Options.DefaultSweeperSettings, messages: { interval: 300, lifetime: 600 } }`</info> + * @type {SweeperOptions} + */ + static get DefaultSweeperSettings() { + return { + threads: { + interval: 3600, + lifetime: 14400, + }, + }; + } +} + +module.exports = Options; + +/** + * @external RESTOptions + * @see {@link https://discord.js.org/docs/packages/rest/stable/RESTOptions:Interface} + */ + +/** + * @external WSWebSocketManager + * @see {@link https://discord.js.org/docs/packages/ws/stable/WebSocketManager:Class} + */ + +/** + * @external IShardingStrategy + * @see {@link https://discord.js.org/docs/packages/ws/stable/IShardingStrategy:Interface} + */ + +/** + * @external IIdentifyThrottler + * @see {@link https://discord.js.org/docs/packages/ws/stable/IIdentifyThrottler:Interface} + */ diff --git a/node_modules/discord.js/src/util/Partials.js b/node_modules/discord.js/src/util/Partials.js new file mode 100644 index 0000000..31d9223 --- /dev/null +++ b/node_modules/discord.js/src/util/Partials.js @@ -0,0 +1,44 @@ +'use strict'; + +const { createEnum } = require('./Enums'); + +/** + * The enumeration for partials. + * ```js + * import { Client, Partials } from 'discord.js'; + * + * const client = new Client({ + * intents: [ + * // Intents... + * ], + * partials: [ + * Partials.User, // We want to receive uncached users! + * Partials.Message // We want to receive uncached messages! + * ] + * }); + * ``` + * @typedef {Object} Partials + * @property {number} User The partial to receive uncached users. + * @property {number} Channel The partial to receive uncached channels. + * <info>This is required to receive direct messages!</info> + * @property {number} GuildMember The partial to receive uncached guild members. + * @property {number} Message The partial to receive uncached messages. + * @property {number} Reaction The partial to receive uncached reactions. + * @property {number} GuildScheduledEvent The partial to receive uncached guild scheduled events. + * @property {number} ThreadMember The partial to receive uncached thread members. + */ + +// JSDoc for IntelliSense purposes +/** + * @type {Partials} + * @ignore + */ +module.exports = createEnum([ + 'User', + 'Channel', + 'GuildMember', + 'Message', + 'Reaction', + 'GuildScheduledEvent', + 'ThreadMember', +]); diff --git a/node_modules/discord.js/src/util/PermissionsBitField.js b/node_modules/discord.js/src/util/PermissionsBitField.js new file mode 100644 index 0000000..c1ec72d --- /dev/null +++ b/node_modules/discord.js/src/util/PermissionsBitField.js @@ -0,0 +1,104 @@ +'use strict'; + +const { PermissionFlagsBits } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a permission bitfield. All {@link GuildMember}s have a set of + * permissions in their guild, and each channel in the guild may also have {@link PermissionOverwrites} for the member + * that override their default permissions. + * @extends {BitField} + */ +class PermissionsBitField extends BitField { + /** + * Numeric permission flags. + * @type {PermissionFlagsBits} + * @memberof PermissionsBitField + * @see {@link https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags} + */ + static Flags = PermissionFlagsBits; + + /** + * Bitfield representing every permission combined + * @type {bigint} + * @memberof PermissionsBitField + */ + static All = Object.values(PermissionFlagsBits).reduce((all, p) => all | p, 0n); + + /** + * Bitfield representing the default permissions for users + * @type {bigint} + * @memberof PermissionsBitField + */ + static Default = BigInt(104324673); + + /** + * Bitfield representing the permissions required for moderators of stage channels + * @type {bigint} + * @memberof PermissionsBitField + */ + static StageModerator = + PermissionFlagsBits.ManageChannels | PermissionFlagsBits.MuteMembers | PermissionFlagsBits.MoveMembers; + + /** + * @type {bigint} + * @memberof PermissionsBitField + * @private + */ + static DefaultBit = BigInt(0); + + /** + * Bitfield of the packed bits + * @type {bigint} + * @name PermissionsBitField#bitfield + */ + + /** + * Data that can be resolved to give a permission number. This can be: + * * A string (see {@link PermissionsBitField.Flags}) + * * A permission number + * * An instance of {@link PermissionsBitField} + * * An Array of PermissionResolvable + * @typedef {string|bigint|PermissionsBitField|PermissionResolvable[]} PermissionResolvable + */ + + /** + * Gets all given bits that are missing from the bitfield. + * @param {BitFieldResolvable} bits Bit(s) to check for + * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override + * @returns {string[]} + */ + missing(bits, checkAdmin = true) { + return checkAdmin && this.has(PermissionFlagsBits.Administrator) ? [] : super.missing(bits); + } + + /** + * Checks whether the bitfield has a permission, or any of multiple permissions. + * @param {PermissionResolvable} permission Permission(s) to check for + * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override + * @returns {boolean} + */ + any(permission, checkAdmin = true) { + return (checkAdmin && super.has(PermissionFlagsBits.Administrator)) || super.any(permission); + } + + /** + * Checks whether the bitfield has a permission, or multiple permissions. + * @param {PermissionResolvable} permission Permission(s) to check for + * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override + * @returns {boolean} + */ + has(permission, checkAdmin = true) { + return (checkAdmin && super.has(PermissionFlagsBits.Administrator)) || super.has(permission); + } + + /** + * Gets an {@link Array} of bitfield names based on the permissions available. + * @returns {string[]} + */ + toArray() { + return super.toArray(false); + } +} + +module.exports = PermissionsBitField; diff --git a/node_modules/discord.js/src/util/RoleFlagsBitField.js b/node_modules/discord.js/src/util/RoleFlagsBitField.js new file mode 100644 index 0000000..1e0f895 --- /dev/null +++ b/node_modules/discord.js/src/util/RoleFlagsBitField.js @@ -0,0 +1,26 @@ +'use strict'; + +const { RoleFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link Role#flags} bitfield. + * @extends {BitField} + */ +class RoleFlagsBitField extends BitField { + /** + * Numeric role flags. + * @type {RoleFlags} + * @memberof RoleFlagsBitField + */ + static Flags = RoleFlags; +} + +/** + * @name RoleFlagsBitField + * @kind constructor + * @memberof RoleFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +module.exports = RoleFlagsBitField; diff --git a/node_modules/discord.js/src/util/ShardEvents.js b/node_modules/discord.js/src/util/ShardEvents.js new file mode 100644 index 0000000..f5ba961 --- /dev/null +++ b/node_modules/discord.js/src/util/ShardEvents.js @@ -0,0 +1,29 @@ +'use strict'; + +/** + * @typedef {Object} ShardEvents + * @property {string} Death death + * @property {string} Disconnect disconnect + * @property {string} Error error + * @property {string} Message message + * @property {string} Ready ready + * @property {string} Reconnecting reconnecting + * @property {string} Resume resume + * @property {string} Spawn spawn + */ + +// JSDoc for IntelliSense purposes +/** + * @type {ShardEvents} + * @ignore + */ +module.exports = { + Death: 'death', + Disconnect: 'disconnect', + Error: 'error', + Message: 'message', + Ready: 'ready', + Reconnecting: 'reconnecting', + Resume: 'resume', + Spawn: 'spawn', +}; diff --git a/node_modules/discord.js/src/util/Status.js b/node_modules/discord.js/src/util/Status.js new file mode 100644 index 0000000..e524197 --- /dev/null +++ b/node_modules/discord.js/src/util/Status.js @@ -0,0 +1,33 @@ +'use strict'; + +const { createEnum } = require('./Enums'); + +/** + * @typedef {Object} Status + * @property {number} Ready + * @property {number} Connecting + * @property {number} Reconnecting + * @property {number} Idle + * @property {number} Nearly + * @property {number} Disconnected + * @property {number} WaitingForGuilds + * @property {number} Identifying + * @property {number} Resuming + */ + +// JSDoc for IntelliSense purposes +/** + * @type {Status} + * @ignore + */ +module.exports = createEnum([ + 'Ready', + 'Connecting', + 'Reconnecting', + 'Idle', + 'Nearly', + 'Disconnected', + 'WaitingForGuilds', + 'Identifying', + 'Resuming', +]); diff --git a/node_modules/discord.js/src/util/Sweepers.js b/node_modules/discord.js/src/util/Sweepers.js new file mode 100644 index 0000000..6eb2dc6 --- /dev/null +++ b/node_modules/discord.js/src/util/Sweepers.js @@ -0,0 +1,467 @@ +'use strict'; + +const { setInterval, clearInterval } = require('node:timers'); +const { ThreadChannelTypes, SweeperKeys } = require('./Constants'); +const Events = require('./Events'); +const { DiscordjsTypeError, ErrorCodes } = require('../errors'); + +/** + * @typedef {Function} GlobalSweepFilter + * @returns {Function|null} Return `null` to skip sweeping, otherwise a function passed to `sweep()`, + * See {@link [Collection#sweep](https://discord.js.org/docs/packages/collection/stable/Collection:Class#sweep)} + * for the definition of this function. + */ + +/** + * A container for all cache sweeping intervals and their associated sweep methods. + */ +class Sweepers { + constructor(client, options) { + /** + * The client that instantiated this + * @type {Client} + * @readonly + */ + Object.defineProperty(this, 'client', { value: client }); + + /** + * The options the sweepers were instantiated with + * @type {SweeperOptions} + */ + this.options = options; + + /** + * A record of interval timeout that is used to sweep the indicated items, or null if not being swept + * @type {Object<SweeperKey, ?Timeout>} + */ + this.intervals = Object.fromEntries(SweeperKeys.map(key => [key, null])); + + for (const key of SweeperKeys) { + if (!(key in options)) continue; + + this._validateProperties(key); + + const clonedOptions = { ...this.options[key] }; + + // Handle cases that have a "lifetime" + if (!('filter' in clonedOptions)) { + switch (key) { + case 'invites': + clonedOptions.filter = this.constructor.expiredInviteSweepFilter(clonedOptions.lifetime); + break; + case 'messages': + clonedOptions.filter = this.constructor.outdatedMessageSweepFilter(clonedOptions.lifetime); + break; + case 'threads': + clonedOptions.filter = this.constructor.archivedThreadSweepFilter(clonedOptions.lifetime); + } + } + + this._initInterval(key, `sweep${key[0].toUpperCase()}${key.slice(1)}`, clonedOptions); + } + } + + /** + * Sweeps all guild and global application commands and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which commands will be removed from the caches. + * @returns {number} Amount of commands that were removed from the caches + */ + sweepApplicationCommands(filter) { + const { guilds, items: guildCommands } = this._sweepGuildDirectProp('commands', filter, { emit: false }); + + const globalCommands = this.client.application?.commands.cache.sweep(filter) ?? 0; + + this.client.emit( + Events.CacheSweep, + `Swept ${globalCommands} global application commands and ${guildCommands} guild commands in ${guilds} guilds.`, + ); + return guildCommands + globalCommands; + } + + /** + * Sweeps all auto moderation rules and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine + * which auto moderation rules will be removed from the caches + * @returns {number} Amount of auto moderation rules that were removed from the caches + */ + sweepAutoModerationRules(filter) { + return this._sweepGuildDirectProp('autoModerationRules', filter).items; + } + + /** + * Sweeps all guild bans and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which bans will be removed from the caches. + * @returns {number} Amount of bans that were removed from the caches + */ + sweepBans(filter) { + return this._sweepGuildDirectProp('bans', filter).items; + } + + /** + * Sweeps all guild emojis and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which emojis will be removed from the caches. + * @returns {number} Amount of emojis that were removed from the caches + */ + sweepEmojis(filter) { + return this._sweepGuildDirectProp('emojis', filter).items; + } + + /** + * Sweeps all guild invites and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which invites will be removed from the caches. + * @returns {number} Amount of invites that were removed from the caches + */ + sweepInvites(filter) { + return this._sweepGuildDirectProp('invites', filter).items; + } + + /** + * Sweeps all guild members and removes the ones which are indicated by the filter. + * <info>It is highly recommended to keep the client guild member cached</info> + * @param {Function} filter The function used to determine which guild members will be removed from the caches. + * @returns {number} Amount of guild members that were removed from the caches + */ + sweepGuildMembers(filter) { + return this._sweepGuildDirectProp('members', filter, { outputName: 'guild members' }).items; + } + + /** + * Sweeps all text-based channels' messages and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which messages will be removed from the caches. + * @returns {number} Amount of messages that were removed from the caches + * @example + * // Remove all messages older than 1800 seconds from the messages cache + * const amount = sweepers.sweepMessages( + * Sweepers.filterByLifetime({ + * lifetime: 1800, + * getComparisonTimestamp: m => m.editedTimestamp ?? m.createdTimestamp, + * })(), + * ); + * console.log(`Successfully removed ${amount} messages from the cache.`); + */ + sweepMessages(filter) { + if (typeof filter !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'filter', 'function'); + } + let channels = 0; + let messages = 0; + + for (const channel of this.client.channels.cache.values()) { + if (!channel.isTextBased()) continue; + + channels++; + messages += channel.messages.cache.sweep(filter); + } + this.client.emit(Events.CacheSweep, `Swept ${messages} messages in ${channels} text-based channels.`); + return messages; + } + + /** + * Sweeps all presences and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which presences will be removed from the caches. + * @returns {number} Amount of presences that were removed from the caches + */ + sweepPresences(filter) { + return this._sweepGuildDirectProp('presences', filter).items; + } + + /** + * Sweeps all message reactions and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which reactions will be removed from the caches. + * @returns {number} Amount of reactions that were removed from the caches + */ + sweepReactions(filter) { + if (typeof filter !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'filter', 'function'); + } + let channels = 0; + let messages = 0; + let reactions = 0; + + for (const channel of this.client.channels.cache.values()) { + if (!channel.isTextBased()) continue; + channels++; + + for (const message of channel.messages.cache.values()) { + messages++; + reactions += message.reactions.cache.sweep(filter); + } + } + this.client.emit( + Events.CacheSweep, + `Swept ${reactions} reactions on ${messages} messages in ${channels} text-based channels.`, + ); + return reactions; + } + + /** + * Sweeps all guild stage instances and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which stage instances will be removed from the caches. + * @returns {number} Amount of stage instances that were removed from the caches + */ + sweepStageInstances(filter) { + return this._sweepGuildDirectProp('stageInstances', filter, { outputName: 'stage instances' }).items; + } + + /** + * Sweeps all guild stickers and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which stickers will be removed from the caches. + * @returns {number} Amount of stickers that were removed from the caches + */ + sweepStickers(filter) { + return this._sweepGuildDirectProp('stickers', filter).items; + } + + /** + * Sweeps all thread members and removes the ones which are indicated by the filter. + * <info>It is highly recommended to keep the client thread member cached</info> + * @param {Function} filter The function used to determine which thread members will be removed from the caches. + * @returns {number} Amount of thread members that were removed from the caches + */ + sweepThreadMembers(filter) { + if (typeof filter !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'filter', 'function'); + } + + let threads = 0; + let members = 0; + for (const channel of this.client.channels.cache.values()) { + if (!ThreadChannelTypes.includes(channel.type)) continue; + threads++; + members += channel.members.cache.sweep(filter); + } + this.client.emit(Events.CacheSweep, `Swept ${members} thread members in ${threads} threads.`); + return members; + } + + /** + * Sweeps all threads and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which threads will be removed from the caches. + * @returns {number} filter Amount of threads that were removed from the caches + * @example + * // Remove all threads archived greater than 1 day ago from all the channel caches + * const amount = sweepers.sweepThreads( + * Sweepers.filterByLifetime({ + * getComparisonTimestamp: t => t.archivedTimestamp, + * excludeFromSweep: t => !t.archived, + * })(), + * ); + * console.log(`Successfully removed ${amount} threads from the cache.`); + */ + sweepThreads(filter) { + if (typeof filter !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'filter', 'function'); + } + + let threads = 0; + for (const [key, val] of this.client.channels.cache.entries()) { + if (!ThreadChannelTypes.includes(val.type)) continue; + if (filter(val, key, this.client.channels.cache)) { + threads++; + this.client.channels._remove(key); + } + } + this.client.emit(Events.CacheSweep, `Swept ${threads} threads.`); + return threads; + } + + /** + * Sweeps all users and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which users will be removed from the caches. + * @returns {number} Amount of users that were removed from the caches + */ + sweepUsers(filter) { + if (typeof filter !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'filter', 'function'); + } + + const users = this.client.users.cache.sweep(filter); + + this.client.emit(Events.CacheSweep, `Swept ${users} users.`); + + return users; + } + + /** + * Sweeps all guild voice states and removes the ones which are indicated by the filter. + * @param {Function} filter The function used to determine which voice states will be removed from the caches. + * @returns {number} Amount of voice states that were removed from the caches + */ + sweepVoiceStates(filter) { + return this._sweepGuildDirectProp('voiceStates', filter, { outputName: 'voice states' }).items; + } + + /** + * Cancels all sweeping intervals + * @returns {void} + */ + destroy() { + for (const key of SweeperKeys) { + if (this.intervals[key]) clearInterval(this.intervals[key]); + } + } + + /** + * Options for generating a filter function based on lifetime + * @typedef {Object} LifetimeFilterOptions + * @property {number} [lifetime=14400] How long, in seconds, an entry should stay in the collection + * before it is considered sweepable. + * @property {Function} [getComparisonTimestamp=e => e?.createdTimestamp] A function that takes an entry, key, + * and the collection and returns a timestamp to compare against in order to determine the lifetime of the entry. + * @property {Function} [excludeFromSweep=() => false] A function that takes an entry, key, and the collection + * and returns a boolean, `true` when the entry should not be checked for sweepability. + */ + + /** + * Create a sweepFilter function that uses a lifetime to determine sweepability. + * @param {LifetimeFilterOptions} [options={}] The options used to generate the filter function + * @returns {GlobalSweepFilter} + */ + static filterByLifetime({ + lifetime = 14400, + getComparisonTimestamp = e => e?.createdTimestamp, + excludeFromSweep = () => false, + } = {}) { + if (typeof lifetime !== 'number') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'lifetime', 'number'); + } + if (typeof getComparisonTimestamp !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'getComparisonTimestamp', 'function'); + } + if (typeof excludeFromSweep !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'excludeFromSweep', 'function'); + } + return () => { + if (lifetime <= 0) return null; + const lifetimeMs = lifetime * 1_000; + const now = Date.now(); + return (entry, key, coll) => { + if (excludeFromSweep(entry, key, coll)) { + return false; + } + const comparisonTimestamp = getComparisonTimestamp(entry, key, coll); + if (!comparisonTimestamp || typeof comparisonTimestamp !== 'number') return false; + return now - comparisonTimestamp > lifetimeMs; + }; + }; + } + + /** + * Creates a sweep filter that sweeps archived threads + * @param {number} [lifetime=14400] How long a thread has to be archived to be valid for sweeping + * @returns {GlobalSweepFilter} + */ + static archivedThreadSweepFilter(lifetime = 14400) { + return this.filterByLifetime({ + lifetime, + getComparisonTimestamp: e => e.archiveTimestamp, + excludeFromSweep: e => !e.archived, + }); + } + + /** + * Creates a sweep filter that sweeps expired invites + * @param {number} [lifetime=14400] How long ago an invite has to have expired to be valid for sweeping + * @returns {GlobalSweepFilter} + */ + static expiredInviteSweepFilter(lifetime = 14400) { + return this.filterByLifetime({ + lifetime, + getComparisonTimestamp: i => i.expiresTimestamp, + }); + } + + /** + * Creates a sweep filter that sweeps outdated messages (edits taken into account) + * @param {number} [lifetime=3600] How long ago a message has to have been sent or edited to be valid for sweeping + * @returns {GlobalSweepFilter} + */ + static outdatedMessageSweepFilter(lifetime = 3600) { + return this.filterByLifetime({ + lifetime, + getComparisonTimestamp: m => m.editedTimestamp ?? m.createdTimestamp, + }); + } + + /** + * Configuration options for emitting the cache sweep client event + * @typedef {Object} SweepEventOptions + * @property {boolean} [emit=true] Whether to emit the client event in this method + * @property {string} [outputName] A name to output in the client event if it should differ from the key + * @private + */ + + /** + * Sweep a direct sub property of all guilds + * @param {string} key The name of the property + * @param {Function} filter Filter function passed to sweep + * @param {SweepEventOptions} [eventOptions={}] Options for the Client event emitted here + * @returns {Object} Object containing the number of guilds swept and the number of items swept + * @private + */ + _sweepGuildDirectProp(key, filter, { emit = true, outputName } = {}) { + if (typeof filter !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'filter', 'function'); + } + + let guilds = 0; + let items = 0; + + for (const guild of this.client.guilds.cache.values()) { + const { cache } = guild[key]; + + guilds++; + items += cache.sweep(filter); + } + + if (emit) { + this.client.emit(Events.CacheSweep, `Swept ${items} ${outputName ?? key} in ${guilds} guilds.`); + } + + return { guilds, items }; + } + + /** + * Validates a set of properties + * @param {string} key Key of the options object to check + * @private + */ + _validateProperties(key) { + const props = this.options[key]; + if (typeof props !== 'object') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, `sweepers.${key}`, 'object', true); + } + if (typeof props.interval !== 'number') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, `sweepers.${key}.interval`, 'number'); + } + // Invites, Messages, and Threads can be provided a lifetime parameter, which we use to generate the filter + if (['invites', 'messages', 'threads'].includes(key) && !('filter' in props)) { + if (typeof props.lifetime !== 'number') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, `sweepers.${key}.lifetime`, 'number'); + } + return; + } + if (typeof props.filter !== 'function') { + throw new DiscordjsTypeError(ErrorCodes.InvalidType, `sweepers.${key}.filter`, 'function'); + } + } + + /** + * Initialize an interval for sweeping + * @param {string} intervalKey The name of the property that stores the interval for this sweeper + * @param {string} sweepKey The name of the function that sweeps the desired caches + * @param {Object} opts Validated options for a sweep + * @private + */ + _initInterval(intervalKey, sweepKey, opts) { + if (opts.interval <= 0 || opts.interval === Infinity) return; + this.intervals[intervalKey] = setInterval(() => { + const sweepFn = opts.filter(); + if (sweepFn === null) return; + if (typeof sweepFn !== 'function') throw new DiscordjsTypeError(ErrorCodes.SweepFilterReturn); + this[sweepKey](sweepFn); + }, opts.interval * 1_000).unref(); + } +} + +module.exports = Sweepers; diff --git a/node_modules/discord.js/src/util/Symbols.js b/node_modules/discord.js/src/util/Symbols.js new file mode 100644 index 0000000..36d4ed8 --- /dev/null +++ b/node_modules/discord.js/src/util/Symbols.js @@ -0,0 +1,3 @@ +'use strict'; + +exports.MakeCacheOverrideSymbol = Symbol('djs.managers.makeCacheOverride'); diff --git a/node_modules/discord.js/src/util/SystemChannelFlagsBitField.js b/node_modules/discord.js/src/util/SystemChannelFlagsBitField.js new file mode 100644 index 0000000..cbca48f --- /dev/null +++ b/node_modules/discord.js/src/util/SystemChannelFlagsBitField.js @@ -0,0 +1,43 @@ +'use strict'; + +const { GuildSystemChannelFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link Guild#systemChannelFlags} bitfield. + * <info>Note that all event message types are enabled by default, + * and by setting their corresponding flags you are disabling them</info> + * @extends {BitField} + */ +class SystemChannelFlagsBitField extends BitField { + /** + * Numeric system channel flags. + * @type {GuildSystemChannelFlags} + * @memberof SystemChannelFlagsBitField + */ + static Flags = GuildSystemChannelFlags; +} + +/** + * @name SystemChannelFlagsBitField + * @kind constructor + * @memberof SystemChannelFlagsBitField + * @param {SystemChannelFlagsResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Bitfield of the packed bits + * @type {number} + * @name SystemChannelFlagsBitField#bitfield + */ + +/** + * Data that can be resolved to give a system channel flag bitfield. This can be: + * * A string (see {@link SystemChannelFlagsBitField.Flags}) + * * A system channel flag + * * An instance of SystemChannelFlagsBitField + * * An Array of SystemChannelFlagsResolvable + * @typedef {string|number|SystemChannelFlagsBitField|SystemChannelFlagsResolvable[]} SystemChannelFlagsResolvable + */ + +module.exports = SystemChannelFlagsBitField; diff --git a/node_modules/discord.js/src/util/ThreadMemberFlagsBitField.js b/node_modules/discord.js/src/util/ThreadMemberFlagsBitField.js new file mode 100644 index 0000000..c5073a8 --- /dev/null +++ b/node_modules/discord.js/src/util/ThreadMemberFlagsBitField.js @@ -0,0 +1,31 @@ +'use strict'; + +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link ThreadMember#flags} bitfield. + * @extends {BitField} + */ +class ThreadMemberFlagsBitField extends BitField { + /** + * Numeric thread member flags. There are currently no bitflags relevant to bots for this. + * @type {Object<string, number>} + * @memberof ThreadMemberFlagsBitField + */ + static Flags = {}; +} + +/** + * @name ThreadMemberFlagsBitField + * @kind constructor + * @memberof ThreadMemberFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Bitfield of the packed bits + * @type {number} + * @name ThreadMemberFlagsBitField#bitfield + */ + +module.exports = ThreadMemberFlagsBitField; diff --git a/node_modules/discord.js/src/util/Transformers.js b/node_modules/discord.js/src/util/Transformers.js new file mode 100644 index 0000000..f4d7af0 --- /dev/null +++ b/node_modules/discord.js/src/util/Transformers.js @@ -0,0 +1,36 @@ +'use strict'; + +const { isJSONEncodable } = require('@discordjs/util'); +const snakeCase = require('lodash.snakecase'); + +/** + * Transforms camel-cased keys into snake cased keys + * @param {*} obj The object to transform + * @returns {*} + */ +function toSnakeCase(obj) { + if (typeof obj !== 'object' || !obj) return obj; + if (obj instanceof Date) return obj; + if (isJSONEncodable(obj)) return toSnakeCase(obj.toJSON()); + if (Array.isArray(obj)) return obj.map(toSnakeCase); + return Object.fromEntries(Object.entries(obj).map(([key, value]) => [snakeCase(key), toSnakeCase(value)])); +} + +/** + * Transforms an API auto moderation action object to a camel-cased variant. + * @param {APIAutoModerationAction} autoModerationAction The action to transform + * @returns {AutoModerationAction} + * @ignore + */ +function _transformAPIAutoModerationAction(autoModerationAction) { + return { + type: autoModerationAction.type, + metadata: { + durationSeconds: autoModerationAction.metadata.duration_seconds ?? null, + channelId: autoModerationAction.metadata.channel_id ?? null, + customMessage: autoModerationAction.metadata.custom_message ?? null, + }, + }; +} + +module.exports = { toSnakeCase, _transformAPIAutoModerationAction }; diff --git a/node_modules/discord.js/src/util/UserFlagsBitField.js b/node_modules/discord.js/src/util/UserFlagsBitField.js new file mode 100644 index 0000000..ea9f835 --- /dev/null +++ b/node_modules/discord.js/src/util/UserFlagsBitField.js @@ -0,0 +1,32 @@ +'use strict'; + +const { UserFlags } = require('discord-api-types/v10'); +const BitField = require('./BitField'); + +/** + * Data structure that makes it easy to interact with a {@link User#flags} bitfield. + * @extends {BitField} + */ +class UserFlagsBitField extends BitField { + /** + * Numeric user flags. + * @type {UserFlags} + * @memberof UserFlagsBitField + */ + static Flags = UserFlags; +} + +/** + * @name UserFlagsBitField + * @kind constructor + * @memberof UserFlagsBitField + * @param {BitFieldResolvable} [bits=0] Bit(s) to read from + */ + +/** + * Bitfield of the packed bits + * @type {number} + * @name UserFlagsBitField#bitfield + */ + +module.exports = UserFlagsBitField; diff --git a/node_modules/discord.js/src/util/Util.js b/node_modules/discord.js/src/util/Util.js new file mode 100644 index 0000000..e42bc5a --- /dev/null +++ b/node_modules/discord.js/src/util/Util.js @@ -0,0 +1,424 @@ +'use strict'; + +const { parse } = require('node:path'); +const { Collection } = require('@discordjs/collection'); +const { ChannelType, RouteBases, Routes } = require('discord-api-types/v10'); +const { fetch } = require('undici'); +const Colors = require('./Colors'); +const { DiscordjsError, DiscordjsRangeError, DiscordjsTypeError, ErrorCodes } = require('../errors'); +const isObject = d => typeof d === 'object' && d !== null; + +/** + * Flatten an object. Any properties that are collections will get converted to an array of keys. + * @param {Object} obj The object to flatten. + * @param {...Object<string, boolean|string>} [props] Specific properties to include/exclude. + * @returns {Object} + */ +function flatten(obj, ...props) { + if (!isObject(obj)) return obj; + + const objProps = Object.keys(obj) + .filter(k => !k.startsWith('_')) + .map(k => ({ [k]: true })); + + props = objProps.length ? Object.assign(...objProps, ...props) : Object.assign({}, ...props); + + const out = {}; + + for (let [prop, newProp] of Object.entries(props)) { + if (!newProp) continue; + newProp = newProp === true ? prop : newProp; + + const element = obj[prop]; + const elemIsObj = isObject(element); + const valueOf = elemIsObj && typeof element.valueOf === 'function' ? element.valueOf() : null; + const hasToJSON = elemIsObj && typeof element.toJSON === 'function'; + + // If it's a Collection, make the array of keys + if (element instanceof Collection) out[newProp] = Array.from(element.keys()); + // If the valueOf is a Collection, use its array of keys + else if (valueOf instanceof Collection) out[newProp] = Array.from(valueOf.keys()); + // If it's an array, call toJSON function on each element if present, otherwise flatten each element + else if (Array.isArray(element)) out[newProp] = element.map(e => e.toJSON?.() ?? flatten(e)); + // If it's an object with a primitive `valueOf`, use that value + else if (typeof valueOf !== 'object') out[newProp] = valueOf; + // If it's an object with a toJSON function, use the return value of it + else if (hasToJSON) out[newProp] = element.toJSON(); + // If element is an object, use the flattened version of it + else if (typeof element === 'object') out[newProp] = flatten(element); + // If it's a primitive + else if (!elemIsObj) out[newProp] = element; + } + + return out; +} + +/** + * @typedef {Object} FetchRecommendedShardCountOptions + * @property {number} [guildsPerShard=1000] Number of guilds assigned per shard + * @property {number} [multipleOf=1] The multiple the shard count should round up to. (16 for large bot sharding) + */ + +/** + * Gets the recommended shard count from Discord. + * @param {string} token Discord auth token + * @param {FetchRecommendedShardCountOptions} [options] Options for fetching the recommended shard count + * @returns {Promise<number>} The recommended number of shards + */ +async function fetchRecommendedShardCount(token, { guildsPerShard = 1_000, multipleOf = 1 } = {}) { + if (!token) throw new DiscordjsError(ErrorCodes.TokenMissing); + const response = await fetch(RouteBases.api + Routes.gatewayBot(), { + method: 'GET', + headers: { Authorization: `Bot ${token.replace(/^Bot\s*/i, '')}` }, + }); + if (!response.ok) { + if (response.status === 401) throw new DiscordjsError(ErrorCodes.TokenInvalid); + throw response; + } + const { shards } = await response.json(); + return Math.ceil((shards * (1_000 / guildsPerShard)) / multipleOf) * multipleOf; +} + +/** + * Parses emoji info out of a string. The string must be one of: + * * A UTF-8 emoji (no id) + * * A URL-encoded UTF-8 emoji (no id) + * * A Discord custom emoji (`<:name:id>` or `<a:name:id>`) + * @param {string} text Emoji string to parse + * @returns {APIEmoji} Object with `animated`, `name`, and `id` properties + */ +function parseEmoji(text) { + if (text.includes('%')) text = decodeURIComponent(text); + if (!text.includes(':')) return { animated: false, name: text, id: undefined }; + const match = text.match(/<?(?:(a):)?(\w{2,32}):(\d{17,19})?>?/); + return match && { animated: Boolean(match[1]), name: match[2], id: match[3] }; +} + +/** + * Resolves a partial emoji object from an {@link EmojiIdentifierResolvable}, without checking a Client. + * @param {EmojiIdentifierResolvable} emoji Emoji identifier to resolve + * @returns {?RawEmoji} + * @private + */ +function resolvePartialEmoji(emoji) { + if (!emoji) return null; + if (typeof emoji === 'string') return /^\d{17,19}$/.test(emoji) ? { id: emoji } : parseEmoji(emoji); + const { id, name, animated } = emoji; + if (!id && !name) return null; + return { id, name, animated: Boolean(animated) }; +} + +/** + * Sets default properties on an object that aren't already specified. + * @param {Object} def Default properties + * @param {Object} given Object to assign defaults to + * @returns {Object} + * @private + */ +function mergeDefault(def, given) { + if (!given) return def; + for (const key in def) { + if (!Object.hasOwn(given, key) || given[key] === undefined) { + given[key] = def[key]; + } else if (given[key] === Object(given[key])) { + given[key] = mergeDefault(def[key], given[key]); + } + } + + return given; +} + +/** + * Options used to make an error object. + * @typedef {Object} MakeErrorOptions + * @property {string} name Error type + * @property {string} message Message for the error + * @property {string} stack Stack for the error + */ + +/** + * Makes an Error from a plain info object. + * @param {MakeErrorOptions} obj Error info + * @returns {Error} + * @private + */ +function makeError(obj) { + const err = new Error(obj.message); + err.name = obj.name; + err.stack = obj.stack; + return err; +} + +/** + * Makes a plain error info object from an Error. + * @param {Error} err Error to get info from + * @returns {MakeErrorOptions} + * @private + */ +function makePlainError(err) { + return { + name: err.name, + message: err.message, + stack: err.stack, + }; +} + +const TextSortableGroupTypes = [ChannelType.GuildText, ChannelType.GuildAnnouncement, ChannelType.GuildForum]; +const VoiceSortableGroupTypes = [ChannelType.GuildVoice, ChannelType.GuildStageVoice]; +const CategorySortableGroupTypes = [ChannelType.GuildCategory]; + +/** + * Gets an array of the channel types that can be moved in the channel group. For example, a GuildText channel would + * return an array containing the types that can be ordered within the text channels (always at the top), and a voice + * channel would return an array containing the types that can be ordered within the voice channels (always at the + * bottom). + * @param {ChannelType} type The type of the channel + * @returns {ChannelType[]} + * @private + */ +function getSortableGroupTypes(type) { + switch (type) { + case ChannelType.GuildText: + case ChannelType.GuildAnnouncement: + case ChannelType.GuildForum: + return TextSortableGroupTypes; + case ChannelType.GuildVoice: + case ChannelType.GuildStageVoice: + return VoiceSortableGroupTypes; + case ChannelType.GuildCategory: + return CategorySortableGroupTypes; + default: + return [type]; + } +} + +/** + * Moves an element in an array *in place*. + * @param {Array<*>} array Array to modify + * @param {*} element Element to move + * @param {number} newIndex Index or offset to move the element to + * @param {boolean} [offset=false] Move the element by an offset amount rather than to a set index + * @returns {number} + * @private + */ +function moveElementInArray(array, element, newIndex, offset = false) { + const index = array.indexOf(element); + newIndex = (offset ? index : 0) + newIndex; + if (newIndex > -1 && newIndex < array.length) { + const removedElement = array.splice(index, 1)[0]; + array.splice(newIndex, 0, removedElement); + } + return array.indexOf(element); +} + +/** + * Verifies the provided data is a string, otherwise throws provided error. + * @param {string} data The string resolvable to resolve + * @param {Function} [error] The Error constructor to instantiate. Defaults to Error + * @param {string} [errorMessage] The error message to throw with. Defaults to "Expected string, got <data> instead." + * @param {boolean} [allowEmpty=true] Whether an empty string should be allowed + * @returns {string} + */ +function verifyString( + data, + error = Error, + errorMessage = `Expected a string, got ${data} instead.`, + allowEmpty = true, +) { + if (typeof data !== 'string') throw new error(errorMessage); + if (!allowEmpty && data.length === 0) throw new error(errorMessage); + return data; +} + +/** + * Can be a number, hex string, an RGB array like: + * ```js + * [255, 0, 255] // purple + * ``` + * or one of the following strings: + * - `Default` + * - `White` + * - `Aqua` + * - `Green` + * - `Blue` + * - `Yellow` + * - `Purple` + * - `LuminousVividPink` + * - `Fuchsia` + * - `Gold` + * - `Orange` + * - `Red` + * - `Grey` + * - `Navy` + * - `DarkAqua` + * - `DarkGreen` + * - `DarkBlue` + * - `DarkPurple` + * - `DarkVividPink` + * - `DarkGold` + * - `DarkOrange` + * - `DarkRed` + * - `DarkGrey` + * - `DarkerGrey` + * - `LightGrey` + * - `DarkNavy` + * - `Blurple` + * - `Greyple` + * - `DarkButNotBlack` + * - `NotQuiteBlack` + * - `Random` + * @typedef {string|number|number[]} ColorResolvable + */ + +/** + * Resolves a ColorResolvable into a color number. + * @param {ColorResolvable} color Color to resolve + * @returns {number} A color + */ +function resolveColor(color) { + if (typeof color === 'string') { + if (color === 'Random') return Math.floor(Math.random() * (0xffffff + 1)); + if (color === 'Default') return 0; + if (/^#?[\da-f]{6}$/i.test(color)) return parseInt(color.replace('#', ''), 16); + color = Colors[color]; + } else if (Array.isArray(color)) { + color = (color[0] << 16) + (color[1] << 8) + color[2]; + } + + if (color < 0 || color > 0xffffff) throw new DiscordjsRangeError(ErrorCodes.ColorRange); + if (typeof color !== 'number' || Number.isNaN(color)) throw new DiscordjsTypeError(ErrorCodes.ColorConvert); + + return color; +} + +/** + * Sorts by Discord's position and id. + * @param {Collection} collection Collection of objects to sort + * @returns {Collection} + */ +function discordSort(collection) { + const isGuildChannel = collection.first() instanceof GuildChannel; + return collection.sorted( + isGuildChannel + ? (a, b) => a.rawPosition - b.rawPosition || Number(BigInt(a.id) - BigInt(b.id)) + : (a, b) => a.rawPosition - b.rawPosition || Number(BigInt(b.id) - BigInt(a.id)), + ); +} + +/** + * Sets the position of a Channel or Role. + * @param {BaseChannel|Role} item Object to set the position of + * @param {number} position New position for the object + * @param {boolean} relative Whether `position` is relative to its current position + * @param {Collection<string, BaseChannel|Role>} sorted A collection of the objects sorted properly + * @param {Client} client The client to use to patch the data + * @param {string} route Route to call PATCH on + * @param {string} [reason] Reason for the change + * @returns {Promise<BaseChannel[]|Role[]>} Updated item list, with `id` and `position` properties + * @private + */ +async function setPosition(item, position, relative, sorted, client, route, reason) { + let updatedItems = [...sorted.values()]; + moveElementInArray(updatedItems, item, position, relative); + updatedItems = updatedItems.map((r, i) => ({ id: r.id, position: i })); + await client.rest.patch(route, { body: updatedItems, reason }); + return updatedItems; +} + +/** + * Alternative to Node's `path.basename`, removing query string after the extension if it exists. + * @param {string} path Path to get the basename of + * @param {string} [ext] File extension to remove + * @returns {string} Basename of the path + * @private + */ +function basename(path, ext) { + const res = parse(path); + return ext && res.ext.startsWith(ext) ? res.name : res.base.split('?')[0]; +} + +/** + * The content to have all mentions replaced by the equivalent text. + * @param {string} str The string to be converted + * @param {TextBasedChannels} channel The channel the string was sent in + * @returns {string} + */ +function cleanContent(str, channel) { + return str.replaceAll(/<(@[!&]?|#)(\d{17,19})>/g, (match, type, id) => { + switch (type) { + case '@': + case '@!': { + const member = channel.guild?.members.cache.get(id); + if (member) { + return `@${member.displayName}`; + } + + const user = channel.client.users.cache.get(id); + return user ? `@${user.username}` : match; + } + case '@&': { + if (channel.type === ChannelType.DM) return match; + const role = channel.guild.roles.cache.get(id); + return role ? `@${role.name}` : match; + } + case '#': { + const mentionedChannel = channel.client.channels.cache.get(id); + return mentionedChannel ? `#${mentionedChannel.name}` : match; + } + default: { + return match; + } + } + }); +} + +/** + * The content to put in a code block with all code block fences replaced by the equivalent backticks. + * @param {string} text The string to be converted + * @returns {string} + */ +function cleanCodeBlockContent(text) { + return text.replaceAll('```', '`\u200b``'); +} + +/** + * Parses a webhook URL for the id and token. + * @param {string} url The URL to parse + * @returns {?WebhookClientDataIdWithToken} `null` if the URL is invalid, otherwise the id and the token + */ +function parseWebhookURL(url) { + const matches = url.match( + /https?:\/\/(?:ptb\.|canary\.)?discord\.com\/api(?:\/v\d{1,2})?\/webhooks\/(\d{17,19})\/([\w-]{68})/i, + ); + + if (!matches || matches.length <= 2) return null; + + const [, id, token] = matches; + return { + id, + token, + }; +} + +module.exports = { + flatten, + fetchRecommendedShardCount, + parseEmoji, + resolvePartialEmoji, + mergeDefault, + makeError, + makePlainError, + getSortableGroupTypes, + moveElementInArray, + verifyString, + resolveColor, + discordSort, + setPosition, + basename, + cleanContent, + cleanCodeBlockContent, + parseWebhookURL, +}; + +// Fixes Circular +const GuildChannel = require('../structures/GuildChannel'); diff --git a/node_modules/discord.js/src/util/WebSocketShardEvents.js b/node_modules/discord.js/src/util/WebSocketShardEvents.js new file mode 100644 index 0000000..81e05f2 --- /dev/null +++ b/node_modules/discord.js/src/util/WebSocketShardEvents.js @@ -0,0 +1,25 @@ +'use strict'; + +/** + * @typedef {Object} WebSocketShardEvents + * @property {string} Close close + * @property {string} Destroyed destroyed + * @property {string} InvalidSession invalidSession + * @property {string} Ready ready + * @property {string} Resumed resumed + * @property {string} AllReady allReady + */ + +// JSDoc for IntelliSense purposes +/** + * @type {WebSocketShardEvents} + * @ignore + */ +module.exports = { + Close: 'close', + Destroyed: 'destroyed', + InvalidSession: 'invalidSession', + Ready: 'ready', + Resumed: 'resumed', + AllReady: 'allReady', +}; |