update
This commit is contained in:
		@@ -1,10 +1,9 @@
 | 
			
		||||
/* eslint-disable newline-per-chained-call */
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const { Buffer } = require('node:buffer');
 | 
			
		||||
const { setTimeout } = require('node:timers');
 | 
			
		||||
const { Collection } = require('@discordjs/collection');
 | 
			
		||||
require('lodash.permutations');
 | 
			
		||||
const _ = require('lodash');
 | 
			
		||||
const CachedManager = require('./CachedManager');
 | 
			
		||||
const { Error, TypeError, RangeError } = require('../errors');
 | 
			
		||||
const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel');
 | 
			
		||||
@@ -191,25 +190,17 @@ class GuildMemberManager extends CachedManager {
 | 
			
		||||
   * guild.members.fetch({ query: 'hydra', limit: 1 })
 | 
			
		||||
   *   .then(console.log)
 | 
			
		||||
   *   .catch(console.error);
 | 
			
		||||
   * @see {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/FetchGuildMember.md}
 | 
			
		||||
   */
 | 
			
		||||
  fetch(options) {
 | 
			
		||||
    if (!options || (typeof options === 'object' && !('user' in options) && !('query' in options))) {
 | 
			
		||||
    if (!options) {
 | 
			
		||||
      if (
 | 
			
		||||
        this.guild.members.me.permissions.has('KICK_MEMBERS') ||
 | 
			
		||||
        this.guild.members.me.permissions.has('BAN_MEMBERS') ||
 | 
			
		||||
        this.guild.members.me.permissions.has('MANAGE_ROLES')
 | 
			
		||||
        this.me.permissions.has('KICK_MEMBERS') ||
 | 
			
		||||
        this.me.permissions.has('BAN_MEMBERS') ||
 | 
			
		||||
        this.me.permissions.has('MANAGE_ROLES')
 | 
			
		||||
      ) {
 | 
			
		||||
        return this._fetchMany();
 | 
			
		||||
      } else if (this.guild.memberCount <= 10000) {
 | 
			
		||||
        return this.fetchByMemberSafety();
 | 
			
		||||
      } else {
 | 
			
		||||
        // NOTE: This is a very slow method, and can take up to 999+ minutes to complete.
 | 
			
		||||
        return this.fetchBruteforce({
 | 
			
		||||
          delay: 50,
 | 
			
		||||
          skipWarn: true,
 | 
			
		||||
          depth: 1,
 | 
			
		||||
        });
 | 
			
		||||
        return this.fetchByMemberSafety();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    const user = this.client.users.resolveId(options);
 | 
			
		||||
@@ -471,221 +462,6 @@ class GuildMemberManager extends CachedManager {
 | 
			
		||||
    return this._add(data, cache);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Options used to fetch multiple members from a guild.
 | 
			
		||||
   * @typedef {Object} BruteforceOptions
 | 
			
		||||
   * @property {number} [limit=100] Maximum number of members per request
 | 
			
		||||
   * @property {number} [delay=500] Timeout for new requests in ms
 | 
			
		||||
   * @property {number} [depth=1] Permutations length
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Fetches multiple members from the guild.
 | 
			
		||||
   * @param {BruteforceOptions} options Options for the bruteforce
 | 
			
		||||
   * @returns {Collection<Snowflake, GuildMember>} (All) members in the guild
 | 
			
		||||
   * @see https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/FetchGuildMember.md
 | 
			
		||||
   * @example
 | 
			
		||||
   * guild.members.fetchBruteforce()
 | 
			
		||||
   * .then(members => console.log(`Fetched ${members.size} members`))
 | 
			
		||||
   * .catch(console.error);
 | 
			
		||||
   */
 | 
			
		||||
  fetchBruteforce(options = {}) {
 | 
			
		||||
    const defaultQuery = 'abcdefghijklmnopqrstuvwxyz0123456789!"#$%&\'()*+,-./:;<=>?@[]^_`{|}~ ';
 | 
			
		||||
    let dictionary;
 | 
			
		||||
    let limit = 100;
 | 
			
		||||
    let delay = 500;
 | 
			
		||||
    let depth = 1;
 | 
			
		||||
    if (options?.limit) limit = options?.limit;
 | 
			
		||||
    if (options?.delay) delay = options?.delay;
 | 
			
		||||
    if (options?.depth) depth = options?.depth;
 | 
			
		||||
    if (typeof limit !== 'number') throw new TypeError('INVALID_TYPE', 'limit', 'Number');
 | 
			
		||||
    if (limit < 1 || limit > 100) throw new RangeError('INVALID_RANGE_QUERY_MEMBER');
 | 
			
		||||
    if (typeof delay !== 'number') throw new TypeError('INVALID_TYPE', 'delay', 'Number');
 | 
			
		||||
    if (typeof depth !== 'number') throw new TypeError('INVALID_TYPE', 'depth', 'Number');
 | 
			
		||||
    if (depth < 1) throw new RangeError('INVALID_RANGE_QUERY_MEMBER');
 | 
			
		||||
    if (depth > 2) {
 | 
			
		||||
      console.warn(`[WARNING] GuildMemberManager#fetchBruteforce: depth greater than 2, can lead to very slow speeds`);
 | 
			
		||||
    }
 | 
			
		||||
    if (delay < 500 && !options?.skipWarn) {
 | 
			
		||||
      console.warn(
 | 
			
		||||
        `[WARNING] GuildMemberManager#fetchBruteforce: delay is less than 500ms, this may cause rate limits.`,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    let skipValues = [];
 | 
			
		||||
    // eslint-disable-next-line no-async-promise-executor
 | 
			
		||||
    return new Promise(async (resolve, reject) => {
 | 
			
		||||
      for (let i = 1; i <= depth; i++) {
 | 
			
		||||
        dictionary = _(defaultQuery)
 | 
			
		||||
          .permutations(i)
 | 
			
		||||
          .map(v => _.join(v, ''))
 | 
			
		||||
          .value();
 | 
			
		||||
        for (const query of dictionary) {
 | 
			
		||||
          if (this.guild.members.cache.size >= this.guild.memberCount) break;
 | 
			
		||||
          this.client.emit(
 | 
			
		||||
            'debug',
 | 
			
		||||
            `[INFO] GuildMemberManager#fetchBruteforce: Querying ${query}, Skip: [${skipValues.join(', ')}]`,
 | 
			
		||||
          );
 | 
			
		||||
          if (skipValues.some(v => query.startsWith(v))) continue;
 | 
			
		||||
          await this._fetchMany({ query, limit })
 | 
			
		||||
            .then(members => {
 | 
			
		||||
              if (members.size === 0) skipValues.push(query);
 | 
			
		||||
            })
 | 
			
		||||
            .catch(reject);
 | 
			
		||||
          await this.guild.client.sleep(delay);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      resolve(this.guild.members.cache);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Experimental method to fetch members from the guild.
 | 
			
		||||
   * <info>Lists up to 10000 members of the guild.</info>
 | 
			
		||||
   * @param {number} [timeout=15_000] Timeout for receipt of members in ms
 | 
			
		||||
   * @returns {Promise<Collection<Snowflake, GuildMember>>}
 | 
			
		||||
   */
 | 
			
		||||
  fetchByMemberSafety(timeout = 15_000) {
 | 
			
		||||
    return new Promise(resolve => {
 | 
			
		||||
      const nonce = SnowflakeUtil.generate();
 | 
			
		||||
      let timeout_ = setTimeout(() => {
 | 
			
		||||
        this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
 | 
			
		||||
        resolve(this.guild.members.cache);
 | 
			
		||||
      }, timeout).unref();
 | 
			
		||||
      const handler = (members, guild, raw) => {
 | 
			
		||||
        if (guild.id == this.guild.id && raw.nonce == nonce) {
 | 
			
		||||
          if (members.size > 0) {
 | 
			
		||||
            this.client.ws.broadcast({
 | 
			
		||||
              op: 35,
 | 
			
		||||
              d: {
 | 
			
		||||
                guild_id: this.guild.id,
 | 
			
		||||
                query: '',
 | 
			
		||||
                continuation_token: members.first()?.id,
 | 
			
		||||
                nonce,
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
          } else {
 | 
			
		||||
            clearTimeout(timeout_);
 | 
			
		||||
            this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
 | 
			
		||||
            resolve(this.guild.members.cache);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      this.client.on('guildMembersChunk', handler);
 | 
			
		||||
      this.client.ws.broadcast({
 | 
			
		||||
        op: 35,
 | 
			
		||||
        d: {
 | 
			
		||||
          guild_id: this.guild.id,
 | 
			
		||||
          query: '',
 | 
			
		||||
          continuation_token: null,
 | 
			
		||||
          nonce,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Fetches multiple members from the guild in the channel.
 | 
			
		||||
   * @param {GuildTextChannelResolvable} channel The channel to get members from (Members has VIEW_CHANNEL permission)
 | 
			
		||||
   * @param {number} [offset=0] Start index of the members to get
 | 
			
		||||
   * @param {boolean} [double=false] Whether to use double range
 | 
			
		||||
   * @param {number} [retryMax=3] Number of retries
 | 
			
		||||
   * @param {number} [time=10e3] Timeout for receipt of members
 | 
			
		||||
   * @returns {Collection<Snowflake, GuildMember>} Members in the guild
 | 
			
		||||
   * @see {@link https://github.com/aiko-chan-ai/discord.js-selfbot-v13/blob/main/Document/FetchGuildMember.md}
 | 
			
		||||
   * @example
 | 
			
		||||
   * const guild = client.guilds.cache.get('id');
 | 
			
		||||
   * const channel = guild.channels.cache.get('id');
 | 
			
		||||
   * // Overlap (slow)
 | 
			
		||||
   * for (let index = 0; index <= guild.memberCount; index += 100) {
 | 
			
		||||
   *   await guild.members.fetchMemberList(channel, index, index !== 100).catch(() => {});
 | 
			
		||||
   *   await client.sleep(500);
 | 
			
		||||
   * }
 | 
			
		||||
   * // Non-overlap (fast)
 | 
			
		||||
   * for (let index = 0; index <= guild.memberCount; index += 200) {
 | 
			
		||||
   *   await guild.members.fetchMemberList(channel, index == 0 ? 100 : index, index !== 100).catch(() => {});
 | 
			
		||||
   *   await client.sleep(500);
 | 
			
		||||
   * }
 | 
			
		||||
   * console.log(guild.members.cache.size); // will print the number of members in the guild
 | 
			
		||||
   */
 | 
			
		||||
  fetchMemberList(channel, offset = 0, double = false, retryMax = 3, time = 10_000) {
 | 
			
		||||
    const channel_ = this.guild.channels.resolve(channel);
 | 
			
		||||
    if (!channel_?.isText()) throw new TypeError('INVALID_TYPE', 'channel', 'GuildTextChannelResolvable');
 | 
			
		||||
    if (typeof offset !== 'number') throw new TypeError('INVALID_TYPE', 'offset', 'Number');
 | 
			
		||||
    if (typeof time !== 'number') throw new TypeError('INVALID_TYPE', 'time', 'Number');
 | 
			
		||||
    if (typeof retryMax !== 'number') throw new TypeError('INVALID_TYPE', 'retryMax', 'Number');
 | 
			
		||||
    if (retryMax < 1) throw new RangeError('INVALID_RANGE_RETRY');
 | 
			
		||||
    if (typeof double !== 'boolean') throw new TypeError('INVALID_TYPE', 'double', 'Boolean');
 | 
			
		||||
    // TODO: if (this.guild.large) throw new Error('GUILD_IS_LARGE');
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      const default_ = [[0, 99]];
 | 
			
		||||
      const fetchedMembers = new Collection();
 | 
			
		||||
      if (offset > 99) {
 | 
			
		||||
        // eslint-disable-next-line no-unused-expressions
 | 
			
		||||
        double
 | 
			
		||||
          ? default_.push([offset, offset + 99], [offset + 100, offset + 199])
 | 
			
		||||
          : default_.push([offset, offset + 99]);
 | 
			
		||||
      }
 | 
			
		||||
      let retry = 0;
 | 
			
		||||
      const handler = (members, guild, type, raw) => {
 | 
			
		||||
        timeout.refresh();
 | 
			
		||||
        if (guild.id !== this.guild.id) return;
 | 
			
		||||
        if (type == 'INVALIDATE' && offset > 100) {
 | 
			
		||||
          if (retry < retryMax) {
 | 
			
		||||
            this.guild.shard.send({
 | 
			
		||||
              op: Opcodes.GUILD_SUBSCRIPTIONS,
 | 
			
		||||
              d: {
 | 
			
		||||
                guild_id: this.guild.id,
 | 
			
		||||
                typing: true,
 | 
			
		||||
                threads: true,
 | 
			
		||||
                activities: true,
 | 
			
		||||
                channels: {
 | 
			
		||||
                  [channel_.id]: default_,
 | 
			
		||||
                },
 | 
			
		||||
                thread_member_lists: [],
 | 
			
		||||
                members: [],
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
            retry++;
 | 
			
		||||
          } else {
 | 
			
		||||
            clearTimeout(timeout);
 | 
			
		||||
            this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
 | 
			
		||||
            this.client.decrementMaxListeners();
 | 
			
		||||
            reject(new Error('INVALIDATE_MEMBER', raw.ops[0].range));
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          for (const member of members.values()) {
 | 
			
		||||
            fetchedMembers.set(member.id, member);
 | 
			
		||||
          }
 | 
			
		||||
          clearTimeout(timeout);
 | 
			
		||||
          this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
 | 
			
		||||
          this.client.decrementMaxListeners();
 | 
			
		||||
          resolve(fetchedMembers);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      const timeout = setTimeout(() => {
 | 
			
		||||
        this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
 | 
			
		||||
        this.client.decrementMaxListeners();
 | 
			
		||||
        reject(new Error('GUILD_MEMBERS_TIMEOUT'));
 | 
			
		||||
      }, time).unref();
 | 
			
		||||
      this.client.incrementMaxListeners();
 | 
			
		||||
      this.client.on(Events.GUILD_MEMBER_LIST_UPDATE, handler);
 | 
			
		||||
      this.guild.shard.send({
 | 
			
		||||
        op: Opcodes.GUILD_SUBSCRIPTIONS,
 | 
			
		||||
        d: {
 | 
			
		||||
          guild_id: this.guild.id,
 | 
			
		||||
          typing: true,
 | 
			
		||||
          threads: true,
 | 
			
		||||
          activities: true,
 | 
			
		||||
          channels: {
 | 
			
		||||
            [channel_.id]: default_,
 | 
			
		||||
          },
 | 
			
		||||
          thread_member_lists: [],
 | 
			
		||||
          members: [],
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Adds a role to a member.
 | 
			
		||||
   * @param {GuildMemberResolvable} user The user to add the role from
 | 
			
		||||
@@ -718,6 +494,51 @@ class GuildMemberManager extends CachedManager {
 | 
			
		||||
    return this.resolve(user) ?? this.client.users.resolve(user) ?? userId;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Experimental method to fetch members from the guild.
 | 
			
		||||
   * <info>Lists up to 10000 members of the guild.</info>
 | 
			
		||||
   * @param {number} [timeout=15_000] Timeout for receipt of members in ms
 | 
			
		||||
   * @returns {Promise<Collection<Snowflake, GuildMember>>}
 | 
			
		||||
   */
 | 
			
		||||
  fetchByMemberSafety(timeout = 15_000) {
 | 
			
		||||
    return new Promise(resolve => {
 | 
			
		||||
      const nonce = SnowflakeUtil.generate();
 | 
			
		||||
      let timeout_ = setTimeout(() => {
 | 
			
		||||
        this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
 | 
			
		||||
        resolve(this.guild.members.cache);
 | 
			
		||||
      }, timeout).unref();
 | 
			
		||||
      const handler = (members, guild, raw) => {
 | 
			
		||||
        if (guild.id == this.guild.id && raw.nonce == nonce) {
 | 
			
		||||
          if (members.size > 0) {
 | 
			
		||||
            this.client.ws.broadcast({
 | 
			
		||||
              op: Opcodes.SEARCH_RECENT_MEMBERS,
 | 
			
		||||
              d: {
 | 
			
		||||
                guild_id: this.guild.id,
 | 
			
		||||
                query: '',
 | 
			
		||||
                continuation_token: members.first()?.id,
 | 
			
		||||
                nonce,
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
          } else {
 | 
			
		||||
            clearTimeout(timeout_);
 | 
			
		||||
            this.client.removeListener(Events.GUILD_MEMBER_LIST_UPDATE, handler);
 | 
			
		||||
            resolve(this.guild.members.cache);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
      this.client.on('guildMembersChunk', handler);
 | 
			
		||||
      this.client.ws.broadcast({
 | 
			
		||||
        op: Opcodes.SEARCH_RECENT_MEMBERS,
 | 
			
		||||
        d: {
 | 
			
		||||
          guild_id: this.guild.id,
 | 
			
		||||
          query: '',
 | 
			
		||||
          continuation_token: null,
 | 
			
		||||
          nonce,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _fetchMany({
 | 
			
		||||
    limit = 0,
 | 
			
		||||
    withPresences: presences = true,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,10 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const { Collection } = require('@discordjs/collection');
 | 
			
		||||
const { joinVoiceChannel, entersState, VoiceConnectionStatus } = require('@discordjs/voice');
 | 
			
		||||
const { Channel } = require('./Channel');
 | 
			
		||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
 | 
			
		||||
const InteractionManager = require('../managers/InteractionManager');
 | 
			
		||||
const MessageManager = require('../managers/MessageManager');
 | 
			
		||||
const { Status, Opcodes } = require('../util/Constants');
 | 
			
		||||
const { Opcodes, Status } = require('../util/Constants');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a direct message channel between two users.
 | 
			
		||||
@@ -25,12 +23,6 @@ class DMChannel extends Channel {
 | 
			
		||||
     * @type {MessageManager}
 | 
			
		||||
     */
 | 
			
		||||
    this.messages = new MessageManager(this);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A manager of the interactions sent to this channel
 | 
			
		||||
     * @type {InteractionManager}
 | 
			
		||||
     */
 | 
			
		||||
    this.interactions = new InteractionManager(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _patch(data) {
 | 
			
		||||
@@ -65,7 +57,7 @@ class DMChannel extends Channel {
 | 
			
		||||
    if ('is_message_request' in data) {
 | 
			
		||||
      /**
 | 
			
		||||
       * Whether the channel is a message request
 | 
			
		||||
       * @type {boolean}
 | 
			
		||||
       * @type {?boolean}
 | 
			
		||||
       */
 | 
			
		||||
      this.messageRequest = data.is_message_request;
 | 
			
		||||
    }
 | 
			
		||||
@@ -138,78 +130,6 @@ class DMChannel extends Channel {
 | 
			
		||||
    return this.recipient.toString();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // These are here only for documentation purposes - they are implemented by TextBasedChannel
 | 
			
		||||
  /* eslint-disable no-empty-function */
 | 
			
		||||
  get lastMessage() {}
 | 
			
		||||
  get lastPinAt() {}
 | 
			
		||||
  send() {}
 | 
			
		||||
  sendTyping() {}
 | 
			
		||||
  createMessageCollector() {}
 | 
			
		||||
  awaitMessages() {}
 | 
			
		||||
  createMessageComponentCollector() {}
 | 
			
		||||
  awaitMessageComponent() {}
 | 
			
		||||
  sendSlash() {}
 | 
			
		||||
  searchInteraction() {}
 | 
			
		||||
  // Doesn't work on DM channels; bulkDelete() {}
 | 
			
		||||
  // Doesn't work on DM channels; setRateLimitPerUser() {}
 | 
			
		||||
  // Doesn't work on DM channels; setNSFW() {}
 | 
			
		||||
  // Testing feature: Call
 | 
			
		||||
  // URL: https://discord.com/api/v9/channels/:DMchannelId/call/ring
 | 
			
		||||
  /**
 | 
			
		||||
   * Call this DMChannel. Return discordjs/voice VoiceConnection
 | 
			
		||||
   * @param {CallOptions} options Options for the call
 | 
			
		||||
   * @returns {Promise<VoiceConnection>}
 | 
			
		||||
   */
 | 
			
		||||
  call(options = {}) {
 | 
			
		||||
    options = Object.assign(
 | 
			
		||||
      {
 | 
			
		||||
        ring: true,
 | 
			
		||||
      },
 | 
			
		||||
      options || {},
 | 
			
		||||
    );
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      if (!this.client.options.patchVoice) {
 | 
			
		||||
        reject(
 | 
			
		||||
          new Error(
 | 
			
		||||
            'VOICE_NOT_PATCHED',
 | 
			
		||||
            'Enable voice patching in client options\nhttps://discordjs-self-v13.netlify.app/#/docs/docs/main/typedef/ClientOptions',
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      } else {
 | 
			
		||||
        if (options.ring) {
 | 
			
		||||
          this.ring();
 | 
			
		||||
        }
 | 
			
		||||
        const connection = joinVoiceChannel({
 | 
			
		||||
          channelId: this.id,
 | 
			
		||||
          guildId: null,
 | 
			
		||||
          adapterCreator: this.voiceAdapterCreator,
 | 
			
		||||
          selfDeaf: options.selfDeaf ?? false,
 | 
			
		||||
          selfMute: options.selfMute ?? false,
 | 
			
		||||
        });
 | 
			
		||||
        entersState(connection, VoiceConnectionStatus.Ready, 30000)
 | 
			
		||||
          .then(connection => {
 | 
			
		||||
            resolve(connection);
 | 
			
		||||
          })
 | 
			
		||||
          .catch(err => {
 | 
			
		||||
            connection.destroy();
 | 
			
		||||
            reject(err);
 | 
			
		||||
          });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ring the user's phone / PC (call)
 | 
			
		||||
   * @returns {Promise<any>}
 | 
			
		||||
   */
 | 
			
		||||
  ring() {
 | 
			
		||||
    return this.client.api.channels(this.id).call.ring.post({
 | 
			
		||||
      data: {
 | 
			
		||||
        recipients: null,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sync VoiceState of this DMChannel.
 | 
			
		||||
   * @returns {undefined}
 | 
			
		||||
@@ -222,6 +142,19 @@ class DMChannel extends Channel {
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ring the user's phone / PC (call)
 | 
			
		||||
   * @returns {Promise<void>}
 | 
			
		||||
   */
 | 
			
		||||
  ring() {
 | 
			
		||||
    return this.client.api.channels(this.id).call.ring.post({
 | 
			
		||||
      data: {
 | 
			
		||||
        recipients: null,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The user in this voice-based channel
 | 
			
		||||
   * @type {Collection<Snowflake, User>}
 | 
			
		||||
@@ -236,18 +169,7 @@ class DMChannel extends Channel {
 | 
			
		||||
    }
 | 
			
		||||
    return coll;
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * Get connection to current call
 | 
			
		||||
   * @type {?VoiceConnection}
 | 
			
		||||
   * @readonly
 | 
			
		||||
   */
 | 
			
		||||
  get voiceConnection() {
 | 
			
		||||
    const check = this.client.callVoice?.joinConfig?.channelId == this.id;
 | 
			
		||||
    if (check) {
 | 
			
		||||
      return this.client.callVoice;
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get current shard
 | 
			
		||||
   * @type {WebSocketShard}
 | 
			
		||||
@@ -256,6 +178,7 @@ class DMChannel extends Channel {
 | 
			
		||||
  get shard() {
 | 
			
		||||
    return this.client.ws.shards.first();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The voice state adapter for this client that can be used with @discordjs/voice to play audio in DM / Group DM channels.
 | 
			
		||||
   * @type {?Function}
 | 
			
		||||
@@ -276,14 +199,19 @@ class DMChannel extends Channel {
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // These are here only for documentation purposes - they are implemented by TextBasedChannel
 | 
			
		||||
  /* eslint-disable no-empty-function */
 | 
			
		||||
  get lastMessage() {}
 | 
			
		||||
  get lastPinAt() {}
 | 
			
		||||
  send() {}
 | 
			
		||||
  sendTyping() {}
 | 
			
		||||
  createMessageCollector() {}
 | 
			
		||||
  awaitMessages() {}
 | 
			
		||||
  // Doesn't work on DM channels; setRateLimitPerUser() {}
 | 
			
		||||
  // Doesn't work on DM channels; setNSFW() {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TextBasedChannel.applyToClass(DMChannel, true, [
 | 
			
		||||
  'bulkDelete',
 | 
			
		||||
  'fetchWebhooks',
 | 
			
		||||
  'createWebhook',
 | 
			
		||||
  'setRateLimitPerUser',
 | 
			
		||||
  'setNSFW',
 | 
			
		||||
]);
 | 
			
		||||
TextBasedChannel.applyToClass(DMChannel, true, ['fetchWebhooks', 'createWebhook', 'setRateLimitPerUser', 'setNSFW']);
 | 
			
		||||
 | 
			
		||||
module.exports = DMChannel;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ const { Channel } = require('./Channel');
 | 
			
		||||
const Invite = require('./Invite');
 | 
			
		||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
 | 
			
		||||
const MessageManager = require('../managers/MessageManager');
 | 
			
		||||
const { Status, Opcodes } = require('../util/Constants');
 | 
			
		||||
const DataResolver = require('../util/DataResolver');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -291,6 +292,79 @@ class GroupDMChannel extends Channel {
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ring the user's phone / PC (call)
 | 
			
		||||
   * @param {UserResolvable[]} [recipients] Array of recipients
 | 
			
		||||
   * @returns {Promise<any>}
 | 
			
		||||
   */
 | 
			
		||||
  ring(recipients) {
 | 
			
		||||
    if (!recipients || !Array.isArray(recipients) || recipients.length == 0) recipients = null;
 | 
			
		||||
    recipients = recipients.map(r => this.client.users.resolveId(r)).filter(r => r && this.recipients.get(r));
 | 
			
		||||
    return this.client.api.channels(this.id).call.ring.post({
 | 
			
		||||
      data: {
 | 
			
		||||
        recipients,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sync VoiceState of this Group DMChannel.
 | 
			
		||||
   * @returns {undefined}
 | 
			
		||||
   */
 | 
			
		||||
  sync() {
 | 
			
		||||
    this.client.ws.broadcast({
 | 
			
		||||
      op: Opcodes.DM_UPDATE,
 | 
			
		||||
      d: {
 | 
			
		||||
        channel_id: this.id,
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The user in this voice-based channel
 | 
			
		||||
   * @type {Collection<Snowflake, User>}
 | 
			
		||||
   * @readonly
 | 
			
		||||
   */
 | 
			
		||||
  get voiceUsers() {
 | 
			
		||||
    const coll = new Collection();
 | 
			
		||||
    for (const state of this.client.voiceStates.cache.values()) {
 | 
			
		||||
      if (state.channelId === this.id && state.user) {
 | 
			
		||||
        coll.set(state.id, state.user);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return coll;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get current shard
 | 
			
		||||
   * @type {WebSocketShard}
 | 
			
		||||
   * @readonly
 | 
			
		||||
   */
 | 
			
		||||
  get shard() {
 | 
			
		||||
    return this.client.ws.shards.first();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The voice state adapter for this client that can be used with @discordjs/voice to play audio in DM / Group DM channels.
 | 
			
		||||
   * @type {?Function}
 | 
			
		||||
   * @readonly
 | 
			
		||||
   */
 | 
			
		||||
  get voiceAdapterCreator() {
 | 
			
		||||
    return methods => {
 | 
			
		||||
      this.client.voice.adapters.set(this.id, methods);
 | 
			
		||||
      return {
 | 
			
		||||
        sendPayload: data => {
 | 
			
		||||
          if (this.shard.status !== Status.READY) return false;
 | 
			
		||||
          this.shard.send(data);
 | 
			
		||||
          return true;
 | 
			
		||||
        },
 | 
			
		||||
        destroy: () => {
 | 
			
		||||
          this.client.voice.adapters.delete(this.id);
 | 
			
		||||
        },
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // These are here only for documentation purposes - they are implemented by TextBasedChannel
 | 
			
		||||
  /* eslint-disable no-empty-function */
 | 
			
		||||
  get lastMessage() {}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user