mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 03:23:29 +01:00
voice rewrite part 1
This commit is contained in:
@@ -2,6 +2,135 @@ const Collection = require('../../util/Collection');
|
|||||||
const mergeDefault = require('../../util/MergeDefault');
|
const mergeDefault = require('../../util/MergeDefault');
|
||||||
const Constants = require('../../util/Constants');
|
const Constants = require('../../util/Constants');
|
||||||
const VoiceConnection = require('./VoiceConnection');
|
const VoiceConnection = require('./VoiceConnection');
|
||||||
|
const EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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('Automatic failure after 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages all the voice stuff for the Client
|
* Manages all the voice stuff for the Client
|
||||||
@@ -26,6 +155,9 @@ class ClientVoiceManager {
|
|||||||
* @type {Collection<string, VoiceChannel>}
|
* @type {Collection<string, VoiceChannel>}
|
||||||
*/
|
*/
|
||||||
this.pending = new Collection();
|
this.pending = new Collection();
|
||||||
|
|
||||||
|
this.client.on('self.voiceServer', this.onVoiceServer.bind(this));
|
||||||
|
this.client.on('self.voiceStateUpdate', this.onVoiceStateUpdate.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,31 +179,16 @@ class ClientVoiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
onVoiceServer(data) {
|
||||||
* Called when the Client receives information about this voice server update.
|
if (this.pending.has(data.guild_id)) {
|
||||||
* @param {string} guildID The ID of the Guild
|
this.pending.get(data.guild_id).setTokenAndEndpoint(data.token, data.endpoint);
|
||||||
* @param {string} token The token to authorise with
|
}
|
||||||
* @param {string} endpoint The endpoint to connect to
|
|
||||||
*/
|
|
||||||
_receivedVoiceServer(guildID, token, endpoint) {
|
|
||||||
const pendingRequest = this.pending.get(guildID);
|
|
||||||
if (!pendingRequest) throw new Error('Guild not pending.');
|
|
||||||
pendingRequest.token = token;
|
|
||||||
// remove the port otherwise it errors ¯\_(ツ)_/¯
|
|
||||||
pendingRequest.endpoint = endpoint.match(/([^:]*)/)[0];
|
|
||||||
this._checkPendingReady(guildID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
onVoiceStateUpdate(data) {
|
||||||
* Called when the Client receives information about the voice state update.
|
if (this.pending.has(data.guild_id)) {
|
||||||
* @param {string} guildID The ID of the Guild
|
this.pending.get(data.guild_id).setSessionID(data.session_id);
|
||||||
* @param {string} sessionID The session id to authorise with
|
}
|
||||||
*/
|
|
||||||
_receivedVoiceStateUpdate(guildID, sessionID) {
|
|
||||||
const pendingRequest = this.pending.get(guildID);
|
|
||||||
if (!pendingRequest) throw new Error('Guild not pending.');
|
|
||||||
pendingRequest.sessionID = sessionID;
|
|
||||||
this._checkPendingReady(guildID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,13 +196,31 @@ class ClientVoiceManager {
|
|||||||
* @param {VoiceChannel} channel The channel to join
|
* @param {VoiceChannel} channel The channel to join
|
||||||
* @param {Object} [options] The options to provide
|
* @param {Object} [options] The options to provide
|
||||||
*/
|
*/
|
||||||
_sendWSJoin(channel, options = {}) {
|
sendVoiceStateUpdate(channel, options = {}) {
|
||||||
|
if (!this.client.user) {
|
||||||
|
throw new Error('You cannot 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({
|
options = mergeDefault({
|
||||||
guild_id: channel.guild.id,
|
guild_id: channel.guild.id,
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
self_mute: false,
|
self_mute: false,
|
||||||
self_deaf: false,
|
self_deaf: false,
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
this.client.ws.send({
|
this.client.ws.send({
|
||||||
op: Constants.OPCodes.VOICE_STATE_UPDATE,
|
op: Constants.OPCodes.VOICE_STATE_UPDATE,
|
||||||
d: options,
|
d: options,
|
||||||
@@ -99,26 +234,32 @@ class ClientVoiceManager {
|
|||||||
*/
|
*/
|
||||||
joinChannel(channel) {
|
joinChannel(channel) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.pending.get(channel.guild.id)) throw new Error(`Already connecting to a channel in guild.`);
|
// if already connecting to this voice server, error
|
||||||
const existingConn = this.connections.get(channel.guild.id);
|
if (this.pending.get(channel.guild.id)) {
|
||||||
if (existingConn) {
|
throw new Error(`Already connecting to this guild's voice server.`);
|
||||||
if (existingConn.channel.id !== channel.id) {
|
}
|
||||||
this._sendWSJoin(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;
|
this.connections.get(channel.guild.id).channel = channel;
|
||||||
}
|
}
|
||||||
resolve(existingConn);
|
resolve(existingConnection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.pending.set(channel.guild.id, {
|
|
||||||
channel,
|
const pendingConnection = new PendingVoiceConnection(this, channel);
|
||||||
sessionID: null,
|
this.pending.set(channel.guild.id, pendingConnection);
|
||||||
token: null,
|
|
||||||
endpoint: null,
|
pendingConnection.on('fail', reason => {
|
||||||
resolve,
|
this.pending.delete(channel.guild.id);
|
||||||
reject,
|
reject(reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
pendingConnection.on('pass', voiceConnection => {
|
||||||
|
// do stuff
|
||||||
});
|
});
|
||||||
this._sendWSJoin(channel);
|
|
||||||
this.client.setTimeout(() => reject(new Error('Connection not established within 15 seconds.')), 15000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ class VoiceServerUpdate extends AbstractHandler {
|
|||||||
handle(packet) {
|
handle(packet) {
|
||||||
const client = this.packetManager.client;
|
const client = this.packetManager.client;
|
||||||
const data = packet.d;
|
const data = packet.d;
|
||||||
if (client.voice.pending.has(data.guild_id) && data.endpoint) {
|
client.emit('self.voiceServer', data);
|
||||||
client.voice._receivedVoiceServer(data.guild_id, data.token, data.endpoint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ class VoiceStateUpdateHandler extends AbstractHandler {
|
|||||||
// if the member left the voice channel, unset their speaking property
|
// if the member left the voice channel, unset their speaking property
|
||||||
if (!data.channel_id) member.speaking = null;
|
if (!data.channel_id) member.speaking = null;
|
||||||
|
|
||||||
if (client.voice.pending.has(guild.id) && member.user.id === client.user.id && data.channel_id) {
|
if (member.user.id === client.user.id && data.channel_id) {
|
||||||
client.voice._receivedVoiceStateUpdate(data.guild_id, data.session_id);
|
client.emit('self.voiceStateUpdate', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newChannel = client.channels.get(data.channel_id);
|
const newChannel = client.channels.get(data.channel_id);
|
||||||
|
|||||||
Reference in New Issue
Block a user