update
This commit is contained in:
		@@ -13,12 +13,15 @@ const Util = require('../util/Util');
 | 
			
		||||
class BaseClient extends EventEmitter {
 | 
			
		||||
  constructor(options = {}) {
 | 
			
		||||
    super();
 | 
			
		||||
 | 
			
		||||
    if (options.intents) {
 | 
			
		||||
      process.emitWarning('Intents is not available.', 'DeprecationWarning');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (typeof options.captchaSolver === 'function') {
 | 
			
		||||
      options.captchaService = 'custom';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The options the client was instantiated with
 | 
			
		||||
     * @type {ClientOptions}
 | 
			
		||||
 
 | 
			
		||||
@@ -126,36 +126,15 @@ class WebSocketManager extends EventEmitter {
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  async connect() {
 | 
			
		||||
    // eslint-disable-next-line no-unused-vars
 | 
			
		||||
    const invalidToken = new Error(WSCodes[4004]);
 | 
			
		||||
    /*
 | 
			
		||||
    BOT
 | 
			
		||||
    const {
 | 
			
		||||
      url: gatewayURL,
 | 
			
		||||
      shards: recommendedShards,
 | 
			
		||||
      session_start_limit: sessionStartLimit,
 | 
			
		||||
    } = await this.client.api.gateway.bot.get().catch(error => {
 | 
			
		||||
      throw error.httpStatus === 401 ? invalidToken : error;
 | 
			
		||||
    });
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    let gatewayURL = 'wss://gateway.discord.gg';
 | 
			
		||||
    const { url } = await this.client.api.gateway.get({ auth: false }).catch(() => ({ url: gatewayURL }));
 | 
			
		||||
    // eslint-disable-next-line no-unused-vars
 | 
			
		||||
    /*
 | 
			
		||||
      .catch(error => {
 | 
			
		||||
        // Never throw error :v
 | 
			
		||||
        // throw error.httpStatus === 401 ? invalidToken : error;
 | 
			
		||||
      });
 | 
			
		||||
      */
 | 
			
		||||
    if (url) gatewayURL = url;
 | 
			
		||||
    const recommendedShards = 1;
 | 
			
		||||
    const sessionStartLimit = {
 | 
			
		||||
      total: Infinity,
 | 
			
		||||
      remaining: Infinity,
 | 
			
		||||
    };
 | 
			
		||||
    await this.client.api.gateway
 | 
			
		||||
      .get({ auth: false })
 | 
			
		||||
      .then(r => (gatewayURL = r.url))
 | 
			
		||||
      .catch(() => {});
 | 
			
		||||
 | 
			
		||||
    const { total, remaining } = sessionStartLimit;
 | 
			
		||||
    const total = Infinity;
 | 
			
		||||
    const remaining = Infinity;
 | 
			
		||||
    const recommendedShards = 1;
 | 
			
		||||
 | 
			
		||||
    this.debug(`Fetched Gateway Information
 | 
			
		||||
    URL: ${gatewayURL}
 | 
			
		||||
@@ -294,7 +273,7 @@ class WebSocketManager extends EventEmitter {
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      this.debug(`Couldn't reconnect or fetch information about the gateway. ${error}`);
 | 
			
		||||
      if (error.httpStatus !== 401) {
 | 
			
		||||
        this.debug('Possible network error occurred. Retrying in 5s...');
 | 
			
		||||
        this.debug(`Possible network error occurred. Retrying in 5s...`);
 | 
			
		||||
        await sleep(5_000);
 | 
			
		||||
        this.reconnecting = false;
 | 
			
		||||
        return this.reconnect();
 | 
			
		||||
@@ -368,11 +347,12 @@ class WebSocketManager extends EventEmitter {
 | 
			
		||||
      /**
 | 
			
		||||
       * Emitted whenever a packet isn't handled.
 | 
			
		||||
       * @event Client#unhandledPacket
 | 
			
		||||
       * @param {Object} packet The packet (t: Event name, d: Data)
 | 
			
		||||
       * @param {Number} shard The shard that received the packet (Auto = 0)
 | 
			
		||||
       * @param {Object} packet The packet (t: EVENT_NAME, d: any)
 | 
			
		||||
       * @param {Number} shard The shard that received the packet (Shard 0)
 | 
			
		||||
       */
 | 
			
		||||
      this.client.emit(Events.UNHANDLED_PACKET, packet, shard);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const { isJSONEncodable } = require('@discordjs/builders');
 | 
			
		||||
const { Collection } = require('@discordjs/collection');
 | 
			
		||||
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
 | 
			
		||||
const CachedManager = require('./CachedManager');
 | 
			
		||||
@@ -13,15 +14,14 @@ const Permissions = require('../util/Permissions');
 | 
			
		||||
 * @extends {CachedManager}
 | 
			
		||||
 */
 | 
			
		||||
class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
  constructor(client, iterable, user) {
 | 
			
		||||
  constructor(client, iterable) {
 | 
			
		||||
    super(client, ApplicationCommand, iterable);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The manager for permissions of arbitrary commands on arbitrary guilds
 | 
			
		||||
     * @type {ApplicationCommandPermissionsManager}
 | 
			
		||||
     */
 | 
			
		||||
    this.permissions = new ApplicationCommandPermissionsManager(this, user);
 | 
			
		||||
    this.user = user;
 | 
			
		||||
    this.permissions = new ApplicationCommandPermissionsManager(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -43,7 +43,7 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  commandPath({ id, guildId } = {}) {
 | 
			
		||||
    let path = this.client.api.applications(this.user.id);
 | 
			
		||||
    let path = this.client.api.applications(this.client.application.id);
 | 
			
		||||
    if (this.guild ?? guildId) path = path.guilds(this.guild?.id ?? guildId);
 | 
			
		||||
    return id ? path.commands(id) : path.commands;
 | 
			
		||||
  }
 | 
			
		||||
@@ -58,7 +58,7 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
  /* eslint-disable max-len */
 | 
			
		||||
  /**
 | 
			
		||||
   * Data that resolves to the data of an ApplicationCommand
 | 
			
		||||
   * @typedef {ApplicationCommandDataResolvable|SlashCommandBuilder|ContextMenuCommandBuilder} ApplicationCommandDataResolvable
 | 
			
		||||
   * @typedef {ApplicationCommandData|APIApplicationCommand|SlashCommandBuilder|ContextMenuCommandBuilder} ApplicationCommandDataResolvable
 | 
			
		||||
   */
 | 
			
		||||
  /* eslint-enable max-len */
 | 
			
		||||
 | 
			
		||||
@@ -94,7 +94,6 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) {
 | 
			
		||||
    // Change from user.createDM to opcode (risky action)
 | 
			
		||||
    if (typeof id === 'object') {
 | 
			
		||||
      ({ guildId, cache = true, locale, withLocalizations } = id);
 | 
			
		||||
    } else if (id) {
 | 
			
		||||
@@ -102,11 +101,10 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
        const existing = this.cache.get(id);
 | 
			
		||||
        if (existing) return existing;
 | 
			
		||||
      }
 | 
			
		||||
      await this.user.createDM().catch(() => {});
 | 
			
		||||
      const command = await this.commandPath({ id, guildId }).get();
 | 
			
		||||
      return this._add(command, cache);
 | 
			
		||||
    }
 | 
			
		||||
    await this.user.createDM().catch(() => {});
 | 
			
		||||
 | 
			
		||||
    const data = await this.commandPath({ guildId }).get({
 | 
			
		||||
      headers: {
 | 
			
		||||
        'X-Discord-Locale': locale,
 | 
			
		||||
@@ -132,7 +130,6 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  async create(command, guildId) {
 | 
			
		||||
    if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
 | 
			
		||||
    const data = await this.commandPath({ guildId }).post({
 | 
			
		||||
      data: this.constructor.transformCommand(command),
 | 
			
		||||
    });
 | 
			
		||||
@@ -162,7 +159,6 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  async set(commands, guildId) {
 | 
			
		||||
    if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
 | 
			
		||||
    const data = await this.commandPath({ guildId }).put({
 | 
			
		||||
      data: commands.map(c => this.constructor.transformCommand(c)),
 | 
			
		||||
    });
 | 
			
		||||
@@ -185,7 +181,6 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  async edit(command, data, guildId) {
 | 
			
		||||
    if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
 | 
			
		||||
    const id = this.resolveId(command);
 | 
			
		||||
    if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
 | 
			
		||||
 | 
			
		||||
@@ -208,7 +203,6 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  async delete(command, guildId) {
 | 
			
		||||
    if (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
 | 
			
		||||
    const id = this.resolveId(command);
 | 
			
		||||
    if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
 | 
			
		||||
 | 
			
		||||
@@ -226,6 +220,8 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  static transformCommand(command) {
 | 
			
		||||
    if (isJSONEncodable(command)) return command.toJSON();
 | 
			
		||||
 | 
			
		||||
    let default_member_permissions;
 | 
			
		||||
 | 
			
		||||
    if ('default_member_permissions' in command) {
 | 
			
		||||
@@ -240,6 +236,7 @@ class ApplicationCommandManager extends CachedManager {
 | 
			
		||||
          ? new Permissions(command.defaultMemberPermissions).bitfield.toString()
 | 
			
		||||
          : command.defaultMemberPermissions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      name: command.name,
 | 
			
		||||
      name_localizations: command.nameLocalizations ?? command.name_localizations,
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Consta
 | 
			
		||||
 * @extends {BaseManager}
 | 
			
		||||
 */
 | 
			
		||||
class ApplicationCommandPermissionsManager extends BaseManager {
 | 
			
		||||
  constructor(manager, user) {
 | 
			
		||||
  constructor(manager) {
 | 
			
		||||
    super(manager.client);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -37,8 +37,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
 | 
			
		||||
     * @type {?Snowflake}
 | 
			
		||||
     */
 | 
			
		||||
    this.commandId = manager.id ?? null;
 | 
			
		||||
 | 
			
		||||
    this.user = user;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -49,10 +47,7 @@ class ApplicationCommandPermissionsManager extends BaseManager {
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  permissionsPath(guildId, commandId) {
 | 
			
		||||
    return this.client.api
 | 
			
		||||
      .applications(typeof this.user === 'string' ? this.user : this.user.id)
 | 
			
		||||
      .guilds(guildId)
 | 
			
		||||
      .commands(commandId).permissions;
 | 
			
		||||
    return this.client.api.applications(this.client.application.id).guilds(guildId).commands(commandId).permissions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -164,7 +159,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  async set({ guild, command, permissions, fullPermissions } = {}) {
 | 
			
		||||
    if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
 | 
			
		||||
    const { guildId, commandId } = this._validateOptions(guild, command);
 | 
			
		||||
 | 
			
		||||
    if (commandId) {
 | 
			
		||||
@@ -226,7 +220,6 @@ class ApplicationCommandPermissionsManager extends BaseManager {
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  async add({ guild, command, permissions }) {
 | 
			
		||||
    if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
 | 
			
		||||
    const { guildId, commandId } = this._validateOptions(guild, command);
 | 
			
		||||
    if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
 | 
			
		||||
    if (!Array.isArray(permissions)) {
 | 
			
		||||
@@ -278,13 +271,12 @@ class ApplicationCommandPermissionsManager extends BaseManager {
 | 
			
		||||
   *    .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  async remove({ guild, command, users, roles }) {
 | 
			
		||||
    if (!this.manager.client.user.bot) throw new Error('INVALID_USER_METHOD');
 | 
			
		||||
    const { guildId, commandId } = this._validateOptions(guild, command);
 | 
			
		||||
    if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
 | 
			
		||||
 | 
			
		||||
    if (!users && !roles) throw new TypeError('INVALID_TYPE', 'users OR roles', 'Array or Resolvable', true);
 | 
			
		||||
 | 
			
		||||
    const resolvedIds = [];
 | 
			
		||||
    let resolvedIds = [];
 | 
			
		||||
    if (Array.isArray(users)) {
 | 
			
		||||
      users.forEach(user => {
 | 
			
		||||
        const userId = this.client.users.resolveId(user);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,15 @@
 | 
			
		||||
const { Collection } = require('@discordjs/collection');
 | 
			
		||||
const CachedManager = require('./CachedManager');
 | 
			
		||||
const { Error } = require('../errors');
 | 
			
		||||
const { lazy } = require('../util/Util');
 | 
			
		||||
const User = lazy(() => require('../structures/User'));
 | 
			
		||||
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);
 | 
			
		||||
    super(reaction.client, User, iterable);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The reaction that this manager belongs to
 | 
			
		||||
@@ -22,7 +22,7 @@ class ReactionUserManager extends CachedManager {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The cache of this manager
 | 
			
		||||
   * @type {Collection<Snowflake, Discord.User>}
 | 
			
		||||
   * @type {Collection<Snowflake, User>}
 | 
			
		||||
   * @name ReactionUserManager#cache
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +36,7 @@ class ReactionUserManager extends CachedManager {
 | 
			
		||||
  /**
 | 
			
		||||
   * 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, Discord.User>>}
 | 
			
		||||
   * @returns {Promise<Collection<Snowflake, User>>}
 | 
			
		||||
   */
 | 
			
		||||
  async fetch({ limit = 100, after } = {}) {
 | 
			
		||||
    const message = this.reaction.message;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,17 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const { setTimeout } = require('node:timers');
 | 
			
		||||
const { findBestMatch } = require('string-similarity');
 | 
			
		||||
const Base = require('./Base');
 | 
			
		||||
const MessagePayload = require('./MessagePayload');
 | 
			
		||||
const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
 | 
			
		||||
const {
 | 
			
		||||
  ApplicationCommandOptionTypes,
 | 
			
		||||
  ApplicationCommandTypes,
 | 
			
		||||
  ChannelTypes,
 | 
			
		||||
  Events,
 | 
			
		||||
  InteractionTypes,
 | 
			
		||||
} = require('../util/Constants');
 | 
			
		||||
const { ApplicationCommandOptionTypes, ApplicationCommandTypes, ChannelTypes } = require('../util/Constants');
 | 
			
		||||
const Permissions = require('../util/Permissions');
 | 
			
		||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
 | 
			
		||||
const { lazy, getAttachments, uploadFile } = require('../util/Util');
 | 
			
		||||
const Message = lazy(() => require('../structures/Message').Message);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents an application command.
 | 
			
		||||
 * @extends {Base}
 | 
			
		||||
 */
 | 
			
		||||
class ApplicationCommand extends Base {
 | 
			
		||||
  constructor(client, data) {
 | 
			
		||||
  constructor(client, data, guild, guildId) {
 | 
			
		||||
    super(client);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -37,11 +26,24 @@ class ApplicationCommand extends Base {
 | 
			
		||||
     */
 | 
			
		||||
    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, this.applicationId);
 | 
			
		||||
    this.permissions = new ApplicationCommandPermissionsManager(this);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The type of this application command
 | 
			
		||||
@@ -49,30 +51,10 @@ class ApplicationCommand extends Base {
 | 
			
		||||
     */
 | 
			
		||||
    this.type = ApplicationCommandTypes[data.type];
 | 
			
		||||
 | 
			
		||||
    this.user = client.users.cache.get(this.applicationId);
 | 
			
		||||
 | 
			
		||||
    this._patch(data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The guild this command is part of
 | 
			
		||||
   * @type {?Guild}
 | 
			
		||||
   * @readonly
 | 
			
		||||
   */
 | 
			
		||||
  get guild() {
 | 
			
		||||
    return this.client.guilds.resolve(this.guildId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _patch(data) {
 | 
			
		||||
    if ('guild_id' in data) {
 | 
			
		||||
      /**
 | 
			
		||||
       * 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 = data.guild_id ?? null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ('name' in data) {
 | 
			
		||||
      /**
 | 
			
		||||
       * The name of this command
 | 
			
		||||
@@ -148,7 +130,6 @@ class ApplicationCommand extends Base {
 | 
			
		||||
       */
 | 
			
		||||
      this.defaultPermission = data.default_permission;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* eslint-disable max-len */
 | 
			
		||||
 | 
			
		||||
    if ('default_member_permissions' in data) {
 | 
			
		||||
@@ -336,7 +317,6 @@ class ApplicationCommand extends Base {
 | 
			
		||||
  setDefaultPermission(defaultPermission = true) {
 | 
			
		||||
    return this.edit({ defaultPermission });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* eslint-enable max-len */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -391,6 +371,7 @@ class ApplicationCommand extends Base {
 | 
			
		||||
  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;
 | 
			
		||||
 | 
			
		||||
@@ -404,6 +385,7 @@ class ApplicationCommand extends Base {
 | 
			
		||||
      defaultMemberPermissions =
 | 
			
		||||
        command.defaultMemberPermissions !== null ? new Permissions(command.defaultMemberPermissions).bitfield : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check top level parameters
 | 
			
		||||
    const commandType = typeof command.type === 'string' ? command.type : ApplicationCommandTypes[command.type];
 | 
			
		||||
    if (
 | 
			
		||||
@@ -446,9 +428,7 @@ class ApplicationCommand extends Base {
 | 
			
		||||
    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;
 | 
			
		||||
      }
 | 
			
		||||
      if (!foundOption || !this._optionEquals(option, foundOption)) return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
@@ -597,423 +577,6 @@ class ApplicationCommand extends Base {
 | 
			
		||||
      [maxLengthKey]: option.maxLength ?? option.max_length,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * Send Slash command to channel
 | 
			
		||||
   * @param {Message} message Discord Message
 | 
			
		||||
   * @param {Array<string>} subCommandArray SubCommand Array
 | 
			
		||||
   * @param {Array<any>} options The options to Slash Command
 | 
			
		||||
   * @returns {Promise<InteractionResponse>}
 | 
			
		||||
   */
 | 
			
		||||
  // eslint-disable-next-line consistent-return
 | 
			
		||||
  async sendSlashCommand(message, subCommandArray = [], options = []) {
 | 
			
		||||
    // Todo: Refactor [Done]
 | 
			
		||||
    const buildError = (type, value, array, msg) =>
 | 
			
		||||
      new Error(`Invalid ${type}: ${value} ${msg}\nList of ${type}:\n${array}`);
 | 
			
		||||
    // Check Options
 | 
			
		||||
    if (!(message instanceof Message())) {
 | 
			
		||||
      throw new TypeError('The message must be a Discord.Message');
 | 
			
		||||
    }
 | 
			
		||||
    if (!Array.isArray(options)) {
 | 
			
		||||
      throw new TypeError('The options must be an array of strings');
 | 
			
		||||
    }
 | 
			
		||||
    if (this.type !== 'CHAT_INPUT') throw new Error('This command is not a chat input [/]');
 | 
			
		||||
    const optionFormat = [];
 | 
			
		||||
    const attachments = [];
 | 
			
		||||
    const attachmentsBuffer = [];
 | 
			
		||||
    const parseChoices = (list_choices, value) => {
 | 
			
		||||
      if (value !== undefined) {
 | 
			
		||||
        if (Array.isArray(list_choices) && list_choices.length) {
 | 
			
		||||
          const choice = list_choices.find(c => c.name === value) || list_choices.find(c => c.value === value);
 | 
			
		||||
          if (choice) {
 | 
			
		||||
            return choice.value;
 | 
			
		||||
          }
 | 
			
		||||
          throw buildError(
 | 
			
		||||
            'choice',
 | 
			
		||||
            value,
 | 
			
		||||
            list_choices.map((c, i) => `  #${i + 1} Name: ${c.name} Value: ${c.value}`).join('\n'),
 | 
			
		||||
            'is not a valid choice for this option',
 | 
			
		||||
          );
 | 
			
		||||
        } else {
 | 
			
		||||
          return value;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        return undefined;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    const parseOption = async (optionCommand, value) => {
 | 
			
		||||
      const data = {
 | 
			
		||||
        type: ApplicationCommandOptionTypes[optionCommand.type],
 | 
			
		||||
        name: optionCommand.name,
 | 
			
		||||
      };
 | 
			
		||||
      if (value !== undefined) {
 | 
			
		||||
        value = parseChoices(optionCommand.choices, value);
 | 
			
		||||
        switch (optionCommand.type) {
 | 
			
		||||
          case 'BOOLEAN': {
 | 
			
		||||
            data.value = Boolean(value);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case 'INTEGER': {
 | 
			
		||||
            data.value = Number(value);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case 'ATTACHMENT': {
 | 
			
		||||
            data.value = await addDataFromAttachment(value, this.client);
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          case 'SUB_COMMAND_GROUP': {
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
          default: {
 | 
			
		||||
            if (optionCommand.autocomplete) {
 | 
			
		||||
              let optionsBuild;
 | 
			
		||||
              switch (subCommandArray.length) {
 | 
			
		||||
                case 0: {
 | 
			
		||||
                  optionsBuild = [
 | 
			
		||||
                    ...optionFormat,
 | 
			
		||||
                    {
 | 
			
		||||
                      type: ApplicationCommandOptionTypes[optionCommand.type],
 | 
			
		||||
                      name: optionCommand.name,
 | 
			
		||||
                      value,
 | 
			
		||||
                      focused: true,
 | 
			
		||||
                    },
 | 
			
		||||
                  ];
 | 
			
		||||
                  break;
 | 
			
		||||
                }
 | 
			
		||||
                case 1: {
 | 
			
		||||
                  const subCommand = this.options.find(o => o.name == subCommandArray[0] && o.type == 'SUB_COMMAND');
 | 
			
		||||
                  optionsBuild = [
 | 
			
		||||
                    {
 | 
			
		||||
                      type: ApplicationCommandOptionTypes[subCommand.type],
 | 
			
		||||
                      name: subCommand.name,
 | 
			
		||||
                      options: [
 | 
			
		||||
                        ...optionFormat,
 | 
			
		||||
                        {
 | 
			
		||||
                          type: ApplicationCommandOptionTypes[optionCommand.type],
 | 
			
		||||
                          name: optionCommand.name,
 | 
			
		||||
                          value,
 | 
			
		||||
                          focused: true,
 | 
			
		||||
                        },
 | 
			
		||||
                      ],
 | 
			
		||||
                    },
 | 
			
		||||
                  ];
 | 
			
		||||
                  break;
 | 
			
		||||
                }
 | 
			
		||||
                case 2: {
 | 
			
		||||
                  const subGroup = this.options.find(
 | 
			
		||||
                    o => o.name == subCommandArray[0] && o.type == 'SUB_COMMAND_GROUP',
 | 
			
		||||
                  );
 | 
			
		||||
                  const subCommand = subGroup.options.find(
 | 
			
		||||
                    o => o.name == subCommandArray[1] && o.type == 'SUB_COMMAND',
 | 
			
		||||
                  );
 | 
			
		||||
                  optionsBuild = [
 | 
			
		||||
                    {
 | 
			
		||||
                      type: ApplicationCommandOptionTypes[subGroup.type],
 | 
			
		||||
                      name: subGroup.name,
 | 
			
		||||
                      options: [
 | 
			
		||||
                        {
 | 
			
		||||
                          type: ApplicationCommandOptionTypes[subCommand.type],
 | 
			
		||||
                          name: subCommand.name,
 | 
			
		||||
                          options: [
 | 
			
		||||
                            ...optionFormat,
 | 
			
		||||
                            {
 | 
			
		||||
                              type: ApplicationCommandOptionTypes[optionCommand.type],
 | 
			
		||||
                              name: optionCommand.name,
 | 
			
		||||
                              value,
 | 
			
		||||
                              focused: true,
 | 
			
		||||
                            },
 | 
			
		||||
                          ],
 | 
			
		||||
                        },
 | 
			
		||||
                      ],
 | 
			
		||||
                    },
 | 
			
		||||
                  ];
 | 
			
		||||
                  break;
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              const autoValue = await getAutoResponse(optionsBuild, value);
 | 
			
		||||
              data.value = autoValue;
 | 
			
		||||
            } else {
 | 
			
		||||
              data.value = value;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        optionFormat.push(data);
 | 
			
		||||
      }
 | 
			
		||||
      return optionFormat;
 | 
			
		||||
    };
 | 
			
		||||
    const parseSubCommand = async (subCommandName, options, subGroup) => {
 | 
			
		||||
      const options_sub = subGroup ? subGroup.options : this.options;
 | 
			
		||||
      const subCommand = options_sub.find(
 | 
			
		||||
        o => (o.name == subCommandName || o.nameLocalized == subCommandName) && o.type == 'SUB_COMMAND',
 | 
			
		||||
      );
 | 
			
		||||
      if (!subCommand) {
 | 
			
		||||
        throw buildError(
 | 
			
		||||
          'SubCommand',
 | 
			
		||||
          subCommandName,
 | 
			
		||||
          options_sub.map((o, i) => `  #${i + 1} Name: ${o.name}`).join('\n'),
 | 
			
		||||
          'is not a valid sub command',
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      const valueRequired = subCommand.options?.filter(o => o.required).length || 0;
 | 
			
		||||
      for (let i = 0; i < options.length; i++) {
 | 
			
		||||
        const optionInput = subCommand.options[i];
 | 
			
		||||
        const value = options[i];
 | 
			
		||||
        await parseOption(optionInput, value);
 | 
			
		||||
      }
 | 
			
		||||
      if (valueRequired > options.length) {
 | 
			
		||||
        throw new Error(`Value required missing\nDebug:
 | 
			
		||||
        Required: ${valueRequired} - Options: ${optionFormat.length}`);
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        type: ApplicationCommandOptionTypes[subCommand.type],
 | 
			
		||||
        name: subCommand.name,
 | 
			
		||||
        options: optionFormat,
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
    const parseSubGroupCommand = async (subGroupName, subName) => {
 | 
			
		||||
      const subGroup = this.options.find(
 | 
			
		||||
        o => (o.name == subGroupName || o.nameLocalized == subGroupName) && o.type == 'SUB_COMMAND_GROUP',
 | 
			
		||||
      );
 | 
			
		||||
      if (!subGroup) {
 | 
			
		||||
        throw buildError(
 | 
			
		||||
          'SubGroupCommand',
 | 
			
		||||
          subGroupName,
 | 
			
		||||
          this.options.map((o, i) => `  #${i + 1} Name: ${o.name}`).join('\n'),
 | 
			
		||||
          'is not a valid sub group command',
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      const data = await parseSubCommand(subName, options, subGroup);
 | 
			
		||||
      return {
 | 
			
		||||
        type: ApplicationCommandOptionTypes[subGroup.type],
 | 
			
		||||
        name: subGroup.name,
 | 
			
		||||
        options: [data],
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
    async function addDataFromAttachment(data, client) {
 | 
			
		||||
      const data_ = await MessagePayload.resolveFile(data);
 | 
			
		||||
      if (!data_.file) {
 | 
			
		||||
        throw new TypeError(
 | 
			
		||||
          'The attachment data must be a BufferResolvable or Stream or FileOptions of MessageAttachment',
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      if (client.options.usingNewAttachmentAPI === true) {
 | 
			
		||||
        const attachments_ = await getAttachments(client, message.channelId, data_);
 | 
			
		||||
        await uploadFile(data_.file, attachments_[0].upload_url);
 | 
			
		||||
        const id = attachments.length;
 | 
			
		||||
        attachments.push({
 | 
			
		||||
          id: id,
 | 
			
		||||
          filename: data_.name,
 | 
			
		||||
          uploaded_filename: attachments_[0].upload_filename,
 | 
			
		||||
        });
 | 
			
		||||
        return id;
 | 
			
		||||
      } else {
 | 
			
		||||
        const id = attachments.length;
 | 
			
		||||
        attachments.push({
 | 
			
		||||
          id: id,
 | 
			
		||||
          filename: data_.name,
 | 
			
		||||
        });
 | 
			
		||||
        attachmentsBuffer.push(data_);
 | 
			
		||||
        return id;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    const getDataPost = (dataAdd = [], nonce, autocomplete = false) => {
 | 
			
		||||
      if (!Array.isArray(dataAdd) && typeof dataAdd == 'object') {
 | 
			
		||||
        dataAdd = [dataAdd];
 | 
			
		||||
      }
 | 
			
		||||
      const data = {
 | 
			
		||||
        type: autocomplete ? InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE : InteractionTypes.APPLICATION_COMMAND,
 | 
			
		||||
        application_id: this.applicationId,
 | 
			
		||||
        guild_id: message.guildId,
 | 
			
		||||
        channel_id: message.channelId,
 | 
			
		||||
        session_id: this.client.sessionId,
 | 
			
		||||
        data: {
 | 
			
		||||
          version: this.version,
 | 
			
		||||
          id: this.id,
 | 
			
		||||
          name: this.name,
 | 
			
		||||
          type: ApplicationCommandTypes[this.type],
 | 
			
		||||
          options: dataAdd,
 | 
			
		||||
          attachments: attachments,
 | 
			
		||||
        },
 | 
			
		||||
        nonce,
 | 
			
		||||
      };
 | 
			
		||||
      if (this.guildId) {
 | 
			
		||||
        data.data.guild_id = message.guildId;
 | 
			
		||||
      }
 | 
			
		||||
      return data;
 | 
			
		||||
    };
 | 
			
		||||
    const getAutoResponse = async (sendData, value) => {
 | 
			
		||||
      let nonce = SnowflakeUtil.generate();
 | 
			
		||||
      const data = getDataPost(sendData, nonce, true);
 | 
			
		||||
      await this.client.api.interactions.post({
 | 
			
		||||
        data,
 | 
			
		||||
        files: attachmentsBuffer,
 | 
			
		||||
      });
 | 
			
		||||
      return new Promise(resolve => {
 | 
			
		||||
        const handler = data => {
 | 
			
		||||
          timeout.refresh();
 | 
			
		||||
          if (data.nonce !== nonce) return;
 | 
			
		||||
          clearTimeout(timeout);
 | 
			
		||||
          this.client.removeListener(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
 | 
			
		||||
          this.client.decrementMaxListeners();
 | 
			
		||||
          if (data.choices.length > 1) {
 | 
			
		||||
            // Find best match name
 | 
			
		||||
            const bestMatch = findBestMatch(
 | 
			
		||||
              value,
 | 
			
		||||
              data.choices.map(c => c.name),
 | 
			
		||||
            );
 | 
			
		||||
            const result = data.choices.find(c => c.name == bestMatch.bestMatch.target);
 | 
			
		||||
            resolve(result.value);
 | 
			
		||||
          } else {
 | 
			
		||||
            resolve(value);
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
        const timeout = setTimeout(() => {
 | 
			
		||||
          this.client.removeListener(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
 | 
			
		||||
          this.client.decrementMaxListeners();
 | 
			
		||||
          resolve(value);
 | 
			
		||||
        }, this.client.options.interactionTimeout).unref();
 | 
			
		||||
        this.client.incrementMaxListeners();
 | 
			
		||||
        this.client.on(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
    const sendData = async (optionsData = []) => {
 | 
			
		||||
      let nonce = SnowflakeUtil.generate();
 | 
			
		||||
      const data = getDataPost(optionsData, nonce);
 | 
			
		||||
      await this.client.api.interactions.post({
 | 
			
		||||
        data,
 | 
			
		||||
        useFormDataPayloadJSON: true,
 | 
			
		||||
        files: attachmentsBuffer,
 | 
			
		||||
      });
 | 
			
		||||
      this.client._interactionCache.set(nonce, {
 | 
			
		||||
        channelId: message.channelId,
 | 
			
		||||
        guildId: message.guildId,
 | 
			
		||||
        metadata: data,
 | 
			
		||||
      });
 | 
			
		||||
      return new Promise((resolve, reject) => {
 | 
			
		||||
        const handler = data => {
 | 
			
		||||
          timeout.refresh();
 | 
			
		||||
          if (data.metadata?.nonce !== nonce) return;
 | 
			
		||||
          clearTimeout(timeout);
 | 
			
		||||
          this.client.removeListener('interactionResponse', handler);
 | 
			
		||||
          this.client.decrementMaxListeners();
 | 
			
		||||
          if (data.status) {
 | 
			
		||||
            resolve(data.metadata);
 | 
			
		||||
          } else {
 | 
			
		||||
            reject(
 | 
			
		||||
              new Error('INTERACTION_ERROR', {
 | 
			
		||||
                cause: data,
 | 
			
		||||
              }),
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
        const timeout = setTimeout(() => {
 | 
			
		||||
          this.client.removeListener('interactionResponse', handler);
 | 
			
		||||
          this.client.decrementMaxListeners();
 | 
			
		||||
          reject(
 | 
			
		||||
            new Error('INTERACTION_TIMEOUT', {
 | 
			
		||||
              cause: data,
 | 
			
		||||
            }),
 | 
			
		||||
          );
 | 
			
		||||
        }, this.client.options.interactionTimeout).unref();
 | 
			
		||||
        this.client.incrementMaxListeners();
 | 
			
		||||
        this.client.on('interactionResponse', handler);
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
    // SubCommandArray length max 2
 | 
			
		||||
    // length = 0 => no sub command
 | 
			
		||||
    // length = 1 => sub command
 | 
			
		||||
    // length = 2 => sub command group + sub command
 | 
			
		||||
    switch (subCommandArray.length) {
 | 
			
		||||
      case 0: {
 | 
			
		||||
        const valueRequired = this.options?.filter(o => o.required).length || 0;
 | 
			
		||||
        for (let i = 0; i < options.length; i++) {
 | 
			
		||||
          const optionInput = this.options[i];
 | 
			
		||||
          const value = options[i];
 | 
			
		||||
          await parseOption(optionInput, value);
 | 
			
		||||
        }
 | 
			
		||||
        if (valueRequired > options.length) {
 | 
			
		||||
          throw new Error(`Value required missing\nDebug:
 | 
			
		||||
        Required: ${valueRequired} - Options: ${optionFormat.length}`);
 | 
			
		||||
        }
 | 
			
		||||
        return sendData(optionFormat);
 | 
			
		||||
      }
 | 
			
		||||
      case 1: {
 | 
			
		||||
        const optionsData = await parseSubCommand(subCommandArray[0], options);
 | 
			
		||||
        return sendData(optionsData);
 | 
			
		||||
      }
 | 
			
		||||
      case 2: {
 | 
			
		||||
        const optionsData = await parseSubGroupCommand(subCommandArray[0], subCommandArray[1], options);
 | 
			
		||||
        return sendData(optionsData);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * Message Context Menu
 | 
			
		||||
   * @param {Message} message Discord Message
 | 
			
		||||
   * @returns {Promise<InteractionResponse>}
 | 
			
		||||
   */
 | 
			
		||||
  async sendContextMenu(message) {
 | 
			
		||||
    if (!(message instanceof Message())) {
 | 
			
		||||
      throw new TypeError('The message must be a Discord.Message');
 | 
			
		||||
    }
 | 
			
		||||
    if (this.type == 'CHAT_INPUT') return false;
 | 
			
		||||
    const nonce = SnowflakeUtil.generate();
 | 
			
		||||
    const data = {
 | 
			
		||||
      type: InteractionTypes.APPLICATION_COMMAND,
 | 
			
		||||
      application_id: this.applicationId,
 | 
			
		||||
      guild_id: message.guildId,
 | 
			
		||||
      channel_id: message.channelId,
 | 
			
		||||
      session_id: this.client.sessionId,
 | 
			
		||||
      data: {
 | 
			
		||||
        version: this.version,
 | 
			
		||||
        id: this.id,
 | 
			
		||||
        name: this.name,
 | 
			
		||||
        type: ApplicationCommandTypes[this.type],
 | 
			
		||||
        target_id: ApplicationCommandTypes[this.type] == 1 ? message.author.id : message.id,
 | 
			
		||||
      },
 | 
			
		||||
      nonce,
 | 
			
		||||
    };
 | 
			
		||||
    if (this.guildId) {
 | 
			
		||||
      data.data.guild_id = message.guildId;
 | 
			
		||||
    }
 | 
			
		||||
    await this.client.api.interactions.post({
 | 
			
		||||
      data,
 | 
			
		||||
      useFormDataPayloadJSON: true,
 | 
			
		||||
    });
 | 
			
		||||
    this.client._interactionCache.set(nonce, {
 | 
			
		||||
      channelId: message.channelId,
 | 
			
		||||
      guildId: message.guildId,
 | 
			
		||||
      metadata: data,
 | 
			
		||||
    });
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      const handler = data => {
 | 
			
		||||
        timeout.refresh();
 | 
			
		||||
        if (data.metadata?.nonce !== nonce) return;
 | 
			
		||||
        clearTimeout(timeout);
 | 
			
		||||
        this.client.removeListener('interactionResponse', handler);
 | 
			
		||||
        this.client.decrementMaxListeners();
 | 
			
		||||
        if (data.status) {
 | 
			
		||||
          resolve(data.metadata);
 | 
			
		||||
        } else {
 | 
			
		||||
          reject(
 | 
			
		||||
            new Error('INTERACTION_ERROR', {
 | 
			
		||||
              cause: data,
 | 
			
		||||
            }),
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      const timeout = setTimeout(() => {
 | 
			
		||||
        this.client.removeListener('interactionResponse', handler);
 | 
			
		||||
        this.client.decrementMaxListeners();
 | 
			
		||||
        reject(
 | 
			
		||||
          new Error('INTERACTION_TIMEOUT', {
 | 
			
		||||
            cause: data,
 | 
			
		||||
          }),
 | 
			
		||||
        );
 | 
			
		||||
      }, this.client.options.interactionTimeout).unref();
 | 
			
		||||
      this.client.incrementMaxListeners();
 | 
			
		||||
      this.client.on('interactionResponse', handler);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = ApplicationCommand;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
const GuildChannel = require('./GuildChannel');
 | 
			
		||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
 | 
			
		||||
const GuildTextThreadManager = require('../managers/GuildTextThreadManager');
 | 
			
		||||
const InteractionManager = require('../managers/InteractionManager');
 | 
			
		||||
const MessageManager = require('../managers/MessageManager');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -21,12 +20,6 @@ class BaseGuildTextChannel extends GuildChannel {
 | 
			
		||||
     */
 | 
			
		||||
    this.messages = new MessageManager(this);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A manager of the interactions sent to this channel
 | 
			
		||||
     * @type {InteractionManager}
 | 
			
		||||
     */
 | 
			
		||||
    this.interactions = new InteractionManager(this);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A manager of the threads belonging to this channel
 | 
			
		||||
     * @type {GuildTextThreadManager}
 | 
			
		||||
@@ -187,15 +180,10 @@ class BaseGuildTextChannel extends GuildChannel {
 | 
			
		||||
  sendTyping() {}
 | 
			
		||||
  createMessageCollector() {}
 | 
			
		||||
  awaitMessages() {}
 | 
			
		||||
  createMessageComponentCollector() {}
 | 
			
		||||
  awaitMessageComponent() {}
 | 
			
		||||
  bulkDelete() {}
 | 
			
		||||
  fetchWebhooks() {}
 | 
			
		||||
  createWebhook() {}
 | 
			
		||||
  setRateLimitPerUser() {}
 | 
			
		||||
  setNSFW() {}
 | 
			
		||||
  sendSlash() {}
 | 
			
		||||
  searchInteraction() {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TextBasedChannel.applyToClass(BaseGuildTextChannel, true);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
const { Collection } = require('@discordjs/collection');
 | 
			
		||||
const GuildChannel = require('./GuildChannel');
 | 
			
		||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
 | 
			
		||||
const InteractionManager = require('../managers/InteractionManager');
 | 
			
		||||
const MessageManager = require('../managers/MessageManager');
 | 
			
		||||
const { VideoQualityModes } = require('../util/Constants');
 | 
			
		||||
const Permissions = require('../util/Permissions');
 | 
			
		||||
@@ -28,12 +27,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
 | 
			
		||||
     */
 | 
			
		||||
    this.nsfw = Boolean(data.nsfw);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A manager of the interactions sent to this channel
 | 
			
		||||
     * @type {InteractionManager}
 | 
			
		||||
     */
 | 
			
		||||
    this.interactions = new InteractionManager(this);
 | 
			
		||||
 | 
			
		||||
    this._patch(data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -95,7 +88,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ('nsfw' in data) {
 | 
			
		||||
      this.nsfw = Boolean(data.nsfw);
 | 
			
		||||
      this.nsfw = data.nsfw;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -170,11 +163,11 @@ class BaseGuildVoiceChannel extends GuildChannel {
 | 
			
		||||
   * Sets the bitrate of the channel.
 | 
			
		||||
   * @param {number} bitrate The new bitrate
 | 
			
		||||
   * @param {string} [reason] Reason for changing the channel's bitrate
 | 
			
		||||
   * @returns {Promise<VoiceChannel>}
 | 
			
		||||
   * @returns {Promise<BaseGuildVoiceChannel>}
 | 
			
		||||
   * @example
 | 
			
		||||
   * // Set the bitrate of a voice channel
 | 
			
		||||
   * voiceChannel.setBitrate(48_000)
 | 
			
		||||
   *   .then(vc => console.log(`Set bitrate to ${vc.bitrate}bps for ${vc.name}`))
 | 
			
		||||
   * channel.setBitrate(48_000)
 | 
			
		||||
   *   .then(channel => console.log(`Set bitrate to ${channel.bitrate}bps for ${channel.name}`))
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  setBitrate(bitrate, reason) {
 | 
			
		||||
@@ -201,11 +194,11 @@ class BaseGuildVoiceChannel extends GuildChannel {
 | 
			
		||||
   * 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<VoiceChannel>}
 | 
			
		||||
   * @returns {Promise<BaseGuildVoiceChannel>}
 | 
			
		||||
   * @example
 | 
			
		||||
   * // Set the user limit of a voice channel
 | 
			
		||||
   * voiceChannel.setUserLimit(42)
 | 
			
		||||
   *   .then(vc => console.log(`Set user limit to ${vc.userLimit} for ${vc.name}`))
 | 
			
		||||
   * channel.setUserLimit(42)
 | 
			
		||||
   *   .then(channel => console.log(`Set user limit to ${channel.userLimit} for ${channel.name}`))
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  setUserLimit(userLimit, reason) {
 | 
			
		||||
@@ -216,7 +209,7 @@ class BaseGuildVoiceChannel extends GuildChannel {
 | 
			
		||||
   * Sets the camera video quality mode of the channel.
 | 
			
		||||
   * @param {VideoQualityMode|number} videoQualityMode The new camera video quality mode.
 | 
			
		||||
   * @param {string} [reason] Reason for changing the camera video quality mode.
 | 
			
		||||
   * @returns {Promise<VoiceChannel>}
 | 
			
		||||
   * @returns {Promise<BaseGuildVoiceChannel>}
 | 
			
		||||
   */
 | 
			
		||||
  setVideoQualityMode(videoQualityMode, reason) {
 | 
			
		||||
    return this.edit({ videoQualityMode }, reason);
 | 
			
		||||
@@ -229,9 +222,6 @@ class BaseGuildVoiceChannel extends GuildChannel {
 | 
			
		||||
  sendTyping() {}
 | 
			
		||||
  createMessageCollector() {}
 | 
			
		||||
  awaitMessages() {}
 | 
			
		||||
  createMessageComponentCollector() {}
 | 
			
		||||
  awaitMessageComponent() {}
 | 
			
		||||
  bulkDelete() {}
 | 
			
		||||
  fetchWebhooks() {}
 | 
			
		||||
  createWebhook() {}
 | 
			
		||||
  setRateLimitPerUser() {}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ const Base = require('./Base');
 | 
			
		||||
 * Represents a call
 | 
			
		||||
 * @extends {Base}
 | 
			
		||||
 */
 | 
			
		||||
class Call extends Base {
 | 
			
		||||
class CallState extends Base {
 | 
			
		||||
  constructor(client, data) {
 | 
			
		||||
    super(client);
 | 
			
		||||
    /**
 | 
			
		||||
@@ -16,14 +16,11 @@ class Call extends Base {
 | 
			
		||||
     */
 | 
			
		||||
    this.channelId = data.channel_id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The list of user ID who is ringing
 | 
			
		||||
     * @type {Collection<Snowflake, User>}
 | 
			
		||||
     */
 | 
			
		||||
    this.ringing = new Collection();
 | 
			
		||||
    this._ringing = [];
 | 
			
		||||
 | 
			
		||||
    this._patch(data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _patch(data) {
 | 
			
		||||
    if ('region' in data) {
 | 
			
		||||
      /**
 | 
			
		||||
@@ -33,11 +30,10 @@ class Call extends Base {
 | 
			
		||||
      this.region = data.region;
 | 
			
		||||
    }
 | 
			
		||||
    if ('ringing' in data) {
 | 
			
		||||
      for (const userId of data.ringing) {
 | 
			
		||||
        this.ringing.set(userId, this.client.users.cache.get(userId));
 | 
			
		||||
      }
 | 
			
		||||
      this._ringing = data.ringing;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The channel of the call
 | 
			
		||||
   * @type {?DMChannel|PartialGroupDMChannel}
 | 
			
		||||
@@ -45,14 +41,23 @@ class Call extends Base {
 | 
			
		||||
  get channel() {
 | 
			
		||||
    return this.client.channels.cache.get(this.channelId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the voice region of the call
 | 
			
		||||
   * @param {string} region Region of the call
 | 
			
		||||
   * @returns {Promise<void>}
 | 
			
		||||
   */
 | 
			
		||||
  setVoiceRegion(region) {
 | 
			
		||||
  setRTCRegion(region) {
 | 
			
		||||
    return this.client.api.channels(this.channelId).call.patch({ data: { region } });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The list of user ID who is ringing
 | 
			
		||||
   * @type {Collection<Snowflake, User>}
 | 
			
		||||
   */
 | 
			
		||||
  get ringing() {
 | 
			
		||||
    return new Collection(this._ringing.map(id => [id, this.client.users.cache.get(id)]));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = Call;
 | 
			
		||||
module.exports = CallState;
 | 
			
		||||
@@ -224,7 +224,6 @@ class GuildAuditLogs {
 | 
			
		||||
        this.applicationCommands.set(command.id, new ApplicationCommand(guild.client, command, guild));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cached auto moderation rules.
 | 
			
		||||
     * @type {Collection<Snowflake, AutoModerationRule>}
 | 
			
		||||
@@ -487,7 +486,6 @@ class GuildAuditLogsEntry {
 | 
			
		||||
          count: Number(data.options.count),
 | 
			
		||||
        };
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case Actions.MESSAGE_PIN:
 | 
			
		||||
      case Actions.MESSAGE_UNPIN:
 | 
			
		||||
        this.extra = {
 | 
			
		||||
@@ -533,13 +531,11 @@ class GuildAuditLogsEntry {
 | 
			
		||||
          channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
 | 
			
		||||
        };
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case Actions.APPLICATION_COMMAND_PERMISSION_UPDATE:
 | 
			
		||||
        this.extra = {
 | 
			
		||||
          applicationId: data.options.application_id,
 | 
			
		||||
        };
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case Actions.AUTO_MODERATION_BLOCK_MESSAGE:
 | 
			
		||||
      case Actions.AUTO_MODERATION_FLAG_TO_CHANNEL:
 | 
			
		||||
      case Actions.AUTO_MODERATION_USER_COMMUNICATION_DISABLED:
 | 
			
		||||
@@ -549,7 +545,6 @@ class GuildAuditLogsEntry {
 | 
			
		||||
          channel: guild.client.channels.cache.get(data.options?.channel_id) ?? { id: data.options?.channel_id },
 | 
			
		||||
        };
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
const { Buffer } = require('node:buffer');
 | 
			
		||||
const BaseMessageComponent = require('./BaseMessageComponent');
 | 
			
		||||
const MessageEmbed = require('./MessageEmbed');
 | 
			
		||||
const WebEmbed = require('./WebEmbed');
 | 
			
		||||
const { RangeError } = require('../errors');
 | 
			
		||||
const ActivityFlags = require('../util/ActivityFlags');
 | 
			
		||||
const DataResolver = require('../util/DataResolver');
 | 
			
		||||
@@ -42,7 +41,6 @@ class MessagePayload {
 | 
			
		||||
     * @property {Buffer|string|Stream} attachment The original attachment that generated this file
 | 
			
		||||
     * @property {string} name The name of this file
 | 
			
		||||
     * @property {Buffer|Stream} file The file to be sent to the API
 | 
			
		||||
     * @extends {APIAttachment}
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -52,15 +50,6 @@ class MessagePayload {
 | 
			
		||||
    this.files = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether or not using new API to upload files
 | 
			
		||||
   * @type {boolean}
 | 
			
		||||
   * @readonly
 | 
			
		||||
   */
 | 
			
		||||
  get usingNewAttachmentAPI() {
 | 
			
		||||
    return Boolean(this.options?.usingNewAttachmentAPI);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Whether or not the target is a {@link Webhook} or a {@link WebhookClient}
 | 
			
		||||
   * @type {boolean}
 | 
			
		||||
@@ -133,12 +122,12 @@ class MessagePayload {
 | 
			
		||||
   * Resolves data.
 | 
			
		||||
   * @returns {MessagePayload}
 | 
			
		||||
   */
 | 
			
		||||
  async resolveData() {
 | 
			
		||||
  resolveData() {
 | 
			
		||||
    if (this.data) return this;
 | 
			
		||||
    const isInteraction = this.isInteraction;
 | 
			
		||||
    const isWebhook = this.isWebhook;
 | 
			
		||||
 | 
			
		||||
    let content = this.makeContent();
 | 
			
		||||
    const content = this.makeContent();
 | 
			
		||||
    const tts = Boolean(this.options.tts);
 | 
			
		||||
 | 
			
		||||
    let nonce;
 | 
			
		||||
@@ -208,37 +197,6 @@ class MessagePayload {
 | 
			
		||||
      this.options.attachments = attachments;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.options.embeds) {
 | 
			
		||||
      if (!Array.isArray(this.options.embeds)) {
 | 
			
		||||
        this.options.embeds = [this.options.embeds];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const webembeds = this.options.embeds.filter(e => e instanceof WebEmbed);
 | 
			
		||||
      this.options.embeds = this.options.embeds.filter(e => !(e instanceof WebEmbed));
 | 
			
		||||
 | 
			
		||||
      if (webembeds.length > 0) {
 | 
			
		||||
        if (!content) content = '';
 | 
			
		||||
        // Add hidden embed link
 | 
			
		||||
        content += `\n${WebEmbed.hiddenEmbed} \n`;
 | 
			
		||||
        if (webembeds.length > 1) {
 | 
			
		||||
          console.warn('[WARN] Multiple webembeds are not supported, this will be ignored.');
 | 
			
		||||
        }
 | 
			
		||||
        // Const embed = webembeds[0];
 | 
			
		||||
        for (const webE of webembeds) {
 | 
			
		||||
          const data = await webE.toMessage();
 | 
			
		||||
          content += `\n${data}`;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // Check content
 | 
			
		||||
      if (typeof content == 'string' && content.length > 2000) {
 | 
			
		||||
        console.warn('[WARN] Content is longer than 2000 characters.');
 | 
			
		||||
      }
 | 
			
		||||
      if (typeof content == 'string' && content.length > 4000) {
 | 
			
		||||
        // Max length if user has nitro boost
 | 
			
		||||
        throw new RangeError('MESSAGE_EMBED_LINK_LENGTH');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Activity
 | 
			
		||||
    let activity;
 | 
			
		||||
    if (
 | 
			
		||||
@@ -247,7 +205,7 @@ class MessagePayload {
 | 
			
		||||
      this.options.activity.type
 | 
			
		||||
    ) {
 | 
			
		||||
      const type = ActivityFlags.resolve(this.options.activity.type);
 | 
			
		||||
      const sessionId = this.target.client.sessionId;
 | 
			
		||||
      const sessionId = this.target.client.ws.shards.first()?.sessionId;
 | 
			
		||||
      const partyId = this.options.activity.partyId;
 | 
			
		||||
      activity = {
 | 
			
		||||
        type,
 | 
			
		||||
@@ -348,7 +306,7 @@ module.exports = MessagePayload;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A target for a message.
 | 
			
		||||
 * @typedef {TextBasedChannels|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
 | 
			
		||||
 * @typedef {TextBasedChannels|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
 | 
			
		||||
 * Message|MessageManager} MessageTarget
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,6 @@
 | 
			
		||||
const { Collection } = require('@discordjs/collection');
 | 
			
		||||
const Base = require('./Base');
 | 
			
		||||
const TeamMember = require('./TeamMember');
 | 
			
		||||
const User = require('./User');
 | 
			
		||||
const { Error } = require('../errors');
 | 
			
		||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -100,53 +98,6 @@ class Team extends Base {
 | 
			
		||||
    return this.client.rest.cdn.TeamIcon(this.id, this.icon, { format, size });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Invite a team member to the team
 | 
			
		||||
   * @param {User} user The user to invite to the team
 | 
			
		||||
   * @param {number} MFACode The mfa code
 | 
			
		||||
   * @returns {Promise<TeamMember>}
 | 
			
		||||
   */
 | 
			
		||||
  async inviteMember(user, MFACode) {
 | 
			
		||||
    if (!(user instanceof User)) return new Error('TEAM_MEMBER_FORMAT');
 | 
			
		||||
    const regex = /([0-9]{6})/g;
 | 
			
		||||
 | 
			
		||||
    const payload = {
 | 
			
		||||
      username: user.username,
 | 
			
		||||
      discriminator: user.discriminator,
 | 
			
		||||
    };
 | 
			
		||||
    if (MFACode && !regex.test(MFACode)) return new Error('MFA_INVALID');
 | 
			
		||||
    if (MFACode) payload.code = MFACode;
 | 
			
		||||
 | 
			
		||||
    const member = await this.client.api.teams(this.id).members.post({
 | 
			
		||||
      data: payload,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.members.set(member.user.id, new TeamMember(this, member));
 | 
			
		||||
    return this.members.get(member.user.id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Remove a member from the team
 | 
			
		||||
   * @param {Snowflake} userID The ID of the user you want to remove
 | 
			
		||||
   * @returns {boolean}
 | 
			
		||||
   */
 | 
			
		||||
  async removeMember(userID) {
 | 
			
		||||
    await this.client.api.teams[this.id].members[userID].delete();
 | 
			
		||||
    return this.members.delete(userID);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Delete this team
 | 
			
		||||
   * @param {number} MFACode The 2fa code
 | 
			
		||||
   * @returns {Promise<boolean>}
 | 
			
		||||
   */
 | 
			
		||||
  async delete(MFACode) {
 | 
			
		||||
    const regex = /([0-9]{6})/g;
 | 
			
		||||
    if (!regex.test(MFACode)) return new Error('MFA_INVALID');
 | 
			
		||||
    await this.client.api.teams[this.id].delete({ data: { code: MFACode } });
 | 
			
		||||
    return this.client.developerPortal.teams.delete(this.id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * When concatenated with a string, this automatically returns the Team's name instead of the
 | 
			
		||||
   * Team object.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,13 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const process = require('node:process');
 | 
			
		||||
const { ApplicationFlags } = require('../../util/ApplicationFlags');
 | 
			
		||||
const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
 | 
			
		||||
const Permissions = require('../../util/Permissions');
 | 
			
		||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
 | 
			
		||||
const { ApplicationRoleConnectionMetadata } = require('../ApplicationRoleConnectionMetadata');
 | 
			
		||||
const Base = require('../Base');
 | 
			
		||||
const Team = require('../Team');
 | 
			
		||||
 | 
			
		||||
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
 | 
			
		||||
 | 
			
		||||
@@ -67,6 +70,132 @@ class Application extends Base {
 | 
			
		||||
    } else {
 | 
			
		||||
      this.roleConnectionsVerificationURL ??= null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ClientApplication
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 Permissions(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 {ApplicationFlags}
 | 
			
		||||
       */
 | 
			
		||||
      this.flags = new ApplicationFlags(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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -88,35 +217,29 @@ class Application extends Base {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Invites this application to a guild / server
 | 
			
		||||
   * @param {Snowflake} guild_id The id of the guild that you want to invite the bot to
 | 
			
		||||
   * @param {PermissionResolvable} [permissions] The permissions for the bot in number form (the default is 8 / Administrator)
 | 
			
		||||
   * @param {string} [captcha] The captcha key to add
 | 
			
		||||
   * @returns {Promise<void>} nothing :)
 | 
			
		||||
   * Obtains this application from Discord.
 | 
			
		||||
   * @returns {Promise<Application>}
 | 
			
		||||
   */
 | 
			
		||||
  async invite(guild_id, permissions, captcha = null) {
 | 
			
		||||
    permissions = Permissions.resolve(permissions || 0n);
 | 
			
		||||
    const postData = {
 | 
			
		||||
      authorize: true,
 | 
			
		||||
      guild_id,
 | 
			
		||||
      permissions: '0',
 | 
			
		||||
    };
 | 
			
		||||
    if (permissions) {
 | 
			
		||||
      postData.permissions = permissions;
 | 
			
		||||
    }
 | 
			
		||||
    if (captcha && typeof captcha === 'string' && captcha.length > 0) {
 | 
			
		||||
      postData.captcha = captcha;
 | 
			
		||||
    }
 | 
			
		||||
    await this.client.api.oauth2.authorize.post({
 | 
			
		||||
  async fetch() {
 | 
			
		||||
    const app = await this.client.api.oauth2.authorize.get({
 | 
			
		||||
      query: {
 | 
			
		||||
        client_id: this.id,
 | 
			
		||||
        scope: 'bot applications.commands',
 | 
			
		||||
      },
 | 
			
		||||
      data: postData,
 | 
			
		||||
      headers: {
 | 
			
		||||
        referer: `https://discord.com/oauth2/authorize?client_id=${this.id}&permissions=${permissions}&scope=bot`,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    const user = this.client.users._add(app.bot);
 | 
			
		||||
    user._partial = false;
 | 
			
		||||
    this._patch(app.application);
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets this application's role connection metadata records
 | 
			
		||||
   * @returns {Promise<ApplicationRoleConnectionMetadata[]>}
 | 
			
		||||
   */
 | 
			
		||||
  async fetchRoleConnectionMetadataRecords() {
 | 
			
		||||
    const metadata = await this.client.api.applications(this.id)('role-connections').metadata.get();
 | 
			
		||||
    return metadata.map(data => new ApplicationRoleConnectionMetadata(data));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,14 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
/* eslint-disable import/order */
 | 
			
		||||
const InteractionManager = require('../../managers/InteractionManager');
 | 
			
		||||
const MessageCollector = require('../MessageCollector');
 | 
			
		||||
const MessagePayload = require('../MessagePayload');
 | 
			
		||||
const { InteractionTypes, ApplicationCommandOptionTypes, Events } = require('../../util/Constants');
 | 
			
		||||
const { Error } = require('../../errors');
 | 
			
		||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
 | 
			
		||||
const { Collection } = require('@discordjs/collection');
 | 
			
		||||
const { InteractionTypes, MaxBulkDeletableMessageAge } = require('../../util/Constants');
 | 
			
		||||
const { TypeError, Error } = require('../../errors');
 | 
			
		||||
const InteractionCollector = require('../InteractionCollector');
 | 
			
		||||
const { lazy, getAttachments, uploadFile } = require('../../util/Util');
 | 
			
		||||
const Message = lazy(() => require('../Message').Message);
 | 
			
		||||
const { setTimeout } = require('node:timers');
 | 
			
		||||
const { s } = require('@sapphire/shapeshift');
 | 
			
		||||
const Util = require('../../util/Util');
 | 
			
		||||
const validateName = stringName =>
 | 
			
		||||
  s.string
 | 
			
		||||
    .lengthGreaterThanOrEqual(1)
 | 
			
		||||
@@ -32,12 +29,6 @@ class TextBasedChannel {
 | 
			
		||||
     */
 | 
			
		||||
    this.messages = new MessageManager(this);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A manager of the interactions sent to this channel
 | 
			
		||||
     * @type {InteractionManager}
 | 
			
		||||
     */
 | 
			
		||||
    this.interactions = new InteractionManager(this);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The channel's last message id, if one was sent
 | 
			
		||||
     * @type {?Snowflake}
 | 
			
		||||
@@ -76,7 +67,7 @@ class TextBasedChannel {
 | 
			
		||||
   * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
 | 
			
		||||
   * @property {string} [nonce=''] The nonce for the message
 | 
			
		||||
   * @property {string} [content=''] The content for the message
 | 
			
		||||
   * @property {Array<(MessageEmbed|APIEmbed|WebEmbed)>} [embeds] The embeds for the message
 | 
			
		||||
   * @property {Array<(MessageEmbed|APIEmbed)>} [embeds] The embeds for the message
 | 
			
		||||
   * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
 | 
			
		||||
   * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
 | 
			
		||||
   * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
 | 
			
		||||
@@ -84,7 +75,6 @@ class TextBasedChannel {
 | 
			
		||||
   * @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
 | 
			
		||||
   * Action rows containing interactive components for the message (buttons, select menus)
 | 
			
		||||
   * @property {MessageAttachment[]} [attachments] Attachments to send in the message
 | 
			
		||||
   * @property {boolean} [usingNewAttachmentAPI] Whether to use the new attachment API (`channels/:id/attachments`)
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -168,41 +158,197 @@ class TextBasedChannel {
 | 
			
		||||
    let messagePayload;
 | 
			
		||||
 | 
			
		||||
    if (options instanceof MessagePayload) {
 | 
			
		||||
      messagePayload = await options.resolveData();
 | 
			
		||||
      messagePayload = options.resolveData();
 | 
			
		||||
    } else {
 | 
			
		||||
      messagePayload = await MessagePayload.create(this, options).resolveData();
 | 
			
		||||
      messagePayload = MessagePayload.create(this, options).resolveData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let { data, files } = await messagePayload.resolveFiles();
 | 
			
		||||
 | 
			
		||||
    if (typeof options == 'object' && typeof options.usingNewAttachmentAPI !== 'boolean') {
 | 
			
		||||
      options.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (options?.usingNewAttachmentAPI === true) {
 | 
			
		||||
      const attachments = await getAttachments(this.client, this.id, ...files);
 | 
			
		||||
      const requestPromises = attachments.map(async attachment => {
 | 
			
		||||
        await uploadFile(files[attachment.id].file, attachment.upload_url);
 | 
			
		||||
        return {
 | 
			
		||||
          id: attachment.id,
 | 
			
		||||
          filename: files[attachment.id].name,
 | 
			
		||||
          uploaded_filename: attachment.upload_filename,
 | 
			
		||||
          description: files[attachment.id].description,
 | 
			
		||||
          duration_secs: files[attachment.id].duration_secs,
 | 
			
		||||
          waveform: files[attachment.id].waveform,
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
      const attachmentsData = await Promise.all(requestPromises);
 | 
			
		||||
      attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
 | 
			
		||||
      data.attachments = attachmentsData;
 | 
			
		||||
      files = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const d = await this.client.api.channels[this.id].messages.post({ data, files });
 | 
			
		||||
    const { data, files } = await messagePayload.resolveFiles();
 | 
			
		||||
    // New API
 | 
			
		||||
    const attachments = await Util.getUploadURL(this.client, this.id, files);
 | 
			
		||||
    const requestPromises = attachments.map(async attachment => {
 | 
			
		||||
      await Util.uploadFile(files[attachment.id].file, attachment.upload_url);
 | 
			
		||||
      return {
 | 
			
		||||
        id: attachment.id,
 | 
			
		||||
        filename: files[attachment.id].name,
 | 
			
		||||
        uploaded_filename: attachment.upload_filename,
 | 
			
		||||
        description: files[attachment.id].description,
 | 
			
		||||
        duration_secs: files[attachment.id].duration_secs,
 | 
			
		||||
        waveform: files[attachment.id].waveform,
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
    const attachmentsData = await Promise.all(requestPromises);
 | 
			
		||||
    attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
 | 
			
		||||
    data.attachments = attachmentsData;
 | 
			
		||||
    // Empty Files
 | 
			
		||||
    const d = await this.client.api.channels[this.id].messages.post({ data });
 | 
			
		||||
 | 
			
		||||
    return this.messages.cache.get(d.id) ?? this.messages._add(d);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  searchInteraction() {
 | 
			
		||||
    // Support Slash / ContextMenu
 | 
			
		||||
    // API https://canary.discord.com/api/v9/guilds/:id/application-command-index // Guild
 | 
			
		||||
    //     https://canary.discord.com/api/v9/channels/:id/application-command-index // DM Channel
 | 
			
		||||
    // Updated: 07/01/2023
 | 
			
		||||
    return this.client.api[this.guild ? 'guilds' : 'channels'][this.guild?.id || this.id][
 | 
			
		||||
      'application-command-index'
 | 
			
		||||
    ].get();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async sendSlash(botOrApplicationId, commandNameString, ...args) {
 | 
			
		||||
    // Parse commandName /role add user
 | 
			
		||||
    const cmd = commandNameString.trim().split(' ');
 | 
			
		||||
    // Ex: role add user => [role, add, user]
 | 
			
		||||
    // Parse:               name, subGr, sub
 | 
			
		||||
    const commandName = validateName(cmd[0]);
 | 
			
		||||
    // Parse: role
 | 
			
		||||
    const sub = cmd.slice(1);
 | 
			
		||||
    // Parse: [add, user]
 | 
			
		||||
    for (let i = 0; i < sub.length; i++) {
 | 
			
		||||
      if (sub.length > 2) {
 | 
			
		||||
        throw new Error('INVALID_COMMAND_NAME', cmd);
 | 
			
		||||
      }
 | 
			
		||||
      validateName(sub[i]);
 | 
			
		||||
    }
 | 
			
		||||
    // Search all
 | 
			
		||||
    const data = await this.searchInteraction();
 | 
			
		||||
    // Find command...
 | 
			
		||||
    const filterCommand = data.application_commands.filter(obj =>
 | 
			
		||||
      // Filter: name | name_default
 | 
			
		||||
      [obj.name, obj.name_default].includes(commandName),
 | 
			
		||||
    );
 | 
			
		||||
    // Filter Bot
 | 
			
		||||
    botOrApplicationId = this.client.users.resolveId(botOrApplicationId);
 | 
			
		||||
    const application = data.applications.find(
 | 
			
		||||
      obj => obj.id == botOrApplicationId || obj.bot?.id == botOrApplicationId,
 | 
			
		||||
    );
 | 
			
		||||
    // Find Command with application
 | 
			
		||||
    const command = filterCommand.find(command => command.application_id == application.id);
 | 
			
		||||
 | 
			
		||||
    args = args.flat(2);
 | 
			
		||||
    let optionFormat = [];
 | 
			
		||||
    let attachments = [];
 | 
			
		||||
    let optionsMaxdepth, subGroup, subCommand;
 | 
			
		||||
    if (sub.length == 2) {
 | 
			
		||||
      // Subcommand Group > Subcommand
 | 
			
		||||
      // Find Sub group
 | 
			
		||||
      subGroup = command.options.find(
 | 
			
		||||
        obj =>
 | 
			
		||||
          obj.type == ApplicationCommandOptionTypes.SUB_COMMAND_GROUP && [obj.name, obj.name_default].includes(sub[0]),
 | 
			
		||||
      );
 | 
			
		||||
      if (!subGroup) throw new Error('SLASH_COMMAND_SUB_COMMAND_GROUP_INVALID', sub[0]);
 | 
			
		||||
      // Find Sub
 | 
			
		||||
      subCommand = subGroup.options.find(
 | 
			
		||||
        obj => obj.type == ApplicationCommandOptionTypes.SUB_COMMAND && [obj.name, obj.name_default].includes(sub[1]),
 | 
			
		||||
      );
 | 
			
		||||
      if (!subCommand) throw new Error('SLASH_COMMAND_SUB_COMMAND_INVALID', sub[1]);
 | 
			
		||||
      // Options
 | 
			
		||||
      optionsMaxdepth = subCommand.options;
 | 
			
		||||
    } else if (sub.length == 1) {
 | 
			
		||||
      // Subcommand
 | 
			
		||||
      subCommand = command.options.find(
 | 
			
		||||
        obj => obj.type == ApplicationCommandOptionTypes.SUB_COMMAND && [obj.name, obj.name_default].includes(sub[0]),
 | 
			
		||||
      );
 | 
			
		||||
      if (!subCommand) throw new Error('SLASH_COMMAND_SUB_COMMAND_INVALID', sub[0]);
 | 
			
		||||
      // Options
 | 
			
		||||
      optionsMaxdepth = subCommand.options;
 | 
			
		||||
    } else {
 | 
			
		||||
      optionsMaxdepth = command.options;
 | 
			
		||||
    }
 | 
			
		||||
    const valueRequired = optionsMaxdepth?.filter(o => o.required).length || 0;
 | 
			
		||||
    for (let i = 0; i < Math.min(args.length, optionsMaxdepth?.length || 0); i++) {
 | 
			
		||||
      const optionInput = optionsMaxdepth[i];
 | 
			
		||||
      const value = args[i];
 | 
			
		||||
      const parseData = await parseOption(
 | 
			
		||||
        this.client,
 | 
			
		||||
        optionInput,
 | 
			
		||||
        value,
 | 
			
		||||
        optionFormat,
 | 
			
		||||
        attachments,
 | 
			
		||||
        command,
 | 
			
		||||
        application.id,
 | 
			
		||||
        this.guild?.id,
 | 
			
		||||
        this.id,
 | 
			
		||||
        subGroup,
 | 
			
		||||
        subCommand,
 | 
			
		||||
      );
 | 
			
		||||
      optionFormat = parseData.optionFormat;
 | 
			
		||||
      attachments = parseData.attachments;
 | 
			
		||||
    }
 | 
			
		||||
    if (valueRequired > args.length) {
 | 
			
		||||
      throw new Error('SLASH_COMMAND_REQUIRED_OPTIONS_MISSING', valueRequired, optionFormat.length);
 | 
			
		||||
    }
 | 
			
		||||
    // Post
 | 
			
		||||
    let postData;
 | 
			
		||||
    if (subGroup) {
 | 
			
		||||
      postData = [
 | 
			
		||||
        {
 | 
			
		||||
          type: ApplicationCommandOptionTypes.SUB_COMMAND_GROUP,
 | 
			
		||||
          name: subGroup.name,
 | 
			
		||||
          options: [
 | 
			
		||||
            {
 | 
			
		||||
              type: ApplicationCommandOptionTypes.SUB_COMMAND,
 | 
			
		||||
              name: subCommand.name,
 | 
			
		||||
              options: optionFormat,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ];
 | 
			
		||||
    } else if (subCommand) {
 | 
			
		||||
      postData = [
 | 
			
		||||
        {
 | 
			
		||||
          type: ApplicationCommandOptionTypes.SUB_COMMAND,
 | 
			
		||||
          name: subCommand.name,
 | 
			
		||||
          options: optionFormat,
 | 
			
		||||
        },
 | 
			
		||||
      ];
 | 
			
		||||
    } else {
 | 
			
		||||
      postData = optionFormat;
 | 
			
		||||
    }
 | 
			
		||||
    const nonce = SnowflakeUtil.generate();
 | 
			
		||||
    const body = createPostData(
 | 
			
		||||
      this.client,
 | 
			
		||||
      false,
 | 
			
		||||
      application.id,
 | 
			
		||||
      nonce,
 | 
			
		||||
      this.guild?.id,
 | 
			
		||||
      Boolean(command.guild_id),
 | 
			
		||||
      this.id,
 | 
			
		||||
      command.version,
 | 
			
		||||
      command.id,
 | 
			
		||||
      command.name_default || command.name,
 | 
			
		||||
      command.type,
 | 
			
		||||
      postData,
 | 
			
		||||
      attachments,
 | 
			
		||||
    );
 | 
			
		||||
    this.client.api.interactions.post({
 | 
			
		||||
      data: body,
 | 
			
		||||
      usePayloadJSON: true,
 | 
			
		||||
    });
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      const timeoutMs = 5_000;
 | 
			
		||||
      // Waiting for MsgCreate / ModalCreate
 | 
			
		||||
      const handler = data => {
 | 
			
		||||
        if (data.nonce !== nonce) return;
 | 
			
		||||
        clearTimeout(timeout);
 | 
			
		||||
        this.client.removeListener(Events.MESSAGE_CREATE, handler);
 | 
			
		||||
        this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
 | 
			
		||||
        this.client.decrementMaxListeners();
 | 
			
		||||
        resolve(data);
 | 
			
		||||
      };
 | 
			
		||||
      const timeout = setTimeout(() => {
 | 
			
		||||
        this.client.removeListener(Events.MESSAGE_CREATE, handler);
 | 
			
		||||
        this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
 | 
			
		||||
        this.client.decrementMaxListeners();
 | 
			
		||||
        reject(new Error('INTERACTION_FAILED'));
 | 
			
		||||
      }, timeoutMs).unref();
 | 
			
		||||
      this.client.incrementMaxListeners();
 | 
			
		||||
      this.client.on(Events.MESSAGE_CREATE, handler);
 | 
			
		||||
      this.client.on(Events.INTERACTION_MODAL_CREATE, handler);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sends a typing indicator in the channel.
 | 
			
		||||
   * @returns {Promise<void>} Resolves upon the typing status being sent
 | 
			
		||||
@@ -261,101 +407,6 @@ class TextBasedChannel {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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: InteractionTypes.MESSAGE_COMPONENT,
 | 
			
		||||
      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 Error('INTERACTION_COLLECTOR_ERROR', 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 (!this.client.user.bot) throw new Error('INVALID_USER_METHOD');
 | 
			
		||||
    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() - SnowflakeUtil.timestampFrom(id) < MaxBulkDeletableMessageAge);
 | 
			
		||||
      }
 | 
			
		||||
      if (messageIds.length === 0) return new Collection();
 | 
			
		||||
      if (messageIds.length === 1) {
 | 
			
		||||
        await this.client.api.channels(this.id).messages(messageIds[0]).delete();
 | 
			
		||||
        const message = this.client.actions.MessageDelete.getMessage(
 | 
			
		||||
          {
 | 
			
		||||
            message_id: messageIds[0],
 | 
			
		||||
          },
 | 
			
		||||
          this,
 | 
			
		||||
        );
 | 
			
		||||
        return message ? new Collection([[message.id, message]]) : new Collection();
 | 
			
		||||
      }
 | 
			
		||||
      await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIds } });
 | 
			
		||||
      return messageIds.reduce(
 | 
			
		||||
        (col, id) =>
 | 
			
		||||
          col.set(
 | 
			
		||||
            id,
 | 
			
		||||
            this.client.actions.MessageDeleteBulk.getMessage(
 | 
			
		||||
              {
 | 
			
		||||
                message_id: id,
 | 
			
		||||
              },
 | 
			
		||||
              this,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        new Collection(),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (!isNaN(messages)) {
 | 
			
		||||
      const msgs = await this.messages.fetch({ limit: messages });
 | 
			
		||||
      return this.bulkDelete(msgs, filterOld);
 | 
			
		||||
    }
 | 
			
		||||
    throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Fetches all webhooks for the channel.
 | 
			
		||||
   * @returns {Promise<Collection<Snowflake, Webhook>>}
 | 
			
		||||
@@ -414,139 +465,21 @@ class TextBasedChannel {
 | 
			
		||||
    return this.edit({ nsfw }, reason);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Search Slash Command (return raw data)
 | 
			
		||||
   * @param {Snowflake} applicationId Application ID
 | 
			
		||||
   * @param {?ApplicationCommandType} type Command Type
 | 
			
		||||
   * @returns {Object}
 | 
			
		||||
   */
 | 
			
		||||
  searchInteraction(applicationId, type = 'CHAT_INPUT') {
 | 
			
		||||
    switch (type) {
 | 
			
		||||
      case 'USER':
 | 
			
		||||
      case 2:
 | 
			
		||||
        type = 2;
 | 
			
		||||
        break;
 | 
			
		||||
      case 'MESSAGE':
 | 
			
		||||
      case 3:
 | 
			
		||||
        type = 3;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        type = 1;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    return this.client.api.channels[this.id]['application-commands'].search.get({
 | 
			
		||||
      query: {
 | 
			
		||||
        type,
 | 
			
		||||
        application_id: applicationId,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Send Slash to this channel
 | 
			
		||||
   * @param {UserResolvable} bot Bot user (BotID, not applicationID)
 | 
			
		||||
   * @param {string} commandString Command name (and sub / group formats)
 | 
			
		||||
   * @param {...?any|any[]} args Command arguments
 | 
			
		||||
   * @returns {Promise<InteractionResponse>}
 | 
			
		||||
   * @example
 | 
			
		||||
   * // Send a basic slash
 | 
			
		||||
   * channel.sendSlash('botid', 'ping')
 | 
			
		||||
   *   .then(console.log)
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   * @example
 | 
			
		||||
   * // Send a remote file
 | 
			
		||||
   * channel.sendSlash('botid', 'emoji upload', 'https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048', 'test')
 | 
			
		||||
   *   .then(console.log)
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   * @see {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/SlashCommand.md}
 | 
			
		||||
   */
 | 
			
		||||
  async sendSlash(bot, commandString, ...args) {
 | 
			
		||||
    const perms =
 | 
			
		||||
      this.type != 'DM'
 | 
			
		||||
        ? this.permissionsFor(this.client.user).toArray()
 | 
			
		||||
        : ['USE_APPLICATION_COMMANDS', `${this.recipient.relationships == 'BLOCKED' ? '' : 'SEND_MESSAGES'}`];
 | 
			
		||||
    if (!perms.includes('SEND_MESSAGES')) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        'INTERACTION_SEND_FAILURE',
 | 
			
		||||
        `Cannot send Slash to ${this.toString()} ${
 | 
			
		||||
          this.recipient ? 'because bot has been blocked' : 'due to missing SEND_MESSAGES permission'
 | 
			
		||||
        }`,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (!perms.includes('USE_APPLICATION_COMMANDS')) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        'INTERACTION_SEND_FAILURE',
 | 
			
		||||
        `Cannot send Slash to ${this.toString()} due to missing USE_APPLICATION_COMMANDS permission`,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    args = args.flat(2);
 | 
			
		||||
    const cmd = commandString.trim().split(' ');
 | 
			
		||||
    // Validate CommandName
 | 
			
		||||
    const commandName = validateName(cmd[0]);
 | 
			
		||||
    const sub = cmd.slice(1);
 | 
			
		||||
    for (let i = 0; i < sub.length; i++) {
 | 
			
		||||
      if (sub.length > 2) {
 | 
			
		||||
        throw new Error('INVALID_COMMAND_NAME', cmd);
 | 
			
		||||
      }
 | 
			
		||||
      validateName(sub[i]);
 | 
			
		||||
    }
 | 
			
		||||
    if (!bot) throw new Error('MUST_SPECIFY_BOT');
 | 
			
		||||
    const botId = this.client.users.resolveId(bot);
 | 
			
		||||
    const user = await this.client.users.fetch(botId).catch(() => {});
 | 
			
		||||
    if (!user || !user.bot || !user.application) {
 | 
			
		||||
      throw new Error('botId is not a bot or does not have an application slash command');
 | 
			
		||||
    }
 | 
			
		||||
    if (user._partial) await user.getProfile().catch(() => {});
 | 
			
		||||
    if (!commandName || typeof commandName !== 'string') throw new Error('Command name is required');
 | 
			
		||||
    const data = await this.searchInteraction(user.application?.id ?? user.id, 'CHAT_INPUT');
 | 
			
		||||
    for (const command of data.application_commands) {
 | 
			
		||||
      if (user.id == command.application_id || user.application.id == command.application_id) {
 | 
			
		||||
        user.application?.commands?._add(command, true);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // Remove
 | 
			
		||||
    const commandTarget = user.application?.commands?.cache.find(
 | 
			
		||||
      c => c.name === commandName && c.type === 'CHAT_INPUT',
 | 
			
		||||
    );
 | 
			
		||||
    if (!commandTarget) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        'INTERACTION_SEND_FAILURE',
 | 
			
		||||
        `SlashCommand ${commandName} is not found (With search)\nDebug:\n+ botId: ${botId} (ApplicationId: ${
 | 
			
		||||
          user.application?.id
 | 
			
		||||
        })\n+ args: ${args.join(' | ') || null}`,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return commandTarget.sendSlashCommand(
 | 
			
		||||
      new (Message())(this.client, {
 | 
			
		||||
        channel_id: this.id,
 | 
			
		||||
        guild_id: this.guild?.id || null,
 | 
			
		||||
        author: this.client.user,
 | 
			
		||||
        content: '',
 | 
			
		||||
        id: this.client.user.id,
 | 
			
		||||
      }),
 | 
			
		||||
      sub && sub.length > 0 ? sub : [],
 | 
			
		||||
      args && args.length ? args : [],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static applyToClass(structure, full = false, ignore = []) {
 | 
			
		||||
    const props = ['send'];
 | 
			
		||||
    if (full) {
 | 
			
		||||
      props.push(
 | 
			
		||||
        'sendSlash',
 | 
			
		||||
        'searchInteraction',
 | 
			
		||||
        'lastMessage',
 | 
			
		||||
        'lastPinAt',
 | 
			
		||||
        'bulkDelete',
 | 
			
		||||
        'sendTyping',
 | 
			
		||||
        'createMessageCollector',
 | 
			
		||||
        'awaitMessages',
 | 
			
		||||
        'createMessageComponentCollector',
 | 
			
		||||
        'awaitMessageComponent',
 | 
			
		||||
        'fetchWebhooks',
 | 
			
		||||
        'createWebhook',
 | 
			
		||||
        'setRateLimitPerUser',
 | 
			
		||||
        'setNSFW',
 | 
			
		||||
        'sendSlash',
 | 
			
		||||
        'searchInteraction',
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    for (const prop of props) {
 | 
			
		||||
@@ -564,3 +497,225 @@ module.exports = TextBasedChannel;
 | 
			
		||||
 | 
			
		||||
// Fixes Circular
 | 
			
		||||
const MessageManager = require('../../managers/MessageManager');
 | 
			
		||||
 | 
			
		||||
// Utils
 | 
			
		||||
function parseChoices(parent, list_choices, value) {
 | 
			
		||||
  if (value !== undefined) {
 | 
			
		||||
    if (Array.isArray(list_choices) && list_choices.length) {
 | 
			
		||||
      const choice = list_choices.find(c => [c.name, c.value].includes(value));
 | 
			
		||||
      if (choice) {
 | 
			
		||||
        return choice.value;
 | 
			
		||||
      } else {
 | 
			
		||||
        throw new Error('INVALID_SLASH_COMMAND_CHOICES', parent, value);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      return value;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function addDataFromAttachment(value, client, channelId, attachments) {
 | 
			
		||||
  value = await MessagePayload.resolveFile(value);
 | 
			
		||||
  if (!value?.file) {
 | 
			
		||||
    throw new TypeError('The attachment data must be a BufferResolvable or Stream or FileOptions of MessageAttachment');
 | 
			
		||||
  }
 | 
			
		||||
  const data = await Util.getUploadURL(client, channelId, [value]);
 | 
			
		||||
  await Util.uploadFile(value.file, data[0].upload_url);
 | 
			
		||||
  const id = attachments.length;
 | 
			
		||||
  attachments.push({
 | 
			
		||||
    id,
 | 
			
		||||
    filename: value.name,
 | 
			
		||||
    uploaded_filename: data[0].upload_filename,
 | 
			
		||||
  });
 | 
			
		||||
  return {
 | 
			
		||||
    id,
 | 
			
		||||
    attachments,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function parseOption(
 | 
			
		||||
  client,
 | 
			
		||||
  optionCommand,
 | 
			
		||||
  value,
 | 
			
		||||
  optionFormat,
 | 
			
		||||
  attachments,
 | 
			
		||||
  command,
 | 
			
		||||
  applicationId,
 | 
			
		||||
  guildId,
 | 
			
		||||
  channelId,
 | 
			
		||||
  subGroup,
 | 
			
		||||
  subCommand,
 | 
			
		||||
) {
 | 
			
		||||
  const data = {
 | 
			
		||||
    type: optionCommand.type,
 | 
			
		||||
    name: optionCommand.name,
 | 
			
		||||
  };
 | 
			
		||||
  if (value !== undefined) {
 | 
			
		||||
    switch (optionCommand.type) {
 | 
			
		||||
      case ApplicationCommandOptionTypes.BOOLEAN:
 | 
			
		||||
      case 'BOOLEAN': {
 | 
			
		||||
        data.value = Boolean(value);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case ApplicationCommandOptionTypes.INTEGER:
 | 
			
		||||
      case 'INTEGER': {
 | 
			
		||||
        data.value = Number(value);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case ApplicationCommandOptionTypes.ATTACHMENT:
 | 
			
		||||
      case 'ATTACHMENT': {
 | 
			
		||||
        const parseData = await addDataFromAttachment(value, client, channelId, attachments);
 | 
			
		||||
        data.value = parseData.id;
 | 
			
		||||
        attachments = parseData.attachments;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case ApplicationCommandOptionTypes.SUB_COMMAND_GROUP:
 | 
			
		||||
      case 'SUB_COMMAND_GROUP': {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default: {
 | 
			
		||||
        value = parseChoices(optionCommand.name, optionCommand.choices, value);
 | 
			
		||||
        if (optionCommand.autocomplete) {
 | 
			
		||||
          const nonce = SnowflakeUtil.generate();
 | 
			
		||||
          // Post
 | 
			
		||||
          let postData;
 | 
			
		||||
          if (subGroup) {
 | 
			
		||||
            postData = [
 | 
			
		||||
              {
 | 
			
		||||
                type: ApplicationCommandOptionTypes.SUB_COMMAND_GROUP,
 | 
			
		||||
                name: subGroup.name,
 | 
			
		||||
                options: [
 | 
			
		||||
                  {
 | 
			
		||||
                    type: ApplicationCommandOptionTypes.SUB_COMMAND,
 | 
			
		||||
                    name: subCommand.name,
 | 
			
		||||
                    options: [
 | 
			
		||||
                      {
 | 
			
		||||
                        type: optionCommand.type,
 | 
			
		||||
                        name: optionCommand.name,
 | 
			
		||||
                        value,
 | 
			
		||||
                        focused: true,
 | 
			
		||||
                      },
 | 
			
		||||
                    ],
 | 
			
		||||
                  },
 | 
			
		||||
                ],
 | 
			
		||||
              },
 | 
			
		||||
            ];
 | 
			
		||||
          } else if (subCommand) {
 | 
			
		||||
            postData = [
 | 
			
		||||
              {
 | 
			
		||||
                type: ApplicationCommandOptionTypes.SUB_COMMAND,
 | 
			
		||||
                name: subCommand.name,
 | 
			
		||||
                options: [
 | 
			
		||||
                  {
 | 
			
		||||
                    type: optionCommand.type,
 | 
			
		||||
                    name: optionCommand.name,
 | 
			
		||||
                    value,
 | 
			
		||||
                    focused: true,
 | 
			
		||||
                  },
 | 
			
		||||
                ],
 | 
			
		||||
              },
 | 
			
		||||
            ];
 | 
			
		||||
          } else {
 | 
			
		||||
            postData = [
 | 
			
		||||
              {
 | 
			
		||||
                type: optionCommand.type,
 | 
			
		||||
                name: optionCommand.name,
 | 
			
		||||
                value,
 | 
			
		||||
                focused: true,
 | 
			
		||||
              },
 | 
			
		||||
            ];
 | 
			
		||||
          }
 | 
			
		||||
          const body = createPostData(
 | 
			
		||||
            client,
 | 
			
		||||
            true,
 | 
			
		||||
            applicationId,
 | 
			
		||||
            nonce,
 | 
			
		||||
            guildId,
 | 
			
		||||
            Boolean(command.guild_id),
 | 
			
		||||
            channelId,
 | 
			
		||||
            command.version,
 | 
			
		||||
            command.id,
 | 
			
		||||
            command.name_default || command.name,
 | 
			
		||||
            command.type,
 | 
			
		||||
            postData,
 | 
			
		||||
            [],
 | 
			
		||||
          );
 | 
			
		||||
          await client.api.interactions.post({
 | 
			
		||||
            data: body,
 | 
			
		||||
          });
 | 
			
		||||
          data.value = await awaitAutocomplete(client, nonce, value);
 | 
			
		||||
        } else {
 | 
			
		||||
          data.value = value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    optionFormat.push(data);
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
    optionFormat,
 | 
			
		||||
    attachments,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function awaitAutocomplete(client, nonce, defaultValue) {
 | 
			
		||||
  return new Promise(resolve => {
 | 
			
		||||
    const handler = data => {
 | 
			
		||||
      if (data.t !== 'APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE') return;
 | 
			
		||||
      if (data.d?.nonce !== nonce) return;
 | 
			
		||||
      clearTimeout(timeout);
 | 
			
		||||
      client.removeListener(Events.UNHANDLED_PACKET, handler);
 | 
			
		||||
      client.decrementMaxListeners();
 | 
			
		||||
      if (data.d.choices.length >= 1) {
 | 
			
		||||
        resolve(data.d.choices[0].value);
 | 
			
		||||
      } else {
 | 
			
		||||
        resolve(defaultValue);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    const timeout = setTimeout(() => {
 | 
			
		||||
      client.removeListener(Events.UNHANDLED_PACKET, handler);
 | 
			
		||||
      client.decrementMaxListeners();
 | 
			
		||||
      resolve(defaultValue);
 | 
			
		||||
    }, 5_000).unref();
 | 
			
		||||
    client.incrementMaxListeners();
 | 
			
		||||
    client.on(Events.UNHANDLED_PACKET, handler);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function createPostData(
 | 
			
		||||
  client,
 | 
			
		||||
  isAutocomplete = false,
 | 
			
		||||
  applicationId,
 | 
			
		||||
  nonce,
 | 
			
		||||
  guildId,
 | 
			
		||||
  isGuildCommand,
 | 
			
		||||
  channelId,
 | 
			
		||||
  commandVersion,
 | 
			
		||||
  commandId,
 | 
			
		||||
  commandName,
 | 
			
		||||
  commandType,
 | 
			
		||||
  postData,
 | 
			
		||||
  attachments = [],
 | 
			
		||||
) {
 | 
			
		||||
  const data = {
 | 
			
		||||
    type: isAutocomplete ? InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE : InteractionTypes.APPLICATION_COMMAND,
 | 
			
		||||
    application_id: applicationId,
 | 
			
		||||
    guild_id: guildId,
 | 
			
		||||
    channel_id: channelId,
 | 
			
		||||
    session_id: client.ws.shards.first()?.sessionId,
 | 
			
		||||
    data: {
 | 
			
		||||
      version: commandVersion,
 | 
			
		||||
      id: commandId,
 | 
			
		||||
      name: commandName,
 | 
			
		||||
      type: commandType,
 | 
			
		||||
      options: postData,
 | 
			
		||||
      attachments: attachments,
 | 
			
		||||
    },
 | 
			
		||||
    nonce,
 | 
			
		||||
  };
 | 
			
		||||
  if (isGuildCommand) {
 | 
			
		||||
    data.data.guild_id = guildId;
 | 
			
		||||
  }
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user