aboutsummaryrefslogtreecommitdiff
path: root/node_modules/discord.js/src/client/voice
diff options
context:
space:
mode:
authorAlee14 <alee14498@gmail.com>2017-03-26 15:18:10 -0400
committerAlee14 <alee14498@gmail.com>2017-03-26 15:18:10 -0400
commit29433e2f7dbd0e4a73d3c78ffe1005b922fb5982 (patch)
treeaa0ad3fe59468cbe452ee597e914839b68c01436 /node_modules/discord.js/src/client/voice
parent878fefb4c4e1f12b804ae5c0def433fa873f4c8b (diff)
downloadAleeBot-29433e2f7dbd0e4a73d3c78ffe1005b922fb5982.tar.gz
AleeBot-29433e2f7dbd0e4a73d3c78ffe1005b922fb5982.tar.bz2
AleeBot-29433e2f7dbd0e4a73d3c78ffe1005b922fb5982.zip
Don't mind me i'm adding the discord.js files
Diffstat (limited to 'node_modules/discord.js/src/client/voice')
-rw-r--r--node_modules/discord.js/src/client/voice/ClientVoiceManager.js245
-rw-r--r--node_modules/discord.js/src/client/voice/VoiceConnection.js276
-rw-r--r--node_modules/discord.js/src/client/voice/VoiceUDPClient.js145
-rw-r--r--node_modules/discord.js/src/client/voice/VoiceWebSocket.js249
-rw-r--r--node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js307
-rw-r--r--node_modules/discord.js/src/client/voice/opus/BaseOpusEngine.js15
-rw-r--r--node_modules/discord.js/src/client/voice/opus/NodeOpusEngine.js27
-rw-r--r--node_modules/discord.js/src/client/voice/opus/OpusEngineList.js24
-rw-r--r--node_modules/discord.js/src/client/voice/opus/OpusScriptEngine.js27
-rw-r--r--node_modules/discord.js/src/client/voice/pcm/ConverterEngine.js14
-rw-r--r--node_modules/discord.js/src/client/voice/pcm/ConverterEngineList.js1
-rw-r--r--node_modules/discord.js/src/client/voice/pcm/FfmpegConverterEngine.js86
-rw-r--r--node_modules/discord.js/src/client/voice/player/AudioPlayer.js80
-rw-r--r--node_modules/discord.js/src/client/voice/player/BasePlayer.js121
-rw-r--r--node_modules/discord.js/src/client/voice/player/DefaultPlayer.js19
-rw-r--r--node_modules/discord.js/src/client/voice/receiver/VoiceReadable.js19
-rw-r--r--node_modules/discord.js/src/client/voice/receiver/VoiceReceiver.js154
-rw-r--r--node_modules/discord.js/src/client/voice/util/SecretKey.js16
18 files changed, 1825 insertions, 0 deletions
diff --git a/node_modules/discord.js/src/client/voice/ClientVoiceManager.js b/node_modules/discord.js/src/client/voice/ClientVoiceManager.js
new file mode 100644
index 0000000..ea83745
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/ClientVoiceManager.js
@@ -0,0 +1,245 @@
+const Collection = require('../../util/Collection');
+const mergeDefault = require('../../util/MergeDefault');
+const Constants = require('../../util/Constants');
+const VoiceConnection = require('./VoiceConnection');
+const EventEmitter = require('events').EventEmitter;
+
+/**
+ * Manages all the voice stuff for the Client
+ * @private
+ */
+class ClientVoiceManager {
+ constructor(client) {
+ /**
+ * The client that instantiated this voice manager
+ * @type {Client}
+ */
+ this.client = client;
+
+ /**
+ * A collection mapping connection IDs to the Connection objects
+ * @type {Collection<string, VoiceConnection>}
+ */
+ this.connections = new Collection();
+
+ /**
+ * Pending connection attempts, maps guild ID to VoiceChannel
+ * @type {Collection<string, VoiceChannel>}
+ */
+ this.pending = new Collection();
+
+ this.client.on('self.voiceServer', this.onVoiceServer.bind(this));
+ this.client.on('self.voiceStateUpdate', this.onVoiceStateUpdate.bind(this));
+ }
+
+ onVoiceServer(data) {
+ if (this.pending.has(data.guild_id)) this.pending.get(data.guild_id).setTokenAndEndpoint(data.token, data.endpoint);
+ }
+
+ onVoiceStateUpdate(data) {
+ if (this.pending.has(data.guild_id)) this.pending.get(data.guild_id).setSessionID(data.session_id);
+ }
+
+ /**
+ * Sends a request to the main gateway to join a voice channel
+ * @param {VoiceChannel} channel The channel to join
+ * @param {Object} [options] The options to provide
+ */
+ sendVoiceStateUpdate(channel, options = {}) {
+ if (!this.client.user) throw new Error('Unable to join because there is no client user.');
+ if (!channel.permissionsFor) {
+ throw new Error('Channel does not support permissionsFor; is it really a voice channel?');
+ }
+ const permissions = channel.permissionsFor(this.client.user);
+ if (!permissions) {
+ throw new Error('There is no permission set for the client user in this channel - are they part of the guild?');
+ }
+ if (!permissions.hasPermission('CONNECT')) {
+ throw new Error('You do not have permission to join this voice channel.');
+ }
+
+ options = mergeDefault({
+ guild_id: channel.guild.id,
+ channel_id: channel.id,
+ self_mute: false,
+ self_deaf: false,
+ }, options);
+
+ this.client.ws.send({
+ op: Constants.OPCodes.VOICE_STATE_UPDATE,
+ d: options,
+ });
+ }
+
+ /**
+ * Sets up a request to join a voice channel
+ * @param {VoiceChannel} channel The voice channel to join
+ * @returns {Promise<VoiceConnection>}
+ */
+ joinChannel(channel) {
+ return new Promise((resolve, reject) => {
+ if (this.pending.get(channel.guild.id)) throw new Error('Already connecting to this guild\'s voice server.');
+ if (!channel.joinable) throw new Error('You do not have permission to join this voice channel.');
+
+ const existingConnection = this.connections.get(channel.guild.id);
+ if (existingConnection) {
+ if (existingConnection.channel.id !== channel.id) {
+ this.sendVoiceStateUpdate(channel);
+ this.connections.get(channel.guild.id).channel = channel;
+ }
+ resolve(existingConnection);
+ return;
+ }
+
+ const pendingConnection = new PendingVoiceConnection(this, channel);
+ this.pending.set(channel.guild.id, pendingConnection);
+
+ pendingConnection.on('fail', reason => {
+ this.pending.delete(channel.guild.id);
+ reject(reason);
+ });
+
+ pendingConnection.on('pass', voiceConnection => {
+ this.pending.delete(channel.guild.id);
+ this.connections.set(channel.guild.id, voiceConnection);
+ voiceConnection.once('ready', () => resolve(voiceConnection));
+ voiceConnection.once('error', reject);
+ voiceConnection.once('disconnect', () => this.connections.delete(channel.guild.id));
+ });
+ });
+ }
+}
+
+/**
+ * Represents a Pending Voice Connection
+ * @private
+ */
+class PendingVoiceConnection extends EventEmitter {
+ constructor(voiceManager, channel) {
+ super();
+
+ /**
+ * The ClientVoiceManager that instantiated this pending connection
+ * @type {ClientVoiceManager}
+ */
+ this.voiceManager = voiceManager;
+
+ /**
+ * The channel that this pending voice connection will attempt to join
+ * @type {VoiceChannel}
+ */
+ this.channel = channel;
+
+ /**
+ * The timeout that will be invoked after 15 seconds signifying a failure to connect
+ * @type {Timeout}
+ */
+ this.deathTimer = this.voiceManager.client.setTimeout(
+ () => this.fail(new Error('Connection not established within 15 seconds.')), 15000);
+
+ /**
+ * An object containing data required to connect to the voice servers with
+ * @type {Object}
+ */
+ this.data = {};
+
+ this.sendVoiceStateUpdate();
+ }
+
+ checkReady() {
+ if (this.data.token && this.data.endpoint && this.data.session_id) {
+ this.pass();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Set the token and endpoint required to connect to the the voice servers
+ * @param {string} token the token
+ * @param {string} endpoint the endpoint
+ * @returns {void}
+ */
+ setTokenAndEndpoint(token, endpoint) {
+ if (!token) {
+ this.fail(new Error('Token not provided from voice server packet.'));
+ return;
+ }
+ if (!endpoint) {
+ this.fail(new Error('Endpoint not provided from voice server packet.'));
+ return;
+ }
+ if (this.data.token) {
+ this.fail(new Error('There is already a registered token for this connection.'));
+ return;
+ }
+ if (this.data.endpoint) {
+ this.fail(new Error('There is already a registered endpoint for this connection.'));
+ return;
+ }
+
+ endpoint = endpoint.match(/([^:]*)/)[0];
+
+ if (!endpoint) {
+ this.fail(new Error('Failed to find an endpoint.'));
+ return;
+ }
+
+ this.data.token = token;
+ this.data.endpoint = endpoint;
+
+ this.checkReady();
+ }
+
+ /**
+ * Sets the Session ID for the connection
+ * @param {string} sessionID the session ID
+ */
+ setSessionID(sessionID) {
+ if (!sessionID) {
+ this.fail(new Error('Session ID not supplied.'));
+ return;
+ }
+ if (this.data.session_id) {
+ this.fail(new Error('There is already a registered session ID for this connection.'));
+ return;
+ }
+ this.data.session_id = sessionID;
+
+ this.checkReady();
+ }
+
+ clean() {
+ clearInterval(this.deathTimer);
+ this.emit('fail', new Error('Clean-up triggered :fourTriggered:'));
+ }
+
+ pass() {
+ clearInterval(this.deathTimer);
+ this.emit('pass', this.upgrade());
+ }
+
+ fail(reason) {
+ this.emit('fail', reason);
+ this.clean();
+ }
+
+ sendVoiceStateUpdate() {
+ try {
+ this.voiceManager.sendVoiceStateUpdate(this.channel);
+ } catch (error) {
+ this.fail(error);
+ }
+ }
+
+ /**
+ * Upgrades this Pending Connection to a full Voice Connection
+ * @returns {VoiceConnection}
+ */
+ upgrade() {
+ return new VoiceConnection(this);
+ }
+}
+
+module.exports = ClientVoiceManager;
diff --git a/node_modules/discord.js/src/client/voice/VoiceConnection.js b/node_modules/discord.js/src/client/voice/VoiceConnection.js
new file mode 100644
index 0000000..ac44ff8
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/VoiceConnection.js
@@ -0,0 +1,276 @@
+const VoiceWebSocket = require('./VoiceWebSocket');
+const VoiceUDP = require('./VoiceUDPClient');
+const Constants = require('../../util/Constants');
+const AudioPlayer = require('./player/AudioPlayer');
+const VoiceReceiver = require('./receiver/VoiceReceiver');
+const EventEmitter = require('events').EventEmitter;
+const fs = require('fs');
+
+/**
+ * Represents a connection to a voice channel in Discord.
+ * ```js
+ * // obtained using:
+ * voiceChannel.join().then(connection => {
+ *
+ * });
+ * ```
+ * @extends {EventEmitter}
+ */
+class VoiceConnection extends EventEmitter {
+ constructor(pendingConnection) {
+ super();
+
+ /**
+ * The Voice Manager that instantiated this connection
+ * @type {ClientVoiceManager}
+ */
+ this.voiceManager = pendingConnection.voiceManager;
+
+ /**
+ * The voice channel this connection is currently serving
+ * @type {VoiceChannel}
+ */
+ this.channel = pendingConnection.channel;
+
+ /**
+ * Whether we're currently transmitting audio
+ * @type {boolean}
+ */
+ this.speaking = false;
+
+ /**
+ * An array of Voice Receivers that have been created for this connection
+ * @type {VoiceReceiver[]}
+ */
+ this.receivers = [];
+
+ /**
+ * The authentication data needed to connect to the voice server
+ * @type {Object}
+ * @private
+ */
+ this.authentication = pendingConnection.data;
+
+ /**
+ * 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.player.cleanup();
+ });
+
+ /**
+ * Map SSRC to speaking values
+ * @type {Map<number, boolean>}
+ * @private
+ */
+ this.ssrcMap = new Map();
+
+ /**
+ * Whether this connection is ready
+ * @type {boolean}
+ * @private
+ */
+ this.ready = false;
+
+ /**
+ * Object that wraps contains the `ws` and `udp` sockets of this voice connection
+ * @type {Object}
+ * @private
+ */
+ this.sockets = {};
+ this.connect();
+ }
+
+ /**
+ * Sets whether the voice connection should display as "speaking" or not
+ * @param {boolean} value whether or not to speak
+ * @private
+ */
+ setSpeaking(value) {
+ if (this.speaking === value) return;
+ this.speaking = value;
+ this.sockets.ws.sendPacket({
+ op: Constants.VoiceOPCodes.SPEAKING,
+ d: {
+ speaking: true,
+ delay: 0,
+ },
+ }).catch(e => {
+ this.emit('debug', e);
+ });
+ }
+
+ /**
+ * Disconnect the voice connection, causing a disconnect and closing event to be emitted.
+ */
+ disconnect() {
+ this.emit('closing');
+ this.voiceManager.client.ws.send({
+ op: Constants.OPCodes.VOICE_STATE_UPDATE,
+ d: {
+ guild_id: this.channel.guild.id,
+ channel_id: null,
+ self_mute: false,
+ self_deaf: false,
+ },
+ });
+ /**
+ * Emitted when the voice connection disconnects
+ * @event VoiceConnection#disconnect
+ */
+ this.emit('disconnect');
+ }
+
+ /**
+ * Connect the voice connection
+ * @private
+ */
+ connect() {
+ if (this.sockets.ws) throw new Error('There is already an existing WebSocket connection.');
+ if (this.sockets.udp) throw new Error('There is already an existing UDP connection.');
+ this.sockets.ws = new VoiceWebSocket(this);
+ this.sockets.udp = new VoiceUDP(this);
+ this.sockets.ws.on('error', e => this.emit('error', e));
+ this.sockets.udp.on('error', e => this.emit('error', e));
+ this.sockets.ws.once('ready', d => {
+ this.authentication.port = d.port;
+ this.authentication.ssrc = d.ssrc;
+ /**
+ * Emitted whenever the connection encounters an error.
+ * @event VoiceConnection#error
+ * @param {Error} error the encountered error
+ */
+ this.sockets.udp.findEndpointAddress()
+ .then(address => {
+ this.sockets.udp.createUDPSocket(address);
+ }, e => this.emit('error', e));
+ });
+ this.sockets.ws.once('sessionDescription', (mode, secret) => {
+ this.authentication.encryptionMode = mode;
+ this.authentication.secretKey = secret;
+ /**
+ * 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');
+ this.ready = true;
+ });
+ this.sockets.ws.on('speaking', data => {
+ const guild = this.channel.guild;
+ const user = this.voiceManager.client.users.get(data.user_id);
+ this.ssrcMap.set(+data.ssrc, user);
+ if (!data.speaking) {
+ for (const receiver of this.receivers) {
+ const opusStream = receiver.opusStreams.get(user.id);
+ const pcmStream = receiver.pcmStreams.get(user.id);
+ if (opusStream) {
+ opusStream.push(null);
+ opusStream.open = false;
+ receiver.opusStreams.delete(user.id);
+ }
+ if (pcmStream) {
+ pcmStream.push(null);
+ pcmStream.open = false;
+ receiver.pcmStreams.delete(user.id);
+ }
+ }
+ }
+ /**
+ * Emitted whenever a user starts/stops speaking
+ * @event VoiceConnection#speaking
+ * @param {User} user The user that has started/stopped speaking
+ * @param {boolean} speaking Whether or not the user is speaking
+ */
+ if (this.ready) this.emit('speaking', user, data.speaking);
+ guild._memberSpeakUpdate(data.user_id, data.speaking);
+ });
+ }
+
+ /**
+ * Options that can be passed to stream-playing methods:
+ * @typedef {Object} StreamOptions
+ * @property {number} [seek=0] The time to seek to
+ * @property {number} [volume=1] The volume to play at
+ * @property {number} [passes=1] How many times to send the voice packet to reduce packet loss
+ */
+
+ /**
+ * Play the given file in the voice connection.
+ * @param {string} file The path to the file
+ * @param {StreamOptions} [options] Options for playing the stream
+ * @returns {StreamDispatcher}
+ * @example
+ * // play files natively
+ * voiceChannel.join()
+ * .then(connection => {
+ * const dispatcher = connection.playFile('C:/Users/Discord/Desktop/music.mp3');
+ * })
+ * .catch(console.error);
+ */
+ playFile(file, options) {
+ return this.playStream(fs.createReadStream(file), options);
+ }
+
+ /**
+ * Plays and converts an audio stream in the voice connection.
+ * @param {ReadableStream} stream The audio stream to play
+ * @param {StreamOptions} [options] Options for playing the stream
+ * @returns {StreamDispatcher}
+ * @example
+ * // play streams using ytdl-core
+ * const ytdl = require('ytdl-core');
+ * const streamOptions = { seek: 0, volume: 1 };
+ * voiceChannel.join()
+ * .then(connection => {
+ * const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', {filter : 'audioonly'});
+ * const dispatcher = connection.playStream(stream, streamOptions);
+ * })
+ * .catch(console.error);
+ */
+ playStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
+ const options = { seek, volume, passes };
+ return this.player.playUnknownStream(stream, options);
+ }
+
+ /**
+ * Plays a stream of 16-bit signed stereo PCM at 48KHz.
+ * @param {ReadableStream} stream The audio stream to play.
+ * @param {StreamOptions} [options] Options for playing the stream
+ * @returns {StreamDispatcher}
+ */
+ playConvertedStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
+ const options = { seek, volume, passes };
+ return this.player.playPCMStream(stream, null, options);
+ }
+
+ /**
+ * Creates a VoiceReceiver so you can start listening to voice data. It's recommended to only create one of these.
+ * @returns {VoiceReceiver}
+ */
+ createReceiver() {
+ const receiver = new VoiceReceiver(this);
+ this.receivers.push(receiver);
+ return receiver;
+ }
+}
+
+module.exports = VoiceConnection;
diff --git a/node_modules/discord.js/src/client/voice/VoiceUDPClient.js b/node_modules/discord.js/src/client/voice/VoiceUDPClient.js
new file mode 100644
index 0000000..b7b0c0c
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/VoiceUDPClient.js
@@ -0,0 +1,145 @@
+const udp = require('dgram');
+const dns = require('dns');
+const Constants = require('../../util/Constants');
+const EventEmitter = require('events').EventEmitter;
+
+/**
+ * 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() {
+ if (this.socket) {
+ try {
+ this.socket.close();
+ } catch (e) {
+ return;
+ }
+ this.socket = null;
+ }
+ }
+
+ /**
+ * The port of the discord voice server
+ * @type {number}
+ * @readonly
+ */
+ get discordPort() {
+ return this.voiceConnection.authentication.port;
+ }
+
+ /**
+ * Tries to resolve the voice server endpoint to an address
+ * @returns {Promise<string>}
+ */
+ findEndpointAddress() {
+ return new Promise((resolve, reject) => {
+ dns.lookup(this.voiceConnection.authentication.endpoint, (error, address) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ this.discordAddress = address;
+ resolve(address);
+ });
+ });
+ }
+
+ /**
+ * 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('Tried to send a UDP packet, but there is no socket available.');
+ if (!this.discordAddress || !this.discordPort) throw new Error('Malformed UDP address or port.');
+ this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => {
+ if (error) reject(error); else resolve(packet);
+ });
+ });
+ }
+
+ createUDPSocket(address) {
+ this.discordAddress = address;
+ const socket = this.socket = udp.createSocket('udp4');
+
+ socket.once('message', message => {
+ const packet = parseLocalPacket(message);
+ if (packet.error) {
+ this.emit('error', packet.error);
+ return;
+ }
+
+ this.localAddress = packet.address;
+ this.localPort = packet.port;
+
+ this.voiceConnection.sockets.ws.sendPacket({
+ op: Constants.VoiceOPCodes.SELECT_PROTOCOL,
+ d: {
+ protocol: 'udp',
+ data: {
+ address: packet.address,
+ port: packet.port,
+ mode: 'xsalsa20_poly1305',
+ },
+ },
+ });
+ });
+
+ const blankMessage = new Buffer(70);
+ blankMessage.writeUIntBE(this.voiceConnection.authentication.ssrc, 0, 4);
+ this.send(blankMessage);
+ }
+}
+
+function parseLocalPacket(message) {
+ try {
+ const packet = new Buffer(message);
+ let address = '';
+ for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]);
+ const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
+ return { address, port };
+ } catch (error) {
+ return { error };
+ }
+}
+
+module.exports = VoiceConnectionUDPClient;
diff --git a/node_modules/discord.js/src/client/voice/VoiceWebSocket.js b/node_modules/discord.js/src/client/voice/VoiceWebSocket.js
new file mode 100644
index 0000000..bafa5dd
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/VoiceWebSocket.js
@@ -0,0 +1,249 @@
+const Constants = require('../../util/Constants');
+const SecretKey = require('./util/SecretKey');
+const EventEmitter = require('events').EventEmitter;
+
+let WebSocket;
+try {
+ WebSocket = require('uws');
+} catch (err) {
+ WebSocket = require('ws');
+}
+
+/**
+ * Represents a Voice Connection's WebSocket
+ * @extends {EventEmitter}
+ * @private
+ */
+class VoiceWebSocket extends EventEmitter {
+ constructor(voiceConnection) {
+ super();
+
+ /**
+ * The Voice Connection that this WebSocket serves
+ * @type {VoiceConnection}
+ */
+ this.voiceConnection = voiceConnection;
+
+ /**
+ * How many connection attempts have been made
+ * @type {number}
+ */
+ this.attempts = 0;
+
+ this.connect();
+ this.dead = false;
+ this.voiceConnection.on('closing', this.shutdown.bind(this));
+ }
+
+ shutdown() {
+ this.dead = true;
+ this.reset();
+ }
+
+ /**
+ * The client of this voice websocket
+ * @type {Client}
+ * @readonly
+ */
+ get client() {
+ return this.voiceConnection.voiceManager.client;
+ }
+
+ /**
+ * Resets the current WebSocket
+ */
+ reset() {
+ 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() {
+ if (this.dead) return;
+ if (this.ws) this.reset();
+ if (this.attempts > 5) {
+ this.emit('error', new Error(`Too many connection attempts (${this.attempts}).`));
+ return;
+ }
+
+ this.attempts++;
+
+ /**
+ * The actual WebSocket used to connect to the Voice WebSocket Server.
+ * @type {WebSocket}
+ */
+ this.ws = new WebSocket(`wss://${this.voiceConnection.authentication.endpoint}`);
+ 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) {
+ return new Promise((resolve, reject) => {
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
+ throw new Error(`Voice websocket not open to send ${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.sendPacket({
+ op: Constants.OPCodes.DISPATCH,
+ d: {
+ server_id: this.voiceConnection.channel.guild.id,
+ user_id: this.client.user.id,
+ token: this.voiceConnection.authentication.token,
+ session_id: this.voiceConnection.authentication.session_id,
+ },
+ }).catch(() => {
+ this.emit('error', new Error('Tried to send join packet, but the WebSocket is not open.'));
+ });
+ }
+
+ /**
+ * 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(JSON.parse(event.data));
+ } catch (error) {
+ return this.onError(error);
+ }
+ }
+
+ /**
+ * Called whenever the connection to the WebSocket Server is lost
+ */
+ onClose() {
+ 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('error', error);
+ }
+
+ /**
+ * Called whenever a valid packet is received from the WebSocket
+ * @param {Object} packet the received packet
+ */
+ onPacket(packet) {
+ switch (packet.op) {
+ case Constants.VoiceOPCodes.READY:
+ this.setHeartbeat(packet.d.heartbeat_interval);
+ /**
+ * Emitted once the voice websocket receives the ready packet
+ * @param {Object} packet the received packet
+ * @event VoiceWebSocket#ready
+ */
+ this.emit('ready', packet.d);
+ break;
+ case Constants.VoiceOPCodes.SESSION_DESCRIPTION:
+ /**
+ * Emitted once the Voice Websocket receives a description of this voice session
+ * @param {string} encryptionMode the type of encryption being used
+ * @param {SecretKey} secretKey the secret key used for encryption
+ * @event VoiceWebSocket#sessionDescription
+ */
+ this.emit('sessionDescription', packet.d.mode, new SecretKey(packet.d.secret_key));
+ break;
+ case Constants.VoiceOPCodes.SPEAKING:
+ /**
+ * Emitted whenever a speaking packet is received
+ * @param {Object} data
+ * @event VoiceWebSocket#speaking
+ */
+ this.emit('speaking', 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('Tried to set voice heartbeat but no valid interval was specified.'));
+ return;
+ }
+ if (this.heartbeatInterval) {
+ /**
+ * Emitted whenver 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');
+ 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;
+ }
+ clearInterval(this.heartbeatInterval);
+ this.heartbeatInterval = null;
+ }
+
+ /**
+ * Sends a heartbeat packet
+ */
+ sendHeartbeat() {
+ this.sendPacket({ op: Constants.VoiceOPCodes.HEARTBEAT, d: null }).catch(() => {
+ this.emit('warn', 'Tried to send heartbeat, but connection is not open');
+ this.clearHeartbeat();
+ });
+ }
+}
+
+module.exports = VoiceWebSocket;
diff --git a/node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js b/node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js
new file mode 100644
index 0000000..e08a365
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/dispatcher/StreamDispatcher.js
@@ -0,0 +1,307 @@
+const EventEmitter = require('events').EventEmitter;
+const NaCl = require('tweetnacl');
+
+const nonce = new Buffer(24);
+nonce.fill(0);
+
+/**
+ * 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.playFile('./file.mp3');
+ * });
+ * ```
+ * @extends {EventEmitter}
+ */
+class StreamDispatcher extends EventEmitter {
+ constructor(player, stream, sd, streamOptions) {
+ super();
+ this.player = player;
+ this.stream = stream;
+ this.streamingData = {
+ channels: 2,
+ count: 0,
+ sequence: sd.sequence,
+ timestamp: sd.timestamp,
+ pausedTime: 0,
+ };
+ this._startStreaming();
+ this._triggered = false;
+ this._volume = streamOptions.volume;
+
+ /**
+ * How many passes the dispatcher should take when sending packets to reduce packet loss. Values over 5
+ * aren't recommended, as it means you are using 5x more bandwidth. You _can_ edit this at runtime.
+ * @type {number}
+ */
+ this.passes = streamOptions.passes || 1;
+
+ /**
+ * Whether playing is paused
+ * @type {boolean}
+ */
+ this.paused = false;
+
+ this.setVolume(streamOptions.volume || 1);
+ }
+
+ /**
+ * How long the stream dispatcher has been "speaking" for
+ * @type {number}
+ * @readonly
+ */
+ get time() {
+ return this.streamingData.count * (this.streamingData.length || 0);
+ }
+
+ /**
+ * The total time, taking into account pauses and skips, that the dispatcher has been streaming for
+ * @type {number}
+ * @readonly
+ */
+ get totalStreamTime() {
+ return this.time + this.streamingData.pausedTime;
+ }
+
+ /**
+ * The volume of the stream, relative to the stream's input volume
+ * @type {number}
+ * @readonly
+ */
+ get volume() {
+ return this._volume;
+ }
+
+ /**
+ * 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) {
+ this._volume = volume;
+ }
+
+ /**
+ * Set the volume in decibels
+ * @param {number} db The decibels
+ */
+ setVolumeDecibels(db) {
+ this._volume = Math.pow(10, db / 20);
+ }
+
+ /**
+ * Set 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._volume = Math.pow(value, 1.660964);
+ }
+
+ /**
+ * Stops sending voice packets to the voice connection (stream may still progress however)
+ */
+ pause() {
+ this._setPaused(true);
+ }
+
+ /**
+ * Resumes sending voice packets to the voice connection (may be further on in the stream than when paused)
+ */
+ resume() {
+ this._setPaused(false);
+ }
+
+ /**
+ * Stops the current stream permanently and emits an `end` event.
+ * @param {string} [reason='user'] An optional reason for stopping the dispatcher.
+ */
+ end(reason = 'user') {
+ this._triggerTerminalState('end', reason);
+ }
+
+ _setSpeaking(value) {
+ this.speaking = 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);
+ }
+
+ _sendBuffer(buffer, sequence, timestamp) {
+ let repeats = this.passes;
+ const packet = this._createPacket(sequence, timestamp, this.player.opusEncoder.encode(buffer));
+ while (repeats--) {
+ this.player.voiceConnection.sockets.udp.send(packet)
+ .catch(e => this.emit('debug', `Failed to send a packet ${e}`));
+ }
+ }
+
+ _createPacket(sequence, timestamp, buffer) {
+ const packetBuffer = new Buffer(buffer.length + 28);
+ packetBuffer.fill(0);
+ 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);
+ buffer = NaCl.secretbox(buffer, nonce, this.player.voiceConnection.authentication.secretKey.key);
+
+ for (let i = 0; i < buffer.length; i++) packetBuffer[i + 12] = buffer[i];
+
+ return packetBuffer;
+ }
+
+ _applyVolume(buffer) {
+ if (this._volume === 1) return buffer;
+
+ const out = new Buffer(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(this._volume * buffer.readInt16LE(i))));
+ out.writeInt16LE(uint, i);
+ }
+
+ return out;
+ }
+
+ _send() {
+ try {
+ if (this._triggered) {
+ this._setSpeaking(false);
+ return;
+ }
+
+ const data = this.streamingData;
+
+ if (data.missed >= 5) {
+ this._triggerTerminalState('end', 'Stream is not generating quickly enough.');
+ return;
+ }
+
+ if (this.paused) {
+ // data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
+ data.pausedTime += data.length * 10;
+ this.player.voiceConnection.voiceManager.client.setTimeout(() => this._send(), data.length * 10);
+ return;
+ }
+
+ this._setSpeaking(true);
+
+ if (!data.startTime) {
+ /**
+ * Emitted once the dispatcher starts streaming
+ * @event StreamDispatcher#start
+ */
+ this.emit('start');
+ data.startTime = Date.now();
+ }
+
+ const bufferLength = 1920 * data.channels;
+ let buffer = this.stream.read(bufferLength);
+ if (!buffer) {
+ data.missed++;
+ data.pausedTime += data.length * 10;
+ this.player.voiceConnection.voiceManager.client.setTimeout(() => this._send(), data.length * 10);
+ return;
+ }
+
+ data.missed = 0;
+
+ if (buffer.length !== bufferLength) {
+ const newBuffer = new Buffer(bufferLength).fill(0);
+ buffer.copy(newBuffer);
+ buffer = newBuffer;
+ }
+
+ buffer = this._applyVolume(buffer);
+
+ data.count++;
+ data.sequence = (data.sequence + 1) < 65536 ? data.sequence + 1 : 0;
+ data.timestamp = data.timestamp + 4294967295 ? data.timestamp + 960 : 0;
+
+ this._sendBuffer(buffer, data.sequence, data.timestamp);
+
+ const nextTime = data.length + (data.startTime + data.pausedTime + (data.count * data.length) - Date.now());
+ this.player.voiceConnection.voiceManager.client.setTimeout(() => this._send(), nextTime);
+ } catch (e) {
+ this._triggerTerminalState('error', e);
+ }
+ }
+
+ _triggerEnd(reason) {
+ /**
+ * Emitted once the stream has ended. Attach a `once` listener to this.
+ * @event StreamDispatcher#end
+ * @param {string} reason The reason for the end of the dispatcher. If it ended because it reached the end of the
+ * stream, this would be `stream`. If you invoke `.end()` without specifying a reason, this would be `user`.
+ */
+ this.emit('end', reason);
+ }
+
+ _triggerError(err) {
+ this.emit('end');
+ /**
+ * Emitted once the stream has encountered an error. Attach a `once` listener to this. Also emits `end`.
+ * @event StreamDispatcher#error
+ * @param {Error} err The encountered error
+ */
+ this.emit('error', err);
+ }
+
+ _triggerTerminalState(state, err) {
+ if (this._triggered) return;
+ /**
+ * Emitted when the stream wants to give debug information.
+ * @event StreamDispatcher#debug
+ * @param {string} information The debug information
+ */
+ this.emit('debug', `Triggered terminal state ${state} - stream is now dead`);
+ this._triggered = true;
+ this._setSpeaking(false);
+ switch (state) {
+ case 'end':
+ this._triggerEnd(err);
+ break;
+ case 'error':
+ this._triggerError(err);
+ break;
+ default:
+ this.emit('error', 'Unknown trigger state');
+ break;
+ }
+ }
+
+ _startStreaming() {
+ if (!this.stream) {
+ this.emit('error', 'No stream');
+ return;
+ }
+
+ this.stream.on('end', err => this._triggerTerminalState('end', err || 'stream'));
+ this.stream.on('error', err => this._triggerTerminalState('error', err));
+
+ const data = this.streamingData;
+ data.length = 20;
+ data.missed = 0;
+
+ this.stream.once('readable', () => this._send());
+ }
+
+ _setPaused(paused) {
+ if (paused) {
+ this.paused = true;
+ this._setSpeaking(false);
+ } else {
+ this.paused = false;
+ this._setSpeaking(true);
+ }
+ }
+}
+
+module.exports = StreamDispatcher;
diff --git a/node_modules/discord.js/src/client/voice/opus/BaseOpusEngine.js b/node_modules/discord.js/src/client/voice/opus/BaseOpusEngine.js
new file mode 100644
index 0000000..6c3ba6e
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/opus/BaseOpusEngine.js
@@ -0,0 +1,15 @@
+class BaseOpus {
+ constructor(player) {
+ this.player = player;
+ }
+
+ encode(buffer) {
+ return buffer;
+ }
+
+ decode(buffer) {
+ return buffer;
+ }
+}
+
+module.exports = BaseOpus;
diff --git a/node_modules/discord.js/src/client/voice/opus/NodeOpusEngine.js b/node_modules/discord.js/src/client/voice/opus/NodeOpusEngine.js
new file mode 100644
index 0000000..10f287b
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/opus/NodeOpusEngine.js
@@ -0,0 +1,27 @@
+const OpusEngine = require('./BaseOpusEngine');
+
+let opus;
+
+class NodeOpusEngine extends OpusEngine {
+ constructor(player) {
+ super(player);
+ try {
+ opus = require('node-opus');
+ } catch (err) {
+ throw err;
+ }
+ this.encoder = new opus.OpusEncoder(48000, 2);
+ }
+
+ encode(buffer) {
+ super.encode(buffer);
+ return this.encoder.encode(buffer, 1920);
+ }
+
+ decode(buffer) {
+ super.decode(buffer);
+ return this.encoder.decode(buffer, 1920);
+ }
+}
+
+module.exports = NodeOpusEngine;
diff --git a/node_modules/discord.js/src/client/voice/opus/OpusEngineList.js b/node_modules/discord.js/src/client/voice/opus/OpusEngineList.js
new file mode 100644
index 0000000..ffd512a
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/opus/OpusEngineList.js
@@ -0,0 +1,24 @@
+const list = [
+ require('./NodeOpusEngine'),
+ require('./OpusScriptEngine'),
+];
+
+function fetch(Encoder) {
+ try {
+ return new Encoder();
+ } catch (err) {
+ return null;
+ }
+}
+
+exports.add = encoder => {
+ list.push(encoder);
+};
+
+exports.fetch = () => {
+ for (const encoder of list) {
+ const fetched = fetch(encoder);
+ if (fetched) return fetched;
+ }
+ throw new Error('Couldn\'t find an Opus engine.');
+};
diff --git a/node_modules/discord.js/src/client/voice/opus/OpusScriptEngine.js b/node_modules/discord.js/src/client/voice/opus/OpusScriptEngine.js
new file mode 100644
index 0000000..33b4ff5
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/opus/OpusScriptEngine.js
@@ -0,0 +1,27 @@
+const OpusEngine = require('./BaseOpusEngine');
+
+let OpusScript;
+
+class NodeOpusEngine extends OpusEngine {
+ constructor(player) {
+ super(player);
+ try {
+ OpusScript = require('opusscript');
+ } catch (err) {
+ throw err;
+ }
+ this.encoder = new OpusScript(48000, 2);
+ }
+
+ encode(buffer) {
+ super.encode(buffer);
+ return this.encoder.encode(buffer, 960);
+ }
+
+ decode(buffer) {
+ super.decode(buffer);
+ return this.encoder.decode(buffer);
+ }
+}
+
+module.exports = NodeOpusEngine;
diff --git a/node_modules/discord.js/src/client/voice/pcm/ConverterEngine.js b/node_modules/discord.js/src/client/voice/pcm/ConverterEngine.js
new file mode 100644
index 0000000..6b7502f
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/pcm/ConverterEngine.js
@@ -0,0 +1,14 @@
+const EventEmitter = require('events').EventEmitter;
+
+class ConverterEngine extends EventEmitter {
+ constructor(player) {
+ super();
+ this.player = player;
+ }
+
+ createConvertStream() {
+ return;
+ }
+}
+
+module.exports = ConverterEngine;
diff --git a/node_modules/discord.js/src/client/voice/pcm/ConverterEngineList.js b/node_modules/discord.js/src/client/voice/pcm/ConverterEngineList.js
new file mode 100644
index 0000000..56d430e
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/pcm/ConverterEngineList.js
@@ -0,0 +1 @@
+exports.fetch = () => require('./FfmpegConverterEngine');
diff --git a/node_modules/discord.js/src/client/voice/pcm/FfmpegConverterEngine.js b/node_modules/discord.js/src/client/voice/pcm/FfmpegConverterEngine.js
new file mode 100644
index 0000000..8fb725b
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/pcm/FfmpegConverterEngine.js
@@ -0,0 +1,86 @@
+const ConverterEngine = require('./ConverterEngine');
+const ChildProcess = require('child_process');
+const EventEmitter = require('events').EventEmitter;
+
+class PCMConversionProcess extends EventEmitter {
+ constructor(process) {
+ super();
+ this.process = process;
+ this.input = null;
+ this.process.on('error', e => this.emit('error', e));
+ }
+
+ setInput(stream) {
+ this.input = stream;
+ stream.pipe(this.process.stdin, { end: false });
+ this.input.on('error', e => this.emit('error', e));
+ this.process.stdin.on('error', e => this.emit('error', e));
+ }
+
+ destroy() {
+ this.emit('debug', 'destroying a ffmpeg process:');
+ if (this.input && this.input.unpipe && this.process.stdin) {
+ this.input.unpipe(this.process.stdin);
+ this.emit('unpiped the user input stream from the process input stream');
+ }
+ if (this.process.stdin) {
+ this.process.stdin.end();
+ this.emit('ended the process stdin');
+ }
+ if (this.process.stdin.destroy) {
+ this.process.stdin.destroy();
+ this.emit('destroyed the process stdin');
+ }
+ if (this.process.kill) {
+ this.process.kill();
+ this.emit('killed the process');
+ }
+ }
+
+}
+
+class FfmpegConverterEngine extends ConverterEngine {
+ constructor(player) {
+ super(player);
+ this.command = chooseCommand();
+ }
+
+ handleError(encoder, err) {
+ if (encoder.destroy) encoder.destroy();
+ this.emit('error', err);
+ }
+
+ createConvertStream(seek = 0) {
+ super.createConvertStream();
+ const encoder = ChildProcess.spawn(this.command, [
+ '-analyzeduration', '0',
+ '-loglevel', '0',
+ '-i', '-',
+ '-f', 's16le',
+ '-ar', '48000',
+ '-ac', '2',
+ '-ss', String(seek),
+ 'pipe:1',
+ ], { stdio: ['pipe', 'pipe', 'ignore'] });
+ return new PCMConversionProcess(encoder);
+ }
+}
+
+function chooseCommand() {
+ for (const cmd of [
+ 'ffmpeg',
+ 'avconv',
+ './ffmpeg',
+ './avconv',
+ 'node_modules\\ffmpeg-binaries\\bin\\ffmpeg',
+ 'node_modules/ffmpeg-binaries/bin/ffmpeg',
+ ]) {
+ if (!ChildProcess.spawnSync(cmd, ['-h']).error) return cmd;
+ }
+ throw new Error(
+ 'FFMPEG was not found on your system, so audio cannot be played. ' +
+ 'Please make sure FFMPEG is installed and in your PATH.'
+ );
+}
+
+module.exports = FfmpegConverterEngine;
diff --git a/node_modules/discord.js/src/client/voice/player/AudioPlayer.js b/node_modules/discord.js/src/client/voice/player/AudioPlayer.js
new file mode 100644
index 0000000..96c6c24
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/player/AudioPlayer.js
@@ -0,0 +1,80 @@
+const PCMConverters = require('../pcm/ConverterEngineList');
+const OpusEncoders = require('../opus/OpusEngineList');
+const EventEmitter = require('events').EventEmitter;
+const StreamDispatcher = require('../dispatcher/StreamDispatcher');
+
+/**
+ * Represents the Audio Player of a Voice Connection
+ * @extends {EventEmitter}
+ * @private
+ */
+class AudioPlayer extends EventEmitter {
+ constructor(voiceConnection) {
+ super();
+ /**
+ * The voice connection the player belongs to
+ * @type {VoiceConnection}
+ */
+ this.voiceConnection = voiceConnection;
+ this.audioToPCM = new (PCMConverters.fetch())();
+ this.opusEncoder = OpusEncoders.fetch();
+ this.currentConverter = null;
+ /**
+ * The current stream dispatcher, if a stream is being played
+ * @type {StreamDispatcher}
+ */
+ this.dispatcher = null;
+ this.audioToPCM.on('error', e => this.emit('error', e));
+ this.streamingData = {
+ channels: 2,
+ count: 0,
+ sequence: 0,
+ timestamp: 0,
+ pausedTime: 0,
+ };
+ this.voiceConnection.on('closing', () => this.cleanup(null, 'voice connection closing'));
+ }
+
+ playUnknownStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
+ const options = { seek, volume, passes };
+ stream.on('end', () => {
+ this.emit('debug', 'Input stream to converter has ended');
+ });
+ stream.on('error', e => this.emit('error', e));
+ const conversionProcess = this.audioToPCM.createConvertStream(options.seek);
+ conversionProcess.on('error', e => this.emit('error', e));
+ conversionProcess.setInput(stream);
+ return this.playPCMStream(conversionProcess.process.stdout, conversionProcess, options);
+ }
+
+ cleanup(checkStream, reason) {
+ // cleanup is a lot less aggressive than v9 because it doesn't try to kill every single stream it is aware of
+ this.emit('debug', `Clean up triggered due to ${reason}`);
+ const filter = checkStream && this.dispatcher && this.dispatcher.stream === checkStream;
+ if (this.currentConverter && (checkStream ? filter : true)) {
+ this.currentConverter.destroy();
+ this.currentConverter = null;
+ }
+ }
+
+ playPCMStream(stream, converter, { seek = 0, volume = 1, passes = 1 } = {}) {
+ const options = { seek, volume, passes };
+ stream.on('end', () => this.emit('debug', 'PCM input stream ended'));
+ this.cleanup(null, 'outstanding play stream');
+ this.currentConverter = converter;
+ if (this.dispatcher) {
+ this.streamingData = this.dispatcher.streamingData;
+ }
+ stream.on('error', e => this.emit('error', e));
+ const dispatcher = new StreamDispatcher(this, stream, this.streamingData, options);
+ dispatcher.on('error', e => this.emit('error', e));
+ dispatcher.on('end', () => this.cleanup(dispatcher.stream, 'dispatcher ended'));
+ dispatcher.on('speaking', value => this.voiceConnection.setSpeaking(value));
+ this.dispatcher = dispatcher;
+ dispatcher.on('debug', m => this.emit('debug', `Stream dispatch - ${m}`));
+ return dispatcher;
+ }
+
+}
+
+module.exports = AudioPlayer;
diff --git a/node_modules/discord.js/src/client/voice/player/BasePlayer.js b/node_modules/discord.js/src/client/voice/player/BasePlayer.js
new file mode 100644
index 0000000..d5285cd
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/player/BasePlayer.js
@@ -0,0 +1,121 @@
+const OpusEngines = require('../opus/OpusEngineList');
+const ConverterEngines = require('../pcm/ConverterEngineList');
+const Constants = require('../../../util/Constants');
+const StreamDispatcher = require('../dispatcher/StreamDispatcher');
+const EventEmitter = require('events').EventEmitter;
+
+class VoiceConnectionPlayer extends EventEmitter {
+ constructor(connection) {
+ super();
+ this.connection = connection;
+ this.opusEncoder = OpusEngines.fetch();
+ const Engine = ConverterEngines.fetch();
+ this.converterEngine = new Engine(this);
+ this.converterEngine.on('error', err => {
+ this._shutdown();
+ this.emit('error', err);
+ });
+ this.speaking = false;
+ this.processMap = new Map();
+ this.dispatcher = null;
+ this._streamingData = {
+ sequence: 0,
+ timestamp: 0,
+ };
+ }
+
+ convertStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
+ const options = { seek, volume, passes };
+ const encoder = this.converterEngine.createConvertStream(options.seek);
+ const pipe = stream.pipe(encoder.stdin, { end: false });
+ pipe.on('unpipe', () => {
+ this.killStream(encoder.stdout);
+ pipe.destroy();
+ });
+ this.processMap.set(encoder.stdout, {
+ pcmConverter: encoder,
+ inputStream: stream,
+ });
+ return encoder.stdout;
+ }
+
+ _shutdown() {
+ this.speaking = false;
+ if (this.dispatcher) this.dispatcher._triggerTerminalState('end', 'ended by parent player shutdown');
+ for (const stream of this.processMap.keys()) this.killStream(stream);
+ }
+
+ killStream(stream) {
+ const streams = this.processMap.get(stream);
+ this._streamingData = this.dispatcher.streamingData;
+ this.emit(Constants.Events.DEBUG, 'Cleaning up player after audio stream ended or encountered an error');
+
+ const dummyHandler = () => null;
+
+ if (streams) {
+ this.processMap.delete(stream);
+ if (streams.inputStream && streams.pcmConverter) {
+ try {
+ streams.inputStream.once('error', dummyHandler);
+ streams.pcmConverter.once('error', dummyHandler);
+ streams.pcmConverter.stdin.once('error', dummyHandler);
+ streams.pcmConverter.stdout.once('error', dummyHandler);
+ if (streams.inputStream.unpipe) {
+ streams.inputStream.unpipe(streams.pcmConverter.stdin);
+ this.emit(Constants.Events.DEBUG, '- Unpiped input stream');
+ } else if (streams.inputStream.destroy) {
+ streams.inputStream.destroy();
+ this.emit(Constants.Events.DEBUG, '- Couldn\'t unpipe input stream, so destroyed input stream');
+ }
+ if (streams.pcmConverter.stdin) {
+ streams.pcmConverter.stdin.end();
+ this.emit(Constants.Events.DEBUG, '- Ended input stream to PCM converter');
+ }
+ if (streams.pcmConverter && streams.pcmConverter.kill) {
+ streams.pcmConverter.kill('SIGINT');
+ this.emit(Constants.Events.DEBUG, '- Killed the PCM converter');
+ }
+ } catch (err) {
+ // if an error happened make sure the pcm converter is killed anyway
+ try {
+ if (streams.pcmConverter && streams.pcmConverter.kill) {
+ streams.pcmConverter.kill('SIGINT');
+ this.emit(Constants.Events.DEBUG, '- Killed the PCM converter after previous error (abnormal)');
+ }
+ } catch (e) {
+ return e;
+ }
+ return err;
+ }
+ }
+ }
+ return null;
+ }
+
+ setSpeaking(value) {
+ if (this.speaking === value) return;
+ this.speaking = value;
+ this.connection.websocket.send({
+ op: Constants.VoiceOPCodes.SPEAKING,
+ d: {
+ speaking: true,
+ delay: 0,
+ },
+ }).catch(e => {
+ this.emit('debug', e);
+ });
+ }
+
+ playPCMStream(pcmStream, { seek = 0, volume = 1, passes = 1 } = {}) {
+ const options = { seek, volume, passes };
+ const dispatcher = new StreamDispatcher(this, pcmStream, this._streamingData, options);
+ dispatcher.on('speaking', value => this.setSpeaking(value));
+ dispatcher.on('end', () => this.killStream(pcmStream));
+ dispatcher.on('error', () => this.killStream(pcmStream));
+ dispatcher.setVolume(options.volume);
+ this.dispatcher = dispatcher;
+ return dispatcher;
+ }
+}
+
+module.exports = VoiceConnectionPlayer;
diff --git a/node_modules/discord.js/src/client/voice/player/DefaultPlayer.js b/node_modules/discord.js/src/client/voice/player/DefaultPlayer.js
new file mode 100644
index 0000000..b465e8c
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/player/DefaultPlayer.js
@@ -0,0 +1,19 @@
+const BasePlayer = require('./BasePlayer');
+const fs = require('fs');
+
+class DefaultPlayer extends BasePlayer {
+ playFile(file, { seek = 0, volume = 1 } = {}) {
+ const options = { seek: seek, volume: volume };
+ return this.playStream(fs.createReadStream(file), options);
+ }
+
+ playStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
+ this._shutdown();
+ const options = { seek, volume, passes };
+ const pcmStream = this.convertStream(stream, options);
+ const dispatcher = this.playPCMStream(pcmStream, options);
+ return dispatcher;
+ }
+}
+
+module.exports = DefaultPlayer;
diff --git a/node_modules/discord.js/src/client/voice/receiver/VoiceReadable.js b/node_modules/discord.js/src/client/voice/receiver/VoiceReadable.js
new file mode 100644
index 0000000..50ace27
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/receiver/VoiceReadable.js
@@ -0,0 +1,19 @@
+const Readable = require('stream').Readable;
+
+class VoiceReadable extends Readable {
+ constructor() {
+ super();
+ this._packets = [];
+ this.open = true;
+ }
+
+ _read() {
+ return;
+ }
+
+ _push(d) {
+ if (this.open) this.push(d);
+ }
+}
+
+module.exports = VoiceReadable;
diff --git a/node_modules/discord.js/src/client/voice/receiver/VoiceReceiver.js b/node_modules/discord.js/src/client/voice/receiver/VoiceReceiver.js
new file mode 100644
index 0000000..bc9156f
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/receiver/VoiceReceiver.js
@@ -0,0 +1,154 @@
+const EventEmitter = require('events').EventEmitter;
+const NaCl = require('tweetnacl');
+const Readable = require('./VoiceReadable');
+
+const nonce = new Buffer(24);
+nonce.fill(0);
+
+/**
+ * Receives voice data from a voice connection.
+ * ```js
+ * // obtained using:
+ * voiceChannel.join().then(connection => {
+ * const receiver = connection.createReceiver();
+ * });
+ * ```
+ * @extends {EventEmitter}
+ */
+class VoiceReceiver extends EventEmitter {
+ constructor(connection) {
+ super();
+ /*
+ need a queue because we don't get the ssrc of the user speaking until after the first few packets,
+ so we queue up unknown SSRCs until they become known, then empty the queue.
+ */
+ this.queues = new Map();
+ this.pcmStreams = new Map();
+ this.opusStreams = new Map();
+
+ /**
+ * Whether or not this receiver has been destroyed.
+ * @type {boolean}
+ */
+ this.destroyed = false;
+
+ /**
+ * The VoiceConnection that instantiated this
+ * @type {VoiceConnection}
+ */
+ this.voiceConnection = connection;
+
+ this._listener = msg => {
+ const ssrc = +msg.readUInt32BE(8).toString(10);
+ const user = this.voiceConnection.ssrcMap.get(ssrc);
+ if (!user) {
+ if (!this.queues.has(ssrc)) this.queues.set(ssrc, []);
+ this.queues.get(ssrc).push(msg);
+ } else {
+ if (this.queues.get(ssrc)) {
+ this.queues.get(ssrc).push(msg);
+ this.queues.get(ssrc).map(m => this.handlePacket(m, user));
+ this.queues.delete(ssrc);
+ return;
+ }
+ this.handlePacket(msg, user);
+ }
+ };
+ this.voiceConnection.sockets.udp.socket.on('message', this._listener);
+ }
+
+ /**
+ * If this VoiceReceiver has been destroyed, running `recreate()` will recreate the listener.
+ * This avoids you having to create a new receiver.
+ * <info>Any streams that you had prior to destroying the receiver will not be recreated.</info>
+ */
+ recreate() {
+ if (!this.destroyed) return;
+ this.voiceConnection.sockets.udp.socket.on('message', this._listener);
+ this.destroyed = false;
+ return;
+ }
+
+ /**
+ * Destroy this VoiceReceiver, also ending any streams that it may be controlling.
+ */
+ destroy() {
+ this.voiceConnection.sockets.udp.socket.removeListener('message', this._listener);
+ for (const stream of this.pcmStreams) {
+ stream[1]._push(null);
+ this.pcmStreams.delete(stream[0]);
+ }
+ for (const stream of this.opusStreams) {
+ stream[1]._push(null);
+ this.opusStreams.delete(stream[0]);
+ }
+ this.destroyed = true;
+ }
+
+ /**
+ * Creates a readable stream for a user that provides opus data while the user is speaking. When the user
+ * stops speaking, the stream is destroyed.
+ * @param {UserResolvable} user The user to create the stream for
+ * @returns {ReadableStream}
+ */
+ createOpusStream(user) {
+ user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user);
+ if (!user) throw new Error('Couldn\'t resolve the user to create Opus stream.');
+ if (this.opusStreams.get(user.id)) throw new Error('There is already an existing stream for that user.');
+ const stream = new Readable();
+ this.opusStreams.set(user.id, stream);
+ return stream;
+ }
+
+ /**
+ * Creates a readable stream for a user that provides PCM data while the user is speaking. When the user
+ * stops speaking, the stream is destroyed. The stream is 32-bit signed stereo PCM at 48KHz.
+ * @param {UserResolvable} user The user to create the stream for
+ * @returns {ReadableStream}
+ */
+ createPCMStream(user) {
+ user = this.voiceConnection.voiceManager.client.resolver.resolveUser(user);
+ if (!user) throw new Error('Couldn\'t resolve the user to create PCM stream.');
+ if (this.pcmStreams.get(user.id)) throw new Error('There is already an existing stream for that user.');
+ const stream = new Readable();
+ this.pcmStreams.set(user.id, stream);
+ return stream;
+ }
+
+ handlePacket(msg, user) {
+ msg.copy(nonce, 0, 0, 12);
+ let data = NaCl.secretbox.open(msg.slice(12), nonce, this.voiceConnection.authentication.secretKey.key);
+ if (!data) {
+ /**
+ * Emitted whenever a voice packet cannot be decrypted
+ * @event VoiceReceiver#warn
+ * @param {string} message The warning message
+ */
+ this.emit('warn', 'Failed to decrypt voice packet');
+ return;
+ }
+ data = new Buffer(data);
+ if (this.opusStreams.get(user.id)) this.opusStreams.get(user.id)._push(data);
+ /**
+ * Emitted whenever voice data is received from the voice connection. This is _always_ emitted (unlike PCM).
+ * @event VoiceReceiver#opus
+ * @param {User} user The user that is sending the buffer (is speaking)
+ * @param {Buffer} buffer The opus buffer
+ */
+ this.emit('opus', user, data);
+ if (this.listenerCount('pcm') > 0 || this.pcmStreams.size > 0) {
+ /**
+ * Emits decoded voice data when it's received. For performance reasons, the decoding will only
+ * happen if there is at least one `pcm` listener on this receiver.
+ * @event VoiceReceiver#pcm
+ * @param {User} user The user that is sending the buffer (is speaking)
+ * @param {Buffer} buffer The decoded buffer
+ */
+ const pcm = this.voiceConnection.player.opusEncoder.decode(data);
+ if (this.pcmStreams.get(user.id)) this.pcmStreams.get(user.id)._push(pcm);
+ this.emit('pcm', user, pcm);
+ }
+ }
+}
+
+module.exports = VoiceReceiver;
diff --git a/node_modules/discord.js/src/client/voice/util/SecretKey.js b/node_modules/discord.js/src/client/voice/util/SecretKey.js
new file mode 100644
index 0000000..508a1ba
--- /dev/null
+++ b/node_modules/discord.js/src/client/voice/util/SecretKey.js
@@ -0,0 +1,16 @@
+/**
+ * Represents a Secret Key used in encryption over voice
+ * @private
+ */
+class SecretKey {
+ constructor(key) {
+ /**
+ * The key used for encryption
+ * @type {Uint8Array}
+ */
+ this.key = new Uint8Array(new ArrayBuffer(key.length));
+ for (const index in key) this.key[index] = key[index];
+ }
+}
+
+module.exports = SecretKey;