Update TextBasedChannel.js
This commit is contained in:
		@@ -1,14 +1,17 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* eslint-disable import/order */
 | 
					/* eslint-disable import/order */
 | 
				
			||||||
 | 
					const InteractionManager = require('../../managers/InteractionManager');
 | 
				
			||||||
const MessageCollector = require('../MessageCollector');
 | 
					const MessageCollector = require('../MessageCollector');
 | 
				
			||||||
const MessagePayload = require('../MessagePayload');
 | 
					const MessagePayload = require('../MessagePayload');
 | 
				
			||||||
const { InteractionTypes, ApplicationCommandOptionTypes, Events } = require('../../util/Constants');
 | 
					 | 
				
			||||||
const { Error } = require('../../errors');
 | 
					 | 
				
			||||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
 | 
					const SnowflakeUtil = require('../../util/SnowflakeUtil');
 | 
				
			||||||
const { setTimeout } = require('node:timers');
 | 
					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 { s } = require('@sapphire/shapeshift');
 | 
					const { s } = require('@sapphire/shapeshift');
 | 
				
			||||||
const Util = require('../../util/Util');
 | 
					 | 
				
			||||||
const validateName = stringName =>
 | 
					const validateName = stringName =>
 | 
				
			||||||
  s.string
 | 
					  s.string
 | 
				
			||||||
    .lengthGreaterThanOrEqual(1)
 | 
					    .lengthGreaterThanOrEqual(1)
 | 
				
			||||||
@@ -29,6 +32,12 @@ class TextBasedChannel {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    this.messages = new MessageManager(this);
 | 
					    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
 | 
					     * The channel's last message id, if one was sent
 | 
				
			||||||
     * @type {?Snowflake}
 | 
					     * @type {?Snowflake}
 | 
				
			||||||
@@ -67,7 +76,7 @@ class TextBasedChannel {
 | 
				
			|||||||
   * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
 | 
					   * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
 | 
				
			||||||
   * @property {string} [nonce=''] The nonce for the message
 | 
					   * @property {string} [nonce=''] The nonce for the message
 | 
				
			||||||
   * @property {string} [content=''] The content for the message
 | 
					   * @property {string} [content=''] The content for the message
 | 
				
			||||||
   * @property {Array<(MessageEmbed|APIEmbed)>} [embeds] The embeds for the message
 | 
					   * @property {Array<(MessageEmbed|APIEmbed|WebEmbed)>} [embeds] The embeds for the message
 | 
				
			||||||
   * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
 | 
					   * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
 | 
				
			||||||
   * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
 | 
					   * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
 | 
				
			||||||
   * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
 | 
					   * (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
 | 
				
			||||||
@@ -75,6 +84,7 @@ class TextBasedChannel {
 | 
				
			|||||||
   * @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
 | 
					   * @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
 | 
				
			||||||
   * Action rows containing interactive components for the message (buttons, select menus)
 | 
					   * Action rows containing interactive components for the message (buttons, select menus)
 | 
				
			||||||
   * @property {MessageAttachment[]} [attachments] Attachments to send in the message
 | 
					   * @property {MessageAttachment[]} [attachments] Attachments to send in the message
 | 
				
			||||||
 | 
					   * @property {boolean} [usingNewAttachmentAPI] Whether to use the new attachment API (`channels/:id/attachments`)
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -158,197 +168,41 @@ class TextBasedChannel {
 | 
				
			|||||||
    let messagePayload;
 | 
					    let messagePayload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (options instanceof MessagePayload) {
 | 
					    if (options instanceof MessagePayload) {
 | 
				
			||||||
      messagePayload = options.resolveData();
 | 
					      messagePayload = await options.resolveData();
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      messagePayload = MessagePayload.create(this, options).resolveData();
 | 
					      messagePayload = await MessagePayload.create(this, options).resolveData();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { data, files } = await messagePayload.resolveFiles();
 | 
					    let { data, files } = await messagePayload.resolveFiles();
 | 
				
			||||||
    // New API
 | 
					
 | 
				
			||||||
    const attachments = await Util.getUploadURL(this.client, this.id, files);
 | 
					    if (typeof options == 'object' && typeof options.usingNewAttachmentAPI !== 'boolean') {
 | 
				
			||||||
    const requestPromises = attachments.map(async attachment => {
 | 
					      options.usingNewAttachmentAPI = this.client.options.usingNewAttachmentAPI;
 | 
				
			||||||
      await Util.uploadFile(files[attachment.id].file, attachment.upload_url);
 | 
					    }
 | 
				
			||||||
      return {
 | 
					
 | 
				
			||||||
        id: attachment.id,
 | 
					    if (options?.usingNewAttachmentAPI === true) {
 | 
				
			||||||
        filename: files[attachment.id].name,
 | 
					      const attachments = await getAttachments(this.client, this.id, ...files);
 | 
				
			||||||
        uploaded_filename: attachment.upload_filename,
 | 
					      const requestPromises = attachments.map(async attachment => {
 | 
				
			||||||
        description: files[attachment.id].description,
 | 
					        await uploadFile(files[attachment.id].file, attachment.upload_url);
 | 
				
			||||||
        duration_secs: files[attachment.id].duration_secs,
 | 
					        return {
 | 
				
			||||||
        waveform: files[attachment.id].waveform,
 | 
					          id: attachment.id,
 | 
				
			||||||
      };
 | 
					          filename: files[attachment.id].name,
 | 
				
			||||||
    });
 | 
					          uploaded_filename: attachment.upload_filename,
 | 
				
			||||||
    const attachmentsData = await Promise.all(requestPromises);
 | 
					          description: files[attachment.id].description,
 | 
				
			||||||
    attachmentsData.sort((a, b) => parseInt(a.id) - parseInt(b.id));
 | 
					          duration_secs: files[attachment.id].duration_secs,
 | 
				
			||||||
    data.attachments = attachmentsData;
 | 
					          waveform: files[attachment.id].waveform,
 | 
				
			||||||
    // Empty Files
 | 
					        };
 | 
				
			||||||
    const d = await this.client.api.channels[this.id].messages.post({ data });
 | 
					      });
 | 
				
			||||||
 | 
					      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 });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.messages.cache.get(d.id) ?? this.messages._add(d);
 | 
					    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.
 | 
					   * Sends a typing indicator in the channel.
 | 
				
			||||||
   * @returns {Promise<void>} Resolves upon the typing status being sent
 | 
					   * @returns {Promise<void>} Resolves upon the typing status being sent
 | 
				
			||||||
@@ -407,6 +261,101 @@ 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.
 | 
					   * Fetches all webhooks for the channel.
 | 
				
			||||||
   * @returns {Promise<Collection<Snowflake, Webhook>>}
 | 
					   * @returns {Promise<Collection<Snowflake, Webhook>>}
 | 
				
			||||||
@@ -465,21 +414,142 @@ class TextBasedChannel {
 | 
				
			|||||||
    return this.edit({ nsfw }, reason);
 | 
					    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 API =
 | 
				
			||||||
 | 
					      this.client.api[this.guild ? 'guilds' : 'channels'][this.guild?.id || this.id]['application-command-index'];
 | 
				
			||||||
 | 
					    const data = await API.get();
 | 
				
			||||||
 | 
					    for (const command of data.application_commands) {
 | 
				
			||||||
 | 
					      if (command.type !== 1) continue;
 | 
				
			||||||
 | 
					      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 = []) {
 | 
					  static applyToClass(structure, full = false, ignore = []) {
 | 
				
			||||||
    const props = ['send'];
 | 
					    const props = ['send'];
 | 
				
			||||||
    if (full) {
 | 
					    if (full) {
 | 
				
			||||||
      props.push(
 | 
					      props.push(
 | 
				
			||||||
        'sendSlash',
 | 
					 | 
				
			||||||
        'searchInteraction',
 | 
					 | 
				
			||||||
        'lastMessage',
 | 
					        'lastMessage',
 | 
				
			||||||
        'lastPinAt',
 | 
					        'lastPinAt',
 | 
				
			||||||
 | 
					        'bulkDelete',
 | 
				
			||||||
        'sendTyping',
 | 
					        'sendTyping',
 | 
				
			||||||
        'createMessageCollector',
 | 
					        'createMessageCollector',
 | 
				
			||||||
        'awaitMessages',
 | 
					        'awaitMessages',
 | 
				
			||||||
 | 
					        'createMessageComponentCollector',
 | 
				
			||||||
 | 
					        'awaitMessageComponent',
 | 
				
			||||||
        'fetchWebhooks',
 | 
					        'fetchWebhooks',
 | 
				
			||||||
        'createWebhook',
 | 
					        'createWebhook',
 | 
				
			||||||
        'setRateLimitPerUser',
 | 
					        'setRateLimitPerUser',
 | 
				
			||||||
        'setNSFW',
 | 
					        'setNSFW',
 | 
				
			||||||
 | 
					        'sendSlash',
 | 
				
			||||||
 | 
					        'searchInteraction',
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    for (const prop of props) {
 | 
					    for (const prop of props) {
 | 
				
			||||||
@@ -497,225 +567,3 @@ module.exports = TextBasedChannel;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Fixes Circular
 | 
					// Fixes Circular
 | 
				
			||||||
const MessageManager = require('../../managers/MessageManager');
 | 
					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