diff --git a/src/client/voice/ClientVoiceManager.js b/src/client/voice/ClientVoiceManager.js index 06cc44941..ee088d8e3 100644 --- a/src/client/voice/ClientVoiceManager.js +++ b/src/client/voice/ClientVoiceManager.js @@ -4,6 +4,114 @@ 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} + */ + this.connections = new Collection(); + + /** + * Pending connection attempts, maps Guild ID to VoiceChannel + * @type {Collection} + */ + 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) { + const permissions = channel.permissionsFor(this.client.user); + if (permissions) { + if (!permissions.hasPermission('CONNECT')) { + throw new Error('You do not have permission to connect to this voice channel.'); + } + } else { + throw new Error('There is no permission set for the client user in this channel - are they part of the guild?'); + } + } else { + throw new Error('Channel does not support permissionsFor; is it really a 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} + */ + 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.'); + + 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 @@ -136,112 +244,4 @@ class PendingVoiceConnection extends 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} - */ - this.connections = new Collection(); - - /** - * Pending connection attempts, maps Guild ID to VoiceChannel - * @type {Collection} - */ - 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) { - const permissions = channel.permissionsFor(this.client.user); - if (permissions) { - if (!permissions.hasPermission('CONNECT')) { - throw new Error('You do not have permission to connect to this voice channel.'); - } - } else { - throw new Error('There is no permission set for the client user in this channel - are they part of the guild?'); - } - } else { - throw new Error('Channel does not support permissionsFor; is it really a 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} - */ - 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.'); - - 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)); - }); - }); - } -} - module.exports = ClientVoiceManager; diff --git a/src/client/voice/VoiceWebSocket.js b/src/client/voice/VoiceWebSocket.js index 1c3cef6fc..1c421a71b 100644 --- a/src/client/voice/VoiceWebSocket.js +++ b/src/client/voice/VoiceWebSocket.js @@ -61,7 +61,7 @@ class VoiceWebSocket extends EventEmitter { if (this.dead) return; if (this.ws) this.reset(); if (this.attempts > 5) { - this.emit('error', new Error(`too many connection attempts (${this.attempts})`)); + this.emit('error', new Error(`Too many connection attempts (${this.attempts}).`)); return; } @@ -85,17 +85,12 @@ class VoiceWebSocket extends EventEmitter { */ send(data) { return new Promise((resolve, reject) => { - if (this.ws && this.ws.readyState === WebSocket.OPEN) { - this.ws.send(data, null, error => { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - } else { - reject(new Error(`Voice websocket not open to send ${data}.`)); + 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); + }); }); } @@ -126,7 +121,7 @@ class VoiceWebSocket extends EventEmitter { session_id: this.voiceConnection.authentication.session_id, }, }).catch(() => { - this.emit('error', new Error('tried to send join packet but WebSocket not open')); + this.emit('error', new Error('Tried to send join packet, but the WebSocket is not open.')); }); } @@ -208,7 +203,7 @@ class VoiceWebSocket extends EventEmitter { */ setHeartbeat(interval) { if (!interval || isNaN(interval)) { - this.onError(new Error('tried to set voice heartbeat but no valid interval was specified')); + this.onError(new Error('Tried to set voice heartbeat but no valid interval was specified.')); return; } if (this.heartbeatInterval) { @@ -217,7 +212,7 @@ class VoiceWebSocket extends EventEmitter { * @param {string} warn the warning * @event VoiceWebSocket#warn */ - this.emit('warn', 'a voice heartbeat interval is being overwritten'); + this.emit('warn', 'A voice heartbeat interval is being overwritten'); clearInterval(this.heartbeatInterval); } this.heartbeatInterval = this.client.setInterval(this.sendHeartbeat.bind(this), interval); @@ -228,7 +223,7 @@ class VoiceWebSocket extends EventEmitter { */ clearHeartbeat() { if (!this.heartbeatInterval) { - this.emit('warn', 'tried to clear a heartbeat interval that does not exist'); + this.emit('warn', 'Tried to clear a heartbeat interval that does not exist'); return; } clearInterval(this.heartbeatInterval); @@ -239,11 +234,10 @@ class VoiceWebSocket extends EventEmitter { * 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(); - }); + this.sendPacket({ op: Constants.VoiceOPCodes.HEARTBEAT, d: null }).catch(() => { + this.emit('warn', 'Tried to send heartbeat, but connection is not open'); + this.clearHeartbeat(); + }); } }