summaryrefslogtreecommitdiff
path: root/node_modules/discord.js/src/client
diff options
context:
space:
mode:
author8cy <[email protected]>2020-04-30 15:46:16 -0700
committer8cy <[email protected]>2020-04-30 15:46:16 -0700
commit3a4deac89054021b56ad5bd8005b2044cc085c98 (patch)
tree3dd6af8503e497e46180b6b5231674f36bdce9f2 /node_modules/discord.js/src/client
downloaduppity-3a4deac89054021b56ad5bd8005b2044cc085c98.tar.xz
uppity-3a4deac89054021b56ad5bd8005b2044cc085c98.zip
Up, up, uppity.
Diffstat (limited to 'node_modules/discord.js/src/client')
-rw-r--r--node_modules/discord.js/src/client/BaseClient.js147
-rw-r--r--node_modules/discord.js/src/client/Client.js455
-rw-r--r--node_modules/discord.js/src/client/WebhookClient.js31
-rw-r--r--node_modules/discord.js/src/client/actions/Action.js103
-rw-r--r--node_modules/discord.js/src/client/actions/ActionsManager.js45
-rw-r--r--node_modules/discord.js/src/client/actions/ChannelCreate.js23
-rw-r--r--node_modules/discord.js/src/client/actions/ChannelDelete.js37
-rw-r--r--node_modules/discord.js/src/client/actions/ChannelUpdate.js33
-rw-r--r--node_modules/discord.js/src/client/actions/GuildBanRemove.js21
-rw-r--r--node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js21
-rw-r--r--node_modules/discord.js/src/client/actions/GuildDelete.js67
-rw-r--r--node_modules/discord.js/src/client/actions/GuildEmojiCreate.js19
-rw-r--r--node_modules/discord.js/src/client/actions/GuildEmojiDelete.js20
-rw-r--r--node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js20
-rw-r--r--node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js34
-rw-r--r--node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js19
-rw-r--r--node_modules/discord.js/src/client/actions/GuildMemberRemove.js30
-rw-r--r--node_modules/discord.js/src/client/actions/GuildRoleCreate.js25
-rw-r--r--node_modules/discord.js/src/client/actions/GuildRoleDelete.js30
-rw-r--r--node_modules/discord.js/src/client/actions/GuildRoleUpdate.js39
-rw-r--r--node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js21
-rw-r--r--node_modules/discord.js/src/client/actions/GuildUpdate.js33
-rw-r--r--node_modules/discord.js/src/client/actions/InviteCreate.js28
-rw-r--r--node_modules/discord.js/src/client/actions/InviteDelete.js29
-rw-r--r--node_modules/discord.js/src/client/actions/MessageCreate.js39
-rw-r--r--node_modules/discord.js/src/client/actions/MessageDelete.js29
-rw-r--r--node_modules/discord.js/src/client/actions/MessageDeleteBulk.js43
-rw-r--r--node_modules/discord.js/src/client/actions/MessageReactionAdd.js50
-rw-r--r--node_modules/discord.js/src/client/actions/MessageReactionRemove.js44
-rw-r--r--node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js29
-rw-r--r--node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js28
-rw-r--r--node_modules/discord.js/src/client/actions/MessageUpdate.js24
-rw-r--r--node_modules/discord.js/src/client/actions/PresenceUpdate.js44
-rw-r--r--node_modules/discord.js/src/client/actions/UserUpdate.js34
-rw-r--r--node_modules/discord.js/src/client/actions/VoiceStateUpdate.js44
-rw-r--r--node_modules/discord.js/src/client/actions/WebhooksUpdate.js19
-rw-r--r--node_modules/discord.js/src/client/voice/ClientVoiceManager.js110
-rw-r--r--node_modules/discord.js/src/client/voice/VoiceBroadcast.js111
-rw-r--r--node_modules/discord.js/src/client/voice/VoiceConnection.js523
-rw-r--r--node_modules/discord.js/src/client/voice/dispatcher/BroadcastDispatcher.js46
-rw-r--r--node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js354
-rw-r--r--node_modules/discord.js/src/client/voice/networking/VoiceUDPClient.js154
-rw-r--r--node_modules/discord.js/src/client/voice/networking/VoiceWebSocket.js264
-rw-r--r--node_modules/discord.js/src/client/voice/player/AudioPlayer.js27
-rw-r--r--node_modules/discord.js/src/client/voice/player/BasePlayer.js92
-rw-r--r--node_modules/discord.js/src/client/voice/player/BroadcastAudioPlayer.js28
-rw-r--r--node_modules/discord.js/src/client/voice/receiver/PacketHandler.js116
-rw-r--r--node_modules/discord.js/src/client/voice/receiver/Receiver.js58
-rw-r--r--node_modules/discord.js/src/client/voice/util/PlayInterface.js94
-rw-r--r--node_modules/discord.js/src/client/voice/util/Secretbox.js32
-rw-r--r--node_modules/discord.js/src/client/voice/util/Silence.js13
-rw-r--r--node_modules/discord.js/src/client/voice/util/VolumeInterface.js103
-rw-r--r--node_modules/discord.js/src/client/websocket/WebSocketManager.js439
-rw-r--r--node_modules/discord.js/src/client/websocket/WebSocketShard.js763
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js22
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js16
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js16
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js36
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js22
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js19
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js22
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js16
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/READY.js21
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/RESUMED.js14
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js49
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js6
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js5
-rw-r--r--node_modules/discord.js/src/client/websocket/handlers/index.js13
91 files changed, 5476 insertions, 0 deletions
diff --git a/node_modules/discord.js/src/client/BaseClient.js b/node_modules/discord.js/src/client/BaseClient.js
new file mode 100644
index 0000000..a381e1a
--- /dev/null
+++ b/node_modules/discord.js/src/client/BaseClient.js
@@ -0,0 +1,147 @@
+'use strict';
+
+require('setimmediate');
+const EventEmitter = require('events');
+const RESTManager = require('../rest/RESTManager');
+const { DefaultOptions } = require('../util/Constants');
+const Util = require('../util/Util');
+
+/**
+ * The base class for all clients.
+ * @extends {EventEmitter}
+ */
+class BaseClient extends EventEmitter {
+ constructor(options = {}) {
+ super();
+
+ /**
+ * Timeouts set by {@link BaseClient#setTimeout} that are still active
+ * @type {Set<Timeout>}
+ * @private
+ */
+ this._timeouts = new Set();
+
+ /**
+ * Intervals set by {@link BaseClient#setInterval} that are still active
+ * @type {Set<Timeout>}
+ * @private
+ */
+ this._intervals = new Set();
+
+ /**
+ * Intervals set by {@link BaseClient#setImmediate} that are still active
+ * @type {Set<Immediate>}
+ * @private
+ */
+ this._immediates = new Set();
+
+ /**
+ * The options the client was instantiated with
+ * @type {ClientOptions}
+ */
+ this.options = Util.mergeDefault(DefaultOptions, options);
+
+ /**
+ * The REST manager of the client
+ * @type {RESTManager}
+ * @private
+ */
+ this.rest = new RESTManager(this, options._tokenType);
+ }
+
+ /**
+ * API shortcut
+ * @type {Object}
+ * @readonly
+ * @private
+ */
+ get api() {
+ return this.rest.api;
+ }
+
+ /**
+ * Destroys all assets used by the base client.
+ */
+ destroy() {
+ for (const t of this._timeouts) this.clearTimeout(t);
+ for (const i of this._intervals) this.clearInterval(i);
+ for (const i of this._immediates) this.clearImmediate(i);
+ this._timeouts.clear();
+ this._intervals.clear();
+ this._immediates.clear();
+ }
+
+ /**
+ * Sets a timeout that will be automatically cancelled if the client is destroyed.
+ * @param {Function} fn Function to execute
+ * @param {number} delay Time to wait before executing (in milliseconds)
+ * @param {...*} args Arguments for the function
+ * @returns {Timeout}
+ */
+ setTimeout(fn, delay, ...args) {
+ const timeout = setTimeout(() => {
+ fn(...args);
+ this._timeouts.delete(timeout);
+ }, delay);
+ this._timeouts.add(timeout);
+ return timeout;
+ }
+
+ /**
+ * Clears a timeout.
+ * @param {Timeout} timeout Timeout to cancel
+ */
+ clearTimeout(timeout) {
+ clearTimeout(timeout);
+ this._timeouts.delete(timeout);
+ }
+
+ /**
+ * Sets an interval that will be automatically cancelled if the client is destroyed.
+ * @param {Function} fn Function to execute
+ * @param {number} delay Time to wait between executions (in milliseconds)
+ * @param {...*} args Arguments for the function
+ * @returns {Timeout}
+ */
+ setInterval(fn, delay, ...args) {
+ const interval = setInterval(fn, delay, ...args);
+ this._intervals.add(interval);
+ return interval;
+ }
+
+ /**
+ * Clears an interval.
+ * @param {Timeout} interval Interval to cancel
+ */
+ clearInterval(interval) {
+ clearInterval(interval);
+ this._intervals.delete(interval);
+ }
+
+ /**
+ * Sets an immediate that will be automatically cancelled if the client is destroyed.
+ * @param {Function} fn Function to execute
+ * @param {...*} args Arguments for the function
+ * @returns {Immediate}
+ */
+ setImmediate(fn, ...args) {
+ const immediate = setImmediate(fn, ...args);
+ this._immediates.add(immediate);
+ return immediate;
+ }
+
+ /**
+ * Clears an immediate.
+ * @param {Immediate} immediate Immediate to cancel
+ */
+ clearImmediate(immediate) {
+ clearImmediate(immediate);
+ this._immediates.delete(immediate);
+ }
+
+ toJSON(...props) {
+ return Util.flatten(this, { domain: false }, ...props);
+ }
+}
+
+module.exports = BaseClient;
diff --git a/node_modules/discord.js/src/client/Client.js b/node_modules/discord.js/src/client/Client.js
new file mode 100644
index 0000000..4a12e85
--- /dev/null
+++ b/node_modules/discord.js/src/client/Client.js
@@ -0,0 +1,455 @@
+'use strict';
+
+const BaseClient = require('./BaseClient');
+const ActionsManager = require('./actions/ActionsManager');
+const ClientVoiceManager = require('./voice/ClientVoiceManager');
+const WebSocketManager = require('./websocket/WebSocketManager');
+const { Error, TypeError, RangeError } = require('../errors');
+const ChannelManager = require('../managers/ChannelManager');
+const GuildEmojiManager = require('../managers/GuildEmojiManager');
+const GuildManager = require('../managers/GuildManager');
+const UserManager = require('../managers/UserManager');
+const ShardClientUtil = require('../sharding/ShardClientUtil');
+const ClientApplication = require('../structures/ClientApplication');
+const GuildPreview = require('../structures/GuildPreview');
+const Invite = require('../structures/Invite');
+const VoiceRegion = require('../structures/VoiceRegion');
+const Webhook = require('../structures/Webhook');
+const Collection = require('../util/Collection');
+const { Events, browser, DefaultOptions } = require('../util/Constants');
+const DataResolver = require('../util/DataResolver');
+const Intents = require('../util/Intents');
+const Permissions = require('../util/Permissions');
+const Structures = require('../util/Structures');
+
+/**
+ * The main hub for interacting with the Discord API, and the starting point for any bot.
+ * @extends {BaseClient}
+ */
+class Client extends BaseClient {
+ /**
+ * @param {ClientOptions} [options] Options for the client
+ */
+ constructor(options = {}) {
+ super(Object.assign({ _tokenType: 'Bot' }, options));
+
+ // Obtain shard details from environment or if present, worker threads
+ let data = process.env;
+ try {
+ // Test if worker threads module is present and used
+ data = require('worker_threads').workerData || data;
+ } catch {
+ // Do nothing
+ }
+
+ if (this.options.shards === DefaultOptions.shards) {
+ if ('SHARDS' in data) {
+ this.options.shards = JSON.parse(data.SHARDS);
+ }
+ }
+
+ if (this.options.shardCount === DefaultOptions.shardCount) {
+ if ('SHARD_COUNT' in data) {
+ this.options.shardCount = Number(data.SHARD_COUNT);
+ } else if (Array.isArray(this.options.shards)) {
+ this.options.shardCount = this.options.shards.length;
+ }
+ }
+
+ const typeofShards = typeof this.options.shards;
+
+ if (typeofShards === 'undefined' && typeof this.options.shardCount === 'number') {
+ this.options.shards = Array.from({ length: this.options.shardCount }, (_, i) => i);
+ }
+
+ if (typeofShards === 'number') this.options.shards = [this.options.shards];
+
+ if (Array.isArray(this.options.shards)) {
+ this.options.shards = [
+ ...new Set(
+ this.options.shards.filter(item => !isNaN(item) && item >= 0 && item < Infinity && item === (item | 0)),
+ ),
+ ];
+ }
+
+ this._validateOptions();
+
+ /**
+ * The WebSocket manager of the client
+ * @type {WebSocketManager}
+ */
+ this.ws = new WebSocketManager(this);
+
+ /**
+ * The action manager of the client
+ * @type {ActionsManager}
+ * @private
+ */
+ this.actions = new ActionsManager(this);
+
+ /**
+ * The voice manager of the client (`null` in browsers)
+ * @type {?ClientVoiceManager}
+ */
+ this.voice = !browser ? new ClientVoiceManager(this) : null;
+
+ /**
+ * Shard helpers for the client (only if the process was spawned from a {@link ShardingManager})
+ * @type {?ShardClientUtil}
+ */
+ this.shard =
+ !browser && process.env.SHARDING_MANAGER
+ ? ShardClientUtil.singleton(this, process.env.SHARDING_MANAGER_MODE)
+ : null;
+
+ /**
+ * All of the {@link User} objects that have been cached at any point, mapped by their IDs
+ * @type {UserManager}
+ */
+ this.users = new UserManager(this);
+
+ /**
+ * All of the guilds the client is currently handling, mapped by their IDs -
+ * as long as sharding isn't being used, this will be *every* guild the bot is a member of
+ * @type {GuildManager}
+ */
+ this.guilds = new GuildManager(this);
+
+ /**
+ * All of the {@link Channel}s that the client is currently handling, mapped by their IDs -
+ * as long as sharding isn't being used, this will be *every* channel in *every* guild the bot
+ * is a member of. Note that DM channels will not be initially cached, and thus not be present
+ * in the Manager without their explicit fetching or use.
+ * @type {ChannelManager}
+ */
+ this.channels = new ChannelManager(this);
+
+ const ClientPresence = Structures.get('ClientPresence');
+ /**
+ * The presence of the Client
+ * @private
+ * @type {ClientPresence}
+ */
+ this.presence = new ClientPresence(this);
+
+ Object.defineProperty(this, 'token', { writable: true });
+ if (!browser && !this.token && 'DISCORD_TOKEN' in process.env) {
+ /**
+ * Authorization token for the logged in bot
+ * <warn>This should be kept private at all times.</warn>
+ * @type {?string}
+ */
+ this.token = process.env.DISCORD_TOKEN;
+ } else {
+ this.token = null;
+ }
+
+ /**
+ * User that the client is logged in as
+ * @type {?ClientUser}
+ */
+ this.user = null;
+
+ /**
+ * Time at which the client was last regarded as being in the `READY` state
+ * (each time the client disconnects and successfully reconnects, this will be overwritten)
+ * @type {?Date}
+ */
+ this.readyAt = null;
+
+ if (this.options.messageSweepInterval > 0) {
+ this.setInterval(this.sweepMessages.bind(this), this.options.messageSweepInterval * 1000);
+ }
+ }
+
+ /**
+ * All custom emojis that the client has access to, mapped by their IDs
+ * @type {GuildEmojiManager}
+ * @readonly
+ */
+ get emojis() {
+ const emojis = new GuildEmojiManager({ client: this });
+ for (const guild of this.guilds.cache.values()) {
+ if (guild.available) for (const emoji of guild.emojis.cache.values()) emojis.cache.set(emoji.id, emoji);
+ }
+ return emojis;
+ }
+
+ /**
+ * Timestamp of the time the client was last `READY` at
+ * @type {?number}
+ * @readonly
+ */
+ get readyTimestamp() {
+ return this.readyAt ? this.readyAt.getTime() : null;
+ }
+
+ /**
+ * How long it has been since the client last entered the `READY` state in milliseconds
+ * @type {?number}
+ * @readonly
+ */
+ get uptime() {
+ return this.readyAt ? Date.now() - this.readyAt : null;
+ }
+
+ /**
+ * Logs the client in, establishing a websocket connection to Discord.
+ * @param {string} token Token of the account to log in with
+ * @returns {Promise<string>} Token of the account used
+ * @example
+ * client.login('my token');
+ */
+ async login(token = this.token) {
+ if (!token || typeof token !== 'string') throw new Error('TOKEN_INVALID');
+ this.token = token = token.replace(/^(Bot|Bearer)\s*/i, '');
+ this.emit(
+ Events.DEBUG,
+ `Provided token: ${token
+ .split('.')
+ .map((val, i) => (i > 1 ? val.replace(/./g, '*') : val))
+ .join('.')}`,
+ );
+
+ if (this.options.presence) {
+ this.options.ws.presence = await this.presence._parse(this.options.presence);
+ }
+
+ this.emit(Events.DEBUG, 'Preparing to connect to the gateway...');
+
+ try {
+ await this.ws.connect();
+ return this.token;
+ } catch (error) {
+ this.destroy();
+ throw error;
+ }
+ }
+
+ /**
+ * Logs out, terminates the connection to Discord, and destroys the client.
+ * @returns {void}
+ */
+ destroy() {
+ super.destroy();
+ this.ws.destroy();
+ this.token = null;
+ }
+
+ /**
+ * Obtains an invite from Discord.
+ * @param {InviteResolvable} invite Invite code or URL
+ * @returns {Promise<Invite>}
+ * @example
+ * client.fetchInvite('https://discord.gg/bRCvFy9')
+ * .then(invite => console.log(`Obtained invite with code: ${invite.code}`))
+ * .catch(console.error);
+ */
+ fetchInvite(invite) {
+ const code = DataResolver.resolveInviteCode(invite);
+ return this.api
+ .invites(code)
+ .get({ query: { with_counts: true } })
+ .then(data => new Invite(this, data));
+ }
+
+ /**
+ * Obtains a webhook from Discord.
+ * @param {Snowflake} id ID of the webhook
+ * @param {string} [token] Token for the webhook
+ * @returns {Promise<Webhook>}
+ * @example
+ * client.fetchWebhook('id', 'token')
+ * .then(webhook => console.log(`Obtained webhook with name: ${webhook.name}`))
+ * .catch(console.error);
+ */
+ fetchWebhook(id, token) {
+ return this.api
+ .webhooks(id, token)
+ .get()
+ .then(data => new Webhook(this, data));
+ }
+
+ /**
+ * Obtains the available voice regions from Discord.
+ * @returns {Promise<Collection<string, VoiceRegion>>}
+ * @example
+ * client.fetchVoiceRegions()
+ * .then(regions => console.log(`Available regions are: ${regions.map(region => region.name).join(', ')}`))
+ * .catch(console.error);
+ */
+ fetchVoiceRegions() {
+ return this.api.voice.regions.get().then(res => {
+ const regions = new Collection();
+ for (const region of res) regions.set(region.id, new VoiceRegion(region));
+ return regions;
+ });
+ }
+
+ /**
+ * Sweeps all text-based channels' messages and removes the ones older than the max message lifetime.
+ * If the message has been edited, the time of the edit is used rather than the time of the original message.
+ * @param {number} [lifetime=this.options.messageCacheLifetime] Messages that are older than this (in seconds)
+ * will be removed from the caches. The default is based on {@link ClientOptions#messageCacheLifetime}
+ * @returns {number} Amount of messages that were removed from the caches,
+ * or -1 if the message cache lifetime is unlimited
+ * @example
+ * // Remove all messages older than 1800 seconds from the messages cache
+ * const amount = client.sweepMessages(1800);
+ * console.log(`Successfully removed ${amount} messages from the cache.`);
+ */
+ sweepMessages(lifetime = this.options.messageCacheLifetime) {
+ if (typeof lifetime !== 'number' || isNaN(lifetime)) {
+ throw new TypeError('INVALID_TYPE', 'lifetime', 'number');
+ }
+ if (lifetime <= 0) {
+ this.emit(Events.DEBUG, "Didn't sweep messages - lifetime is unlimited");
+ return -1;
+ }
+
+ const lifetimeMs = lifetime * 1000;
+ const now = Date.now();
+ let channels = 0;
+ let messages = 0;
+
+ for (const channel of this.channels.cache.values()) {
+ if (!channel.messages) continue;
+ channels++;
+
+ messages += channel.messages.cache.sweep(
+ message => now - (message.editedTimestamp || message.createdTimestamp) > lifetimeMs,
+ );
+ }
+
+ this.emit(
+ Events.DEBUG,
+ `Swept ${messages} messages older than ${lifetime} seconds in ${channels} text-based channels`,
+ );
+ return messages;
+ }
+
+ /**
+ * Obtains the OAuth Application of this bot from Discord.
+ * @returns {Promise<ClientApplication>}
+ */
+ fetchApplication() {
+ return this.api.oauth2
+ .applications('@me')
+ .get()
+ .then(app => new ClientApplication(this, app));
+ }
+
+ /**
+ * Obtains a guild preview from Discord, only available for public guilds.
+ * @param {GuildResolvable} guild The guild to fetch the preview for
+ * @returns {Promise<GuildPreview>}
+ */
+ fetchGuildPreview(guild) {
+ const id = this.guilds.resolveID(guild);
+ if (!id) throw new TypeError('INVALID_TYPE', 'guild', 'GuildResolvable');
+ return this.api
+ .guilds(id)
+ .preview.get()
+ .then(data => new GuildPreview(this, data));
+ }
+
+ /**
+ * Generates a link that can be used to invite the bot to a guild.
+ * @param {PermissionResolvable} [permissions] Permissions to request
+ * @returns {Promise<string>}
+ * @example
+ * client.generateInvite(['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE'])
+ * .then(link => console.log(`Generated bot invite link: ${link}`))
+ * .catch(console.error);
+ */
+ async generateInvite(permissions) {
+ permissions = Permissions.resolve(permissions);
+ const application = await this.fetchApplication();
+ const query = new URLSearchParams({
+ client_id: application.id,
+ permissions: permissions,
+ scope: 'bot',
+ });
+ return `${this.options.http.api}${this.api.oauth2.authorize}?${query}`;
+ }
+
+ toJSON() {
+ return super.toJSON({
+ readyAt: false,
+ presences: false,
+ });
+ }
+
+ /**
+ * Calls {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval} on a script
+ * with the client as `this`.
+ * @param {string} script Script to eval
+ * @returns {*}
+ * @private
+ */
+ _eval(script) {
+ return eval(script);
+ }
+
+ /**
+ * Validates the client options.
+ * @param {ClientOptions} [options=this.options] Options to validate
+ * @private
+ */
+ _validateOptions(options = this.options) {
+ if (typeof options.ws.intents !== 'undefined') {
+ options.ws.intents = Intents.resolve(options.ws.intents);
+ }
+ if (typeof options.shardCount !== 'number' || isNaN(options.shardCount) || options.shardCount < 1) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'shardCount', 'a number greater than or equal to 1');
+ }
+ if (options.shards && !(options.shards === 'auto' || Array.isArray(options.shards))) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'shards', "'auto', a number or array of numbers");
+ }
+ if (options.shards && !options.shards.length) throw new RangeError('CLIENT_INVALID_PROVIDED_SHARDS');
+ if (typeof options.messageCacheMaxSize !== 'number' || isNaN(options.messageCacheMaxSize)) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'messageCacheMaxSize', 'a number');
+ }
+ if (typeof options.messageCacheLifetime !== 'number' || isNaN(options.messageCacheLifetime)) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'The messageCacheLifetime', 'a number');
+ }
+ if (typeof options.messageSweepInterval !== 'number' || isNaN(options.messageSweepInterval)) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'messageSweepInterval', 'a number');
+ }
+ if (typeof options.fetchAllMembers !== 'boolean') {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'fetchAllMembers', 'a boolean');
+ }
+ if (typeof options.disableMentions !== 'string') {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'disableMentions', 'a string');
+ }
+ if (!Array.isArray(options.partials)) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array');
+ }
+ if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'restWsBridgeTimeout', 'a number');
+ }
+ if (typeof options.restRequestTimeout !== 'number' || isNaN(options.restRequestTimeout)) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'restRequestTimeout', 'a number');
+ }
+ if (typeof options.restSweepInterval !== 'number' || isNaN(options.restSweepInterval)) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'restSweepInterval', 'a number');
+ }
+ if (typeof options.retryLimit !== 'number' || isNaN(options.retryLimit)) {
+ throw new TypeError('CLIENT_INVALID_OPTION', 'retryLimit', 'a number');
+ }
+ }
+}
+
+module.exports = Client;
+
+/**
+ * Emitted for general warnings.
+ * @event Client#warn
+ * @param {string} info The warning
+ */
+
+/**
+ * Emitted for general debugging information.
+ * @event Client#debug
+ * @param {string} info The debug information
+ */
diff --git a/node_modules/discord.js/src/client/WebhookClient.js b/node_modules/discord.js/src/client/WebhookClient.js
new file mode 100644
index 0000000..cea6631
--- /dev/null
+++ b/node_modules/discord.js/src/client/WebhookClient.js
@@ -0,0 +1,31 @@
+'use strict';
+
+const BaseClient = require('./BaseClient');
+const Webhook = require('../structures/Webhook');
+
+/**
+ * The webhook client.
+ * @implements {Webhook}
+ * @extends {BaseClient}
+ */
+class WebhookClient extends BaseClient {
+ /**
+ * @param {Snowflake} id ID of the webhook
+ * @param {string} token Token of the webhook
+ * @param {ClientOptions} [options] Options for the client
+ * @example
+ * // Create a new webhook and send a message
+ * const hook = new Discord.WebhookClient('1234', 'abcdef');
+ * hook.send('This will send a message').catch(console.error);
+ */
+ constructor(id, token, options) {
+ super(options);
+ Object.defineProperty(this, 'client', { value: this });
+ this.id = id;
+ Object.defineProperty(this, 'token', { value: token, writable: true, configurable: true });
+ }
+}
+
+Webhook.applyToClass(WebhookClient);
+
+module.exports = WebhookClient;
diff --git a/node_modules/discord.js/src/client/actions/Action.js b/node_modules/discord.js/src/client/actions/Action.js
new file mode 100644
index 0000000..d9519b5
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/Action.js
@@ -0,0 +1,103 @@
+'use strict';
+
+const { PartialTypes } = require('../../util/Constants');
+
+/*
+
+ABOUT ACTIONS
+
+Actions are similar to WebSocket Packet Handlers, but since introducing
+the REST API methods, in order to prevent rewriting code to handle data,
+"actions" have been introduced. They're basically what Packet Handlers
+used to be but they're strictly for manipulating data and making sure
+that WebSocket events don't clash with REST methods.
+
+*/
+
+class GenericAction {
+ constructor(client) {
+ this.client = client;
+ }
+
+ handle(data) {
+ return data;
+ }
+
+ getPayload(data, manager, id, partialType, cache) {
+ const existing = manager.cache.get(id);
+ if (!existing && this.client.options.partials.includes(partialType)) {
+ return manager.add(data, cache);
+ }
+ return existing;
+ }
+
+ getChannel(data) {
+ const id = data.channel_id || data.id;
+ return (
+ data.channel ||
+ this.getPayload(
+ {
+ id,
+ guild_id: data.guild_id,
+ recipients: [data.author || { id: data.user_id }],
+ },
+ this.client.channels,
+ id,
+ PartialTypes.CHANNEL,
+ )
+ );
+ }
+
+ getMessage(data, channel, cache) {
+ const id = data.message_id || data.id;
+ return (
+ data.message ||
+ this.getPayload(
+ {
+ id,
+ channel_id: channel.id,
+ guild_id: data.guild_id || (channel.guild ? channel.guild.id : null),
+ },
+ channel.messages,
+ id,
+ PartialTypes.MESSAGE,
+ cache,
+ )
+ );
+ }
+
+ getReaction(data, message, user) {
+ const id = data.emoji.id || decodeURIComponent(data.emoji.name);
+ return this.getPayload(
+ {
+ emoji: data.emoji,
+ count: message.partial ? null : 0,
+ me: user ? user.id === this.client.user.id : false,
+ },
+ message.reactions,
+ id,
+ PartialTypes.REACTION,
+ );
+ }
+
+ getMember(data, guild) {
+ const id = data.user.id;
+ return this.getPayload(
+ {
+ user: {
+ id,
+ },
+ },
+ guild.members,
+ id,
+ PartialTypes.GUILD_MEMBER,
+ );
+ }
+
+ getUser(data) {
+ const id = data.user_id;
+ return data.user || this.getPayload({ id }, this.client.users, id, PartialTypes.USER);
+ }
+}
+
+module.exports = GenericAction;
diff --git a/node_modules/discord.js/src/client/actions/ActionsManager.js b/node_modules/discord.js/src/client/actions/ActionsManager.js
new file mode 100644
index 0000000..7e1df12
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/ActionsManager.js
@@ -0,0 +1,45 @@
+'use strict';
+
+class ActionsManager {
+ constructor(client) {
+ this.client = client;
+
+ this.register(require('./MessageCreate'));
+ this.register(require('./MessageDelete'));
+ this.register(require('./MessageDeleteBulk'));
+ this.register(require('./MessageUpdate'));
+ this.register(require('./MessageReactionAdd'));
+ this.register(require('./MessageReactionRemove'));
+ this.register(require('./MessageReactionRemoveAll'));
+ this.register(require('./MessageReactionRemoveEmoji'));
+ this.register(require('./ChannelCreate'));
+ this.register(require('./ChannelDelete'));
+ this.register(require('./ChannelUpdate'));
+ this.register(require('./GuildDelete'));
+ this.register(require('./GuildUpdate'));
+ this.register(require('./InviteCreate'));
+ this.register(require('./InviteDelete'));
+ this.register(require('./GuildMemberRemove'));
+ this.register(require('./GuildBanRemove'));
+ this.register(require('./GuildRoleCreate'));
+ this.register(require('./GuildRoleDelete'));
+ this.register(require('./GuildRoleUpdate'));
+ this.register(require('./PresenceUpdate'));
+ this.register(require('./UserUpdate'));
+ this.register(require('./VoiceStateUpdate'));
+ this.register(require('./GuildEmojiCreate'));
+ this.register(require('./GuildEmojiDelete'));
+ this.register(require('./GuildEmojiUpdate'));
+ this.register(require('./GuildEmojisUpdate'));
+ this.register(require('./GuildRolesPositionUpdate'));
+ this.register(require('./GuildChannelsPositionUpdate'));
+ this.register(require('./GuildIntegrationsUpdate'));
+ this.register(require('./WebhooksUpdate'));
+ }
+
+ register(Action) {
+ this[Action.name.replace(/Action$/, '')] = new Action(this.client);
+ }
+}
+
+module.exports = ActionsManager;
diff --git a/node_modules/discord.js/src/client/actions/ChannelCreate.js b/node_modules/discord.js/src/client/actions/ChannelCreate.js
new file mode 100644
index 0000000..fa60a0b
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/ChannelCreate.js
@@ -0,0 +1,23 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class ChannelCreateAction extends Action {
+ handle(data) {
+ const client = this.client;
+ const existing = client.channels.cache.has(data.id);
+ const channel = client.channels.add(data);
+ if (!existing && channel) {
+ /**
+ * Emitted whenever a channel is created.
+ * @event Client#channelCreate
+ * @param {DMChannel|GuildChannel} channel The channel that was created
+ */
+ client.emit(Events.CHANNEL_CREATE, channel);
+ }
+ return { channel };
+ }
+}
+
+module.exports = ChannelCreateAction;
diff --git a/node_modules/discord.js/src/client/actions/ChannelDelete.js b/node_modules/discord.js/src/client/actions/ChannelDelete.js
new file mode 100644
index 0000000..2956d72
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/ChannelDelete.js
@@ -0,0 +1,37 @@
+'use strict';
+
+const Action = require('./Action');
+const DMChannel = require('../../structures/DMChannel');
+const { Events } = require('../../util/Constants');
+
+class ChannelDeleteAction extends Action {
+ constructor(client) {
+ super(client);
+ this.deleted = new Map();
+ }
+
+ handle(data) {
+ const client = this.client;
+ let channel = client.channels.cache.get(data.id);
+
+ if (channel) {
+ client.channels.remove(channel.id);
+ channel.deleted = true;
+ if (channel.messages && !(channel instanceof DMChannel)) {
+ for (const message of channel.messages.cache.values()) {
+ message.deleted = true;
+ }
+ }
+ /**
+ * Emitted whenever a channel is deleted.
+ * @event Client#channelDelete
+ * @param {DMChannel|GuildChannel} channel The channel that was deleted
+ */
+ client.emit(Events.CHANNEL_DELETE, channel);
+ }
+
+ return { channel };
+ }
+}
+
+module.exports = ChannelDeleteAction;
diff --git a/node_modules/discord.js/src/client/actions/ChannelUpdate.js b/node_modules/discord.js/src/client/actions/ChannelUpdate.js
new file mode 100644
index 0000000..06bb71b
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/ChannelUpdate.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const Action = require('./Action');
+const Channel = require('../../structures/Channel');
+const { ChannelTypes } = require('../../util/Constants');
+
+class ChannelUpdateAction extends Action {
+ handle(data) {
+ const client = this.client;
+
+ let channel = client.channels.cache.get(data.id);
+ if (channel) {
+ const old = channel._update(data);
+
+ if (ChannelTypes[channel.type.toUpperCase()] !== data.type) {
+ const newChannel = Channel.create(this.client, data, channel.guild);
+ for (const [id, message] of channel.messages.cache) newChannel.messages.cache.set(id, message);
+ newChannel._typing = new Map(channel._typing);
+ channel = newChannel;
+ this.client.channels.cache.set(channel.id, channel);
+ }
+
+ return {
+ old,
+ updated: channel,
+ };
+ }
+
+ return {};
+ }
+}
+
+module.exports = ChannelUpdateAction;
diff --git a/node_modules/discord.js/src/client/actions/GuildBanRemove.js b/node_modules/discord.js/src/client/actions/GuildBanRemove.js
new file mode 100644
index 0000000..fc28e11
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildBanRemove.js
@@ -0,0 +1,21 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildBanRemove extends Action {
+ handle(data) {
+ const client = this.client;
+ const guild = client.guilds.cache.get(data.guild_id);
+ const user = client.users.add(data.user);
+ /**
+ * Emitted whenever a member is unbanned from a guild.
+ * @event Client#guildBanRemove
+ * @param {Guild} guild The guild that the unban occurred in
+ * @param {User} user The user that was unbanned
+ */
+ if (guild && user) client.emit(Events.GUILD_BAN_REMOVE, guild, user);
+ }
+}
+
+module.exports = GuildBanRemove;
diff --git a/node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js b/node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js
new file mode 100644
index 0000000..a393167
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js
@@ -0,0 +1,21 @@
+'use strict';
+
+const Action = require('./Action');
+
+class GuildChannelsPositionUpdate extends Action {
+ handle(data) {
+ const client = this.client;
+
+ const guild = client.guilds.cache.get(data.guild_id);
+ if (guild) {
+ for (const partialChannel of data.channels) {
+ const channel = guild.channels.cache.get(partialChannel.id);
+ if (channel) channel.rawPosition = partialChannel.position;
+ }
+ }
+
+ return { guild };
+ }
+}
+
+module.exports = GuildChannelsPositionUpdate;
diff --git a/node_modules/discord.js/src/client/actions/GuildDelete.js b/node_modules/discord.js/src/client/actions/GuildDelete.js
new file mode 100644
index 0000000..8bb630f
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildDelete.js
@@ -0,0 +1,67 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildDeleteAction extends Action {
+ constructor(client) {
+ super(client);
+ this.deleted = new Map();
+ }
+
+ handle(data) {
+ const client = this.client;
+
+ let guild = client.guilds.cache.get(data.id);
+ if (guild) {
+ for (const channel of guild.channels.cache.values()) {
+ if (channel.type === 'text') channel.stopTyping(true);
+ }
+
+ if (data.unavailable) {
+ // Guild is unavailable
+ guild.available = false;
+
+ /**
+ * Emitted whenever a guild becomes unavailable, likely due to a server outage.
+ * @event Client#guildUnavailable
+ * @param {Guild} guild The guild that has become unavailable
+ */
+ client.emit(Events.GUILD_UNAVAILABLE, guild);
+
+ // Stops the GuildDelete packet thinking a guild was actually deleted,
+ // handles emitting of event itself
+ return {
+ guild: null,
+ };
+ }
+
+ for (const channel of guild.channels.cache.values()) this.client.channels.remove(channel.id);
+ if (guild.voice && guild.voice.connection) guild.voice.connection.disconnect();
+
+ // Delete guild
+ client.guilds.cache.delete(guild.id);
+ guild.deleted = true;
+
+ /**
+ * Emitted whenever a guild kicks the client or the guild is deleted/left.
+ * @event Client#guildDelete
+ * @param {Guild} guild The guild that was deleted
+ */
+ client.emit(Events.GUILD_DELETE, guild);
+
+ this.deleted.set(guild.id, guild);
+ this.scheduleForDeletion(guild.id);
+ } else {
+ guild = this.deleted.get(data.id) || null;
+ }
+
+ return { guild };
+ }
+
+ scheduleForDeletion(id) {
+ this.client.setTimeout(() => this.deleted.delete(id), this.client.options.restWsBridgeTimeout);
+ }
+}
+
+module.exports = GuildDeleteAction;
diff --git a/node_modules/discord.js/src/client/actions/GuildEmojiCreate.js b/node_modules/discord.js/src/client/actions/GuildEmojiCreate.js
new file mode 100644
index 0000000..379c62e
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildEmojiCreate.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildEmojiCreateAction extends Action {
+ handle(guild, createdEmoji) {
+ const emoji = guild.emojis.add(createdEmoji);
+ /**
+ * Emitted whenever a custom emoji is created in a guild.
+ * @event Client#emojiCreate
+ * @param {GuildEmoji} emoji The emoji that was created
+ */
+ this.client.emit(Events.GUILD_EMOJI_CREATE, emoji);
+ return { emoji };
+ }
+}
+
+module.exports = GuildEmojiCreateAction;
diff --git a/node_modules/discord.js/src/client/actions/GuildEmojiDelete.js b/node_modules/discord.js/src/client/actions/GuildEmojiDelete.js
new file mode 100644
index 0000000..42af70c
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildEmojiDelete.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildEmojiDeleteAction extends Action {
+ handle(emoji) {
+ emoji.guild.emojis.cache.delete(emoji.id);
+ emoji.deleted = true;
+ /**
+ * Emitted whenever a custom emoji is deleted in a guild.
+ * @event Client#emojiDelete
+ * @param {GuildEmoji} emoji The emoji that was deleted
+ */
+ this.client.emit(Events.GUILD_EMOJI_DELETE, emoji);
+ return { emoji };
+ }
+}
+
+module.exports = GuildEmojiDeleteAction;
diff --git a/node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js b/node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js
new file mode 100644
index 0000000..9fa59c9
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildEmojiUpdateAction extends Action {
+ handle(current, data) {
+ const old = current._update(data);
+ /**
+ * Emitted whenever a custom emoji is updated in a guild.
+ * @event Client#emojiUpdate
+ * @param {GuildEmoji} oldEmoji The old emoji
+ * @param {GuildEmoji} newEmoji The new emoji
+ */
+ this.client.emit(Events.GUILD_EMOJI_UPDATE, old, current);
+ return { emoji: current };
+ }
+}
+
+module.exports = GuildEmojiUpdateAction;
diff --git a/node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js b/node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js
new file mode 100644
index 0000000..7711933
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js
@@ -0,0 +1,34 @@
+'use strict';
+
+const Action = require('./Action');
+
+class GuildEmojisUpdateAction extends Action {
+ handle(data) {
+ const guild = this.client.guilds.cache.get(data.guild_id);
+ if (!guild || !guild.emojis) return;
+
+ const deletions = new Map(guild.emojis.cache);
+
+ for (const emoji of data.emojis) {
+ // Determine type of emoji event
+ const cachedEmoji = guild.emojis.cache.get(emoji.id);
+ if (cachedEmoji) {
+ deletions.delete(emoji.id);
+ if (!cachedEmoji.equals(emoji)) {
+ // Emoji updated
+ this.client.actions.GuildEmojiUpdate.handle(cachedEmoji, emoji);
+ }
+ } else {
+ // Emoji added
+ this.client.actions.GuildEmojiCreate.handle(guild, emoji);
+ }
+ }
+
+ for (const emoji of deletions.values()) {
+ // Emoji deleted
+ this.client.actions.GuildEmojiDelete.handle(emoji);
+ }
+ }
+}
+
+module.exports = GuildEmojisUpdateAction;
diff --git a/node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js b/node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js
new file mode 100644
index 0000000..bbce076
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildIntegrationsUpdate extends Action {
+ handle(data) {
+ const client = this.client;
+ const guild = client.guilds.cache.get(data.guild_id);
+ /**
+ * Emitted whenever a guild integration is updated
+ * @event Client#guildIntegrationsUpdate
+ * @param {Guild} guild The guild whose integrations were updated
+ */
+ if (guild) client.emit(Events.GUILD_INTEGRATIONS_UPDATE, guild);
+ }
+}
+
+module.exports = GuildIntegrationsUpdate;
diff --git a/node_modules/discord.js/src/client/actions/GuildMemberRemove.js b/node_modules/discord.js/src/client/actions/GuildMemberRemove.js
new file mode 100644
index 0000000..f0fe3b3
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildMemberRemove.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events, Status } = require('../../util/Constants');
+
+class GuildMemberRemoveAction extends Action {
+ handle(data, shard) {
+ const client = this.client;
+ const guild = client.guilds.cache.get(data.guild_id);
+ let member = null;
+ if (guild) {
+ member = this.getMember(data, guild);
+ guild.memberCount--;
+ if (member) {
+ member.deleted = true;
+ guild.members.cache.delete(member.id);
+ /**
+ * Emitted whenever a member leaves a guild, or is kicked.
+ * @event Client#guildMemberRemove
+ * @param {GuildMember} member The member that has left/been kicked from the guild
+ */
+ if (shard.status === Status.READY) client.emit(Events.GUILD_MEMBER_REMOVE, member);
+ }
+ guild.voiceStates.cache.delete(data.user.id);
+ }
+ return { guild, member };
+ }
+}
+
+module.exports = GuildMemberRemoveAction;
diff --git a/node_modules/discord.js/src/client/actions/GuildRoleCreate.js b/node_modules/discord.js/src/client/actions/GuildRoleCreate.js
new file mode 100644
index 0000000..d7d5214
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildRoleCreate.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildRoleCreate extends Action {
+ handle(data) {
+ const client = this.client;
+ const guild = client.guilds.cache.get(data.guild_id);
+ let role;
+ if (guild) {
+ const already = guild.roles.cache.has(data.role.id);
+ role = guild.roles.add(data.role);
+ /**
+ * Emitted whenever a role is created.
+ * @event Client#roleCreate
+ * @param {Role} role The role that was created
+ */
+ if (!already) client.emit(Events.GUILD_ROLE_CREATE, role);
+ }
+ return { role };
+ }
+}
+
+module.exports = GuildRoleCreate;
diff --git a/node_modules/discord.js/src/client/actions/GuildRoleDelete.js b/node_modules/discord.js/src/client/actions/GuildRoleDelete.js
new file mode 100644
index 0000000..51754b3
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildRoleDelete.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildRoleDeleteAction extends Action {
+ handle(data) {
+ const client = this.client;
+ const guild = client.guilds.cache.get(data.guild_id);
+ let role;
+
+ if (guild) {
+ role = guild.roles.cache.get(data.role_id);
+ if (role) {
+ guild.roles.cache.delete(data.role_id);
+ role.deleted = true;
+ /**
+ * Emitted whenever a guild role is deleted.
+ * @event Client#roleDelete
+ * @param {Role} role The role that was deleted
+ */
+ client.emit(Events.GUILD_ROLE_DELETE, role);
+ }
+ }
+
+ return { role };
+ }
+}
+
+module.exports = GuildRoleDeleteAction;
diff --git a/node_modules/discord.js/src/client/actions/GuildRoleUpdate.js b/node_modules/discord.js/src/client/actions/GuildRoleUpdate.js
new file mode 100644
index 0000000..faea120
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildRoleUpdate.js
@@ -0,0 +1,39 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildRoleUpdateAction extends Action {
+ handle(data) {
+ const client = this.client;
+ const guild = client.guilds.cache.get(data.guild_id);
+
+ if (guild) {
+ let old = null;
+
+ const role = guild.roles.cache.get(data.role.id);
+ if (role) {
+ old = role._update(data.role);
+ /**
+ * Emitted whenever a guild role is updated.
+ * @event Client#roleUpdate
+ * @param {Role} oldRole The role before the update
+ * @param {Role} newRole The role after the update
+ */
+ client.emit(Events.GUILD_ROLE_UPDATE, old, role);
+ }
+
+ return {
+ old,
+ updated: role,
+ };
+ }
+
+ return {
+ old: null,
+ updated: null,
+ };
+ }
+}
+
+module.exports = GuildRoleUpdateAction;
diff --git a/node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js b/node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js
new file mode 100644
index 0000000..d7abca9
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js
@@ -0,0 +1,21 @@
+'use strict';
+
+const Action = require('./Action');
+
+class GuildRolesPositionUpdate extends Action {
+ handle(data) {
+ const client = this.client;
+
+ const guild = client.guilds.cache.get(data.guild_id);
+ if (guild) {
+ for (const partialRole of data.roles) {
+ const role = guild.roles.cache.get(partialRole.id);
+ if (role) role.rawPosition = partialRole.position;
+ }
+ }
+
+ return { guild };
+ }
+}
+
+module.exports = GuildRolesPositionUpdate;
diff --git a/node_modules/discord.js/src/client/actions/GuildUpdate.js b/node_modules/discord.js/src/client/actions/GuildUpdate.js
new file mode 100644
index 0000000..78a8afb
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/GuildUpdate.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class GuildUpdateAction extends Action {
+ handle(data) {
+ const client = this.client;
+
+ const guild = client.guilds.cache.get(data.id);
+ if (guild) {
+ const old = guild._update(data);
+ /**
+ * Emitted whenever a guild is updated - e.g. name change.
+ * @event Client#guildUpdate
+ * @param {Guild} oldGuild The guild before the update
+ * @param {Guild} newGuild The guild after the update
+ */
+ client.emit(Events.GUILD_UPDATE, old, guild);
+ return {
+ old,
+ updated: guild,
+ };
+ }
+
+ return {
+ old: null,
+ updated: null,
+ };
+ }
+}
+
+module.exports = GuildUpdateAction;
diff --git a/node_modules/discord.js/src/client/actions/InviteCreate.js b/node_modules/discord.js/src/client/actions/InviteCreate.js
new file mode 100644
index 0000000..6381331
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/InviteCreate.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const Action = require('./Action');
+const Invite = require('../../structures/Invite');
+const { Events } = require('../../util/Constants');
+
+class InviteCreateAction extends Action {
+ handle(data) {
+ const client = this.client;
+ const channel = client.channels.cache.get(data.channel_id);
+ const guild = client.guilds.cache.get(data.guild_id);
+ if (!channel && !guild) return false;
+
+ const inviteData = Object.assign(data, { channel, guild });
+ const invite = new Invite(client, inviteData);
+ /**
+ * Emitted when an invite is created.
+ * <info> This event only triggers if the client has `MANAGE_GUILD` permissions for the guild,
+ * or `MANAGE_CHANNEL` permissions for the channel.</info>
+ * @event Client#inviteCreate
+ * @param {Invite} invite The invite that was created
+ */
+ client.emit(Events.INVITE_CREATE, invite);
+ return { invite };
+ }
+}
+
+module.exports = InviteCreateAction;
diff --git a/node_modules/discord.js/src/client/actions/InviteDelete.js b/node_modules/discord.js/src/client/actions/InviteDelete.js
new file mode 100644
index 0000000..92692c3
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/InviteDelete.js
@@ -0,0 +1,29 @@
+'use strict';
+
+const Action = require('./Action');
+const Invite = require('../../structures/Invite');
+const { Events } = require('../../util/Constants');
+
+class InviteDeleteAction extends Action {
+ handle(data) {
+ const client = this.client;
+ const channel = client.channels.cache.get(data.channel_id);
+ const guild = client.guilds.cache.get(data.guild_id);
+ if (!channel && !guild) return false;
+
+ const inviteData = Object.assign(data, { channel, guild });
+ const invite = new Invite(client, inviteData);
+
+ /**
+ * Emitted when an invite is deleted.
+ * <info> This event only triggers if the client has `MANAGE_GUILD` permissions for the guild,
+ * or `MANAGE_CHANNEL` permissions for the channel.</info>
+ * @event Client#inviteDelete
+ * @param {Invite} invite The invite that was deleted
+ */
+ client.emit(Events.INVITE_DELETE, invite);
+ return { invite };
+ }
+}
+
+module.exports = InviteDeleteAction;
diff --git a/node_modules/discord.js/src/client/actions/MessageCreate.js b/node_modules/discord.js/src/client/actions/MessageCreate.js
new file mode 100644
index 0000000..7138707
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/MessageCreate.js
@@ -0,0 +1,39 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class MessageCreateAction extends Action {
+ handle(data) {
+ const client = this.client;
+ const channel = client.channels.cache.get(data.channel_id);
+ if (channel) {
+ const existing = channel.messages.cache.get(data.id);
+ if (existing) return { message: existing };
+ const message = channel.messages.add(data);
+ const user = message.author;
+ let member = message.member;
+ channel.lastMessageID = data.id;
+ if (user) {
+ user.lastMessageID = data.id;
+ user.lastMessageChannelID = channel.id;
+ }
+ if (member) {
+ member.lastMessageID = data.id;
+ member.lastMessageChannelID = channel.id;
+ }
+
+ /**
+ * Emitted whenever a message is created.
+ * @event Client#message
+ * @param {Message} message The created message
+ */
+ client.emit(Events.MESSAGE_CREATE, message);
+ return { message };
+ }
+
+ return {};
+ }
+}
+
+module.exports = MessageCreateAction;
diff --git a/node_modules/discord.js/src/client/actions/MessageDelete.js b/node_modules/discord.js/src/client/actions/MessageDelete.js
new file mode 100644
index 0000000..0210625
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/MessageDelete.js
@@ -0,0 +1,29 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class MessageDeleteAction extends Action {
+ handle(data) {
+ const client = this.client;
+ const channel = this.getChannel(data);
+ let message;
+ if (channel) {
+ message = this.getMessage(data, channel);
+ if (message) {
+ channel.messages.cache.delete(message.id);
+ message.deleted = true;
+ /**
+ * Emitted whenever a message is deleted.
+ * @event Client#messageDelete
+ * @param {Message} message The deleted message
+ */
+ client.emit(Events.MESSAGE_DELETE, message);
+ }
+ }
+
+ return { message };
+ }
+}
+
+module.exports = MessageDeleteAction;
diff --git a/node_modules/discord.js/src/client/actions/MessageDeleteBulk.js b/node_modules/discord.js/src/client/actions/MessageDeleteBulk.js
new file mode 100644
index 0000000..8686b1d
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/MessageDeleteBulk.js
@@ -0,0 +1,43 @@
+'use strict';
+
+const Action = require('./Action');
+const Collection = require('../../util/Collection');
+const { Events } = require('../../util/Constants');
+
+class MessageDeleteBulkAction extends Action {
+ handle(data) {
+ const client = this.client;
+ const channel = client.channels.cache.get(data.channel_id);
+
+ if (channel) {
+ const ids = data.ids;
+ const messages = new Collection();
+ for (const id of ids) {
+ const message = this.getMessage(
+ {
+ id,
+ guild_id: data.guild_id,
+ },
+ channel,
+ false,
+ );
+ if (message) {
+ message.deleted = true;
+ messages.set(message.id, message);
+ channel.messages.cache.delete(id);
+ }
+ }
+
+ /**
+ * Emitted whenever messages are deleted in bulk.
+ * @event Client#messageDeleteBulk
+ * @param {Collection<Snowflake, Message>} messages The deleted messages, mapped by their ID
+ */
+ if (messages.size > 0) client.emit(Events.MESSAGE_BULK_DELETE, messages);
+ return { messages };
+ }
+ return {};
+ }
+}
+
+module.exports = MessageDeleteBulkAction;
diff --git a/node_modules/discord.js/src/client/actions/MessageReactionAdd.js b/node_modules/discord.js/src/client/actions/MessageReactionAdd.js
new file mode 100644
index 0000000..22287db
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/MessageReactionAdd.js
@@ -0,0 +1,50 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+const { PartialTypes } = require('../../util/Constants');
+
+/*
+{ user_id: 'id',
+ message_id: 'id',
+ emoji: { name: '�', id: null },
+ channel_id: 'id' } }
+*/
+
+class MessageReactionAdd extends Action {
+ handle(data) {
+ if (!data.emoji) return false;
+
+ const user = this.getUser(data);
+ if (!user) return false;
+
+ // Verify channel
+ const channel = this.getChannel(data);
+ if (!channel || channel.type === 'voice') return false;
+
+ // Verify message
+ const message = this.getMessage(data, channel);
+ if (!message) return false;
+
+ // Verify reaction
+ if (message.partial && !this.client.options.partials.includes(PartialTypes.REACTION)) return false;
+ const reaction = message.reactions.add({
+ emoji: data.emoji,
+ count: message.partial ? null : 0,
+ me: user.id === this.client.user.id,
+ });
+ if (!reaction) return false;
+ reaction._add(user);
+ /**
+ * Emitted whenever a reaction is added to a cached message.
+ * @event Client#messageReactionAdd
+ * @param {MessageReaction} messageReaction The reaction object
+ * @param {User} user The user that applied the guild or reaction emoji
+ */
+ this.client.emit(Events.MESSAGE_REACTION_ADD, reaction, user);
+
+ return { message, reaction, user };
+ }
+}
+
+module.exports = MessageReactionAdd;
diff --git a/node_modules/discord.js/src/client/actions/MessageReactionRemove.js b/node_modules/discord.js/src/client/actions/MessageReactionRemove.js
new file mode 100644
index 0000000..a40aa75
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/MessageReactionRemove.js
@@ -0,0 +1,44 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+/*
+{ user_id: 'id',
+ message_id: 'id',
+ emoji: { name: '�', id: null },
+ channel_id: 'id' } }
+*/
+
+class MessageReactionRemove extends Action {
+ handle(data) {
+ if (!data.emoji) return false;
+
+ const user = this.getUser(data);
+ if (!user) return false;
+
+ // Verify channel
+ const channel = this.getChannel(data);
+ if (!channel || channel.type === 'voice') return false;
+
+ // Verify message
+ const message = this.getMessage(data, channel);
+ if (!message) return false;
+
+ // Verify reaction
+ const reaction = this.getReaction(data, message, user);
+ if (!reaction) return false;
+ reaction._remove(user);
+ /**
+ * Emitted whenever a reaction is removed from a cached message.
+ * @event Client#messageReactionRemove
+ * @param {MessageReaction} messageReaction The reaction object
+ * @param {User} user The user whose emoji or reaction emoji was removed
+ */
+ this.client.emit(Events.MESSAGE_REACTION_REMOVE, reaction, user);
+
+ return { message, reaction, user };
+ }
+}
+
+module.exports = MessageReactionRemove;
diff --git a/node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js b/node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js
new file mode 100644
index 0000000..14b79bf
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js
@@ -0,0 +1,29 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class MessageReactionRemoveAll extends Action {
+ handle(data) {
+ // Verify channel
+ const channel = this.getChannel(data);
+ if (!channel || channel.type === 'voice') return false;
+
+ // Verify message
+ const message = this.getMessage(data, channel);
+ if (!message) return false;
+
+ message.reactions.cache.clear();
+ this.client.emit(Events.MESSAGE_REACTION_REMOVE_ALL, message);
+
+ return { message };
+ }
+}
+
+/**
+ * Emitted whenever all reactions are removed from a cached message.
+ * @event Client#messageReactionRemoveAll
+ * @param {Message} message The message the reactions were removed from
+ */
+
+module.exports = MessageReactionRemoveAll;
diff --git a/node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js b/node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js
new file mode 100644
index 0000000..143702c
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class MessageReactionRemoveEmoji extends Action {
+ handle(data) {
+ const channel = this.getChannel(data);
+ if (!channel || channel.type === 'voice') return false;
+
+ const message = this.getMessage(data, channel);
+ if (!message) return false;
+
+ const reaction = this.getReaction(data, message);
+ if (!reaction) return false;
+ if (!message.partial) message.reactions.cache.delete(reaction.emoji.id || reaction.emoji.name);
+
+ /**
+ * Emitted when a bot removes an emoji reaction from a cached message.
+ * @event Client#messageReactionRemoveEmoji
+ * @param {MessageReaction} reaction The reaction that was removed
+ */
+ this.client.emit(Events.MESSAGE_REACTION_REMOVE_EMOJI, reaction);
+ return { reaction };
+ }
+}
+
+module.exports = MessageReactionRemoveEmoji;
diff --git a/node_modules/discord.js/src/client/actions/MessageUpdate.js b/node_modules/discord.js/src/client/actions/MessageUpdate.js
new file mode 100644
index 0000000..07e2aac
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/MessageUpdate.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const Action = require('./Action');
+
+class MessageUpdateAction extends Action {
+ handle(data) {
+ const channel = this.getChannel(data);
+ if (channel) {
+ const { id, channel_id, guild_id, author, timestamp, type } = data;
+ const message = this.getMessage({ id, channel_id, guild_id, author, timestamp, type }, channel);
+ if (message) {
+ message.patch(data);
+ return {
+ old: message._edits[0],
+ updated: message,
+ };
+ }
+ }
+
+ return {};
+ }
+}
+
+module.exports = MessageUpdateAction;
diff --git a/node_modules/discord.js/src/client/actions/PresenceUpdate.js b/node_modules/discord.js/src/client/actions/PresenceUpdate.js
new file mode 100644
index 0000000..f74fbeb
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/PresenceUpdate.js
@@ -0,0 +1,44 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class PresenceUpdateAction extends Action {
+ handle(data) {
+ let user = this.client.users.cache.get(data.user.id);
+ if (!user && data.user.username) user = this.client.users.add(data.user);
+ if (!user) return;
+
+ if (data.user && data.user.username) {
+ if (!user.equals(data.user)) this.client.actions.UserUpdate.handle(data.user);
+ }
+
+ const guild = this.client.guilds.cache.get(data.guild_id);
+ if (!guild) return;
+
+ let oldPresence = guild.presences.cache.get(user.id);
+ if (oldPresence) oldPresence = oldPresence._clone();
+ let member = guild.members.cache.get(user.id);
+ if (!member && data.status !== 'offline') {
+ member = guild.members.add({
+ user,
+ roles: data.roles,
+ deaf: false,
+ mute: false,
+ });
+ this.client.emit(Events.GUILD_MEMBER_AVAILABLE, member);
+ }
+ guild.presences.add(Object.assign(data, { guild }));
+ if (member && this.client.listenerCount(Events.PRESENCE_UPDATE)) {
+ /**
+ * Emitted whenever a guild member's presence (e.g. status, activity) is changed.
+ * @event Client#presenceUpdate
+ * @param {?Presence} oldPresence The presence before the update, if one at all
+ * @param {Presence} newPresence The presence after the update
+ */
+ this.client.emit(Events.PRESENCE_UPDATE, oldPresence, member.presence);
+ }
+ }
+}
+
+module.exports = PresenceUpdateAction;
diff --git a/node_modules/discord.js/src/client/actions/UserUpdate.js b/node_modules/discord.js/src/client/actions/UserUpdate.js
new file mode 100644
index 0000000..7279ca7
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/UserUpdate.js
@@ -0,0 +1,34 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class UserUpdateAction extends Action {
+ handle(data) {
+ const client = this.client;
+
+ const newUser = client.users.cache.get(data.id);
+ const oldUser = newUser._update(data);
+
+ if (!oldUser.equals(newUser)) {
+ /**
+ * Emitted whenever a user's details (e.g. username) are changed.
+ * @event Client#userUpdate
+ * @param {User} oldUser The user before the update
+ * @param {User} newUser The user after the update
+ */
+ client.emit(Events.USER_UPDATE, oldUser, newUser);
+ return {
+ old: oldUser,
+ updated: newUser,
+ };
+ }
+
+ return {
+ old: null,
+ updated: null,
+ };
+ }
+}
+
+module.exports = UserUpdateAction;
diff --git a/node_modules/discord.js/src/client/actions/VoiceStateUpdate.js b/node_modules/discord.js/src/client/actions/VoiceStateUpdate.js
new file mode 100644
index 0000000..6f2ee9d
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/VoiceStateUpdate.js
@@ -0,0 +1,44 @@
+'use strict';
+
+const Action = require('./Action');
+const VoiceState = require('../../structures/VoiceState');
+const { Events } = require('../../util/Constants');
+
+class VoiceStateUpdate extends Action {
+ handle(data) {
+ const client = this.client;
+ const guild = client.guilds.cache.get(data.guild_id);
+ if (guild) {
+ // Update the state
+ const oldState = guild.voiceStates.cache.has(data.user_id)
+ ? guild.voiceStates.cache.get(data.user_id)._clone()
+ : new VoiceState(guild, { user_id: data.user_id });
+
+ const newState = guild.voiceStates.add(data);
+
+ // Get the member
+ let member = guild.members.cache.get(data.user_id);
+ if (member && data.member) {
+ member._patch(data.member);
+ } else if (data.member && data.member.user && data.member.joined_at) {
+ member = guild.members.add(data.member);
+ }
+
+ // Emit event
+ if (member && member.user.id === client.user.id) {
+ client.emit('debug', `[VOICE] received voice state update: ${JSON.stringify(data)}`);
+ client.voice.onVoiceStateUpdate(data);
+ }
+
+ /**
+ * Emitted whenever a member changes voice state - e.g. joins/leaves a channel, mutes/unmutes.
+ * @event Client#voiceStateUpdate
+ * @param {VoiceState} oldState The voice state before the update
+ * @param {VoiceState} newState The voice state after the update
+ */
+ client.emit(Events.VOICE_STATE_UPDATE, oldState, newState);
+ }
+ }
+}
+
+module.exports = VoiceStateUpdate;
diff --git a/node_modules/discord.js/src/client/actions/WebhooksUpdate.js b/node_modules/discord.js/src/client/actions/WebhooksUpdate.js
new file mode 100644
index 0000000..fdf9c56
--- /dev/null
+++ b/node_modules/discord.js/src/client/actions/WebhooksUpdate.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const Action = require('./Action');
+const { Events } = require('../../util/Constants');
+
+class WebhooksUpdate extends Action {
+ handle(data) {
+ const client = this.client;
+ const channel = client.channels.cache.get(data.channel_id);
+ /**
+ * Emitted whenever a guild text channel has its webhooks changed.
+ * @event Client#webhookUpdate
+ * @param {TextChannel} channel The channel that had a webhook update
+ */
+ if (channel) client.emit(Events.WEBHOOKS_UPDATE, channel);
+ }
+}
+
+module.exports = WebhooksUpdate;
diff --git a/node_modules/discord.js/src/client/voice/ClientVoiceManager.js b/node_modules/discord.js/src/client/voice/ClientVoiceManager.js
new file mode 100644
index 0000000..1eab808
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/ClientVoiceManager.js
@@ -0,0 +1,110 @@
+'use strict';
+
+const VoiceBroadcast = require('./VoiceBroadcast');
+const VoiceConnection = require('./VoiceConnection');
+const { Error } = require('../../errors');
+const Collection = require('../../util/Collection');
+
+/**
+ * Manages voice connections for the client
+ */
+class ClientVoiceManager {
+ constructor(client) {
+ /**
+ * The client that instantiated this voice manager
+ * @type {Client}
+ * @readonly
+ * @name ClientVoiceManager#client
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ /**
+ * A collection mapping connection IDs to the Connection objects
+ * @type {Collection<Snowflake, VoiceConnection>}
+ */
+ this.connections = new Collection();
+
+ /**
+ * Active voice broadcasts that have been created
+ * @type {VoiceBroadcast[]}
+ */
+ this.broadcasts = [];
+ }
+
+ /**
+ * Creates a voice broadcast.
+ * @returns {VoiceBroadcast}
+ */
+ createBroadcast() {
+ const broadcast = new VoiceBroadcast(this.client);
+ this.broadcasts.push(broadcast);
+ return broadcast;
+ }
+
+ onVoiceServer({ guild_id, token, endpoint }) {
+ this.client.emit('debug', `[VOICE] voiceServer guild: ${guild_id} token: ${token} endpoint: ${endpoint}`);
+ const connection = this.connections.get(guild_id);
+ if (connection) connection.setTokenAndEndpoint(token, endpoint);
+ }
+
+ onVoiceStateUpdate({ guild_id, session_id, channel_id }) {
+ const connection = this.connections.get(guild_id);
+ this.client.emit('debug', `[VOICE] connection? ${!!connection}, ${guild_id} ${session_id} ${channel_id}`);
+ if (!connection) return;
+ if (!channel_id) {
+ connection._disconnect();
+ this.connections.delete(guild_id);
+ return;
+ }
+ connection.channel = this.client.channels.cache.get(channel_id);
+ connection.setSessionID(session_id);
+ }
+
+ /**
+ * Sets up a request to join a voice channel.
+ * @param {VoiceChannel} channel The voice channel to join
+ * @returns {Promise<VoiceConnection>}
+ * @private
+ */
+ joinChannel(channel) {
+ return new Promise((resolve, reject) => {
+ if (!channel.joinable) {
+ throw new Error('VOICE_JOIN_CHANNEL', channel.full);
+ }
+
+ let connection = this.connections.get(channel.guild.id);
+
+ if (connection) {
+ if (connection.channel.id !== channel.id) {
+ this.connections.get(channel.guild.id).updateChannel(channel);
+ }
+ resolve(connection);
+ return;
+ } else {
+ connection = new VoiceConnection(this, channel);
+ connection.on('debug', msg =>
+ this.client.emit('debug', `[VOICE (${channel.guild.id}:${connection.status})]: ${msg}`),
+ );
+ connection.authenticate();
+ this.connections.set(channel.guild.id, connection);
+ }
+
+ connection.once('failed', reason => {
+ this.connections.delete(channel.guild.id);
+ reject(reason);
+ });
+
+ connection.on('error', reject);
+
+ connection.once('authenticated', () => {
+ connection.once('ready', () => {
+ resolve(connection);
+ connection.removeListener('error', reject);
+ });
+ connection.once('disconnect', () => this.connections.delete(channel.guild.id));
+ });
+ });
+ }
+}
+
+module.exports = ClientVoiceManager;
diff --git a/node_modules/discord.js/src/client/voice/VoiceBroadcast.js b/node_modules/discord.js/src/client/voice/VoiceBroadcast.js
new file mode 100644
index 0000000..5755b52
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/VoiceBroadcast.js
@@ -0,0 +1,111 @@
+'use strict';
+
+const EventEmitter = require('events');
+const BroadcastAudioPlayer = require('./player/BroadcastAudioPlayer');
+const PlayInterface = require('./util/PlayInterface');
+const { Events } = require('../../util/Constants');
+
+/**
+ * A voice broadcast can be played across multiple voice connections for improved shared-stream efficiency.
+ *
+ * Example usage:
+ * ```js
+ * const broadcast = client.voice.createBroadcast();
+ * broadcast.play('./music.mp3');
+ * // Play "music.mp3" in all voice connections that the client is in
+ * for (const connection of client.voice.connections.values()) {
+ * connection.play(broadcast);
+ * }
+ * ```
+ * @implements {PlayInterface}
+ * @extends {EventEmitter}
+ */
+class VoiceBroadcast extends EventEmitter {
+ constructor(client) {
+ super();
+ /**
+ * The client that created the broadcast
+ * @type {Client}
+ */
+ this.client = client;
+ /**
+ * The subscribed StreamDispatchers of this broadcast
+ * @type {StreamDispatcher[]}
+ */
+ this.subscribers = [];
+ this.player = new BroadcastAudioPlayer(this);
+ }
+
+ /**
+ * The current master dispatcher, if any. This dispatcher controls all that is played by subscribed dispatchers.
+ * @type {?BroadcastDispatcher}
+ * @readonly
+ */
+ get dispatcher() {
+ return this.player.dispatcher;
+ }
+
+ /**
+ * Play an audio resource.
+ * @param {ReadableStream|string} resource The resource to play.
+ * @param {StreamOptions} [options] The options to play.
+ * @example
+ * // Play a local audio file
+ * broadcast.play('/home/hydrabolt/audio.mp3', { volume: 0.5 });
+ * @example
+ * // Play a ReadableStream
+ * broadcast.play(ytdl('https://www.youtube.com/watch?v=ZlAU_w7-Xp8', { filter: 'audioonly' }));
+ * @example
+ * // Using different protocols: https://ffmpeg.org/ffmpeg-protocols.html
+ * broadcast.play('http://www.sample-videos.com/audio/mp3/wave.mp3');
+ * @returns {BroadcastDispatcher}
+ */
+ play() {
+ return null;
+ }
+
+ /**
+ * Ends the broadcast, unsubscribing all subscribed channels and deleting the broadcast
+ */
+ end() {
+ for (const dispatcher of this.subscribers) this.delete(dispatcher);
+ const index = this.client.voice.broadcasts.indexOf(this);
+ if (index !== -1) this.client.voice.broadcasts.splice(index, 1);
+ }
+
+ add(dispatcher) {
+ const index = this.subscribers.indexOf(dispatcher);
+ if (index === -1) {
+ this.subscribers.push(dispatcher);
+ /**
+ * Emitted whenever a stream dispatcher subscribes to the broadcast.
+ * @event VoiceBroadcast#subscribe
+ * @param {StreamDispatcher} subscriber The subscribed dispatcher
+ */
+ this.emit(Events.VOICE_BROADCAST_SUBSCRIBE, dispatcher);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ delete(dispatcher) {
+ const index = this.subscribers.indexOf(dispatcher);
+ if (index !== -1) {
+ this.subscribers.splice(index, 1);
+ dispatcher.destroy();
+ /**
+ * Emitted whenever a stream dispatcher unsubscribes to the broadcast.
+ * @event VoiceBroadcast#unsubscribe
+ * @param {StreamDispatcher} dispatcher The unsubscribed dispatcher
+ */
+ this.emit(Events.VOICE_BROADCAST_UNSUBSCRIBE, dispatcher);
+ return true;
+ }
+ return false;
+ }
+}
+
+PlayInterface.applyToClass(VoiceBroadcast);
+
+module.exports = VoiceBroadcast;
diff --git a/node_modules/discord.js/src/client/voice/VoiceConnection.js b/node_modules/discord.js/src/client/voice/VoiceConnection.js
new file mode 100644
index 0000000..5f0e995
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/VoiceConnection.js
@@ -0,0 +1,523 @@
+'use strict';
+
+const EventEmitter = require('events');
+const VoiceUDP = require('./networking/VoiceUDPClient');
+const VoiceWebSocket = require('./networking/VoiceWebSocket');
+const AudioPlayer = require('./player/AudioPlayer');
+const VoiceReceiver = require('./receiver/Receiver');
+const PlayInterface = require('./util/PlayInterface');
+const Silence = require('./util/Silence');
+const { Error } = require('../../errors');
+const { OPCodes, VoiceOPCodes, VoiceStatus, Events } = require('../../util/Constants');
+const Speaking = require('../../util/Speaking');
+const Util = require('../../util/Util');
+
+// Workaround for Discord now requiring silence to be sent before being able to receive audio
+class SingleSilence extends Silence {
+ _read() {
+ super._read();
+ this.push(null);
+ }
+}
+
+const SUPPORTED_MODES = ['xsalsa20_poly1305_lite', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305'];
+
+/**
+ * Represents a connection to a guild's voice server.
+ * ```js
+ * // Obtained using:
+ * voiceChannel.join()
+ * .then(connection => {
+ *
+ * });
+ * ```
+ * @extends {EventEmitter}
+ * @implements {PlayInterface}
+ */
+class VoiceConnection extends EventEmitter {
+ constructor(voiceManager, channel) {
+ super();
+
+ /**
+ * The voice manager that instantiated this connection
+ * @type {ClientVoiceManager}
+ */
+ this.voiceManager = voiceManager;
+
+ /**
+ * The voice channel this connection is currently serving
+ * @type {VoiceChannel}
+ */
+ this.channel = channel;
+
+ /**
+ * The current status of the voice connection
+ * @type {VoiceStatus}
+ */
+ this.status = VoiceStatus.AUTHENTICATING;
+
+ /**
+ * Our current speaking state
+ * @type {Readonly<Speaking>}
+ */
+ this.speaking = new Speaking().freeze();
+
+ /**
+ * The authentication data needed to connect to the voice server
+ * @type {Object}
+ * @private
+ */
+ this.authentication = {};
+
+ /**
+ * The audio player for this voice connection
+ * @type {AudioPlayer}
+ */
+ this.player = new AudioPlayer(this);
+
+ this.player.on('debug', m => {
+ /**
+ * Debug info from the connection.
+ * @event VoiceConnection#debug
+ * @param {string} message The debug message
+ */
+ this.emit('debug', `audio player - ${m}`);
+ });
+
+ this.player.on('error', e => {
+ /**
+ * Warning info from the connection.
+ * @event VoiceConnection#warn
+ * @param {string|Error} warning The warning
+ */
+ this.emit('warn', e);
+ });
+
+ this.once('closing', () => this.player.destroy());
+
+ /**
+ * Map SSRC values to user IDs
+ * @type {Map<number, Snowflake>}
+ * @private
+ */
+ this.ssrcMap = new Map();
+
+ /**
+ * Tracks which users are talking
+ * @type {Map<Snowflake, Readonly<Speaking>>}
+ * @private
+ */
+ this._speaking = new Map();
+
+ /**
+ * Object that wraps contains the `ws` and `udp` sockets of this voice connection
+ * @type {Object}
+ * @private
+ */
+ this.sockets = {};
+
+ /**
+ * The voice receiver of this connection
+ * @type {VoiceReceiver}
+ */
+ this.receiver = new VoiceReceiver(this);
+ }
+
+ /**
+ * The client that instantiated this connection
+ * @type {Client}
+ * @readonly
+ */
+ get client() {
+ return this.voiceManager.client;
+ }
+
+ /**
+ * The current stream dispatcher (if any)
+ * @type {?StreamDispatcher}
+ * @readonly
+ */
+ get dispatcher() {
+ return this.player.dispatcher;
+ }
+
+ /**
+ * Sets whether the voice connection should display as "speaking", "soundshare" or "none".
+ * @param {BitFieldResolvable} value The new speaking state
+ * @private
+ */
+ setSpeaking(value) {
+ if (this.speaking.equals(value)) return;
+ if (this.status !== VoiceStatus.CONNECTED) return;
+ this.speaking = new Speaking(value).freeze();
+ this.sockets.ws
+ .sendPacket({
+ op: VoiceOPCodes.SPEAKING,
+ d: {
+ speaking: this.speaking.bitfield,
+ delay: 0,
+ ssrc: this.authentication.ssrc,
+ },
+ })
+ .catch(e => {
+ this.emit('debug', e);
+ });
+ }
+
+ /**
+ * The voice state of this connection
+ * @type {VoiceState}
+ */
+ get voice() {
+ return this.channel.guild.voice;
+ }
+
+ /**
+ * Sends a request to the main gateway to join a voice channel.
+ * @param {Object} [options] The options to provide
+ * @returns {Promise<Shard>}
+ * @private
+ */
+ sendVoiceStateUpdate(options = {}) {
+ options = Util.mergeDefault(
+ {
+ guild_id: this.channel.guild.id,
+ channel_id: this.channel.id,
+ self_mute: this.voice ? this.voice.selfMute : false,
+ self_deaf: this.voice ? this.voice.selfDeaf : false,
+ },
+ options,
+ );
+
+ this.emit('debug', `Sending voice state update: ${JSON.stringify(options)}`);
+
+ return this.channel.guild.shard.send(
+ {
+ op: OPCodes.VOICE_STATE_UPDATE,
+ d: options,
+ },
+ true,
+ );
+ }
+
+ /**
+ * Set the token and endpoint required to connect to the voice servers.
+ * @param {string} token The voice token
+ * @param {string} endpoint The voice endpoint
+ * @private
+ * @returns {void}
+ */
+ setTokenAndEndpoint(token, endpoint) {
+ this.emit('debug', `Token "${token}" and endpoint "${endpoint}"`);
+ if (!endpoint) {
+ // Signifies awaiting endpoint stage
+ return;
+ }
+
+ if (!token) {
+ this.authenticateFailed('VOICE_TOKEN_ABSENT');
+ return;
+ }
+
+ endpoint = endpoint.match(/([^:]*)/)[0];
+ this.emit('debug', `Endpoint resolved as ${endpoint}`);
+
+ if (!endpoint) {
+ this.authenticateFailed('VOICE_INVALID_ENDPOINT');
+ return;
+ }
+
+ if (this.status === VoiceStatus.AUTHENTICATING) {
+ this.authentication.token = token;
+ this.authentication.endpoint = endpoint;
+ this.checkAuthenticated();
+ } else if (token !== this.authentication.token || endpoint !== this.authentication.endpoint) {
+ this.reconnect(token, endpoint);
+ }
+ }
+
+ /**
+ * Sets the Session ID for the connection.
+ * @param {string} sessionID The voice session ID
+ * @private
+ */
+ setSessionID(sessionID) {
+ this.emit('debug', `Setting sessionID ${sessionID} (stored as "${this.authentication.sessionID}")`);
+ if (!sessionID) {
+ this.authenticateFailed('VOICE_SESSION_ABSENT');
+ return;
+ }
+
+ if (this.status === VoiceStatus.AUTHENTICATING) {
+ this.authentication.sessionID = sessionID;
+ this.checkAuthenticated();
+ } else if (sessionID !== this.authentication.sessionID) {
+ this.authentication.sessionID = sessionID;
+ /**
+ * Emitted when a new session ID is received.
+ * @event VoiceConnection#newSession
+ * @private
+ */
+ this.emit('newSession', sessionID);
+ }
+ }
+
+ /**
+ * Checks whether the voice connection is authenticated.
+ * @private
+ */
+ checkAuthenticated() {
+ const { token, endpoint, sessionID } = this.authentication;
+ this.emit('debug', `Authenticated with sessionID ${sessionID}`);
+ if (token && endpoint && sessionID) {
+ this.status = VoiceStatus.CONNECTING;
+ /**
+ * Emitted when we successfully initiate a voice connection.
+ * @event VoiceConnection#authenticated
+ */
+ this.emit('authenticated');
+ this.connect();
+ }
+ }
+
+ /**
+ * Invoked when we fail to initiate a voice connection.
+ * @param {string} reason The reason for failure
+ * @private
+ */
+ authenticateFailed(reason) {
+ this.client.clearTimeout(this.connectTimeout);
+ this.emit('debug', `Authenticate failed - ${reason}`);
+ if (this.status === VoiceStatus.AUTHENTICATING) {
+ /**
+ * Emitted when we fail to initiate a voice connection.
+ * @event VoiceConnection#failed
+ * @param {Error} error The encountered error
+ */
+ this.emit('failed', new Error(reason));
+ } else {
+ /**
+ * Emitted whenever the connection encounters an error.
+ * @event VoiceConnection#error
+ * @param {Error} error The encountered error
+ */
+ this.emit('error', new Error(reason));
+ }
+ this.status = VoiceStatus.DISCONNECTED;
+ }
+
+ /**
+ * Move to a different voice channel in the same guild.
+ * @param {VoiceChannel} channel The channel to move to
+ * @private
+ */
+ updateChannel(channel) {
+ this.channel = channel;
+ this.sendVoiceStateUpdate();
+ }
+
+ /**
+ * Attempts to authenticate to the voice server.
+ * @private
+ */
+ authenticate() {
+ this.sendVoiceStateUpdate();
+ this.connectTimeout = this.client.setTimeout(() => this.authenticateFailed('VOICE_CONNECTION_TIMEOUT'), 15000);
+ }
+
+ /**
+ * Attempts to reconnect to the voice server (typically after a region change).
+ * @param {string} token The voice token
+ * @param {string} endpoint The voice endpoint
+ * @private
+ */
+ reconnect(token, endpoint) {
+ this.authentication.token = token;
+ this.authentication.endpoint = endpoint;
+ this.speaking = new Speaking().freeze();
+ this.status = VoiceStatus.RECONNECTING;
+ this.emit('debug', `Reconnecting to ${endpoint}`);
+ /**
+ * Emitted when the voice connection is reconnecting (typically after a region change).
+ * @event VoiceConnection#reconnecting
+ */
+ this.emit('reconnecting');
+ this.connect();
+ }
+
+ /**
+ * Disconnects the voice connection, causing a disconnect and closing event to be emitted.
+ */
+ disconnect() {
+ this.emit('closing');
+ this.emit('debug', 'disconnect() triggered');
+ this.client.clearTimeout(this.connectTimeout);
+ const conn = this.voiceManager.connections.get(this.channel.guild.id);
+ if (conn === this) this.voiceManager.connections.delete(this.channel.guild.id);
+ this.sendVoiceStateUpdate({
+ channel_id: null,
+ });
+ this._disconnect();
+ }
+
+ /**
+ * Internally disconnects (doesn't send disconnect packet).
+ * @private
+ */
+ _disconnect() {
+ this.cleanup();
+ this.status = VoiceStatus.DISCONNECTED;
+ /**
+ * Emitted when the voice connection disconnects.
+ * @event VoiceConnection#disconnect
+ */
+ this.emit('disconnect');
+ }
+
+ /**
+ * Cleans up after disconnect.
+ * @private
+ */
+ cleanup() {
+ this.player.destroy();
+ this.speaking = new Speaking().freeze();
+ const { ws, udp } = this.sockets;
+
+ this.emit('debug', 'Connection clean up');
+
+ if (ws) {
+ ws.removeAllListeners('error');
+ ws.removeAllListeners('ready');
+ ws.removeAllListeners('sessionDescription');
+ ws.removeAllListeners('speaking');
+ ws.shutdown();
+ }
+
+ if (udp) udp.removeAllListeners('error');
+
+ this.sockets.ws = null;
+ this.sockets.udp = null;
+ }
+
+ /**
+ * Connect the voice connection.
+ * @private
+ */
+ connect() {
+ this.emit('debug', `Connect triggered`);
+ if (this.status !== VoiceStatus.RECONNECTING) {
+ if (this.sockets.ws) throw new Error('WS_CONNECTION_EXISTS');
+ if (this.sockets.udp) throw new Error('UDP_CONNECTION_EXISTS');
+ }
+
+ if (this.sockets.ws) this.sockets.ws.shutdown();
+ if (this.sockets.udp) this.sockets.udp.shutdown();
+
+ this.sockets.ws = new VoiceWebSocket(this);
+ this.sockets.udp = new VoiceUDP(this);
+
+ const { ws, udp } = this.sockets;
+
+ ws.on('debug', msg => this.emit('debug', msg));
+ udp.on('debug', msg => this.emit('debug', msg));
+ ws.on('error', err => this.emit('error', err));
+ udp.on('error', err => this.emit('error', err));
+ ws.on('ready', this.onReady.bind(this));
+ ws.on('sessionDescription', this.onSessionDescription.bind(this));
+ ws.on('startSpeaking', this.onStartSpeaking.bind(this));
+
+ this.sockets.ws.connect();
+ }
+
+ /**
+ * Invoked when the voice websocket is ready.
+ * @param {Object} data The received data
+ * @private
+ */
+ onReady(data) {
+ Object.assign(this.authentication, data);
+ for (let mode of data.modes) {
+ if (SUPPORTED_MODES.includes(mode)) {
+ this.authentication.mode = mode;
+ this.emit('debug', `Selecting the ${mode} mode`);
+ break;
+ }
+ }
+ this.sockets.udp.createUDPSocket(data.ip);
+ }
+
+ /**
+ * Invoked when a session description is received.
+ * @param {Object} data The received data
+ * @private
+ */
+ onSessionDescription(data) {
+ Object.assign(this.authentication, data);
+ this.status = VoiceStatus.CONNECTED;
+ const ready = () => {
+ this.client.clearTimeout(this.connectTimeout);
+ this.emit('debug', `Ready with authentication details: ${JSON.stringify(this.authentication)}`);
+ /**
+ * Emitted once the connection is ready, when a promise to join a voice channel resolves,
+ * the connection will already be ready.
+ * @event VoiceConnection#ready
+ */
+ this.emit('ready');
+ };
+ if (this.dispatcher) {
+ ready();
+ } else {
+ // This serves to provide support for voice receive, sending audio is required to receive it.
+ const dispatcher = this.play(new SingleSilence(), { type: 'opus', volume: false });
+ dispatcher.once('finish', ready);
+ }
+ }
+
+ onStartSpeaking({ user_id, ssrc, speaking }) {
+ this.ssrcMap.set(+ssrc, { userID: user_id, speaking: speaking });
+ }
+
+ /**
+ * Invoked when a speaking event is received.
+ * @param {Object} data The received data
+ * @private
+ */
+ onSpeaking({ user_id, speaking }) {
+ speaking = new Speaking(speaking).freeze();
+ const guild = this.channel.guild;
+ const user = this.client.users.cache.get(user_id);
+ const old = this._speaking.get(user_id);
+ this._speaking.set(user_id, speaking);
+ /**
+ * Emitted whenever a user changes speaking state.
+ * @event VoiceConnection#speaking
+ * @param {User} user The user that has changed speaking state
+ * @param {Readonly<Speaking>} speaking The speaking state of the user
+ */
+ if (this.status === VoiceStatus.CONNECTED) {
+ this.emit('speaking', user, speaking);
+ if (!speaking.has(Speaking.FLAGS.SPEAKING)) {
+ this.receiver.packets._stoppedSpeaking(user_id);
+ }
+ }
+
+ if (guild && user && !speaking.equals(old)) {
+ const member = guild.member(user);
+ if (member) {
+ /**
+ * Emitted once a guild member changes speaking state.
+ * @event Client#guildMemberSpeaking
+ * @param {GuildMember} member The member that started/stopped speaking
+ * @param {Readonly<Speaking>} speaking The speaking state of the member
+ */
+ this.client.emit(Events.GUILD_MEMBER_SPEAKING, member, speaking);
+ }
+ }
+ }
+
+ play() {} // eslint-disable-line no-empty-function
+}
+
+PlayInterface.applyToClass(VoiceConnection);
+
+module.exports = VoiceConnection;
diff --git a/node_modules/discord.js/src/client/voice/dispatcher/BroadcastDispatcher.js b/node_modules/discord.js/src/client/voice/dispatcher/BroadcastDispatcher.js
new file mode 100644
index 0000000..ae8d412
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/dispatcher/BroadcastDispatcher.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const StreamDispatcher = require('./StreamDispatcher');
+
+/**
+ * The class that sends voice packet data to the voice connection.
+ * @implements {VolumeInterface}
+ * @extends {StreamDispatcher}
+ */
+class BroadcastDispatcher extends StreamDispatcher {
+ constructor(player, options, streams) {
+ super(player, options, streams);
+ this.broadcast = player.broadcast;
+ }
+
+ _write(chunk, enc, done) {
+ if (!this.startTime) this.startTime = Date.now();
+ for (const dispatcher of this.broadcast.subscribers) {
+ dispatcher._write(chunk, enc);
+ }
+ this._step(done);
+ }
+
+ _destroy(err, cb) {
+ if (this.player.dispatcher === this) this.player.dispatcher = null;
+ const { streams } = this;
+ if (streams.opus) streams.opus.unpipe(this);
+ if (streams.ffmpeg) streams.ffmpeg.destroy();
+ super._destroy(err, cb);
+ }
+
+ /**
+ * Set the bitrate of the current Opus encoder if using a compatible Opus stream.
+ * @param {number} value New bitrate, in kbps
+ * If set to 'auto', 48kbps will be used
+ * @returns {boolean} true if the bitrate has been successfully changed.
+ */
+ setBitrate(value) {
+ if (!value || !this.streams.opus || !this.streams.opus.setBitrate) return false;
+ const bitrate = value === 'auto' ? 48 : value;
+ this.streams.opus.setBitrate(bitrate * 1000);
+ return true;
+ }
+}
+
+module.exports = BroadcastDispatcher;
diff --git a/node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js b/node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js
new file mode 100644
index 0000000..62c46d3
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js
@@ -0,0 +1,354 @@
+'use strict';
+
+const { Writable } = require('stream');
+const secretbox = require('../util/Secretbox');
+const Silence = require('../util/Silence');
+const VolumeInterface = require('../util/VolumeInterface');
+
+const FRAME_LENGTH = 20;
+const CHANNELS = 2;
+const TIMESTAMP_INC = (48000 / 100) * CHANNELS;
+
+const MAX_NONCE_SIZE = 2 ** 32 - 1;
+const nonce = Buffer.alloc(24);
+
+/**
+ * @external WritableStream
+ * @see {@link https://nodejs.org/api/stream.html#stream_class_stream_writable}
+ */
+
+/**
+ * The class that sends voice packet data to the voice connection.
+ * ```js
+ * // Obtained using:
+ * voiceChannel.join().then(connection => {
+ * // You can play a file or a stream here:
+ * const dispatcher = connection.play('/home/hydrabolt/audio.mp3');
+ * });
+ * ```
+ * @implements {VolumeInterface}
+ * @extends {WritableStream}
+ */
+class StreamDispatcher extends Writable {
+ constructor(player, { seek = 0, volume = 1, fec, plp, bitrate = 96, highWaterMark = 12 } = {}, streams) {
+ const streamOptions = { seek, volume, fec, plp, bitrate, highWaterMark };
+ super(streamOptions);
+ /**
+ * The Audio Player that controls this dispatcher
+ * @type {AudioPlayer}
+ */
+ this.player = player;
+ this.streamOptions = streamOptions;
+ this.streams = streams;
+ this.streams.silence = new Silence();
+
+ this._nonce = 0;
+ this._nonceBuffer = Buffer.alloc(24);
+
+ /**
+ * The time that the stream was paused at (null if not paused)
+ * @type {?number}
+ */
+ this.pausedSince = null;
+ this._writeCallback = null;
+
+ /**
+ * The broadcast controlling this dispatcher, if any
+ * @type {?VoiceBroadcast}
+ */
+ this.broadcast = this.streams.broadcast;
+
+ this._pausedTime = 0;
+ this._silentPausedTime = 0;
+ this.count = 0;
+
+ this.on('finish', () => {
+ this._cleanup();
+ this._setSpeaking(0);
+ });
+
+ this.setVolume(volume);
+ this.setBitrate(bitrate);
+ if (typeof fec !== 'undefined') this.setFEC(fec);
+ if (typeof plp !== 'undefined') this.setPLP(plp);
+
+ const streamError = (type, err) => {
+ /**
+ * Emitted when the dispatcher encounters an error.
+ * @event StreamDispatcher#error
+ */
+ if (type && err) {
+ err.message = `${type} stream: ${err.message}`;
+ this.emit(this.player.dispatcher === this ? 'error' : 'debug', err);
+ }
+ this.destroy();
+ };
+
+ this.on('error', () => streamError());
+ if (this.streams.input) this.streams.input.on('error', err => streamError('input', err));
+ if (this.streams.ffmpeg) this.streams.ffmpeg.on('error', err => streamError('ffmpeg', err));
+ if (this.streams.opus) this.streams.opus.on('error', err => streamError('opus', err));
+ if (this.streams.volume) this.streams.volume.on('error', err => streamError('volume', err));
+ }
+
+ get _sdata() {
+ return this.player.streamingData;
+ }
+
+ _write(chunk, enc, done) {
+ if (!this.startTime) {
+ /**
+ * Emitted once the stream has started to play.
+ * @event StreamDispatcher#start
+ */
+ this.emit('start');
+ this.startTime = Date.now();
+ }
+ this._playChunk(chunk);
+ this._step(done);
+ }
+
+ _destroy(err, cb) {
+ this._cleanup();
+ super._destroy(err, cb);
+ }
+
+ _cleanup() {
+ if (this.player.dispatcher === this) this.player.dispatcher = null;
+ const { streams } = this;
+ if (streams.broadcast) streams.broadcast.delete(this);
+ if (streams.opus) streams.opus.destroy();
+ if (streams.ffmpeg) streams.ffmpeg.destroy();
+ }
+
+ /**
+ * Pauses playback
+ * @param {boolean} [silence=false] Whether to play silence while paused to prevent audio glitches
+ */
+ pause(silence = false) {
+ if (this.paused) return;
+ if (this.streams.opus) this.streams.opus.unpipe(this);
+ if (silence) {
+ this.streams.silence.pipe(this);
+ this._silence = true;
+ } else {
+ this._setSpeaking(0);
+ }
+ this.pausedSince = Date.now();
+ }
+
+ /**
+ * Whether or not playback is paused
+ * @type {boolean}
+ * @readonly
+ */
+ get paused() {
+ return Boolean(this.pausedSince);
+ }
+
+ /**
+ * Total time that this dispatcher has been paused in milliseconds
+ * @type {number}
+ * @readonly
+ */
+ get pausedTime() {
+ return this._silentPausedTime + this._pausedTime + (this.paused ? Date.now() - this.pausedSince : 0);
+ }
+
+ /**
+ * Resumes playback
+ */
+ resume() {
+ if (!this.pausedSince) return;
+ this.streams.silence.unpipe(this);
+ if (this.streams.opus) this.streams.opus.pipe(this);
+ if (this._silence) {
+ this._silentPausedTime += Date.now() - this.pausedSince;
+ this._silence = false;
+ } else {
+ this._pausedTime += Date.now() - this.pausedSince;
+ }
+ this.pausedSince = null;
+ if (typeof this._writeCallback === 'function') this._writeCallback();
+ }
+
+ /**
+ * The time (in milliseconds) that the dispatcher has actually been playing audio for
+ * @type {number}
+ * @readonly
+ */
+ get streamTime() {
+ return this.count * FRAME_LENGTH;
+ }
+
+ /**
+ * The time (in milliseconds) that the dispatcher has been playing audio for, taking into account skips and pauses
+ * @type {number}
+ * @readonly
+ */
+ get totalStreamTime() {
+ return Date.now() - this.startTime;
+ }
+
+ /**
+ * Set the bitrate of the current Opus encoder if using a compatible Opus stream.
+ * @param {number} value New bitrate, in kbps
+ * If set to 'auto', the voice channel's bitrate will be used
+ * @returns {boolean} true if the bitrate has been successfully changed.
+ */
+ setBitrate(value) {
+ if (!value || !this.bitrateEditable) return false;
+ const bitrate = value === 'auto' ? this.player.voiceConnection.channel.bitrate : value;
+ this.streams.opus.setBitrate(bitrate * 1000);
+ return true;
+ }
+
+ /**
+ * Sets the expected packet loss percentage if using a compatible Opus stream.
+ * @param {number} value between 0 and 1
+ * @returns {boolean} Returns true if it was successfully set.
+ */
+ setPLP(value) {
+ if (!this.bitrateEditable) return false;
+ this.streams.opus.setPLP(value);
+ return true;
+ }
+
+ /**
+ * Enables or disables forward error correction if using a compatible Opus stream.
+ * @param {boolean} enabled true to enable
+ * @returns {boolean} Returns true if it was successfully set.
+ */
+ setFEC(enabled) {
+ if (!this.bitrateEditable) return false;
+ this.streams.opus.setFEC(enabled);
+ return true;
+ }
+
+ _step(done) {
+ this._writeCallback = () => {
+ this._writeCallback = null;
+ done();
+ };
+ if (!this.streams.broadcast) {
+ const next = FRAME_LENGTH + this.count * FRAME_LENGTH - (Date.now() - this.startTime - this._pausedTime);
+ setTimeout(() => {
+ if ((!this.pausedSince || this._silence) && this._writeCallback) this._writeCallback();
+ }, next);
+ }
+ this._sdata.sequence++;
+ this._sdata.timestamp += TIMESTAMP_INC;
+ if (this._sdata.sequence >= 2 ** 16) this._sdata.sequence = 0;
+ if (this._sdata.timestamp >= 2 ** 32) this._sdata.timestamp = 0;
+ this.count++;
+ }
+
+ _final(callback) {
+ this._writeCallback = null;
+ callback();
+ }
+
+ _playChunk(chunk) {
+ if (this.player.dispatcher !== this || !this.player.voiceConnection.authentication.secret_key) return;
+ this._sendPacket(this._createPacket(this._sdata.sequence, this._sdata.timestamp, chunk));
+ }
+
+ _encrypt(buffer) {
+ const { secret_key, mode } = this.player.voiceConnection.authentication;
+ if (mode === 'xsalsa20_poly1305_lite') {
+ this._nonce++;
+ if (this._nonce > MAX_NONCE_SIZE) this._nonce = 0;
+ this._nonceBuffer.writeUInt32BE(this._nonce, 0);
+ return [secretbox.methods.close(buffer, this._nonceBuffer, secret_key), this._nonceBuffer.slice(0, 4)];
+ } else if (mode === 'xsalsa20_poly1305_suffix') {
+ const random = secretbox.methods.random(24);
+ return [secretbox.methods.close(buffer, random, secret_key), random];
+ } else {
+ return [secretbox.methods.close(buffer, nonce, secret_key)];
+ }
+ }
+
+ _createPacket(sequence, timestamp, buffer) {
+ const packetBuffer = Buffer.alloc(12);
+ packetBuffer[0] = 0x80;
+ packetBuffer[1] = 0x78;
+
+ packetBuffer.writeUIntBE(sequence, 2, 2);
+ packetBuffer.writeUIntBE(timestamp, 4, 4);
+ packetBuffer.writeUIntBE(this.player.voiceConnection.authentication.ssrc, 8, 4);
+
+ packetBuffer.copy(nonce, 0, 0, 12);
+ return Buffer.concat([packetBuffer, ...this._encrypt(buffer)]);
+ }
+
+ _sendPacket(packet) {
+ /**
+ * Emitted whenever the dispatcher has debug information.
+ * @event StreamDispatcher#debug
+ * @param {string} info The debug info
+ */
+ this._setSpeaking(1);
+ if (!this.player.voiceConnection.sockets.udp) {
+ this.emit('debug', 'Failed to send a packet - no UDP socket');
+ return;
+ }
+ this.player.voiceConnection.sockets.udp.send(packet).catch(e => {
+ this._setSpeaking(0);
+ this.emit('debug', `Failed to send a packet - ${e}`);
+ });
+ }
+
+ _setSpeaking(value) {
+ if (typeof this.player.voiceConnection !== 'undefined') {
+ this.player.voiceConnection.setSpeaking(value);
+ }
+ /**
+ * Emitted when the dispatcher starts/stops speaking.
+ * @event StreamDispatcher#speaking
+ * @param {boolean} value Whether or not the dispatcher is speaking
+ */
+ this.emit('speaking', value);
+ }
+
+ get volumeEditable() {
+ return Boolean(this.streams.volume);
+ }
+
+ /**
+ * Whether or not the Opus bitrate of this stream is editable
+ * @type {boolean}
+ * @readonly
+ */
+ get bitrateEditable() {
+ return this.streams.opus && this.streams.opus.setBitrate;
+ }
+
+ // Volume
+ get volume() {
+ return this.streams.volume ? this.streams.volume.volume : 1;
+ }
+
+ setVolume(value) {
+ if (!this.streams.volume) return false;
+ /**
+ * Emitted when the volume of this dispatcher changes.
+ * @event StreamDispatcher#volumeChange
+ * @param {number} oldVolume The old volume of this dispatcher
+ * @param {number} newVolume The new volume of this dispatcher
+ */
+ this.emit('volumeChange', this.volume, value);
+ this.streams.volume.setVolume(value);
+ return true;
+ }
+
+ // Volume stubs for docs
+ /* eslint-disable no-empty-function*/
+ get volumeDecibels() {}
+ get volumeLogarithmic() {}
+ setVolumeDecibels() {}
+ setVolumeLogarithmic() {}
+}
+
+VolumeInterface.applyToClass(StreamDispatcher);
+
+module.exports = StreamDispatcher;
diff --git a/node_modules/discord.js/src/client/voice/networking/VoiceUDPClient.js b/node_modules/discord.js/src/client/voice/networking/VoiceUDPClient.js
new file mode 100644
index 0000000..b86428a
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/networking/VoiceUDPClient.js
@@ -0,0 +1,154 @@
+'use strict';
+
+const udp = require('dgram');
+const EventEmitter = require('events');
+const { Error } = require('../../../errors');
+const { VoiceOPCodes } = require('../../../util/Constants');
+
+/**
+ * Represents a UDP client for a Voice Connection.
+ * @extends {EventEmitter}
+ * @private
+ */
+class VoiceConnectionUDPClient extends EventEmitter {
+ constructor(voiceConnection) {
+ super();
+
+ /**
+ * The voice connection that this UDP client serves
+ * @type {VoiceConnection}
+ */
+ this.voiceConnection = voiceConnection;
+
+ /**
+ * The UDP socket
+ * @type {?Socket}
+ */
+ this.socket = null;
+
+ /**
+ * The address of the Discord voice server
+ * @type {?string}
+ */
+ this.discordAddress = null;
+
+ /**
+ * The local IP address
+ * @type {?string}
+ */
+ this.localAddress = null;
+
+ /**
+ * The local port
+ * @type {?string}
+ */
+ this.localPort = null;
+
+ this.voiceConnection.on('closing', this.shutdown.bind(this));
+ }
+
+ shutdown() {
+ this.emit('debug', `[UDP] shutdown requested`);
+ if (this.socket) {
+ this.socket.removeAllListeners('message');
+ try {
+ this.socket.close();
+ } finally {
+ this.socket = null;
+ }
+ }
+ }
+
+ /**
+ * The port of the Discord voice server
+ * @type {number}
+ * @readonly
+ */
+ get discordPort() {
+ return this.voiceConnection.authentication.port;
+ }
+
+ /**
+ * Send a packet to the UDP client.
+ * @param {Object} packet The packet to send
+ * @returns {Promise<Object>}
+ */
+ send(packet) {
+ return new Promise((resolve, reject) => {
+ if (!this.socket) throw new Error('UDP_SEND_FAIL');
+ if (!this.discordAddress || !this.discordPort) throw new Error('UDP_ADDRESS_MALFORMED');
+ this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => {
+ if (error) {
+ this.emit('debug', `[UDP] >> ERROR: ${error}`);
+ reject(error);
+ } else {
+ resolve(packet);
+ }
+ });
+ });
+ }
+
+ async createUDPSocket(address) {
+ this.discordAddress = address;
+ const socket = (this.socket = udp.createSocket('udp4'));
+ socket.on('error', e => {
+ this.emit('debug', `[UDP] Error: ${e}`);
+ this.emit('error', e);
+ });
+ socket.on('close', () => {
+ this.emit('debug', '[UDP] socket closed');
+ });
+ this.emit('debug', `[UDP] created socket`);
+ socket.once('message', message => {
+ this.emit('debug', `[UDP] message: [${[...message]}] (${message})`);
+ // Stop if the sockets have been deleted because the connection has been closed already
+ if (!this.voiceConnection.sockets.ws) return;
+
+ const packet = parseLocalPacket(message);
+ if (packet.error) {
+ this.emit('debug', `[UDP] ERROR: ${packet.error}`);
+ this.emit('error', packet.error);
+ return;
+ }
+
+ this.localAddress = packet.address;
+ this.localPort = packet.port;
+
+ this.voiceConnection.sockets.ws.sendPacket({
+ op: VoiceOPCodes.SELECT_PROTOCOL,
+ d: {
+ protocol: 'udp',
+ data: {
+ address: packet.address,
+ port: packet.port,
+ mode: this.voiceConnection.authentication.mode,
+ },
+ },
+ });
+
+ this.emit('debug', `[UDP] << ${JSON.stringify(packet)}`);
+
+ socket.on('message', buffer => this.voiceConnection.receiver.packets.push(buffer));
+ });
+
+ const blankMessage = Buffer.alloc(70);
+ blankMessage.writeUIntBE(this.voiceConnection.authentication.ssrc, 0, 4);
+ this.emit('debug', `Sending IP discovery packet: [${[...blankMessage]}]`);
+ await this.send(blankMessage);
+ this.emit('debug', `Successfully sent IP discovery packet`);
+ }
+}
+
+function parseLocalPacket(message) {
+ try {
+ const packet = Buffer.from(message);
+ let address = '';
+ for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]);
+ const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
+ return { address, port };
+ } catch (error) {
+ return { error };
+ }
+}
+
+module.exports = VoiceConnectionUDPClient;
diff --git a/node_modules/discord.js/src/client/voice/networking/VoiceWebSocket.js b/node_modules/discord.js/src/client/voice/networking/VoiceWebSocket.js
new file mode 100644
index 0000000..efc97af
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/networking/VoiceWebSocket.js
@@ -0,0 +1,264 @@
+'use strict';
+
+const EventEmitter = require('events');
+const WebSocket = require('../../../WebSocket');
+const { Error } = require('../../../errors');
+const { OPCodes, VoiceOPCodes } = require('../../../util/Constants');
+
+/**
+ * Represents a Voice Connection's WebSocket.
+ * @extends {EventEmitter}
+ * @private
+ */
+class VoiceWebSocket extends EventEmitter {
+ constructor(connection) {
+ super();
+ /**
+ * The Voice Connection that this WebSocket serves
+ * @type {VoiceConnection}
+ */
+ this.connection = connection;
+
+ /**
+ * How many connection attempts have been made
+ * @type {number}
+ */
+ this.attempts = 0;
+
+ this.dead = false;
+ this.connection.on('closing', this.shutdown.bind(this));
+ }
+
+ /**
+ * The client of this voice WebSocket
+ * @type {Client}
+ * @readonly
+ */
+ get client() {
+ return this.connection.client;
+ }
+
+ shutdown() {
+ this.emit('debug', `[WS] shutdown requested`);
+ this.dead = true;
+ this.reset();
+ }
+
+ /**
+ * Resets the current WebSocket.
+ */
+ reset() {
+ this.emit('debug', `[WS] reset requested`);
+ if (this.ws) {
+ if (this.ws.readyState !== WebSocket.CLOSED) this.ws.close();
+ this.ws = null;
+ }
+ this.clearHeartbeat();
+ }
+
+ /**
+ * Starts connecting to the Voice WebSocket Server.
+ */
+ connect() {
+ this.emit('debug', `[WS] connect requested`);
+ if (this.dead) return;
+ if (this.ws) this.reset();
+ if (this.attempts >= 5) {
+ this.emit('debug', new Error('VOICE_CONNECTION_ATTEMPTS_EXCEEDED', this.attempts));
+ return;
+ }
+
+ this.attempts++;
+
+ /**
+ * The actual WebSocket used to connect to the Voice WebSocket Server.
+ * @type {WebSocket}
+ */
+ this.ws = WebSocket.create(`wss://${this.connection.authentication.endpoint}/`, { v: 4 });
+ this.emit('debug', `[WS] connecting, ${this.attempts} attempts, ${this.ws.url}`);
+ this.ws.onopen = this.onOpen.bind(this);
+ this.ws.onmessage = this.onMessage.bind(this);
+ this.ws.onclose = this.onClose.bind(this);
+ this.ws.onerror = this.onError.bind(this);
+ }
+
+ /**
+ * Sends data to the WebSocket if it is open.
+ * @param {string} data The data to send to the WebSocket
+ * @returns {Promise<string>}
+ */
+ send(data) {
+ this.emit('debug', `[WS] >> ${data}`);
+ return new Promise((resolve, reject) => {
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) throw new Error('WS_NOT_OPEN', data);
+ this.ws.send(data, null, error => {
+ if (error) reject(error);
+ else resolve(data);
+ });
+ });
+ }
+
+ /**
+ * JSON.stringify's a packet and then sends it to the WebSocket Server.
+ * @param {Object} packet The packet to send
+ * @returns {Promise<string>}
+ */
+ sendPacket(packet) {
+ try {
+ packet = JSON.stringify(packet);
+ } catch (error) {
+ return Promise.reject(error);
+ }
+ return this.send(packet);
+ }
+
+ /**
+ * Called whenever the WebSocket opens.
+ */
+ onOpen() {
+ this.emit('debug', `[WS] opened at gateway ${this.connection.authentication.endpoint}`);
+ this.sendPacket({
+ op: OPCodes.DISPATCH,
+ d: {
+ server_id: this.connection.channel.guild.id,
+ user_id: this.client.user.id,
+ token: this.connection.authentication.token,
+ session_id: this.connection.authentication.sessionID,
+ },
+ }).catch(() => {
+ this.emit('error', new Error('VOICE_JOIN_SOCKET_CLOSED'));
+ });
+ }
+
+ /**
+ * Called whenever a message is received from the WebSocket.
+ * @param {MessageEvent} event The message event that was received
+ * @returns {void}
+ */
+ onMessage(event) {
+ try {
+ return this.onPacket(WebSocket.unpack(event.data, 'json'));
+ } catch (error) {
+ return this.onError(error);
+ }
+ }
+
+ /**
+ * Called whenever the connection to the WebSocket server is lost.
+ */
+ onClose() {
+ this.emit('debug', `[WS] closed`);
+ if (!this.dead) this.client.setTimeout(this.connect.bind(this), this.attempts * 1000);
+ }
+
+ /**
+ * Called whenever an error occurs with the WebSocket.
+ * @param {Error} error The error that occurred
+ */
+ onError(error) {
+ this.emit('debug', `[WS] Error: ${error}`);
+ this.emit('error', error);
+ }
+
+ /**
+ * Called whenever a valid packet is received from the WebSocket.
+ * @param {Object} packet The received packet
+ */
+ onPacket(packet) {
+ this.emit('debug', `[WS] << ${JSON.stringify(packet)}`);
+ switch (packet.op) {
+ case VoiceOPCodes.HELLO:
+ this.setHeartbeat(packet.d.heartbeat_interval);
+ break;
+ case VoiceOPCodes.READY:
+ /**
+ * Emitted once the voice WebSocket receives the ready packet.
+ * @param {Object} packet The received packet
+ * @event VoiceWebSocket#ready
+ */
+ this.emit('ready', packet.d);
+ break;
+ /* eslint-disable no-case-declarations */
+ case VoiceOPCodes.SESSION_DESCRIPTION:
+ packet.d.secret_key = new Uint8Array(packet.d.secret_key);
+ /**
+ * Emitted once the Voice Websocket receives a description of this voice session.
+ * @param {Object} packet The received packet
+ * @event VoiceWebSocket#sessionDescription
+ */
+ this.emit('sessionDescription', packet.d);
+ break;
+ case VoiceOPCodes.CLIENT_CONNECT:
+ this.connection.ssrcMap.set(+packet.d.audio_ssrc, packet.d.user_id);
+ break;
+ case VoiceOPCodes.CLIENT_DISCONNECT:
+ const streamInfo = this.connection.receiver && this.connection.receiver.packets.streams.get(packet.d.user_id);
+ if (streamInfo) {
+ this.connection.receiver.packets.streams.delete(packet.d.user_id);
+ streamInfo.stream.push(null);
+ }
+ break;
+ case VoiceOPCodes.SPEAKING:
+ /**
+ * Emitted whenever a speaking packet is received.
+ * @param {Object} data
+ * @event VoiceWebSocket#startSpeaking
+ */
+ this.emit('startSpeaking', packet.d);
+ break;
+ default:
+ /**
+ * Emitted when an unhandled packet is received.
+ * @param {Object} packet
+ * @event VoiceWebSocket#unknownPacket
+ */
+ this.emit('unknownPacket', packet);
+ break;
+ }
+ }
+
+ /**
+ * Sets an interval at which to send a heartbeat packet to the WebSocket.
+ * @param {number} interval The interval at which to send a heartbeat packet
+ */
+ setHeartbeat(interval) {
+ if (!interval || isNaN(interval)) {
+ this.onError(new Error('VOICE_INVALID_HEARTBEAT'));
+ return;
+ }
+ if (this.heartbeatInterval) {
+ /**
+ * Emitted whenever the voice WebSocket encounters a non-fatal error.
+ * @param {string} warn The warning
+ * @event VoiceWebSocket#warn
+ */
+ this.emit('warn', 'A voice heartbeat interval is being overwritten');
+ this.client.clearInterval(this.heartbeatInterval);
+ }
+ this.heartbeatInterval = this.client.setInterval(this.sendHeartbeat.bind(this), interval);
+ }
+
+ /**
+ * Clears a heartbeat interval, if one exists.
+ */
+ clearHeartbeat() {
+ if (!this.heartbeatInterval) {
+ this.emit('warn', 'Tried to clear a heartbeat interval that does not exist');
+ return;
+ }
+ this.client.clearInterval(this.heartbeatInterval);
+ this.heartbeatInterval = null;
+ }
+
+ /**
+ * Sends a heartbeat packet.
+ */
+ sendHeartbeat() {
+ this.sendPacket({ op: VoiceOPCodes.HEARTBEAT, d: Math.floor(Math.random() * 10e10) }).catch(() => {
+ this.emit('warn', 'Tried to send heartbeat, but connection is not open');
+ this.clearHeartbeat();
+ });
+ }
+}
+
+module.exports = VoiceWebSocket;
diff --git a/node_modules/discord.js/src/client/voice/player/AudioPlayer.js b/node_modules/discord.js/src/client/voice/player/AudioPlayer.js
new file mode 100644
index 0000000..6f719a7
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/player/AudioPlayer.js
@@ -0,0 +1,27 @@
+'use strict';
+
+const BasePlayer = require('./BasePlayer');
+
+/**
+ * An Audio Player for a Voice Connection.
+ * @private
+ * @extends {BasePlayer}
+ */
+class AudioPlayer extends BasePlayer {
+ constructor(voiceConnection) {
+ super();
+ /**
+ * The voice connection that the player serves
+ * @type {VoiceConnection}
+ */
+ this.voiceConnection = voiceConnection;
+ }
+
+ playBroadcast(broadcast, options) {
+ const dispatcher = this.createDispatcher(options, { broadcast });
+ broadcast.add(dispatcher);
+ return dispatcher;
+ }
+}
+
+module.exports = AudioPlayer;
diff --git a/node_modules/discord.js/src/client/voice/player/BasePlayer.js b/node_modules/discord.js/src/client/voice/player/BasePlayer.js
new file mode 100644
index 0000000..b968f82
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/player/BasePlayer.js
@@ -0,0 +1,92 @@
+'use strict';
+
+const EventEmitter = require('events');
+const { Readable: ReadableStream } = require('stream');
+const prism = require('prism-media');
+const StreamDispatcher = require('../dispatcher/StreamDispatcher');
+
+const FFMPEG_ARGUMENTS = ['-analyzeduration', '0', '-loglevel', '0', '-f', 's16le', '-ar', '48000', '-ac', '2'];
+
+/**
+ * An Audio Player for a Voice Connection.
+ * @private
+ * @extends {EventEmitter}
+ */
+class BasePlayer extends EventEmitter {
+ constructor() {
+ super();
+
+ this.dispatcher = null;
+
+ this.streamingData = {
+ channels: 2,
+ sequence: 0,
+ timestamp: 0,
+ };
+ }
+
+ destroy() {
+ this.destroyDispatcher();
+ }
+
+ destroyDispatcher() {
+ if (this.dispatcher) {
+ this.dispatcher.destroy();
+ this.dispatcher = null;
+ }
+ }
+
+ playUnknown(input, options) {
+ this.destroyDispatcher();
+
+ const isStream = input instanceof ReadableStream;
+
+ const args = isStream ? FFMPEG_ARGUMENTS.slice() : ['-i', input, ...FFMPEG_ARGUMENTS];
+ if (options.seek) args.unshift('-ss', String(options.seek));
+
+ const ffmpeg = new prism.FFmpeg({ args });
+ const streams = { ffmpeg };
+ if (isStream) {
+ streams.input = input;
+ input.pipe(ffmpeg);
+ }
+ return this.playPCMStream(ffmpeg, options, streams);
+ }
+
+ playPCMStream(stream, options, streams = {}) {
+ this.destroyDispatcher();
+ const opus = (streams.opus = new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 }));
+ if (options && options.volume === false) {
+ stream.pipe(opus);
+ return this.playOpusStream(opus, options, streams);
+ }
+ streams.volume = new prism.VolumeTransformer({ type: 's16le', volume: options ? options.volume : 1 });
+ stream.pipe(streams.volume).pipe(opus);
+ return this.playOpusStream(opus, options, streams);
+ }
+
+ playOpusStream(stream, options, streams = {}) {
+ this.destroyDispatcher();
+ streams.opus = stream;
+ if (options.volume !== false && !streams.input) {
+ streams.input = stream;
+ const decoder = new prism.opus.Decoder({ channels: 2, rate: 48000, frameSize: 960 });
+ streams.volume = new prism.VolumeTransformer({ type: 's16le', volume: options ? options.volume : 1 });
+ streams.opus = stream
+ .pipe(decoder)
+ .pipe(streams.volume)
+ .pipe(new prism.opus.Encoder({ channels: 2, rate: 48000, frameSize: 960 }));
+ }
+ const dispatcher = this.createDispatcher(options, streams);
+ streams.opus.pipe(dispatcher);
+ return dispatcher;
+ }
+
+ createDispatcher(options, streams, broadcast) {
+ this.destroyDispatcher();
+ const dispatcher = (this.dispatcher = new StreamDispatcher(this, options, streams, broadcast));
+ return dispatcher;
+ }
+}
+
+module.exports = BasePlayer;
diff --git a/node_modules/discord.js/src/client/voice/player/BroadcastAudioPlayer.js b/node_modules/discord.js/src/client/voice/player/BroadcastAudioPlayer.js
new file mode 100644
index 0000000..05197a4
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/player/BroadcastAudioPlayer.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const BasePlayer = require('./BasePlayer');
+const BroadcastDispatcher = require('../dispatcher/BroadcastDispatcher');
+
+/**
+ * An Audio Player for a Voice Connection.
+ * @private
+ * @extends {BasePlayer}
+ */
+class AudioPlayer extends BasePlayer {
+ constructor(broadcast) {
+ super();
+ /**
+ * The broadcast that the player serves
+ * @type {VoiceBroadcast}
+ */
+ this.broadcast = broadcast;
+ }
+
+ createDispatcher(options, streams) {
+ this.destroyDispatcher();
+ const dispatcher = (this.dispatcher = new BroadcastDispatcher(this, options, streams));
+ return dispatcher;
+ }
+}
+
+module.exports = AudioPlayer;
diff --git a/node_modules/discord.js/src/client/voice/receiver/PacketHandler.js b/node_modules/discord.js/src/client/voice/receiver/PacketHandler.js
new file mode 100644
index 0000000..c441c5e
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/receiver/PacketHandler.js
@@ -0,0 +1,116 @@
+'use strict';
+
+const EventEmitter = require('events');
+const secretbox = require('../util/Secretbox');
+
+// The delay between packets when a user is considered to have stopped speaking
+// https://github.com/discordjs/discord.js/issues/3524#issuecomment-540373200
+const DISCORD_SPEAKING_DELAY = 250;
+
+class Readable extends require('stream').Readable {
+ _read() {} // eslint-disable-line no-empty-function
+}
+
+class PacketHandler extends EventEmitter {
+ constructor(receiver) {
+ super();
+ this.nonce = Buffer.alloc(24);
+ this.receiver = receiver;
+ this.streams = new Map();
+ this.speakingTimeouts = new Map();
+ }
+
+ get connection() {
+ return this.receiver.connection;
+ }
+
+ _stoppedSpeaking(userID) {
+ const streamInfo = this.streams.get(userID);
+ if (streamInfo && streamInfo.end === 'silence') {
+ this.streams.delete(userID);
+ streamInfo.stream.push(null);
+ }
+ }
+
+ makeStream(user, end) {
+ if (this.streams.has(user)) return this.streams.get(user).stream;
+ const stream = new Readable();
+ stream.on('end', () => this.streams.delete(user));
+ this.streams.set(user, { stream, end });
+ return stream;
+ }
+
+ parseBuffer(buffer) {
+ const { secret_key, mode } = this.receiver.connection.authentication;
+
+ // Choose correct nonce depending on encryption
+ let end;
+ if (mode === 'xsalsa20_poly1305_lite') {
+ buffer.copy(this.nonce, 0, buffer.length - 4);
+ end = buffer.length - 4;
+ } else if (mode === 'xsalsa20_poly1305_suffix') {
+ buffer.copy(this.nonce, 0, buffer.length - 24);
+ end = buffer.length - 24;
+ } else {
+ buffer.copy(this.nonce, 0, 0, 12);
+ }
+
+ // Open packet
+ let packet = secretbox.methods.open(buffer.slice(12, end), this.nonce, secret_key);
+ if (!packet) return new Error('Failed to decrypt voice packet');
+ packet = Buffer.from(packet);
+
+ // Strip RTP Header Extensions (one-byte only)
+ if (packet[0] === 0xbe && packet[1] === 0xde && packet.length > 4) {
+ const headerExtensionLength = packet.readUInt16BE(2);
+ let offset = 4;
+ for (let i = 0; i < headerExtensionLength; i++) {
+ const byte = packet[offset];
+ offset++;
+ if (byte === 0) continue;
+ offset += 1 + (0b1111 & (byte >> 4));
+ }
+ // Skip over undocumented Discord byte
+ offset++;
+
+ packet = packet.slice(offset);
+ }
+
+ return packet;
+ }
+
+ push(buffer) {
+ const ssrc = buffer.readUInt32BE(8);
+ const userStat = this.connection.ssrcMap.get(ssrc);
+ if (!userStat) return;
+
+ let speakingTimeout = this.speakingTimeouts.get(ssrc);
+ if (typeof speakingTimeout === 'undefined') {
+ this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: userStat.speaking });
+ speakingTimeout = this.receiver.connection.client.setTimeout(() => {
+ try {
+ this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: 0 });
+ this.receiver.connection.client.clearTimeout(speakingTimeout);
+ this.speakingTimeouts.delete(ssrc);
+ } catch {
+ // Connection already closed, ignore
+ }
+ }, DISCORD_SPEAKING_DELAY);
+ this.speakingTimeouts.set(ssrc, speakingTimeout);
+ } else {
+ speakingTimeout.refresh();
+ }
+
+ let stream = this.streams.get(userStat.userID);
+ if (!stream) return;
+ stream = stream.stream;
+ const opusPacket = this.parseBuffer(buffer);
+ if (opusPacket instanceof Error) {
+ this.emit('error', opusPacket);
+ return;
+ }
+ stream.push(opusPacket);
+ }
+}
+
+module.exports = PacketHandler;
diff --git a/node_modules/discord.js/src/client/voice/receiver/Receiver.js b/node_modules/discord.js/src/client/voice/receiver/Receiver.js
new file mode 100644
index 0000000..605d992
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/receiver/Receiver.js
@@ -0,0 +1,58 @@
+'use strict';
+
+const EventEmitter = require('events');
+const prism = require('prism-media');
+const PacketHandler = require('./PacketHandler');
+const { Error } = require('../../../errors');
+
+/**
+ * Receives audio packets from a voice connection.
+ * @example
+ * const receiver = connection.createReceiver();
+ * // opusStream is a ReadableStream - that means you could play it back to a voice channel if you wanted to!
+ * const opusStream = receiver.createStream(user);
+ */
+class VoiceReceiver extends EventEmitter {
+ constructor(connection) {
+ super();
+ this.connection = connection;
+ this.packets = new PacketHandler(this);
+ /**
+ * Emitted whenever there is a warning
+ * @event VoiceReceiver#debug
+ * @param {Error|string} error The error or message to debug
+ */
+ this.packets.on('error', err => this.emit('debug', err));
+ }
+
+ /**
+ * Options passed to `VoiceReceiver#createStream`.
+ * @typedef {Object} ReceiveStreamOptions
+ * @property {string} [mode='opus'] The mode for audio output. This defaults to opus, meaning discord.js won't decode
+ * the packets for you. You can set this to 'pcm' so that the stream's output will be 16-bit little-endian stereo
+ * audio
+ * @property {string} [end='silence'] When the stream should be destroyed. If `silence`, this will be when the user
+ * stops talking. Otherwise, if `manual`, this should be handled by you.
+ */
+
+ /**
+ * Creates a new audio receiving stream. If a stream already exists for a user, then that stream will be returned
+ * rather than generating a new one.
+ * @param {UserResolvable} user The user to start listening to.
+ * @param {ReceiveStreamOptions} options Options.
+ * @returns {ReadableStream}
+ */
+ createStream(user, { mode = 'opus', end = 'silence' } = {}) {
+ user = this.connection.client.users.resolve(user);
+ if (!user) throw new Error('VOICE_USER_MISSING');
+ const stream = this.packets.makeStream(user.id, end);
+ if (mode === 'pcm') {
+ const decoder = new prism.opus.Decoder({ channels: 2, rate: 48000, frameSize: 960 });
+ stream.pipe(decoder);
+ return decoder;
+ }
+ return stream;
+ }
+}
+
+module.exports = VoiceReceiver;
diff --git a/node_modules/discord.js/src/client/voice/util/PlayInterface.js b/node_modules/discord.js/src/client/voice/util/PlayInterface.js
new file mode 100644
index 0000000..9478ee8
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/util/PlayInterface.js
@@ -0,0 +1,94 @@
+'use strict';
+
+const { Readable } = require('stream');
+const prism = require('prism-media');
+const { Error } = require('../../../errors');
+
+/**
+ * Options that can be passed to stream-playing methods:
+ * @typedef {Object} StreamOptions
+ * @property {StreamType} [type='unknown'] The type of stream.
+ * @property {number} [seek=0] The time to seek to, will be ignored when playing `ogg/opus` or `webm/opus` streams
+ * @property {number|boolean} [volume=1] The volume to play at. Set this to false to disable volume transforms for
+ * this stream to improve performance.
+ * @property {number} [plp] Expected packet loss percentage
+ * @property {boolean} [fec] Enabled forward error correction
+ * @property {number|string} [bitrate=96] The bitrate (quality) of the audio in kbps.
+ * If set to 'auto', the voice channel's bitrate will be used
+ * @property {number} [highWaterMark=12] The maximum number of opus packets to make and store before they are
+ * actually needed. See https://nodejs.org/en/docs/guides/backpressuring-in-streams/. Setting this value to
+ * 1 means that changes in volume will be more instant.
+ */
+
+/**
+ * An option passed as part of `StreamOptions` specifying the type of the stream.
+ * * `unknown`: The default type, streams/input will be passed through to ffmpeg before encoding.
+ * Will play most streams.
+ * * `converted`: Play a stream of 16bit signed stereo PCM data, skipping ffmpeg.
+ * * `opus`: Play a stream of opus packets, skipping ffmpeg. You lose the ability to alter volume.
+ * * `ogg/opus`: Play an ogg file with the opus encoding, skipping ffmpeg. You lose the ability to alter volume.
+ * * `webm/opus`: Play a webm file with opus audio, skipping ffmpeg. You lose the ability to alter volume.
+ * @typedef {string} StreamType
+ */
+
+/**
+ * An interface class to allow you to play audio over VoiceConnections and VoiceBroadcasts.
+ */
+class PlayInterface {
+ constructor(player) {
+ this.player = player;
+ }
+
+ /**
+ * Play an audio resource.
+ * @param {VoiceBroadcast|ReadableStream|string} resource The resource to play.
+ * @param {StreamOptions} [options] The options to play.
+ * @example
+ * // Play a local audio file
+ * connection.play('/home/hydrabolt/audio.mp3', { volume: 0.5 });
+ * @example
+ * // Play a ReadableStream
+ * connection.play(ytdl('https://www.youtube.com/watch?v=ZlAU_w7-Xp8', { quality: 'highestaudio' }));
+ * @example
+ * // Play a voice broadcast
+ * const broadcast = client.voice.createBroadcast();
+ * broadcast.play('/home/hydrabolt/audio.mp3');
+ * connection.play(broadcast);
+ * @example
+ * // Using different protocols: https://ffmpeg.org/ffmpeg-protocols.html
+ * connection.play('http://www.sample-videos.com/audio/mp3/wave.mp3');
+ * @returns {StreamDispatcher}
+ */
+ play(resource, options = {}) {
+ const VoiceBroadcast = require('../VoiceBroadcast');
+ if (resource instanceof VoiceBroadcast) {
+ if (!this.player.playBroadcast) throw new Error('VOICE_PLAY_INTERFACE_NO_BROADCAST');
+ return this.player.playBroadcast(resource, options);
+ }
+ if (resource instanceof Readable || typeof resource === 'string') {
+ const type = options.type || 'unknown';
+ if (type === 'unknown') {
+ return this.player.playUnknown(resource, options);
+ } else if (type === 'converted') {
+ return this.player.playPCMStream(resource, options);
+ } else if (type === 'opus') {
+ return this.player.playOpusStream(resource, options);
+ } else if (type === 'ogg/opus') {
+ if (!(resource instanceof Readable)) throw new Error('VOICE_PRISM_DEMUXERS_NEED_STREAM');
+ return this.player.playOpusStream(resource.pipe(new prism.opus.OggDemuxer()), options);
+ } else if (type === 'webm/opus') {
+ if (!(resource instanceof Readable)) throw new Error('VOICE_PRISM_DEMUXERS_NEED_STREAM');
+ return this.player.playOpusStream(resource.pipe(new prism.opus.WebmDemuxer()), options);
+ }
+ }
+ throw new Error('VOICE_PLAY_INTERFACE_BAD_TYPE');
+ }
+
+ static applyToClass(structure) {
+ for (const prop of ['play']) {
+ Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(PlayInterface.prototype, prop));
+ }
+ }
+}
+
+module.exports = PlayInterface;
diff --git a/node_modules/discord.js/src/client/voice/util/Secretbox.js b/node_modules/discord.js/src/client/voice/util/Secretbox.js
new file mode 100644
index 0000000..c16a435
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/util/Secretbox.js
@@ -0,0 +1,32 @@
+'use strict';
+
+const libs = {
+ sodium: sodium => ({
+ open: sodium.api.crypto_secretbox_open_easy,
+ close: sodium.api.crypto_secretbox_easy,
+ random: n => sodium.randombytes_buf(n),
+ }),
+ 'libsodium-wrappers': sodium => ({
+ open: sodium.crypto_secretbox_open_easy,
+ close: sodium.crypto_secretbox_easy,
+ random: n => sodium.randombytes_buf(n),
+ }),
+ tweetnacl: tweetnacl => ({
+ open: tweetnacl.secretbox.open,
+ close: tweetnacl.secretbox,
+ random: n => tweetnacl.randomBytes(n),
+ }),
+};
+
+exports.methods = {};
+
+(async () => {
+ for (const libName of Object.keys(libs)) {
+ try {
+ const lib = require(libName);
+ if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready; // eslint-disable-line no-await-in-loop
+ exports.methods = libs[libName](lib);
+ break;
+ } catch {} // eslint-disable-line no-empty
+ }
+})();
diff --git a/node_modules/discord.js/src/client/voice/util/Silence.js b/node_modules/discord.js/src/client/voice/util/Silence.js
new file mode 100644
index 0000000..9bea3d0
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/util/Silence.js
@@ -0,0 +1,13 @@
+'use strict';
+
+const { Readable } = require('stream');
+
+const SILENCE_FRAME = Buffer.from([0xf8, 0xff, 0xfe]);
+
+class Silence extends Readable {
+ _read() {
+ this.push(SILENCE_FRAME);
+ }
+}
+
+module.exports = Silence;
diff --git a/node_modules/discord.js/src/client/voice/util/VolumeInterface.js b/node_modules/discord.js/src/client/voice/util/VolumeInterface.js
new file mode 100644
index 0000000..0dca04f
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/util/VolumeInterface.js
@@ -0,0 +1,103 @@
+'use strict';
+
+const EventEmitter = require('events');
+
+/**
+ * An interface class for volume transformation.
+ * @extends {EventEmitter}
+ */
+class VolumeInterface extends EventEmitter {
+ constructor({ volume = 1 } = {}) {
+ super();
+ this.setVolume(volume);
+ }
+
+ /**
+ * Whether or not the volume of this stream is editable
+ * @type {boolean}
+ * @readonly
+ */
+ get volumeEditable() {
+ return true;
+ }
+
+ /**
+ * The current volume of the stream
+ * @type {number}
+ * @readonly
+ */
+ get volume() {
+ return this._volume;
+ }
+
+ /**
+ * The current volume of the stream in decibels
+ * @type {number}
+ * @readonly
+ */
+ get volumeDecibels() {
+ return Math.log10(this.volume) * 20;
+ }
+
+ /**
+ * The current volume of the stream from a logarithmic scale
+ * @type {number}
+ * @readonly
+ */
+ get volumeLogarithmic() {
+ return Math.pow(this.volume, 1 / 1.660964);
+ }
+
+ applyVolume(buffer, volume) {
+ volume = volume || this._volume;
+ if (volume === 1) return buffer;
+
+ const out = Buffer.alloc(buffer.length);
+ for (let i = 0; i < buffer.length; i += 2) {
+ if (i >= buffer.length - 1) break;
+ const uint = Math.min(32767, Math.max(-32767, Math.floor(volume * buffer.readInt16LE(i))));
+ out.writeInt16LE(uint, i);
+ }
+
+ return out;
+ }
+
+ /**
+ * Sets the volume relative to the input stream - i.e. 1 is normal, 0.5 is half, 2 is double.
+ * @param {number} volume The volume that you want to set
+ */
+ setVolume(volume) {
+ /**
+ * Emitted when the volume of this interface changes.
+ * @event VolumeInterface#volumeChange
+ * @param {number} oldVolume The old volume of this interface
+ * @param {number} newVolume The new volume of this interface
+ */
+ this.emit('volumeChange', this._volume, volume);
+ this._volume = volume;
+ }
+
+ /**
+ * Sets the volume in decibels.
+ * @param {number} db The decibels
+ */
+ setVolumeDecibels(db) {
+ this.setVolume(Math.pow(10, db / 20));
+ }
+
+ /**
+ * Sets the volume so that a perceived value of 0.5 is half the perceived volume etc.
+ * @param {number} value The value for the volume
+ */
+ setVolumeLogarithmic(value) {
+ this.setVolume(Math.pow(value, 1.660964));
+ }
+}
+
+const props = ['volumeDecibels', 'volumeLogarithmic', 'setVolumeDecibels', 'setVolumeLogarithmic'];
+
+exports.applyToClass = function applyToClass(structure) {
+ for (const prop of props) {
+ Object.defineProperty(structure.prototype, prop, Object.getOwnPropertyDescriptor(VolumeInterface.prototype, prop));
+ }
+};
diff --git a/node_modules/discord.js/src/client/websocket/WebSocketManager.js b/node_modules/discord.js/src/client/websocket/WebSocketManager.js
new file mode 100644
index 0000000..1801106
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/WebSocketManager.js
@@ -0,0 +1,439 @@
+'use strict';
+
+const EventEmitter = require('events');
+const WebSocketShard = require('./WebSocketShard');
+const PacketHandlers = require('./handlers');
+const { Error: DJSError } = require('../../errors');
+const Collection = require('../../util/Collection');
+const { Events, ShardEvents, Status, WSCodes, WSEvents } = require('../../util/Constants');
+const Util = require('../../util/Util');
+
+const BeforeReadyWhitelist = [
+ WSEvents.READY,
+ WSEvents.RESUMED,
+ WSEvents.GUILD_CREATE,
+ WSEvents.GUILD_DELETE,
+ WSEvents.GUILD_MEMBERS_CHUNK,
+ WSEvents.GUILD_MEMBER_ADD,
+ WSEvents.GUILD_MEMBER_REMOVE,
+];
+
+const UNRECOVERABLE_CLOSE_CODES = Object.keys(WSCodes)
+ .slice(1)
+ .map(Number);
+const UNRESUMABLE_CLOSE_CODES = [1000, 4006, 4007];
+
+/**
+ * The WebSocket manager for this client.
+ * <info>This class forwards raw dispatch events,
+ * read more about it here {@link https://discordapp.com/developers/docs/topics/gateway}</info>
+ * @extends EventEmitter
+ */
+class WebSocketManager extends EventEmitter {
+ constructor(client) {
+ super();
+
+ /**
+ * The client that instantiated this WebSocketManager
+ * @type {Client}
+ * @readonly
+ * @name WebSocketManager#client
+ */
+ Object.defineProperty(this, 'client', { value: client });
+
+ /**
+ * The gateway this manager uses
+ * @type {?string}
+ */
+ this.gateway = undefined;
+
+ /**
+ * The amount of shards this manager handles
+ * @private
+ * @type {number}
+ */
+ this.totalShards = this.client.options.shards.length;
+
+ /**
+ * A collection of all shards this manager handles
+ * @type {Collection<number, WebSocketShard>}
+ */
+ this.shards = new Collection();
+
+ /**
+ * An array of shards to be connected or that need to reconnect
+ * @type {Set<WebSocketShard>}
+ * @private
+ * @name WebSocketManager#shardQueue
+ */
+ Object.defineProperty(this, 'shardQueue', { value: new Set(), writable: true });
+
+ /**
+ * An array of queued events before this WebSocketManager became ready
+ * @type {object[]}
+ * @private
+ * @name WebSocketManager#packetQueue
+ */
+ Object.defineProperty(this, 'packetQueue', { value: [] });
+
+ /**
+ * The current status of this WebSocketManager
+ * @type {number}
+ */
+ this.status = Status.IDLE;
+
+ /**
+ * If this manager was destroyed. It will prevent shards from reconnecting
+ * @type {boolean}
+ * @private
+ */
+ this.destroyed = false;
+
+ /**
+ * If this manager is currently reconnecting one or multiple shards
+ * @type {boolean}
+ * @private
+ */
+ this.reconnecting = false;
+
+ /**
+ * The current session limit of the client
+ * @private
+ * @type {?Object}
+ * @prop {number} total Total number of identifies available
+ * @prop {number} remaining Number of identifies remaining
+ * @prop {number} reset_after Number of milliseconds after which the limit resets
+ */
+ this.sessionStartLimit = undefined;
+ }
+
+ /**
+ * The average ping of all WebSocketShards
+ * @type {number}
+ * @readonly
+ */
+ get ping() {
+ const sum = this.shards.reduce((a, b) => a + b.ping, 0);
+ return sum / this.shards.size;
+ }
+
+ /**
+ * Emits a debug message.
+ * @param {string} message The debug message
+ * @param {?WebSocketShard} [shard] The shard that emitted this message, if any
+ * @private
+ */
+ debug(message, shard) {
+ this.client.emit(Events.DEBUG, `[WS => ${shard ? `Shard ${shard.id}` : 'Manager'}] ${message}`);
+ }
+
+ /**
+ * Connects this manager to the gateway.
+ * @private
+ */
+ async connect() {
+ const invalidToken = new DJSError(WSCodes[4004]);
+ const {
+ url: gatewayURL,
+ shards: recommendedShards,
+ session_start_limit: sessionStartLimit,
+ } = await this.client.api.gateway.bot.get().catch(error => {
+ throw error.httpStatus === 401 ? invalidToken : error;
+ });
+
+ this.sessionStartLimit = sessionStartLimit;
+
+ const { total, remaining, reset_after } = sessionStartLimit;
+
+ this.debug(`Fetched Gateway Information
+ URL: ${gatewayURL}
+ Recommended Shards: ${recommendedShards}`);
+
+ this.debug(`Session Limit Information
+ Total: ${total}
+ Remaining: ${remaining}`);
+
+ this.gateway = `${gatewayURL}/`;
+
+ let { shards } = this.client.options;
+
+ if (shards === 'auto') {
+ this.debug(`Using the recommended shard count provided by Discord: ${recommendedShards}`);
+ this.totalShards = this.client.options.shardCount = recommendedShards;
+ shards = this.client.options.shards = Array.from({ length: recommendedShards }, (_, i) => i);
+ }
+
+ this.totalShards = shards.length;
+ this.debug(`Spawning shards: ${shards.join(', ')}`);
+ this.shardQueue = new Set(shards.map(id => new WebSocketShard(this, id)));
+
+ await this._handleSessionLimit(remaining, reset_after);
+
+ return this.createShards();
+ }
+
+ /**
+ * Handles the creation of a shard.
+ * @returns {Promise<boolean>}
+ * @private
+ */
+ async createShards() {
+ // If we don't have any shards to handle, return
+ if (!this.shardQueue.size) return false;
+
+ const [shard] = this.shardQueue;
+
+ this.shardQueue.delete(shard);
+
+ if (!shard.eventsAttached) {
+ shard.on(ShardEvents.ALL_READY, unavailableGuilds => {
+ /**
+ * Emitted when a shard turns ready.
+ * @event Client#shardReady
+ * @param {number} id The shard ID that turned ready
+ * @param {?Set<string>} unavailableGuilds Set of unavailable guild IDs, if any
+ */
+ this.client.emit(Events.SHARD_READY, shard.id, unavailableGuilds);
+
+ if (!this.shardQueue.size) this.reconnecting = false;
+ this.checkShardsReady();
+ });
+
+ shard.on(ShardEvents.CLOSE, event => {
+ if (event.code === 1000 ? this.destroyed : UNRECOVERABLE_CLOSE_CODES.includes(event.code)) {
+ /**
+ * Emitted when a shard's WebSocket disconnects and will no longer reconnect.
+ * @event Client#shardDisconnect
+ * @param {CloseEvent} event The WebSocket close event
+ * @param {number} id The shard ID that disconnected
+ */
+ this.client.emit(Events.SHARD_DISCONNECT, event, shard.id);
+ this.debug(WSCodes[event.code], shard);
+ return;
+ }
+
+ if (UNRESUMABLE_CLOSE_CODES.includes(event.code)) {
+ // These event codes cannot be resumed
+ shard.sessionID = undefined;
+ }
+
+ /**
+ * Emitted when a shard is attempting to reconnect or re-identify.
+ * @event Client#shardReconnecting
+ * @param {number} id The shard ID that is attempting to reconnect
+ */
+ this.client.emit(Events.SHARD_RECONNECTING, shard.id);
+
+ this.shardQueue.add(shard);
+
+ if (shard.sessionID) {
+ this.debug(`Session ID is present, attempting an immediate reconnect...`, shard);
+ this.reconnect(true);
+ } else {
+ shard.destroy({ reset: true, emit: false, log: false });
+ this.reconnect();
+ }
+ });
+
+ shard.on(ShardEvents.INVALID_SESSION, () => {
+ this.client.emit(Events.SHARD_RECONNECTING, shard.id);
+ });
+
+ shard.on(ShardEvents.DESTROYED, () => {
+ this.debug('Shard was destroyed but no WebSocket connection was present! Reconnecting...', shard);
+
+ this.client.emit(Events.SHARD_RECONNECTING, shard.id);
+
+ this.shardQueue.add(shard);
+ this.reconnect();
+ });
+
+ shard.eventsAttached = true;
+ }
+
+ this.shards.set(shard.id, shard);
+
+ try {
+ await shard.connect();
+ } catch (error) {
+ if (error && error.code && UNRECOVERABLE_CLOSE_CODES.includes(error.code)) {
+ throw new DJSError(WSCodes[error.code]);
+ // Undefined if session is invalid, error event for regular closes
+ } else if (!error || error.code) {
+ this.debug('Failed to connect to the gateway, requeueing...', shard);
+ this.shardQueue.add(shard);
+ } else {
+ throw error;
+ }
+ }
+ // If we have more shards, add a 5s delay
+ if (this.shardQueue.size) {
+ this.debug(`Shard Queue Size: ${this.shardQueue.size}; continuing in 5 seconds...`);
+ await Util.delayFor(5000);
+ await this._handleSessionLimit();
+ return this.createShards();
+ }
+
+ return true;
+ }
+
+ /**
+ * Handles reconnects for this manager.
+ * @param {boolean} [skipLimit=false] IF this reconnect should skip checking the session limit
+ * @private
+ * @returns {Promise<boolean>}
+ */
+ async reconnect(skipLimit = false) {
+ if (this.reconnecting || this.status !== Status.READY) return false;
+ this.reconnecting = true;
+ try {
+ if (!skipLimit) await this._handleSessionLimit();
+ await this.createShards();
+ } 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...`);
+ await Util.delayFor(5000);
+ this.reconnecting = false;
+ return this.reconnect();
+ }
+ // If we get an error at this point, it means we cannot reconnect anymore
+ if (this.client.listenerCount(Events.INVALIDATED)) {
+ /**
+ * Emitted when the client's session becomes invalidated.
+ * You are expected to handle closing the process gracefully and preventing a boot loop
+ * if you are listening to this event.
+ * @event Client#invalidated
+ */
+ this.client.emit(Events.INVALIDATED);
+ // Destroy just the shards. This means you have to handle the cleanup yourself
+ this.destroy();
+ } else {
+ this.client.destroy();
+ }
+ } finally {
+ this.reconnecting = false;
+ }
+ return true;
+ }
+
+ /**
+ * Broadcasts a packet to every shard this manager handles.
+ * @param {Object} packet The packet to send
+ * @private
+ */
+ broadcast(packet) {
+ for (const shard of this.shards.values()) shard.send(packet);
+ }
+
+ /**
+ * Destroys this manager and all its shards.
+ * @private
+ */
+ destroy() {
+ if (this.destroyed) return;
+ this.debug(`Manager was destroyed. Called by:\n${new Error('MANAGER_DESTROYED').stack}`);
+ this.destroyed = true;
+ this.shardQueue.clear();
+ for (const shard of this.shards.values()) shard.destroy({ closeCode: 1000, reset: true, emit: false, log: false });
+ }
+
+ /**
+ * Handles the timeout required if we cannot identify anymore.
+ * @param {number} [remaining] The amount of remaining identify sessions that can be done today
+ * @param {number} [resetAfter] The amount of time in which the identify counter resets
+ * @private
+ */
+ async _handleSessionLimit(remaining, resetAfter) {
+ if (typeof remaining === 'undefined' && typeof resetAfter === 'undefined') {
+ const { session_start_limit } = await this.client.api.gateway.bot.get();
+ this.sessionStartLimit = session_start_limit;
+ remaining = session_start_limit.remaining;
+ resetAfter = session_start_limit.reset_after;
+ this.debug(`Session Limit Information
+ Total: ${session_start_limit.total}
+ Remaining: ${remaining}`);
+ }
+ if (!remaining) {
+ this.debug(`Exceeded identify threshold. Will attempt a connection in ${resetAfter}ms`);
+ await Util.delayFor(resetAfter);
+ }
+ }
+
+ /**
+ * Processes a packet and queues it if this WebSocketManager is not ready.
+ * @param {Object} [packet] The packet to be handled
+ * @param {WebSocketShard} [shard] The shard that will handle this packet
+ * @returns {boolean}
+ * @private
+ */
+ handlePacket(packet, shard) {
+ if (packet && this.status !== Status.READY) {
+ if (!BeforeReadyWhitelist.includes(packet.t)) {
+ this.packetQueue.push({ packet, shard });
+ return false;
+ }
+ }
+
+ if (this.packetQueue.length) {
+ const item = this.packetQueue.shift();
+ this.client.setImmediate(() => {
+ this.handlePacket(item.packet, item.shard);
+ });
+ }
+
+ if (packet && PacketHandlers[packet.t]) {
+ PacketHandlers[packet.t](this.client, packet, shard);
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks whether the client is ready to be marked as ready.
+ * @private
+ */
+ async checkShardsReady() {
+ if (this.status === Status.READY) return;
+ if (this.shards.size !== this.totalShards || this.shards.some(s => s.status !== Status.READY)) {
+ return;
+ }
+
+ this.status = Status.NEARLY;
+
+ if (this.client.options.fetchAllMembers) {
+ try {
+ const promises = this.client.guilds.cache.map(guild => {
+ if (guild.available) return guild.members.fetch();
+ // Return empty promise if guild is unavailable
+ return Promise.resolve();
+ });
+ await Promise.all(promises);
+ } catch (err) {
+ this.debug(`Failed to fetch all members before ready! ${err}\n${err.stack}`);
+ }
+ }
+
+ this.triggerClientReady();
+ }
+
+ /**
+ * Causes the client to be marked as ready and emits the ready event.
+ * @private
+ */
+ triggerClientReady() {
+ this.status = Status.READY;
+
+ this.client.readyAt = new Date();
+
+ /**
+ * Emitted when the client becomes ready to start working.
+ * @event Client#ready
+ */
+ this.client.emit(Events.CLIENT_READY);
+
+ this.handlePacket();
+ }
+}
+
+module.exports = WebSocketManager;
diff --git a/node_modules/discord.js/src/client/websocket/WebSocketShard.js b/node_modules/discord.js/src/client/websocket/WebSocketShard.js
new file mode 100644
index 0000000..9f80f45
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/WebSocketShard.js
@@ -0,0 +1,763 @@
+'use strict';
+
+const EventEmitter = require('events');
+const WebSocket = require('../../WebSocket');
+const { browser, Status, Events, ShardEvents, OPCodes, WSEvents } = require('../../util/Constants');
+
+const STATUS_KEYS = Object.keys(Status);
+const CONNECTION_STATE = Object.keys(WebSocket.WebSocket);
+
+let zlib;
+
+if (!browser) {
+ try {
+ zlib = require('zlib-sync');
+ } catch {} // eslint-disable-line no-empty
+}
+
+/**
+ * Represents a Shard's WebSocket connection
+ */
+class WebSocketShard extends EventEmitter {
+ constructor(manager, id) {
+ super();
+
+ /**
+ * The WebSocketManager of the shard
+ * @type {WebSocketManager}
+ */
+ this.manager = manager;
+
+ /**
+ * The ID of the shard
+ * @type {number}
+ */
+ this.id = id;
+
+ /**
+ * The current status of the shard
+ * @type {Status}
+ */
+ this.status = Status.IDLE;
+
+ /**
+ * The current sequence of the shard
+ * @type {number}
+ * @private
+ */
+ this.sequence = -1;
+
+ /**
+ * The sequence of the shard after close
+ * @type {number}
+ * @private
+ */
+ this.closeSequence = 0;
+
+ /**
+ * The current session ID of the shard
+ * @type {string}
+ * @private
+ */
+ this.sessionID = undefined;
+
+ /**
+ * The previous heartbeat ping of the shard
+ * @type {number}
+ */
+ this.ping = -1;
+
+ /**
+ * The last time a ping was sent (a timestamp)
+ * @type {number}
+ * @private
+ */
+ this.lastPingTimestamp = -1;
+
+ /**
+ * If we received a heartbeat ack back. Used to identify zombie connections
+ * @type {boolean}
+ * @private
+ */
+ this.lastHeartbeatAcked = true;
+
+ /**
+ * Contains the rate limit queue and metadata
+ * @type {Object}
+ * @private
+ */
+ Object.defineProperty(this, 'ratelimit', {
+ value: {
+ queue: [],
+ total: 120,
+ remaining: 120,
+ time: 60e3,
+ timer: null,
+ },
+ });
+
+ /**
+ * The WebSocket connection for the current shard
+ * @type {?WebSocket}
+ * @private
+ */
+ Object.defineProperty(this, 'connection', { value: null, writable: true });
+
+ /**
+ * @external Inflate
+ * @see {@link https://www.npmjs.com/package/zlib-sync}
+ */
+
+ /**
+ * The compression to use
+ * @type {?Inflate}
+ * @private
+ */
+ Object.defineProperty(this, 'inflate', { value: null, writable: true });
+
+ /**
+ * The HELLO timeout
+ * @type {?NodeJS.Timer}
+ * @private
+ */
+ Object.defineProperty(this, 'helloTimeout', { value: undefined, writable: true });
+
+ /**
+ * If the manager attached its event handlers on the shard
+ * @type {boolean}
+ * @private
+ */
+ Object.defineProperty(this, 'eventsAttached', { value: false, writable: true });
+
+ /**
+ * A set of guild IDs this shard expects to receive
+ * @type {?Set<string>}
+ * @private
+ */
+ Object.defineProperty(this, 'expectedGuilds', { value: undefined, writable: true });
+
+ /**
+ * The ready timeout
+ * @type {?NodeJS.Timer}
+ * @private
+ */
+ Object.defineProperty(this, 'readyTimeout', { value: undefined, writable: true });
+
+ /**
+ * Time when the WebSocket connection was opened
+ * @type {number}
+ * @private
+ */
+ Object.defineProperty(this, 'connectedAt', { value: 0, writable: true });
+ }
+
+ /**
+ * Emits a debug event.
+ * @param {string} message The debug message
+ * @private
+ */
+ debug(message) {
+ this.manager.debug(message, this);
+ }
+
+ /**
+ * Connects the shard to the gateway.
+ * @private
+ * @returns {Promise<void>} A promise that will resolve if the shard turns ready successfully,
+ * or reject if we couldn't connect
+ */
+ connect() {
+ const { gateway, client } = this.manager;
+
+ if (this.connection && this.connection.readyState === WebSocket.OPEN && this.status === Status.READY) {
+ return Promise.resolve();
+ }
+
+ return new Promise((resolve, reject) => {
+ const cleanup = () => {
+ this.removeListener(ShardEvents.CLOSE, onClose);
+ this.removeListener(ShardEvents.READY, onReady);
+ this.removeListener(ShardEvents.RESUMED, onResumed);
+ this.removeListener(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
+ this.removeListener(ShardEvents.DESTROYED, onInvalidOrDestroyed);
+ };
+
+ const onReady = () => {
+ cleanup();
+ resolve();
+ };
+
+ const onResumed = () => {
+ cleanup();
+ resolve();
+ };
+
+ const onClose = event => {
+ cleanup();
+ reject(event);
+ };
+
+ const onInvalidOrDestroyed = () => {
+ cleanup();
+ // eslint-disable-next-line prefer-promise-reject-errors
+ reject();
+ };
+
+ this.once(ShardEvents.READY, onReady);
+ this.once(ShardEvents.RESUMED, onResumed);
+ this.once(ShardEvents.CLOSE, onClose);
+ this.once(ShardEvents.INVALID_SESSION, onInvalidOrDestroyed);
+ this.once(ShardEvents.DESTROYED, onInvalidOrDestroyed);
+
+ if (this.connection && this.connection.readyState === WebSocket.OPEN) {
+ this.debug('An open connection was found, attempting an immediate identify.');
+ this.identify();
+ return;
+ }
+
+ if (this.connection) {
+ this.debug(`A connection object was found. Cleaning up before continuing.
+ State: ${CONNECTION_STATE[this.connection.readyState]}`);
+ this.destroy({ emit: false });
+ }
+
+ const wsQuery = { v: client.options.ws.version };
+
+ if (zlib) {
+ this.inflate = new zlib.Inflate({
+ chunkSize: 65535,
+ flush: zlib.Z_SYNC_FLUSH,
+ to: WebSocket.encoding === 'json' ? 'string' : '',
+ });
+ wsQuery.compress = 'zlib-stream';
+ }
+
+ this.debug(
+ `[CONNECT]
+ Gateway : ${gateway}
+ Version : ${client.options.ws.version}
+ Encoding : ${WebSocket.encoding}
+ Compression: ${zlib ? 'zlib-stream' : 'none'}`,
+ );
+
+ this.status = this.status === Status.DISCONNECTED ? Status.RECONNECTING : Status.CONNECTING;
+ this.setHelloTimeout();
+
+ this.connectedAt = Date.now();
+
+ const ws = (this.connection = WebSocket.create(gateway, wsQuery));
+ ws.onopen = this.onOpen.bind(this);
+ ws.onmessage = this.onMessage.bind(this);
+ ws.onerror = this.onError.bind(this);
+ ws.onclose = this.onClose.bind(this);
+ });
+ }
+
+ /**
+ * Called whenever a connection is opened to the gateway.
+ * @private
+ */
+ onOpen() {
+ this.debug(`[CONNECTED] ${this.connection.url} in ${Date.now() - this.connectedAt}ms`);
+ this.status = Status.NEARLY;
+ }
+
+ /**
+ * Called whenever a message is received.
+ * @param {MessageEvent} event Event received
+ * @private
+ */
+ onMessage({ data }) {
+ let raw;
+ if (data instanceof ArrayBuffer) data = new Uint8Array(data);
+ if (zlib) {
+ const l = data.length;
+ const flush =
+ l >= 4 && data[l - 4] === 0x00 && data[l - 3] === 0x00 && data[l - 2] === 0xff && data[l - 1] === 0xff;
+
+ this.inflate.push(data, flush && zlib.Z_SYNC_FLUSH);
+ if (!flush) return;
+ raw = this.inflate.result;
+ } else {
+ raw = data;
+ }
+ let packet;
+ try {
+ packet = WebSocket.unpack(raw);
+ this.manager.client.emit(Events.RAW, packet, this.id);
+ if (packet.op === OPCodes.DISPATCH) this.manager.emit(packet.t, packet.d, this.id);
+ } catch (err) {
+ this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
+ return;
+ }
+ this.onPacket(packet);
+ }
+
+ /**
+ * Called whenever an error occurs with the WebSocket.
+ * @param {ErrorEvent} event The error that occurred
+ * @private
+ */
+ onError(event) {
+ const error = event && event.error ? event.error : event;
+ if (!error) return;
+
+ /**
+ * Emitted whenever a shard's WebSocket encounters a connection error.
+ * @event Client#shardError
+ * @param {Error} error The encountered error
+ * @param {number} shardID The shard that encountered this error
+ */
+ this.manager.client.emit(Events.SHARD_ERROR, error, this.id);
+ }
+
+ /**
+ * @external CloseEvent
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}
+ */
+
+ /**
+ * @external ErrorEvent
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent}
+ */
+
+ /**
+ * @external MessageEvent
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent}
+ */
+
+ /**
+ * Called whenever a connection to the gateway is closed.
+ * @param {CloseEvent} event Close event that was received
+ * @private
+ */
+ onClose(event) {
+ if (this.sequence !== -1) this.closeSequence = this.sequence;
+ this.sequence = -1;
+
+ this.debug(`[CLOSE]
+ Event Code: ${event.code}
+ Clean : ${event.wasClean}
+ Reason : ${event.reason || 'No reason received'}`);
+
+ this.setHeartbeatTimer(-1);
+ this.setHelloTimeout(-1);
+ // If we still have a connection object, clean up its listeners
+ if (this.connection) this._cleanupConnection();
+
+ this.status = Status.DISCONNECTED;
+
+ /**
+ * Emitted when a shard's WebSocket closes.
+ * @private
+ * @event WebSocketShard#close
+ * @param {CloseEvent} event The received event
+ */
+ this.emit(ShardEvents.CLOSE, event);
+ }
+
+ /**
+ * Called whenever a packet is received.
+ * @param {Object} packet The received packet
+ * @private
+ */
+ onPacket(packet) {
+ if (!packet) {
+ this.debug(`Received broken packet: '${packet}'.`);
+ return;
+ }
+
+ switch (packet.t) {
+ case WSEvents.READY:
+ /**
+ * Emitted when the shard receives the READY payload and is now waiting for guilds
+ * @event WebSocketShard#ready
+ */
+ this.emit(ShardEvents.READY);
+
+ this.sessionID = packet.d.session_id;
+ this.expectedGuilds = new Set(packet.d.guilds.map(d => d.id));
+ this.status = Status.WAITING_FOR_GUILDS;
+ this.debug(`[READY] Session ${this.sessionID}.`);
+ this.lastHeartbeatAcked = true;
+ this.sendHeartbeat('ReadyHeartbeat');
+ break;
+ case WSEvents.RESUMED: {
+ /**
+ * Emitted when the shard resumes successfully
+ * @event WebSocketShard#resumed
+ */
+ this.emit(ShardEvents.RESUMED);
+
+ this.status = Status.READY;
+ const replayed = packet.s - this.closeSequence;
+ this.debug(`[RESUMED] Session ${this.sessionID} | Replayed ${replayed} events.`);
+ this.lastHeartbeatAcked = true;
+ this.sendHeartbeat('ResumeHeartbeat');
+ break;
+ }
+ }
+
+ if (packet.s > this.sequence) this.sequence = packet.s;
+
+ switch (packet.op) {
+ case OPCodes.HELLO:
+ this.setHelloTimeout(-1);
+ this.setHeartbeatTimer(packet.d.heartbeat_interval);
+ this.identify();
+ break;
+ case OPCodes.RECONNECT:
+ this.debug('[RECONNECT] Discord asked us to reconnect');
+ this.destroy({ closeCode: 4000 });
+ break;
+ case OPCodes.INVALID_SESSION:
+ this.debug(`[INVALID SESSION] Resumable: ${packet.d}.`);
+ // If we can resume the session, do so immediately
+ if (packet.d) {
+ this.identifyResume();
+ return;
+ }
+ // Reset the sequence
+ this.sequence = -1;
+ // Reset the session ID as it's invalid
+ this.sessionID = undefined;
+ // Set the status to reconnecting
+ this.status = Status.RECONNECTING;
+ // Finally, emit the INVALID_SESSION event
+ this.emit(ShardEvents.INVALID_SESSION);
+ break;
+ case OPCodes.HEARTBEAT_ACK:
+ this.ackHeartbeat();
+ break;
+ case OPCodes.HEARTBEAT:
+ this.sendHeartbeat('HeartbeatRequest', true);
+ break;
+ default:
+ this.manager.handlePacket(packet, this);
+ if (this.status === Status.WAITING_FOR_GUILDS && packet.t === WSEvents.GUILD_CREATE) {
+ this.expectedGuilds.delete(packet.d.id);
+ this.checkReady();
+ }
+ }
+ }
+
+ /**
+ * Checks if the shard can be marked as ready
+ * @private
+ */
+ checkReady() {
+ // Step 0. Clear the ready timeout, if it exists
+ if (this.readyTimeout) {
+ this.manager.client.clearTimeout(this.readyTimeout);
+ this.readyTimeout = undefined;
+ }
+ // Step 1. If we don't have any other guilds pending, we are ready
+ if (!this.expectedGuilds.size) {
+ this.debug('Shard received all its guilds. Marking as fully ready.');
+ this.status = Status.READY;
+
+ /**
+ * Emitted when the shard is fully ready.
+ * This event is emitted if:
+ * * all guilds were received by this shard
+ * * the ready timeout expired, and some guilds are unavailable
+ * @event WebSocketShard#allReady
+ * @param {?Set<string>} unavailableGuilds Set of unavailable guilds, if any
+ */
+ this.emit(ShardEvents.ALL_READY);
+ return;
+ }
+ // Step 2. Create a 15s timeout that will mark the shard as ready if there are still unavailable guilds
+ this.readyTimeout = this.manager.client.setTimeout(() => {
+ this.debug(`Shard did not receive any more guild packets in 15 seconds.
+ Unavailable guild count: ${this.expectedGuilds.size}`);
+
+ this.readyTimeout = undefined;
+
+ this.status = Status.READY;
+
+ this.emit(ShardEvents.ALL_READY, this.expectedGuilds);
+ }, 15000);
+ }
+
+ /**
+ * Sets the HELLO packet timeout.
+ * @param {number} [time] If set to -1, it will clear the hello timeout timeout
+ * @private
+ */
+ setHelloTimeout(time) {
+ if (time === -1) {
+ if (this.helloTimeout) {
+ this.debug('Clearing the HELLO timeout.');
+ this.manager.client.clearTimeout(this.helloTimeout);
+ this.helloTimeout = undefined;
+ }
+ return;
+ }
+ this.debug('Setting a HELLO timeout for 20s.');
+ this.helloTimeout = this.manager.client.setTimeout(() => {
+ this.debug('Did not receive HELLO in time. Destroying and connecting again.');
+ this.destroy({ reset: true, closeCode: 4009 });
+ }, 20000);
+ }
+
+ /**
+ * Sets the heartbeat timer for this shard.
+ * @param {number} time If -1, clears the interval, any other number sets an interval
+ * @private
+ */
+ setHeartbeatTimer(time) {
+ if (time === -1) {
+ if (this.heartbeatInterval) {
+ this.debug('Clearing the heartbeat interval.');
+ this.manager.client.clearInterval(this.heartbeatInterval);
+ this.heartbeatInterval = undefined;
+ }
+ return;
+ }
+ this.debug(`Setting a heartbeat interval for ${time}ms.`);
+ // Sanity checks
+ if (this.heartbeatInterval) this.manager.client.clearInterval(this.heartbeatInterval);
+ this.heartbeatInterval = this.manager.client.setInterval(() => this.sendHeartbeat(), time);
+ }
+
+ /**
+ * Sends a heartbeat to the WebSocket.
+ * If this shard didn't receive a heartbeat last time, it will destroy it and reconnect
+ * @param {string} [tag='HeartbeatTimer'] What caused this heartbeat to be sent
+ * @param {boolean} [ignoreHeartbeatAck] If we should send the heartbeat forcefully.
+ * @private
+ */
+ sendHeartbeat(
+ tag = 'HeartbeatTimer',
+ ignoreHeartbeatAck = [Status.WAITING_FOR_GUILDS, Status.IDENTIFYING, Status.RESUMING].includes(this.status),
+ ) {
+ if (ignoreHeartbeatAck && !this.lastHeartbeatAcked) {
+ this.debug(`[${tag}] Didn't process heartbeat ack yet but we are still connected. Sending one now.`);
+ } else if (!this.lastHeartbeatAcked) {
+ this.debug(
+ `[${tag}] Didn't receive a heartbeat ack last time, assuming zombie connection. Destroying and reconnecting.
+ Status : ${STATUS_KEYS[this.status]}
+ Sequence : ${this.sequence}
+ Connection State: ${this.connection ? CONNECTION_STATE[this.connection.readyState] : 'No Connection??'}`,
+ );
+
+ this.destroy({ closeCode: 4009, reset: true });
+ return;
+ }
+
+ this.debug(`[${tag}] Sending a heartbeat.`);
+ this.lastHeartbeatAcked = false;
+ this.lastPingTimestamp = Date.now();
+ this.send({ op: OPCodes.HEARTBEAT, d: this.sequence }, true);
+ }
+
+ /**
+ * Acknowledges a heartbeat.
+ * @private
+ */
+ ackHeartbeat() {
+ this.lastHeartbeatAcked = true;
+ const latency = Date.now() - this.lastPingTimestamp;
+ this.debug(`Heartbeat acknowledged, latency of ${latency}ms.`);
+ this.ping = latency;
+ }
+
+ /**
+ * Identifies the client on the connection.
+ * @private
+ * @returns {void}
+ */
+ identify() {
+ return this.sessionID ? this.identifyResume() : this.identifyNew();
+ }
+
+ /**
+ * Identifies as a new connection on the gateway.
+ * @private
+ */
+ identifyNew() {
+ const { client } = this.manager;
+ if (!client.token) {
+ this.debug('[IDENTIFY] No token available to identify a new session.');
+ return;
+ }
+
+ this.status = Status.IDENTIFYING;
+
+ // Clone the identify payload and assign the token and shard info
+ const d = {
+ ...client.options.ws,
+ token: client.token,
+ shard: [this.id, Number(client.options.shardCount)],
+ };
+
+ this.debug(`[IDENTIFY] Shard ${this.id}/${client.options.shardCount}`);
+ this.send({ op: OPCodes.IDENTIFY, d }, true);
+ }
+
+ /**
+ * Resumes a session on the gateway.
+ * @private
+ */
+ identifyResume() {
+ if (!this.sessionID) {
+ this.debug('[RESUME] No session ID was present; identifying as a new session.');
+ this.identifyNew();
+ return;
+ }
+
+ this.status = Status.RESUMING;
+
+ this.debug(`[RESUME] Session ${this.sessionID}, sequence ${this.closeSequence}`);
+
+ const d = {
+ token: this.manager.client.token,
+ session_id: this.sessionID,
+ seq: this.closeSequence,
+ };
+
+ this.send({ op: OPCodes.RESUME, d }, true);
+ }
+
+ /**
+ * Adds a packet to the queue to be sent to the gateway.
+ * <warn>If you use this method, make sure you understand that you need to provide
+ * a full [Payload](https://discordapp.com/developers/docs/topics/gateway#commands-and-events-gateway-commands).
+ * Do not use this method if you don't know what you're doing.</warn>
+ * @param {Object} data The full packet to send
+ * @param {boolean} [important=false] If this packet should be added first in queue
+ */
+ send(data, important = false) {
+ this.ratelimit.queue[important ? 'unshift' : 'push'](data);
+ this.processQueue();
+ }
+
+ /**
+ * Sends data, bypassing the queue.
+ * @param {Object} data Packet to send
+ * @returns {void}
+ * @private
+ */
+ _send(data) {
+ if (!this.connection || this.connection.readyState !== WebSocket.OPEN) {
+ this.debug(`Tried to send packet '${JSON.stringify(data)}' but no WebSocket is available!`);
+ this.destroy({ close: 4000 });
+ return;
+ }
+
+ this.connection.send(WebSocket.pack(data), err => {
+ if (err) this.manager.client.emit(Events.SHARD_ERROR, err, this.id);
+ });
+ }
+
+ /**
+ * Processes the current WebSocket queue.
+ * @returns {void}
+ * @private
+ */
+ processQueue() {
+ if (this.ratelimit.remaining === 0) return;
+ if (this.ratelimit.queue.length === 0) return;
+ if (this.ratelimit.remaining === this.ratelimit.total) {
+ this.ratelimit.timer = this.manager.client.setTimeout(() => {
+ this.ratelimit.remaining = this.ratelimit.total;
+ this.processQueue();
+ }, this.ratelimit.time);
+ }
+ while (this.ratelimit.remaining > 0) {
+ const item = this.ratelimit.queue.shift();
+ if (!item) return;
+ this._send(item);
+ this.ratelimit.remaining--;
+ }
+ }
+
+ /**
+ * Destroys this shard and closes its WebSocket connection.
+ * @param {Object} [options={ closeCode: 1000, reset: false, emit: true, log: true }] Options for destroying the shard
+ * @private
+ */
+ destroy({ closeCode = 1000, reset = false, emit = true, log = true } = {}) {
+ if (log) {
+ this.debug(`[DESTROY]
+ Close Code : ${closeCode}
+ Reset : ${reset}
+ Emit DESTROYED: ${emit}`);
+ }
+
+ // Step 0: Remove all timers
+ this.setHeartbeatTimer(-1);
+ this.setHelloTimeout(-1);
+
+ // Step 1: Close the WebSocket connection, if any, otherwise, emit DESTROYED
+ if (this.connection) {
+ // If the connection is currently opened, we will (hopefully) receive close
+ if (this.connection.readyState === WebSocket.OPEN) {
+ this.connection.close(closeCode);
+ } else {
+ // Connection is not OPEN
+ this.debug(`WS State: ${CONNECTION_STATE[this.connection.readyState]}`);
+ // Remove listeners from the connection
+ this._cleanupConnection();
+ // Attempt to close the connection just in case
+ try {
+ this.connection.close(closeCode);
+ } catch {
+ // No-op
+ }
+ // Emit the destroyed event if needed
+ if (emit) this._emitDestroyed();
+ }
+ } else if (emit) {
+ // We requested a destroy, but we had no connection. Emit destroyed
+ this._emitDestroyed();
+ }
+
+ // Step 2: Null the connection object
+ this.connection = null;
+
+ // Step 3: Set the shard status to DISCONNECTED
+ this.status = Status.DISCONNECTED;
+
+ // Step 4: Cache the old sequence (use to attempt a resume)
+ if (this.sequence !== -1) this.closeSequence = this.sequence;
+
+ // Step 5: Reset the sequence and session ID if requested
+ if (reset) {
+ this.sequence = -1;
+ this.sessionID = undefined;
+ }
+
+ // Step 6: reset the ratelimit data
+ this.ratelimit.remaining = this.ratelimit.total;
+ this.ratelimit.queue.length = 0;
+ if (this.ratelimit.timer) {
+ this.manager.client.clearTimeout(this.ratelimit.timer);
+ this.ratelimit.timer = null;
+ }
+ }
+
+ /**
+ * Cleans up the WebSocket connection listeners.
+ * @private
+ */
+ _cleanupConnection() {
+ this.connection.onopen = this.connection.onclose = this.connection.onerror = this.connection.onmessage = null;
+ }
+
+ /**
+ * Emits the DESTROYED event on the shard
+ * @private
+ */
+ _emitDestroyed() {
+ /**
+ * Emitted when a shard is destroyed, but no WebSocket connection was present.
+ * @private
+ * @event WebSocketShard#destroyed
+ */
+ this.emit(ShardEvents.DESTROYED);
+ }
+}
+
+module.exports = WebSocketShard;
diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js
new file mode 100644
index 0000000..d6d560d
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.ChannelCreate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js
new file mode 100644
index 0000000..cb9f3d8
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.ChannelDelete.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
new file mode 100644
index 0000000..13e6f0f
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
@@ -0,0 +1,22 @@
+'use strict';
+
+const { Events } = require('../../../util/Constants');
+
+module.exports = (client, { d: data }) => {
+ const channel = client.channels.cache.get(data.channel_id);
+ const time = new Date(data.last_pin_timestamp);
+
+ if (channel && !Number.isNaN(time.getTime())) {
+ // Discord sends null for last_pin_timestamp if the last pinned message was removed
+ channel.lastPinTimestamp = time.getTime() || null;
+
+ /**
+ * Emitted whenever the pins of a channel are updated. Due to the nature of the WebSocket event,
+ * not much information can be provided easily here - you need to manually check the pins yourself.
+ * @event Client#channelPinsUpdate
+ * @param {DMChannel|TextChannel} channel The channel that the pins update occurred in
+ * @param {Date} time The time of the pins update
+ */
+ client.emit(Events.CHANNEL_PINS_UPDATE, channel, time);
+ }
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js
new file mode 100644
index 0000000..d441478
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js
@@ -0,0 +1,16 @@
+'use strict';
+
+const { Events } = require('../../../util/Constants');
+
+module.exports = (client, packet) => {
+ const { old, updated } = client.actions.ChannelUpdate.handle(packet.d);
+ if (old && updated) {
+ /**
+ * Emitted whenever a channel is updated - e.g. name change, topic change, channel type change.
+ * @event Client#channelUpdate
+ * @param {DMChannel|GuildChannel} oldChannel The channel before the update
+ * @param {DMChannel|GuildChannel} newChannel The channel after the update
+ */
+ client.emit(Events.CHANNEL_UPDATE, old, updated);
+ }
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js
new file mode 100644
index 0000000..5d4a096
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js
@@ -0,0 +1,16 @@
+'use strict';
+
+const { Events } = require('../../../util/Constants');
+
+module.exports = (client, { d: data }) => {
+ const guild = client.guilds.cache.get(data.guild_id);
+ const user = client.users.add(data.user);
+
+ /**
+ * Emitted whenever a member is banned from a guild.
+ * @event Client#guildBanAdd
+ * @param {Guild} guild The guild that the ban occurred in
+ * @param {User} user The user that was banned
+ */
+ if (guild && user) client.emit(Events.GUILD_BAN_ADD, guild, user);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js
new file mode 100644
index 0000000..8389e46
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.GuildBanRemove.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js
new file mode 100644
index 0000000..6743204
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js
@@ -0,0 +1,36 @@
+'use strict';
+
+const { Events, Status } = require('../../../util/Constants');
+
+module.exports = async (client, { d: data }, shard) => {
+ let guild = client.guilds.cache.get(data.id);
+ if (guild) {
+ if (!guild.available && !data.unavailable) {
+ // A newly available guild
+ guild._patch(data);
+ // If the client was ready before and we had unavailable guilds, fetch them
+ if (client.ws.status === Status.READY && client.options.fetchAllMembers) {
+ await guild.members
+ .fetch()
+ .catch(err => client.emit(Events.DEBUG, `Failed to fetch all members: ${err}\n${err.stack}`));
+ }
+ }
+ } else {
+ // A new guild
+ data.shardID = shard.id;
+ guild = client.guilds.add(data);
+ if (client.ws.status === Status.READY) {
+ /**
+ * Emitted whenever the client joins a guild.
+ * @event Client#guildCreate
+ * @param {Guild} guild The created guild
+ */
+ if (client.options.fetchAllMembers) {
+ await guild.members
+ .fetch()
+ .catch(err => client.emit(Events.DEBUG, `Failed to fetch all members: ${err}\n${err.stack}`));
+ }
+ client.emit(Events.GUILD_CREATE, guild);
+ }
+ }
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js
new file mode 100644
index 0000000..27a3256
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.GuildDelete.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js
new file mode 100644
index 0000000..e23b671
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.GuildEmojisUpdate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js
new file mode 100644
index 0000000..e90a72c
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.GuildIntegrationsUpdate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
new file mode 100644
index 0000000..3ceb622
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
@@ -0,0 +1,22 @@
+'use strict';
+
+const Collection = require('../../../util/Collection');
+const { Events } = require('../../../util/Constants');
+
+module.exports = (client, { d: data }) => {
+ const guild = client.guilds.cache.get(data.guild_id);
+ if (!guild) return;
+ const members = new Collection();
+
+ for (const member of data.members) members.set(member.user.id, guild.members.add(member));
+ if (data.presences) {
+ for (const presence of data.presences) guild.presences.cache.add(Object.assign(presence, { guild }));
+ }
+ /**
+ * Emitted whenever a chunk of guild members is received (all members come from the same guild).
+ * @event Client#guildMembersChunk
+ * @param {Collection<Snowflake, GuildMember>} members The members in the chunk
+ * @param {Guild} guild The guild related to the member chunk
+ */
+ client.emit(Events.GUILD_MEMBERS_CHUNK, members, guild);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js
new file mode 100644
index 0000000..5128756
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const { Events, Status } = require('../../../util/Constants');
+
+module.exports = (client, { d: data }, shard) => {
+ const guild = client.guilds.cache.get(data.guild_id);
+ if (guild) {
+ guild.memberCount++;
+ const member = guild.members.add(data);
+ if (shard.status === Status.READY) {
+ /**
+ * Emitted whenever a user joins a guild.
+ * @event Client#guildMemberAdd
+ * @param {GuildMember} member The member that has joined a guild
+ */
+ client.emit(Events.GUILD_MEMBER_ADD, member);
+ }
+ }
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js
new file mode 100644
index 0000000..72432af
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet, shard) => {
+ client.actions.GuildMemberRemove.handle(packet.d, shard);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js
new file mode 100644
index 0000000..92c9da6
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js
@@ -0,0 +1,22 @@
+'use strict';
+
+const { Status, Events } = require('../../../util/Constants');
+
+module.exports = (client, { d: data }, shard) => {
+ const guild = client.guilds.cache.get(data.guild_id);
+ if (guild) {
+ const member = guild.members.cache.get(data.user.id);
+ if (member) {
+ const old = member._update(data);
+ if (shard.status === Status.READY) {
+ /**
+ * Emitted whenever a guild member changes - i.e. new role, removed role, nickname.
+ * @event Client#guildMemberUpdate
+ * @param {GuildMember} oldMember The member before the update
+ * @param {GuildMember} newMember The member after the update
+ */
+ client.emit(Events.GUILD_MEMBER_UPDATE, old, member);
+ }
+ }
+ }
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js
new file mode 100644
index 0000000..da9e7bc
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.GuildRoleCreate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js
new file mode 100644
index 0000000..cdc6353
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.GuildRoleDelete.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js
new file mode 100644
index 0000000..3a9b62e
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.GuildRoleUpdate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js
new file mode 100644
index 0000000..fd0012a
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.GuildUpdate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js
new file mode 100644
index 0000000..50a2e72
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.InviteCreate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js
new file mode 100644
index 0000000..5971852
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.InviteDelete.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js
new file mode 100644
index 0000000..c9b79a8
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.MessageCreate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js
new file mode 100644
index 0000000..85ae2bc
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.MessageDelete.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js
new file mode 100644
index 0000000..fbcf80f
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.MessageDeleteBulk.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js
new file mode 100644
index 0000000..e219b4a
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.MessageReactionAdd.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js
new file mode 100644
index 0000000..2980e69
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.MessageReactionRemove.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js
new file mode 100644
index 0000000..ead80f7
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.MessageReactionRemoveAll.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js
new file mode 100644
index 0000000..579444c
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.MessageReactionRemoveEmoji.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js
new file mode 100644
index 0000000..7428e90
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js
@@ -0,0 +1,16 @@
+'use strict';
+
+const { Events } = require('../../../util/Constants');
+
+module.exports = (client, packet) => {
+ const { old, updated } = client.actions.MessageUpdate.handle(packet.d);
+ if (old && updated) {
+ /**
+ * Emitted whenever a message is updated - e.g. embed or content change.
+ * @event Client#messageUpdate
+ * @param {Message} oldMessage The message before the update
+ * @param {Message} newMessage The message after the update
+ */
+ client.emit(Events.MESSAGE_UPDATE, old, updated);
+ }
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js
new file mode 100644
index 0000000..bde3629
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.PresenceUpdate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/READY.js b/node_modules/discord.js/src/client/websocket/handlers/READY.js
new file mode 100644
index 0000000..c38b681
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/READY.js
@@ -0,0 +1,21 @@
+'use strict';
+
+let ClientUser;
+
+module.exports = (client, { d: data }, shard) => {
+ if (client.user) {
+ client.user._patch(data.user);
+ } else {
+ if (!ClientUser) ClientUser = require('../../../structures/ClientUser');
+ const clientUser = new ClientUser(client, data.user);
+ client.user = clientUser;
+ client.users.cache.set(clientUser.id, clientUser);
+ }
+
+ for (const guild of data.guilds) {
+ guild.shardID = shard.id;
+ client.guilds.add(guild);
+ }
+
+ shard.checkReady();
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/RESUMED.js b/node_modules/discord.js/src/client/websocket/handlers/RESUMED.js
new file mode 100644
index 0000000..5e5f403
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/RESUMED.js
@@ -0,0 +1,14 @@
+'use strict';
+
+const { Events } = require('../../../util/Constants');
+
+module.exports = (client, packet, shard) => {
+ const replayed = shard.sequence - shard.closeSequence;
+ /**
+ * Emitted when a shard resumes successfully.
+ * @event Client#shardResume
+ * @param {number} id The shard ID that resumed
+ * @param {number} replayedEvents The amount of replayed events
+ */
+ client.emit(Events.SHARD_RESUME, shard.id, replayed);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js b/node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js
new file mode 100644
index 0000000..86fb26b
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js
@@ -0,0 +1,49 @@
+'use strict';
+
+const { Events } = require('../../../util/Constants');
+
+module.exports = (client, { d: data }) => {
+ const channel = client.channels.cache.get(data.channel_id);
+ const user = client.users.cache.get(data.user_id);
+ const timestamp = new Date(data.timestamp * 1000);
+
+ if (channel && user) {
+ if (channel.type === 'voice') {
+ client.emit(Events.WARN, `Discord sent a typing packet to a voice channel ${channel.id}`);
+ return;
+ }
+
+ if (channel._typing.has(user.id)) {
+ const typing = channel._typing.get(user.id);
+
+ typing.lastTimestamp = timestamp;
+ typing.elapsedTime = Date.now() - typing.since;
+ client.clearTimeout(typing.timeout);
+ typing.timeout = tooLate(channel, user);
+ } else {
+ const since = new Date();
+ const lastTimestamp = new Date();
+ channel._typing.set(user.id, {
+ user,
+ since,
+ lastTimestamp,
+ elapsedTime: Date.now() - since,
+ timeout: tooLate(channel, user),
+ });
+
+ /**
+ * Emitted whenever a user starts typing in a channel.
+ * @event Client#typingStart
+ * @param {Channel} channel The channel the user started typing in
+ * @param {User} user The user that started typing
+ */
+ client.emit(Events.TYPING_START, channel, user);
+ }
+ }
+};
+
+function tooLate(channel, user) {
+ return channel.client.setTimeout(() => {
+ channel._typing.delete(user.id);
+ }, 10000);
+}
diff --git a/node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js
new file mode 100644
index 0000000..a02bf58
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.UserUpdate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js
new file mode 100644
index 0000000..f9cf534
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js
@@ -0,0 +1,6 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.emit('debug', `[VOICE] received voice server: ${JSON.stringify(packet)}`);
+ client.voice.onVoiceServer(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js
new file mode 100644
index 0000000..dbff6ea
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.VoiceStateUpdate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js b/node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js
new file mode 100644
index 0000000..46cacee
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = (client, packet) => {
+ client.actions.WebhooksUpdate.handle(packet.d);
+};
diff --git a/node_modules/discord.js/src/client/websocket/handlers/index.js b/node_modules/discord.js/src/client/websocket/handlers/index.js
new file mode 100644
index 0000000..d69c105
--- /dev/null
+++ b/node_modules/discord.js/src/client/websocket/handlers/index.js
@@ -0,0 +1,13 @@
+'use strict';
+
+const { WSEvents } = require('../../../util/Constants');
+
+const handlers = {};
+
+for (const name of Object.keys(WSEvents)) {
+ try {
+ handlers[name] = require(`./${name}.js`);
+ } catch {} // eslint-disable-line no-empty
+}
+
+module.exports = handlers;