Every new change
This commit is contained in:
49
node_modules/discord.js/src/WebSocket.js
generated
vendored
Normal file
49
node_modules/discord.js/src/WebSocket.js
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
const { browser } = require('./util/Constants');
|
||||
|
||||
let erlpack;
|
||||
|
||||
try {
|
||||
erlpack = require('erlpack');
|
||||
if (!erlpack.pack) erlpack = null;
|
||||
} catch {} // eslint-disable-line no-empty
|
||||
|
||||
let TextDecoder;
|
||||
|
||||
if (browser) {
|
||||
TextDecoder = window.TextDecoder; // eslint-disable-line no-undef
|
||||
exports.WebSocket = window.WebSocket; // eslint-disable-line no-undef
|
||||
} else {
|
||||
TextDecoder = require('util').TextDecoder;
|
||||
exports.WebSocket = require('ws');
|
||||
}
|
||||
|
||||
const ab = new TextDecoder();
|
||||
|
||||
exports.encoding = erlpack ? 'etf' : 'json';
|
||||
|
||||
exports.pack = erlpack ? erlpack.pack : JSON.stringify;
|
||||
|
||||
exports.unpack = (data, type) => {
|
||||
if (exports.encoding === 'json' || type === 'json') {
|
||||
if (typeof data !== 'string') {
|
||||
data = ab.decode(data);
|
||||
}
|
||||
return JSON.parse(data);
|
||||
}
|
||||
if (!Buffer.isBuffer(data)) data = Buffer.from(new Uint8Array(data));
|
||||
return erlpack.unpack(data);
|
||||
};
|
||||
|
||||
exports.create = (gateway, query = {}, ...args) => {
|
||||
const [g, q] = gateway.split('?');
|
||||
query.encoding = exports.encoding;
|
||||
query = new URLSearchParams(query);
|
||||
if (q) new URLSearchParams(q).forEach((v, k) => query.set(k, v));
|
||||
const ws = new exports.WebSocket(`${g}?${query}`, ...args);
|
||||
if (browser) ws.binaryType = 'arraybuffer';
|
||||
return ws;
|
||||
};
|
||||
|
||||
for (const state of ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED']) exports[state] = exports.WebSocket[state];
|
147
node_modules/discord.js/src/client/BaseClient.js
generated
vendored
Normal file
147
node_modules/discord.js/src/client/BaseClient.js
generated
vendored
Normal file
@ -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;
|
440
node_modules/discord.js/src/client/Client.js
generated
vendored
Normal file
440
node_modules/discord.js/src/client/Client.js
generated
vendored
Normal file
@ -0,0 +1,440 @@
|
||||
'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 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
31
node_modules/discord.js/src/client/WebhookClient.js
generated
vendored
Normal file
31
node_modules/discord.js/src/client/WebhookClient.js
generated
vendored
Normal file
@ -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;
|
103
node_modules/discord.js/src/client/actions/Action.js
generated
vendored
Normal file
103
node_modules/discord.js/src/client/actions/Action.js
generated
vendored
Normal file
@ -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;
|
45
node_modules/discord.js/src/client/actions/ActionsManager.js
generated
vendored
Normal file
45
node_modules/discord.js/src/client/actions/ActionsManager.js
generated
vendored
Normal file
@ -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;
|
23
node_modules/discord.js/src/client/actions/ChannelCreate.js
generated
vendored
Normal file
23
node_modules/discord.js/src/client/actions/ChannelCreate.js
generated
vendored
Normal file
@ -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;
|
37
node_modules/discord.js/src/client/actions/ChannelDelete.js
generated
vendored
Normal file
37
node_modules/discord.js/src/client/actions/ChannelDelete.js
generated
vendored
Normal file
@ -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;
|
33
node_modules/discord.js/src/client/actions/ChannelUpdate.js
generated
vendored
Normal file
33
node_modules/discord.js/src/client/actions/ChannelUpdate.js
generated
vendored
Normal file
@ -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;
|
21
node_modules/discord.js/src/client/actions/GuildBanRemove.js
generated
vendored
Normal file
21
node_modules/discord.js/src/client/actions/GuildBanRemove.js
generated
vendored
Normal file
@ -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;
|
21
node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js
generated
vendored
Normal file
21
node_modules/discord.js/src/client/actions/GuildChannelsPositionUpdate.js
generated
vendored
Normal file
@ -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;
|
67
node_modules/discord.js/src/client/actions/GuildDelete.js
generated
vendored
Normal file
67
node_modules/discord.js/src/client/actions/GuildDelete.js
generated
vendored
Normal file
@ -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 (guild.available && 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;
|
19
node_modules/discord.js/src/client/actions/GuildEmojiCreate.js
generated
vendored
Normal file
19
node_modules/discord.js/src/client/actions/GuildEmojiCreate.js
generated
vendored
Normal file
@ -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;
|
20
node_modules/discord.js/src/client/actions/GuildEmojiDelete.js
generated
vendored
Normal file
20
node_modules/discord.js/src/client/actions/GuildEmojiDelete.js
generated
vendored
Normal file
@ -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;
|
20
node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js
generated
vendored
Normal file
20
node_modules/discord.js/src/client/actions/GuildEmojiUpdate.js
generated
vendored
Normal file
@ -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;
|
34
node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js
generated
vendored
Normal file
34
node_modules/discord.js/src/client/actions/GuildEmojisUpdate.js
generated
vendored
Normal file
@ -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;
|
19
node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js
generated
vendored
Normal file
19
node_modules/discord.js/src/client/actions/GuildIntegrationsUpdate.js
generated
vendored
Normal file
@ -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;
|
30
node_modules/discord.js/src/client/actions/GuildMemberRemove.js
generated
vendored
Normal file
30
node_modules/discord.js/src/client/actions/GuildMemberRemove.js
generated
vendored
Normal file
@ -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;
|
25
node_modules/discord.js/src/client/actions/GuildRoleCreate.js
generated
vendored
Normal file
25
node_modules/discord.js/src/client/actions/GuildRoleCreate.js
generated
vendored
Normal file
@ -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;
|
30
node_modules/discord.js/src/client/actions/GuildRoleDelete.js
generated
vendored
Normal file
30
node_modules/discord.js/src/client/actions/GuildRoleDelete.js
generated
vendored
Normal file
@ -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;
|
39
node_modules/discord.js/src/client/actions/GuildRoleUpdate.js
generated
vendored
Normal file
39
node_modules/discord.js/src/client/actions/GuildRoleUpdate.js
generated
vendored
Normal file
@ -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;
|
21
node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js
generated
vendored
Normal file
21
node_modules/discord.js/src/client/actions/GuildRolesPositionUpdate.js
generated
vendored
Normal file
@ -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;
|
33
node_modules/discord.js/src/client/actions/GuildUpdate.js
generated
vendored
Normal file
33
node_modules/discord.js/src/client/actions/GuildUpdate.js
generated
vendored
Normal file
@ -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;
|
28
node_modules/discord.js/src/client/actions/InviteCreate.js
generated
vendored
Normal file
28
node_modules/discord.js/src/client/actions/InviteCreate.js
generated
vendored
Normal file
@ -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;
|
29
node_modules/discord.js/src/client/actions/InviteDelete.js
generated
vendored
Normal file
29
node_modules/discord.js/src/client/actions/InviteDelete.js
generated
vendored
Normal file
@ -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;
|
39
node_modules/discord.js/src/client/actions/MessageCreate.js
generated
vendored
Normal file
39
node_modules/discord.js/src/client/actions/MessageCreate.js
generated
vendored
Normal file
@ -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;
|
29
node_modules/discord.js/src/client/actions/MessageDelete.js
generated
vendored
Normal file
29
node_modules/discord.js/src/client/actions/MessageDelete.js
generated
vendored
Normal file
@ -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;
|
43
node_modules/discord.js/src/client/actions/MessageDeleteBulk.js
generated
vendored
Normal file
43
node_modules/discord.js/src/client/actions/MessageDeleteBulk.js
generated
vendored
Normal file
@ -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;
|
50
node_modules/discord.js/src/client/actions/MessageReactionAdd.js
generated
vendored
Normal file
50
node_modules/discord.js/src/client/actions/MessageReactionAdd.js
generated
vendored
Normal file
@ -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: '<27>', 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;
|
44
node_modules/discord.js/src/client/actions/MessageReactionRemove.js
generated
vendored
Normal file
44
node_modules/discord.js/src/client/actions/MessageReactionRemove.js
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
const Action = require('./Action');
|
||||
const { Events } = require('../../util/Constants');
|
||||
|
||||
/*
|
||||
{ user_id: 'id',
|
||||
message_id: 'id',
|
||||
emoji: { name: '<27>', 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;
|
29
node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js
generated
vendored
Normal file
29
node_modules/discord.js/src/client/actions/MessageReactionRemoveAll.js
generated
vendored
Normal file
@ -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;
|
28
node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js
generated
vendored
Normal file
28
node_modules/discord.js/src/client/actions/MessageReactionRemoveEmoji.js
generated
vendored
Normal file
@ -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;
|
24
node_modules/discord.js/src/client/actions/MessageUpdate.js
generated
vendored
Normal file
24
node_modules/discord.js/src/client/actions/MessageUpdate.js
generated
vendored
Normal file
@ -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;
|
44
node_modules/discord.js/src/client/actions/PresenceUpdate.js
generated
vendored
Normal file
44
node_modules/discord.js/src/client/actions/PresenceUpdate.js
generated
vendored
Normal file
@ -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;
|
34
node_modules/discord.js/src/client/actions/UserUpdate.js
generated
vendored
Normal file
34
node_modules/discord.js/src/client/actions/UserUpdate.js
generated
vendored
Normal file
@ -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;
|
44
node_modules/discord.js/src/client/actions/VoiceStateUpdate.js
generated
vendored
Normal file
44
node_modules/discord.js/src/client/actions/VoiceStateUpdate.js
generated
vendored
Normal file
@ -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;
|
19
node_modules/discord.js/src/client/actions/WebhooksUpdate.js
generated
vendored
Normal file
19
node_modules/discord.js/src/client/actions/WebhooksUpdate.js
generated
vendored
Normal file
@ -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;
|
110
node_modules/discord.js/src/client/voice/ClientVoiceManager.js
generated
vendored
Normal file
110
node_modules/discord.js/src/client/voice/ClientVoiceManager.js
generated
vendored
Normal file
@ -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;
|
110
node_modules/discord.js/src/client/voice/VoiceBroadcast.js
generated
vendored
Normal file
110
node_modules/discord.js/src/client/voice/VoiceBroadcast.js
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
'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}
|
||||
*/
|
||||
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;
|
523
node_modules/discord.js/src/client/voice/VoiceConnection.js
generated
vendored
Normal file
523
node_modules/discord.js/src/client/voice/VoiceConnection.js
generated
vendored
Normal file
@ -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;
|
46
node_modules/discord.js/src/client/voice/dispatcher/BroadcastDispatcher.js
generated
vendored
Normal file
46
node_modules/discord.js/src/client/voice/dispatcher/BroadcastDispatcher.js
generated
vendored
Normal file
@ -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;
|
354
node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js
generated
vendored
Normal file
354
node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js
generated
vendored
Normal file
@ -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;
|
154
node_modules/discord.js/src/client/voice/networking/VoiceUDPClient.js
generated
vendored
Normal file
154
node_modules/discord.js/src/client/voice/networking/VoiceUDPClient.js
generated
vendored
Normal file
@ -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;
|
264
node_modules/discord.js/src/client/voice/networking/VoiceWebSocket.js
generated
vendored
Normal file
264
node_modules/discord.js/src/client/voice/networking/VoiceWebSocket.js
generated
vendored
Normal file
@ -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;
|
27
node_modules/discord.js/src/client/voice/player/AudioPlayer.js
generated
vendored
Normal file
27
node_modules/discord.js/src/client/voice/player/AudioPlayer.js
generated
vendored
Normal file
@ -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;
|
92
node_modules/discord.js/src/client/voice/player/BasePlayer.js
generated
vendored
Normal file
92
node_modules/discord.js/src/client/voice/player/BasePlayer.js
generated
vendored
Normal file
@ -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;
|
28
node_modules/discord.js/src/client/voice/player/BroadcastAudioPlayer.js
generated
vendored
Normal file
28
node_modules/discord.js/src/client/voice/player/BroadcastAudioPlayer.js
generated
vendored
Normal file
@ -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;
|
116
node_modules/discord.js/src/client/voice/receiver/PacketHandler.js
generated
vendored
Normal file
116
node_modules/discord.js/src/client/voice/receiver/PacketHandler.js
generated
vendored
Normal file
@ -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;
|
58
node_modules/discord.js/src/client/voice/receiver/Receiver.js
generated
vendored
Normal file
58
node_modules/discord.js/src/client/voice/receiver/Receiver.js
generated
vendored
Normal file
@ -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;
|
94
node_modules/discord.js/src/client/voice/util/PlayInterface.js
generated
vendored
Normal file
94
node_modules/discord.js/src/client/voice/util/PlayInterface.js
generated
vendored
Normal file
@ -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.createVoiceBroadcast();
|
||||
* 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;
|
32
node_modules/discord.js/src/client/voice/util/Secretbox.js
generated
vendored
Normal file
32
node_modules/discord.js/src/client/voice/util/Secretbox.js
generated
vendored
Normal file
@ -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
|
||||
}
|
||||
})();
|
13
node_modules/discord.js/src/client/voice/util/Silence.js
generated
vendored
Normal file
13
node_modules/discord.js/src/client/voice/util/Silence.js
generated
vendored
Normal file
@ -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;
|
103
node_modules/discord.js/src/client/voice/util/VolumeInterface.js
generated
vendored
Normal file
103
node_modules/discord.js/src/client/voice/util/VolumeInterface.js
generated
vendored
Normal file
@ -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));
|
||||
}
|
||||
};
|
439
node_modules/discord.js/src/client/websocket/WebSocketManager.js
generated
vendored
Normal file
439
node_modules/discord.js/src/client/websocket/WebSocketManager.js
generated
vendored
Normal file
@ -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;
|
762
node_modules/discord.js/src/client/websocket/WebSocketShard.js
generated
vendored
Normal file
762
node_modules/discord.js/src/client/websocket/WebSocketShard.js
generated
vendored
Normal file
@ -0,0 +1,762 @@
|
||||
'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.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');
|
||||
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;
|
5
node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/CHANNEL_CREATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ChannelCreate.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/CHANNEL_DELETE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.ChannelDelete.handle(packet.d);
|
||||
};
|
22
node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
generated
vendored
Normal file
22
node_modules/discord.js/src/client/websocket/handlers/CHANNEL_PINS_UPDATE.js
generated
vendored
Normal file
@ -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);
|
||||
}
|
||||
};
|
16
node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js
generated
vendored
Normal file
16
node_modules/discord.js/src/client/websocket/handlers/CHANNEL_UPDATE.js
generated
vendored
Normal file
@ -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);
|
||||
}
|
||||
};
|
16
node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js
generated
vendored
Normal file
16
node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_ADD.js
generated
vendored
Normal file
@ -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);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_BAN_REMOVE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildBanRemove.handle(packet.d);
|
||||
};
|
36
node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js
generated
vendored
Normal file
36
node_modules/discord.js/src/client/websocket/handlers/GUILD_CREATE.js
generated
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_DELETE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildDelete.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_EMOJIS_UPDATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildEmojisUpdate.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_INTEGRATIONS_UPDATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildIntegrationsUpdate.handle(packet.d);
|
||||
};
|
22
node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
generated
vendored
Normal file
22
node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBERS_CHUNK.js
generated
vendored
Normal file
@ -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);
|
||||
};
|
19
node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js
generated
vendored
Normal file
19
node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_ADD.js
generated
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_REMOVE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet, shard) => {
|
||||
client.actions.GuildMemberRemove.handle(packet.d, shard);
|
||||
};
|
22
node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js
generated
vendored
Normal file
22
node_modules/discord.js/src/client/websocket/handlers/GUILD_MEMBER_UPDATE.js
generated
vendored
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_CREATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildRoleCreate.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_DELETE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildRoleDelete.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_ROLE_UPDATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildRoleUpdate.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/GUILD_UPDATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.GuildUpdate.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/INVITE_CREATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.InviteCreate.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/INVITE_DELETE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.InviteDelete.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_CREATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageCreate.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageDelete.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_DELETE_BULK.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageDeleteBulk.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_ADD.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionAdd.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionRemove.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_ALL.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionRemoveAll.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_REACTION_REMOVE_EMOJI.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.MessageReactionRemoveEmoji.handle(packet.d);
|
||||
};
|
16
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js
generated
vendored
Normal file
16
node_modules/discord.js/src/client/websocket/handlers/MESSAGE_UPDATE.js
generated
vendored
Normal file
@ -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);
|
||||
}
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/PRESENCE_UPDATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.PresenceUpdate.handle(packet.d);
|
||||
};
|
21
node_modules/discord.js/src/client/websocket/handlers/READY.js
generated
vendored
Normal file
21
node_modules/discord.js/src/client/websocket/handlers/READY.js
generated
vendored
Normal file
@ -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();
|
||||
};
|
14
node_modules/discord.js/src/client/websocket/handlers/RESUMED.js
generated
vendored
Normal file
14
node_modules/discord.js/src/client/websocket/handlers/RESUMED.js
generated
vendored
Normal file
@ -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);
|
||||
};
|
18
node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js
generated
vendored
Normal file
18
node_modules/discord.js/src/client/websocket/handlers/TYPING_START.js
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
'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);
|
||||
|
||||
if (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);
|
||||
}
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/USER_UPDATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.UserUpdate.handle(packet.d);
|
||||
};
|
6
node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js
generated
vendored
Normal file
6
node_modules/discord.js/src/client/websocket/handlers/VOICE_SERVER_UPDATE.js
generated
vendored
Normal file
@ -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);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/VOICE_STATE_UPDATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.VoiceStateUpdate.handle(packet.d);
|
||||
};
|
5
node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js
generated
vendored
Normal file
5
node_modules/discord.js/src/client/websocket/handlers/WEBHOOKS_UPDATE.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.WebhooksUpdate.handle(packet.d);
|
||||
};
|
13
node_modules/discord.js/src/client/websocket/handlers/index.js
generated
vendored
Normal file
13
node_modules/discord.js/src/client/websocket/handlers/index.js
generated
vendored
Normal file
@ -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;
|
61
node_modules/discord.js/src/errors/DJSError.js
generated
vendored
Normal file
61
node_modules/discord.js/src/errors/DJSError.js
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
// Heavily inspired by node's `internal/errors` module
|
||||
|
||||
const kCode = Symbol('code');
|
||||
const messages = new Map();
|
||||
|
||||
/**
|
||||
* Extend an error of some sort into a DiscordjsError.
|
||||
* @param {Error} Base Base error to extend
|
||||
* @returns {DiscordjsError}
|
||||
*/
|
||||
function makeDiscordjsError(Base) {
|
||||
return class DiscordjsError extends Base {
|
||||
constructor(key, ...args) {
|
||||
super(message(key, args));
|
||||
this[kCode] = key;
|
||||
if (Error.captureStackTrace) Error.captureStackTrace(this, DiscordjsError);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return `${super.name} [${this[kCode]}]`;
|
||||
}
|
||||
|
||||
get code() {
|
||||
return this[kCode];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the message for an error.
|
||||
* @param {string} key Error key
|
||||
* @param {Array<*>} args Arguments to pass for util format or as function args
|
||||
* @returns {string} Formatted string
|
||||
*/
|
||||
function message(key, args) {
|
||||
if (typeof key !== 'string') throw new Error('Error message key must be a string');
|
||||
const msg = messages.get(key);
|
||||
if (!msg) throw new Error(`An invalid error message key was used: ${key}.`);
|
||||
if (typeof msg === 'function') return msg(...args);
|
||||
if (args === undefined || args.length === 0) return msg;
|
||||
args.unshift(msg);
|
||||
return String(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an error code and message.
|
||||
* @param {string} sym Unique name for the error
|
||||
* @param {*} val Value of the error
|
||||
*/
|
||||
function register(sym, val) {
|
||||
messages.set(sym, typeof val === 'function' ? val : String(val));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
register,
|
||||
Error: makeDiscordjsError(Error),
|
||||
TypeError: makeDiscordjsError(TypeError),
|
||||
RangeError: makeDiscordjsError(RangeError),
|
||||
};
|
104
node_modules/discord.js/src/errors/Messages.js
generated
vendored
Normal file
104
node_modules/discord.js/src/errors/Messages.js
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
const { register } = require('./DJSError');
|
||||
|
||||
const Messages = {
|
||||
CLIENT_INVALID_OPTION: (prop, must) => `The ${prop} option must be ${must}`,
|
||||
CLIENT_INVALID_PROVIDED_SHARDS: 'None of the provided shards were valid.',
|
||||
|
||||
TOKEN_INVALID: 'An invalid token was provided.',
|
||||
TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.',
|
||||
|
||||
WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.',
|
||||
WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.',
|
||||
WS_NOT_OPEN: (data = 'data') => `Websocket not open to send ${data}`,
|
||||
|
||||
BITFIELD_INVALID: 'Invalid bitfield flag or number.',
|
||||
|
||||
SHARDING_INVALID: 'Invalid shard settings were provided.',
|
||||
SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.',
|
||||
INVALID_INTENTS: 'Invalid intent provided for WebSocket intents.',
|
||||
DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.',
|
||||
SHARDING_NO_SHARDS: 'No shards have been spawned.',
|
||||
SHARDING_IN_PROCESS: 'Shards are still being spawned.',
|
||||
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`,
|
||||
SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`,
|
||||
SHARDING_READY_TIMEOUT: id => `Shard ${id}'s Client took too long to become ready.`,
|
||||
SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`,
|
||||
SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`,
|
||||
|
||||
COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).',
|
||||
COLOR_CONVERT: 'Unable to convert color to a number.',
|
||||
|
||||
EMBED_FIELD_NAME: 'MessageEmbed field names may not be empty.',
|
||||
EMBED_FIELD_VALUE: 'MessageEmbed field values may not be empty.',
|
||||
|
||||
FILE_NOT_FOUND: file => `File could not be found: ${file}`,
|
||||
|
||||
USER_NO_DMCHANNEL: 'No DM Channel exists!',
|
||||
|
||||
VOICE_INVALID_HEARTBEAT: 'Tried to set voice heartbeat but no valid interval was specified.',
|
||||
VOICE_USER_MISSING: "Couldn't resolve the user to create stream.",
|
||||
VOICE_JOIN_CHANNEL: (full = false) =>
|
||||
`You do not have permission to join this voice channel${full ? '; it is full.' : '.'}`,
|
||||
VOICE_CONNECTION_TIMEOUT: 'Connection not established within 15 seconds.',
|
||||
VOICE_TOKEN_ABSENT: 'Token not provided from voice server packet.',
|
||||
VOICE_SESSION_ABSENT: 'Session ID not supplied.',
|
||||
VOICE_INVALID_ENDPOINT: 'Invalid endpoint received.',
|
||||
VOICE_NO_BROWSER: 'Voice connections are not available in browsers.',
|
||||
VOICE_CONNECTION_ATTEMPTS_EXCEEDED: attempts => `Too many connection attempts (${attempts}).`,
|
||||
VOICE_JOIN_SOCKET_CLOSED: 'Tried to send join packet, but the WebSocket is not open.',
|
||||
VOICE_PLAY_INTERFACE_NO_BROADCAST: 'A broadcast cannot be played in this context.',
|
||||
VOICE_PLAY_INTERFACE_BAD_TYPE: 'Unknown stream type',
|
||||
VOICE_PRISM_DEMUXERS_NEED_STREAM: 'To play a webm/ogg stream, you need to pass a ReadableStream.',
|
||||
|
||||
VOICE_STATE_UNCACHED_MEMBER: 'The member of this voice state is uncached.',
|
||||
VOICE_STATE_NOT_OWN: 'You cannot self-deafen/mute on VoiceStates that do not belong to the ClientUser.',
|
||||
VOICE_STATE_INVALID_TYPE: name => `${name} must be a boolean.`,
|
||||
|
||||
UDP_SEND_FAIL: 'Tried to send a UDP packet, but there is no socket available.',
|
||||
UDP_ADDRESS_MALFORMED: 'Malformed UDP address or port.',
|
||||
UDP_CONNECTION_EXISTS: 'There is already an existing UDP connection.',
|
||||
|
||||
REQ_RESOURCE_TYPE: 'The resource must be a string, Buffer or a valid file stream.',
|
||||
|
||||
IMAGE_FORMAT: format => `Invalid image format: ${format}`,
|
||||
IMAGE_SIZE: size => `Invalid image size: ${size}`,
|
||||
|
||||
MESSAGE_BULK_DELETE_TYPE: 'The messages must be an Array, Collection, or number.',
|
||||
MESSAGE_NONCE_TYPE: 'Message nonce must fit in an unsigned 64-bit integer.',
|
||||
|
||||
TYPING_COUNT: 'Count must be at least 1',
|
||||
|
||||
SPLIT_MAX_LEN: 'Chunk exceeds the max length and contains no split characters.',
|
||||
|
||||
BAN_RESOLVE_ID: (ban = false) => `Couldn't resolve the user ID to ${ban ? 'ban' : 'unban'}.`,
|
||||
FETCH_BAN_RESOLVE_ID: "Couldn't resolve the user ID to fetch the ban.",
|
||||
|
||||
PRUNE_DAYS_TYPE: 'Days must be a number',
|
||||
|
||||
GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.',
|
||||
GUILD_VOICE_CHANNEL_RESOLVE: 'Could not resolve channel to a guild voice channel.',
|
||||
GUILD_CHANNEL_ORPHAN: 'Could not find a parent to this guild channel.',
|
||||
GUILD_OWNED: 'Guild is owned by the client.',
|
||||
GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.",
|
||||
GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.',
|
||||
|
||||
INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`,
|
||||
|
||||
WEBHOOK_MESSAGE: 'The message was not sent by a webhook.',
|
||||
|
||||
EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji',
|
||||
EMOJI_MANAGED: 'Emoji is managed and has no Author.',
|
||||
MISSING_MANAGE_EMOJIS_PERMISSION: guild =>
|
||||
`Client must have Manage Emoji permission in guild ${guild} to see emoji authors.`,
|
||||
|
||||
REACTION_RESOLVE_USER: "Couldn't resolve the user ID to remove from the reaction.",
|
||||
|
||||
VANITY_URL: 'This guild does not have the VANITY_URL feature enabled.',
|
||||
|
||||
DELETE_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot delete them",
|
||||
FETCH_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot fetch them",
|
||||
};
|
||||
|
||||
for (const [name, message] of Object.entries(Messages)) register(name, message);
|
4
node_modules/discord.js/src/errors/index.js
generated
vendored
Normal file
4
node_modules/discord.js/src/errors/index.js
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./DJSError');
|
||||
module.exports.Messages = require('./Messages');
|
102
node_modules/discord.js/src/index.js
generated
vendored
Normal file
102
node_modules/discord.js/src/index.js
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
const Util = require('./util/Util');
|
||||
|
||||
module.exports = {
|
||||
// "Root" classes (starting points)
|
||||
BaseClient: require('./client/BaseClient'),
|
||||
Client: require('./client/Client'),
|
||||
Shard: require('./sharding/Shard'),
|
||||
ShardClientUtil: require('./sharding/ShardClientUtil'),
|
||||
ShardingManager: require('./sharding/ShardingManager'),
|
||||
WebhookClient: require('./client/WebhookClient'),
|
||||
|
||||
// Utilities
|
||||
ActivityFlags: require('./util/ActivityFlags'),
|
||||
BitField: require('./util/BitField'),
|
||||
Collection: require('./util/Collection'),
|
||||
Constants: require('./util/Constants'),
|
||||
DataResolver: require('./util/DataResolver'),
|
||||
BaseManager: require('./managers/BaseManager'),
|
||||
DiscordAPIError: require('./rest/DiscordAPIError'),
|
||||
HTTPError: require('./rest/HTTPError'),
|
||||
MessageFlags: require('./util/MessageFlags'),
|
||||
Intents: require('./util/Intents'),
|
||||
Permissions: require('./util/Permissions'),
|
||||
Speaking: require('./util/Speaking'),
|
||||
Snowflake: require('./util/Snowflake'),
|
||||
SnowflakeUtil: require('./util/Snowflake'),
|
||||
Structures: require('./util/Structures'),
|
||||
SystemChannelFlags: require('./util/SystemChannelFlags'),
|
||||
Util: Util,
|
||||
version: require('../package.json').version,
|
||||
|
||||
// Managers
|
||||
ChannelManager: require('./managers/ChannelManager'),
|
||||
GuildChannelManager: require('./managers/GuildChannelManager'),
|
||||
GuildEmojiManager: require('./managers/GuildEmojiManager'),
|
||||
GuildEmojiRoleManager: require('./managers/GuildEmojiRoleManager'),
|
||||
GuildMemberManager: require('./managers/GuildMemberManager'),
|
||||
GuildMemberRoleManager: require('./managers/GuildMemberRoleManager'),
|
||||
GuildManager: require('./managers/GuildManager'),
|
||||
ReactionUserManager: require('./managers/ReactionUserManager'),
|
||||
MessageManager: require('./managers/MessageManager'),
|
||||
PresenceManager: require('./managers/PresenceManager'),
|
||||
RoleManager: require('./managers/RoleManager'),
|
||||
UserManager: require('./managers/UserManager'),
|
||||
|
||||
// Shortcuts to Util methods
|
||||
discordSort: Util.discordSort,
|
||||
escapeMarkdown: Util.escapeMarkdown,
|
||||
fetchRecommendedShards: Util.fetchRecommendedShards,
|
||||
resolveColor: Util.resolveColor,
|
||||
resolveString: Util.resolveString,
|
||||
splitMessage: Util.splitMessage,
|
||||
|
||||
// Structures
|
||||
Base: require('./structures/Base'),
|
||||
Activity: require('./structures/Presence').Activity,
|
||||
APIMessage: require('./structures/APIMessage'),
|
||||
CategoryChannel: require('./structures/CategoryChannel'),
|
||||
Channel: require('./structures/Channel'),
|
||||
ClientApplication: require('./structures/ClientApplication'),
|
||||
get ClientUser() {
|
||||
// This is a getter so that it properly extends any custom User class
|
||||
return require('./structures/ClientUser');
|
||||
},
|
||||
Collector: require('./structures/interfaces/Collector'),
|
||||
DMChannel: require('./structures/DMChannel'),
|
||||
Emoji: require('./structures/Emoji'),
|
||||
Guild: require('./structures/Guild'),
|
||||
GuildAuditLogs: require('./structures/GuildAuditLogs'),
|
||||
GuildChannel: require('./structures/GuildChannel'),
|
||||
GuildEmoji: require('./structures/GuildEmoji'),
|
||||
GuildMember: require('./structures/GuildMember'),
|
||||
Integration: require('./structures/Integration'),
|
||||
Invite: require('./structures/Invite'),
|
||||
Message: require('./structures/Message'),
|
||||
MessageAttachment: require('./structures/MessageAttachment'),
|
||||
MessageCollector: require('./structures/MessageCollector'),
|
||||
MessageEmbed: require('./structures/MessageEmbed'),
|
||||
MessageMentions: require('./structures/MessageMentions'),
|
||||
MessageReaction: require('./structures/MessageReaction'),
|
||||
NewsChannel: require('./structures/NewsChannel'),
|
||||
PermissionOverwrites: require('./structures/PermissionOverwrites'),
|
||||
Presence: require('./structures/Presence').Presence,
|
||||
ClientPresence: require('./structures/ClientPresence'),
|
||||
ReactionCollector: require('./structures/ReactionCollector'),
|
||||
ReactionEmoji: require('./structures/ReactionEmoji'),
|
||||
RichPresenceAssets: require('./structures/Presence').RichPresenceAssets,
|
||||
Role: require('./structures/Role'),
|
||||
StoreChannel: require('./structures/StoreChannel'),
|
||||
Team: require('./structures/Team'),
|
||||
TeamMember: require('./structures/TeamMember'),
|
||||
TextChannel: require('./structures/TextChannel'),
|
||||
User: require('./structures/User'),
|
||||
VoiceChannel: require('./structures/VoiceChannel'),
|
||||
VoiceRegion: require('./structures/VoiceRegion'),
|
||||
VoiceState: require('./structures/VoiceState'),
|
||||
Webhook: require('./structures/Webhook'),
|
||||
|
||||
WebSocket: require('./WebSocket'),
|
||||
};
|
81
node_modules/discord.js/src/managers/BaseManager.js
generated
vendored
Normal file
81
node_modules/discord.js/src/managers/BaseManager.js
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
const Collection = require('../util/Collection');
|
||||
let Structures;
|
||||
|
||||
/**
|
||||
* Manages the API methods of a data model and holds its cache.
|
||||
* @abstract
|
||||
*/
|
||||
class BaseManager {
|
||||
constructor(client, iterable, holds, cacheType = Collection, ...cacheOptions) {
|
||||
if (!Structures) Structures = require('../util/Structures');
|
||||
/**
|
||||
* The data structure belonging to this manager
|
||||
* @name BaseManager#holds
|
||||
* @type {Function}
|
||||
* @private
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(this, 'holds', { value: Structures.get(holds.name) || holds });
|
||||
|
||||
/**
|
||||
* The client that instantiated this Manager
|
||||
* @name BaseManager#client
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(this, 'client', { value: client });
|
||||
|
||||
/**
|
||||
* The type of Collection of the Manager
|
||||
* @type {Collection}
|
||||
*/
|
||||
this.cacheType = cacheType;
|
||||
|
||||
/**
|
||||
* Holds the cache for the data model
|
||||
* @type {Collection}
|
||||
*/
|
||||
this.cache = new cacheType(...cacheOptions);
|
||||
if (iterable) for (const i of iterable) this.add(i);
|
||||
}
|
||||
|
||||
add(data, cache = true, { id, extras = [] } = {}) {
|
||||
const existing = this.cache.get(id || data.id);
|
||||
if (existing && existing._patch && cache) existing._patch(data);
|
||||
if (existing) return existing;
|
||||
|
||||
const entry = this.holds ? new this.holds(this.client, data, ...extras) : data;
|
||||
if (cache) this.cache.set(id || entry.id, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a data entry to a data Object.
|
||||
* @param {string|Object} idOrInstance The id or instance of something in this Manager
|
||||
* @returns {?Object} An instance from this Manager
|
||||
*/
|
||||
resolve(idOrInstance) {
|
||||
if (idOrInstance instanceof this.holds) return idOrInstance;
|
||||
if (typeof idOrInstance === 'string') return this.cache.get(idOrInstance) || null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a data entry to a instance ID.
|
||||
* @param {string|Object} idOrInstance The id or instance of something in this Manager
|
||||
* @returns {?Snowflake}
|
||||
*/
|
||||
resolveID(idOrInstance) {
|
||||
if (idOrInstance instanceof this.holds) return idOrInstance.id;
|
||||
if (typeof idOrInstance === 'string') return idOrInstance;
|
||||
return null;
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return this.cache;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseManager;
|
93
node_modules/discord.js/src/managers/ChannelManager.js
generated
vendored
Normal file
93
node_modules/discord.js/src/managers/ChannelManager.js
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
const BaseManager = require('./BaseManager');
|
||||
const Channel = require('../structures/Channel');
|
||||
const { Events } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* A manager of channels belonging to a client
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class ChannelManager extends BaseManager {
|
||||
constructor(client, iterable) {
|
||||
super(client, iterable, Channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of Channels
|
||||
* @type {Collection<Snowflake, Channel>}
|
||||
* @name ChannelManager#cache
|
||||
*/
|
||||
|
||||
add(data, guild, cache = true) {
|
||||
const existing = this.cache.get(data.id);
|
||||
if (existing) {
|
||||
if (existing._patch && cache) existing._patch(data);
|
||||
if (guild) guild.channels.add(existing);
|
||||
return existing;
|
||||
}
|
||||
|
||||
const channel = Channel.create(this.client, data, guild);
|
||||
|
||||
if (!channel) {
|
||||
this.client.emit(Events.DEBUG, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cache) this.cache.set(channel.id, channel);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
remove(id) {
|
||||
const channel = this.cache.get(id);
|
||||
if (channel.guild) channel.guild.channels.cache.delete(id);
|
||||
this.cache.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give a Channel object. This can be:
|
||||
* * A Channel object
|
||||
* * A Snowflake
|
||||
* @typedef {Channel|Snowflake} ChannelResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a ChannelResolvable to a Channel object.
|
||||
* @method resolve
|
||||
* @memberof ChannelManager
|
||||
* @instance
|
||||
* @param {ChannelResolvable} channel The channel resolvable to resolve
|
||||
* @returns {?Channel}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a ChannelResolvable to a channel ID string.
|
||||
* @method resolveID
|
||||
* @memberof ChannelManager
|
||||
* @instance
|
||||
* @param {ChannelResolvable} channel The channel resolvable to resolve
|
||||
* @returns {?Snowflake}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtains a channel from Discord, or the channel cache if it's already available.
|
||||
* @param {Snowflake} id ID of the channel
|
||||
* @param {boolean} [cache=true] Whether to cache the new channel object if it isn't already
|
||||
* @returns {Promise<Channel>}
|
||||
* @example
|
||||
* // Fetch a channel by its id
|
||||
* client.channels.fetch('222109930545610754')
|
||||
* .then(channel => console.log(channel.name))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async fetch(id, cache = true) {
|
||||
const existing = this.cache.get(id);
|
||||
if (existing && !existing.partial) return existing;
|
||||
|
||||
const data = await this.client.api.channels(id).get();
|
||||
return this.add(data, null, cache);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChannelManager;
|
131
node_modules/discord.js/src/managers/GuildChannelManager.js
generated
vendored
Normal file
131
node_modules/discord.js/src/managers/GuildChannelManager.js
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
'use strict';
|
||||
|
||||
const BaseManager = require('./BaseManager');
|
||||
const GuildChannel = require('../structures/GuildChannel');
|
||||
const PermissionOverwrites = require('../structures/PermissionOverwrites');
|
||||
const { ChannelTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Manages API methods for GuildChannels and stores their cache.
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class GuildChannelManager extends BaseManager {
|
||||
constructor(guild, iterable) {
|
||||
super(guild.client, iterable, GuildChannel);
|
||||
|
||||
/**
|
||||
* The guild this Manager belongs to
|
||||
* @type {Guild}
|
||||
*/
|
||||
this.guild = guild;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of this Manager
|
||||
* @type {Collection<Snowflake, GuildChannel>}
|
||||
* @name GuildChannelManager#cache
|
||||
*/
|
||||
|
||||
add(channel) {
|
||||
const existing = this.cache.get(channel.id);
|
||||
if (existing) return existing;
|
||||
this.cache.set(channel.id, channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give a Guild Channel object. This can be:
|
||||
* * A GuildChannel object
|
||||
* * A Snowflake
|
||||
* @typedef {GuildChannel|Snowflake} GuildChannelResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a GuildChannelResolvable to a Channel object.
|
||||
* @method resolve
|
||||
* @memberof GuildChannelManager
|
||||
* @instance
|
||||
* @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve
|
||||
* @returns {?Channel}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a GuildChannelResolvable to a channel ID string.
|
||||
* @method resolveID
|
||||
* @memberof GuildChannelManager
|
||||
* @instance
|
||||
* @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve
|
||||
* @returns {?Snowflake}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new channel in the guild.
|
||||
* @param {string} name The name of the new channel
|
||||
* @param {Object} [options] Options
|
||||
* @param {string} [options.type='text'] The type of the new channel, either `text`, `voice`, or `category`
|
||||
* @param {string} [options.topic] The topic for the new channel
|
||||
* @param {boolean} [options.nsfw] Whether the new channel is nsfw
|
||||
* @param {number} [options.bitrate] Bitrate of the new channel in bits (only voice)
|
||||
* @param {number} [options.userLimit] Maximum amount of users allowed in the new channel (only voice)
|
||||
* @param {ChannelResolvable} [options.parent] Parent of the new channel
|
||||
* @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [options.permissionOverwrites]
|
||||
* Permission overwrites of the new channel
|
||||
* @param {number} [options.position] Position of the new channel
|
||||
* @param {number} [options.rateLimitPerUser] The ratelimit per user for the channel
|
||||
* @param {string} [options.reason] Reason for creating the channel
|
||||
* @returns {Promise<GuildChannel>}
|
||||
* @example
|
||||
* // Create a new text channel
|
||||
* guild.channels.create('new-general', { reason: 'Needed a cool new channel' })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Create a new channel with permission overwrites
|
||||
* guild.channels.create('new-voice', {
|
||||
* type: 'voice',
|
||||
* permissionOverwrites: [
|
||||
* {
|
||||
* id: message.author.id,
|
||||
* deny: ['VIEW_CHANNEL'],
|
||||
* },
|
||||
* ],
|
||||
* })
|
||||
*/
|
||||
async create(name, options = {}) {
|
||||
let {
|
||||
type,
|
||||
topic,
|
||||
nsfw,
|
||||
bitrate,
|
||||
userLimit,
|
||||
parent,
|
||||
permissionOverwrites,
|
||||
position,
|
||||
rateLimitPerUser,
|
||||
reason,
|
||||
} = options;
|
||||
if (parent) parent = this.client.channels.resolveID(parent);
|
||||
if (permissionOverwrites) {
|
||||
permissionOverwrites = permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild));
|
||||
}
|
||||
|
||||
const data = await this.client.api.guilds(this.guild.id).channels.post({
|
||||
data: {
|
||||
name,
|
||||
topic,
|
||||
type: type ? ChannelTypes[type.toUpperCase()] : ChannelTypes.TEXT,
|
||||
nsfw,
|
||||
bitrate,
|
||||
user_limit: userLimit,
|
||||
parent_id: parent,
|
||||
position,
|
||||
permission_overwrites: permissionOverwrites,
|
||||
rate_limit_per_user: rateLimitPerUser,
|
||||
},
|
||||
reason,
|
||||
});
|
||||
return this.client.actions.ChannelCreate.handle(data).channel;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildChannelManager;
|
130
node_modules/discord.js/src/managers/GuildEmojiManager.js
generated
vendored
Normal file
130
node_modules/discord.js/src/managers/GuildEmojiManager.js
generated
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
'use strict';
|
||||
|
||||
const BaseManager = require('./BaseManager');
|
||||
const { TypeError } = require('../errors');
|
||||
const GuildEmoji = require('../structures/GuildEmoji');
|
||||
const ReactionEmoji = require('../structures/ReactionEmoji');
|
||||
const Collection = require('../util/Collection');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
|
||||
/**
|
||||
* Manages API methods for GuildEmojis and stores their cache.
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class GuildEmojiManager extends BaseManager {
|
||||
constructor(guild, iterable) {
|
||||
super(guild.client, iterable, GuildEmoji);
|
||||
/**
|
||||
* The guild this manager belongs to
|
||||
* @type {Guild}
|
||||
*/
|
||||
this.guild = guild;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of GuildEmojis
|
||||
* @type {Collection<Snowflake, GuildEmoji>}
|
||||
* @name GuildEmojiManager#cache
|
||||
*/
|
||||
|
||||
add(data, cache) {
|
||||
return super.add(data, cache, { extras: [this.guild] });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new custom emoji in the guild.
|
||||
* @param {BufferResolvable|Base64Resolvable} attachment The image for the emoji
|
||||
* @param {string} name The name for the emoji
|
||||
* @param {Object} [options] Options
|
||||
* @param {Collection<Snowflake, Role>|RoleResolvable[]} [options.roles] Roles to limit the emoji to
|
||||
* @param {string} [options.reason] Reason for creating the emoji
|
||||
* @returns {Promise<Emoji>} The created emoji
|
||||
* @example
|
||||
* // Create a new emoji from a url
|
||||
* guild.emojis.create('https://i.imgur.com/w3duR07.png', 'rip')
|
||||
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Create a new emoji from a file on your computer
|
||||
* guild.emojis.create('./memes/banana.png', 'banana')
|
||||
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
create(attachment, name, { roles, reason } = {}) {
|
||||
if (typeof attachment === 'string' && attachment.startsWith('data:')) {
|
||||
const data = { image: attachment, name };
|
||||
if (roles) {
|
||||
data.roles = [];
|
||||
for (let role of roles instanceof Collection ? roles.values() : roles) {
|
||||
role = this.guild.roles.resolve(role);
|
||||
if (!role) {
|
||||
return Promise.reject(
|
||||
new TypeError('INVALID_TYPE', 'options.roles', 'Array or Collection of Roles or Snowflakes', true),
|
||||
);
|
||||
}
|
||||
data.roles.push(role.id);
|
||||
}
|
||||
}
|
||||
|
||||
return this.client.api
|
||||
.guilds(this.guild.id)
|
||||
.emojis.post({ data, reason })
|
||||
.then(emoji => this.client.actions.GuildEmojiCreate.handle(this.guild, emoji).emoji);
|
||||
}
|
||||
|
||||
return DataResolver.resolveImage(attachment).then(image => this.create(image, name, { roles, reason }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved into an GuildEmoji object. This can be:
|
||||
* * A custom emoji ID
|
||||
* * A GuildEmoji object
|
||||
* * A ReactionEmoji object
|
||||
* @typedef {Snowflake|GuildEmoji|ReactionEmoji} EmojiResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves an EmojiResolvable to an Emoji object.
|
||||
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
|
||||
* @returns {?GuildEmoji}
|
||||
*/
|
||||
resolve(emoji) {
|
||||
if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id);
|
||||
return super.resolve(emoji);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an EmojiResolvable to an Emoji ID string.
|
||||
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
|
||||
* @returns {?Snowflake}
|
||||
*/
|
||||
resolveID(emoji) {
|
||||
if (emoji instanceof ReactionEmoji) return emoji.id;
|
||||
return super.resolveID(emoji);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give an emoji identifier. This can be:
|
||||
* * The unicode representation of an emoji
|
||||
* * An EmojiResolvable
|
||||
* @typedef {string|EmojiResolvable} EmojiIdentifierResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves an EmojiResolvable to an emoji identifier.
|
||||
* @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve
|
||||
* @returns {?string}
|
||||
*/
|
||||
resolveIdentifier(emoji) {
|
||||
const emojiResolvable = this.resolve(emoji);
|
||||
if (emojiResolvable) return emojiResolvable.identifier;
|
||||
if (emoji instanceof ReactionEmoji) return emoji.identifier;
|
||||
if (typeof emoji === 'string') {
|
||||
if (!emoji.includes('%')) return encodeURIComponent(emoji);
|
||||
else return emoji;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildEmojiManager;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user