mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-15 11:03:30 +01:00
Merge branch 'indev' of https://github.com/hydrabolt/discord.js into indev
This commit is contained in:
@@ -59,7 +59,7 @@ class RESTMethods {
|
|||||||
|
|
||||||
if (content) {
|
if (content) {
|
||||||
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.rest.client.options.disableEveryone)) {
|
if (disableEveryone || (typeof disableEveryone === 'undefined' && this.rest.client.options.disableEveryone)) {
|
||||||
content = content.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere');
|
content = content.replace(/@(everyone|here)/g, '@\u200b$1');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (split) content = splitMessage(content, typeof split === 'object' ? split : {});
|
if (split) content = splitMessage(content, typeof split === 'object' ? split : {});
|
||||||
|
|||||||
@@ -4,6 +4,111 @@ const Constants = require('../../util/Constants');
|
|||||||
const VoiceConnection = require('./VoiceConnection');
|
const VoiceConnection = require('./VoiceConnection');
|
||||||
const EventEmitter = require('events').EventEmitter;
|
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 connect to 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.');
|
||||||
|
|
||||||
|
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
|
* Represents a Pending Voice Connection
|
||||||
* @private
|
* @private
|
||||||
@@ -11,22 +116,26 @@ const EventEmitter = require('events').EventEmitter;
|
|||||||
class PendingVoiceConnection extends EventEmitter {
|
class PendingVoiceConnection extends EventEmitter {
|
||||||
constructor(voiceManager, channel) {
|
constructor(voiceManager, channel) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ClientVoiceManager that instantiated this pending connection
|
* The ClientVoiceManager that instantiated this pending connection
|
||||||
* @type {ClientVoiceManager}
|
* @type {ClientVoiceManager}
|
||||||
*/
|
*/
|
||||||
this.voiceManager = voiceManager;
|
this.voiceManager = voiceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The channel that this pending voice connection will attempt to join
|
* The channel that this pending voice connection will attempt to join
|
||||||
* @type {VoiceChannel}
|
* @type {VoiceChannel}
|
||||||
*/
|
*/
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timeout that will be invoked after 15 seconds signifying a failure to connect
|
* The timeout that will be invoked after 15 seconds signifying a failure to connect
|
||||||
* @type {Timeout}
|
* @type {Timeout}
|
||||||
*/
|
*/
|
||||||
this.deathTimer = this.voiceManager.client.setTimeout(
|
this.deathTimer = this.voiceManager.client.setTimeout(
|
||||||
() => this.fail(new Error('Automatic failure after 15 seconds')), 15000);
|
() => this.fail(new Error('Connection not established within 15 seconds.')), 15000);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object containing data required to connect to the voice servers with
|
* An object containing data required to connect to the voice servers with
|
||||||
* @type {object}
|
* @type {object}
|
||||||
@@ -53,26 +162,26 @@ class PendingVoiceConnection extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
setTokenAndEndpoint(token, endpoint) {
|
setTokenAndEndpoint(token, endpoint) {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
this.fail(new Error('Token not provided from voice server packet'));
|
this.fail(new Error('Token not provided from voice server packet.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!endpoint) {
|
if (!endpoint) {
|
||||||
this.fail(new Error('Endpoint not provided from voice server packet'));
|
this.fail(new Error('Endpoint not provided from voice server packet.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.data.token) {
|
if (this.data.token) {
|
||||||
this.fail(new Error('There is already a registered token for this connection'));
|
this.fail(new Error('There is already a registered token for this connection.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.data.endpoint) {
|
if (this.data.endpoint) {
|
||||||
this.fail(new Error('There is already a registered endpoint for this connection'));
|
this.fail(new Error('There is already a registered endpoint for this connection.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint = endpoint.match(/([^:]*)/)[0];
|
endpoint = endpoint.match(/([^:]*)/)[0];
|
||||||
|
|
||||||
if (!endpoint) {
|
if (!endpoint) {
|
||||||
this.fail(new Error('failed to find an endpoint'));
|
this.fail(new Error('Failed to find an endpoint.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,11 +197,11 @@ class PendingVoiceConnection extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
setSessionID(sessionID) {
|
setSessionID(sessionID) {
|
||||||
if (!sessionID) {
|
if (!sessionID) {
|
||||||
this.fail(new Error('Session ID not supplied'));
|
this.fail(new Error('Session ID not supplied.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.data.session_id) {
|
if (this.data.session_id) {
|
||||||
this.fail(new Error('There is already a registered session ID for this connection'));
|
this.fail(new Error('There is already a registered session ID for this connection.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.data.session_id = sessionID;
|
this.data.session_id = sessionID;
|
||||||
@@ -132,121 +241,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<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('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({
|
|
||||||
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 already connecting to this voice server, error
|
|
||||||
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('disconnected', () => this.connections.delete(channel.guild.id));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ClientVoiceManager;
|
module.exports = ClientVoiceManager;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class VoiceConnection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnect the voice connection, causing a disconnected and closing event to be emitted.
|
* Disconnect the voice connection, causing a disconnect and closing event to be emitted.
|
||||||
*/
|
*/
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this.emit('closing');
|
this.emit('closing');
|
||||||
@@ -119,9 +119,9 @@ class VoiceConnection extends EventEmitter {
|
|||||||
});
|
});
|
||||||
/**
|
/**
|
||||||
* Emitted when the voice connection disconnects
|
* Emitted when the voice connection disconnects
|
||||||
* @event VoiceConnection#disconnected
|
* @event VoiceConnection#disconnect
|
||||||
*/
|
*/
|
||||||
this.emit('disconnected');
|
this.emit('disconnect');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,12 +129,8 @@ class VoiceConnection extends EventEmitter {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
connect() {
|
connect() {
|
||||||
if (this.sockets.ws) {
|
if (this.sockets.ws) throw new Error('There is already an existing WebSocket connection.');
|
||||||
throw new Error('There is already an existing WebSocket connection!');
|
if (this.sockets.udp) throw new Error('There is already an existing UDP connection.');
|
||||||
}
|
|
||||||
if (this.sockets.udp) {
|
|
||||||
throw new Error('There is already an existing UDP connection!');
|
|
||||||
}
|
|
||||||
this.sockets.ws = new VoiceWebSocket(this);
|
this.sockets.ws = new VoiceWebSocket(this);
|
||||||
this.sockets.udp = new VoiceUDP(this);
|
this.sockets.udp = new VoiceUDP(this);
|
||||||
this.sockets.ws.on('error', e => this.emit('error', e));
|
this.sockets.ws.on('error', e => this.emit('error', e));
|
||||||
@@ -260,7 +256,6 @@ class VoiceConnection extends EventEmitter {
|
|||||||
this.receivers.push(receiver);
|
this.receivers.push(receiver);
|
||||||
return receiver;
|
return receiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = VoiceConnection;
|
module.exports = VoiceConnection;
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ function parseLocalPacket(message) {
|
|||||||
try {
|
try {
|
||||||
const packet = new Buffer(message);
|
const packet = new Buffer(message);
|
||||||
let address = '';
|
let address = '';
|
||||||
for (let i = 4; i < packet.indexOf(0, i); i++) {
|
for (let i = 4; i < packet.indexOf(0, i); i++) address += String.fromCharCode(packet[i]);
|
||||||
address += String.fromCharCode(packet[i]);
|
|
||||||
}
|
|
||||||
const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
|
const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10);
|
||||||
return { address, port };
|
return { address, port };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -24,33 +22,40 @@ function parseLocalPacket(message) {
|
|||||||
class VoiceConnectionUDPClient extends EventEmitter {
|
class VoiceConnectionUDPClient extends EventEmitter {
|
||||||
constructor(voiceConnection) {
|
constructor(voiceConnection) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voice connection that this UDP client serves
|
* The voice connection that this UDP client serves
|
||||||
* @type {VoiceConnection}
|
* @type {VoiceConnection}
|
||||||
*/
|
*/
|
||||||
this.voiceConnection = voiceConnection;
|
this.voiceConnection = voiceConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The UDP socket
|
* The UDP socket
|
||||||
* @type {?Socket}
|
* @type {?Socket}
|
||||||
*/
|
*/
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The address of the discord voice server
|
* The address of the discord voice server
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
this.discordAddress = null;
|
this.discordAddress = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The local IP address
|
* The local IP address
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
this.localAddress = null;
|
this.localAddress = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The local port
|
* The local port
|
||||||
* @type {?string}
|
* @type {?string}
|
||||||
*/
|
*/
|
||||||
this.localPort = null;
|
this.localPort = null;
|
||||||
|
|
||||||
this.voiceConnection.on('closing', this.shutdown.bind(this));
|
this.voiceConnection.on('closing', this.shutdown.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
shutdown() {
|
shutdown() {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
try {
|
try {
|
||||||
@@ -61,6 +66,7 @@ class VoiceConnectionUDPClient extends EventEmitter {
|
|||||||
this.socket = null;
|
this.socket = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The port of the discord voice server
|
* The port of the discord voice server
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -69,6 +75,7 @@ class VoiceConnectionUDPClient extends EventEmitter {
|
|||||||
get discordPort() {
|
get discordPort() {
|
||||||
return this.voiceConnection.authentication.port;
|
return this.voiceConnection.authentication.port;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to resolve the voice server endpoint to an address
|
* Tries to resolve the voice server endpoint to an address
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
@@ -93,22 +100,11 @@ class VoiceConnectionUDPClient extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
send(packet) {
|
send(packet) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.socket) {
|
if (!this.socket) throw new Error('Tried to send a UDP packet, but there is no socket available.');
|
||||||
if (!this.discordAddress || !this.discordPort) {
|
if (!this.discordAddress || !this.discordPort) throw new Error('Malformed UDP address or port.');
|
||||||
reject(new Error('malformed UDP address or port'));
|
this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => {
|
||||||
return;
|
if (error) reject(error); else resolve(packet);
|
||||||
}
|
});
|
||||||
// console.log('sendin', packet);
|
|
||||||
this.socket.send(packet, 0, packet.length, this.discordPort, this.discordAddress, error => {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
resolve(packet);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
reject(new Error('tried to send a UDP packet but there is no socket available'));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,20 +11,21 @@ const EventEmitter = require('events').EventEmitter;
|
|||||||
class VoiceWebSocket extends EventEmitter {
|
class VoiceWebSocket extends EventEmitter {
|
||||||
constructor(voiceConnection) {
|
constructor(voiceConnection) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Voice Connection that this WebSocket serves
|
* The Voice Connection that this WebSocket serves
|
||||||
* @type {VoiceConnection}
|
* @type {VoiceConnection}
|
||||||
*/
|
*/
|
||||||
this.voiceConnection = voiceConnection;
|
this.voiceConnection = voiceConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How many connection attempts have been made
|
* How many connection attempts have been made
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.attempts = 0;
|
this.attempts = 0;
|
||||||
|
|
||||||
this.connect();
|
this.connect();
|
||||||
|
|
||||||
this.dead = false;
|
this.dead = false;
|
||||||
|
|
||||||
this.voiceConnection.on('closing', this.shutdown.bind(this));
|
this.voiceConnection.on('closing', this.shutdown.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,9 +48,7 @@ class VoiceWebSocket extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
reset() {
|
reset() {
|
||||||
if (this.ws) {
|
if (this.ws) {
|
||||||
if (this.ws.readyState !== WebSocket.CLOSED) {
|
if (this.ws.readyState !== WebSocket.CLOSED) this.ws.close();
|
||||||
this.ws.close();
|
|
||||||
}
|
|
||||||
this.ws = null;
|
this.ws = null;
|
||||||
}
|
}
|
||||||
this.clearHeartbeat();
|
this.clearHeartbeat();
|
||||||
@@ -59,17 +58,15 @@ class VoiceWebSocket extends EventEmitter {
|
|||||||
* Starts connecting to the Voice WebSocket Server.
|
* Starts connecting to the Voice WebSocket Server.
|
||||||
*/
|
*/
|
||||||
connect() {
|
connect() {
|
||||||
if (this.dead) {
|
if (this.dead) return;
|
||||||
return;
|
if (this.ws) this.reset();
|
||||||
}
|
|
||||||
if (this.ws) {
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
if (this.attempts > 5) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.attempts++;
|
this.attempts++;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The actual WebSocket used to connect to the Voice WebSocket Server.
|
* The actual WebSocket used to connect to the Voice WebSocket Server.
|
||||||
* @type {WebSocket}
|
* @type {WebSocket}
|
||||||
@@ -88,17 +85,12 @@ class VoiceWebSocket extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
send(data) {
|
send(data) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||||
this.ws.send(data, null, error => {
|
throw new Error(`Voice websocket not open to send ${data}.`);
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
resolve(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
reject(new Error(`voice websocket not open to send ${data}`));
|
|
||||||
}
|
}
|
||||||
|
this.ws.send(data, null, error => {
|
||||||
|
if (error) reject(error); else resolve(data);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +121,7 @@ class VoiceWebSocket extends EventEmitter {
|
|||||||
session_id: this.voiceConnection.authentication.session_id,
|
session_id: this.voiceConnection.authentication.session_id,
|
||||||
},
|
},
|
||||||
}).catch(() => {
|
}).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.'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +142,7 @@ class VoiceWebSocket extends EventEmitter {
|
|||||||
* Called whenever the connection to the WebSocket Server is lost
|
* Called whenever the connection to the WebSocket Server is lost
|
||||||
*/
|
*/
|
||||||
onClose() {
|
onClose() {
|
||||||
// #todo see if the connection is open before reconnecting
|
// TODO see if the connection is open before reconnecting
|
||||||
if (!this.dead) this.client.setTimeout(this.connect.bind(this), this.attempts * 1000);
|
if (!this.dead) this.client.setTimeout(this.connect.bind(this), this.attempts * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +203,7 @@ class VoiceWebSocket extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
setHeartbeat(interval) {
|
setHeartbeat(interval) {
|
||||||
if (!interval || isNaN(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;
|
return;
|
||||||
}
|
}
|
||||||
if (this.heartbeatInterval) {
|
if (this.heartbeatInterval) {
|
||||||
@@ -220,7 +212,7 @@ class VoiceWebSocket extends EventEmitter {
|
|||||||
* @param {string} warn the warning
|
* @param {string} warn the warning
|
||||||
* @event VoiceWebSocket#warn
|
* @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);
|
clearInterval(this.heartbeatInterval);
|
||||||
}
|
}
|
||||||
this.heartbeatInterval = this.client.setInterval(this.sendHeartbeat.bind(this), interval);
|
this.heartbeatInterval = this.client.setInterval(this.sendHeartbeat.bind(this), interval);
|
||||||
@@ -231,7 +223,7 @@ class VoiceWebSocket extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
clearHeartbeat() {
|
clearHeartbeat() {
|
||||||
if (!this.heartbeatInterval) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
clearInterval(this.heartbeatInterval);
|
clearInterval(this.heartbeatInterval);
|
||||||
@@ -242,11 +234,10 @@ class VoiceWebSocket extends EventEmitter {
|
|||||||
* Sends a heartbeat packet
|
* Sends a heartbeat packet
|
||||||
*/
|
*/
|
||||||
sendHeartbeat() {
|
sendHeartbeat() {
|
||||||
this.sendPacket({ op: Constants.VoiceOPCodes.HEARTBEAT, d: null })
|
this.sendPacket({ op: Constants.VoiceOPCodes.HEARTBEAT, d: null }).catch(() => {
|
||||||
.catch(() => {
|
this.emit('warn', 'Tried to send heartbeat, but connection is not open');
|
||||||
this.emit('warn', 'tried to send heartbeat, but connection is not open');
|
this.clearHeartbeat();
|
||||||
this.clearHeartbeat();
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ const ChildProcess = require('child_process');
|
|||||||
const EventEmitter = require('events').EventEmitter;
|
const EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
class PCMConversionProcess extends EventEmitter {
|
class PCMConversionProcess extends EventEmitter {
|
||||||
|
|
||||||
constructor(process) {
|
constructor(process) {
|
||||||
super();
|
super();
|
||||||
this.process = process;
|
this.process = process;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ const EventEmitter = require('events').EventEmitter;
|
|||||||
const StreamDispatcher = require('../dispatcher/StreamDispatcher');
|
const StreamDispatcher = require('../dispatcher/StreamDispatcher');
|
||||||
|
|
||||||
class AudioPlayer extends EventEmitter {
|
class AudioPlayer extends EventEmitter {
|
||||||
|
|
||||||
constructor(voiceConnection) {
|
constructor(voiceConnection) {
|
||||||
super();
|
super();
|
||||||
this.voiceConnection = voiceConnection;
|
this.voiceConnection = voiceConnection;
|
||||||
@@ -20,13 +19,13 @@ class AudioPlayer extends EventEmitter {
|
|||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
pausedTime: 0,
|
pausedTime: 0,
|
||||||
};
|
};
|
||||||
this.voiceConnection.on('closing', () => this.cleanup(null, 'voice connection is closing'));
|
this.voiceConnection.on('closing', () => this.cleanup(null, 'voice connection closing'));
|
||||||
}
|
}
|
||||||
|
|
||||||
playUnknownStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
|
playUnknownStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
|
||||||
const options = { seek, volume, passes };
|
const options = { seek, volume, passes };
|
||||||
stream.on('end', () => {
|
stream.on('end', () => {
|
||||||
this.emit('debug', 'input stream to converter has ended');
|
this.emit('debug', 'Input stream to converter has ended');
|
||||||
});
|
});
|
||||||
stream.on('error', e => this.emit('error', e));
|
stream.on('error', e => this.emit('error', e));
|
||||||
const conversionProcess = this.audioToPCM.createConvertStream(options.seek);
|
const conversionProcess = this.audioToPCM.createConvertStream(options.seek);
|
||||||
@@ -37,7 +36,7 @@ class AudioPlayer extends EventEmitter {
|
|||||||
|
|
||||||
cleanup(checkStream, reason) {
|
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
|
// 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}`);
|
this.emit('debug', `Clean up triggered due to ${reason}`);
|
||||||
const filter = checkStream && this.dispatcher && this.dispatcher.stream === checkStream;
|
const filter = checkStream && this.dispatcher && this.dispatcher.stream === checkStream;
|
||||||
if (this.currentConverter && (checkStream ? filter : true)) {
|
if (this.currentConverter && (checkStream ? filter : true)) {
|
||||||
this.currentConverter.destroy();
|
this.currentConverter.destroy();
|
||||||
@@ -47,7 +46,7 @@ class AudioPlayer extends EventEmitter {
|
|||||||
|
|
||||||
playPCMStream(stream, converter, { seek = 0, volume = 1, passes = 1 } = {}) {
|
playPCMStream(stream, converter, { seek = 0, volume = 1, passes = 1 } = {}) {
|
||||||
const options = { seek, volume, passes };
|
const options = { seek, volume, passes };
|
||||||
stream.on('end', () => this.emit('debug', 'pcm input stream ended'));
|
stream.on('end', () => this.emit('debug', 'PCM input stream ended'));
|
||||||
this.cleanup(null, 'outstanding play stream');
|
this.cleanup(null, 'outstanding play stream');
|
||||||
this.currentConverter = converter;
|
this.currentConverter = converter;
|
||||||
if (this.dispatcher) {
|
if (this.dispatcher) {
|
||||||
@@ -56,10 +55,10 @@ class AudioPlayer extends EventEmitter {
|
|||||||
stream.on('error', e => this.emit('error', e));
|
stream.on('error', e => this.emit('error', e));
|
||||||
const dispatcher = new StreamDispatcher(this, stream, this.streamingData, options);
|
const dispatcher = new StreamDispatcher(this, stream, this.streamingData, options);
|
||||||
dispatcher.on('error', e => this.emit('error', e));
|
dispatcher.on('error', e => this.emit('error', e));
|
||||||
dispatcher.on('end', () => this.cleanup(dispatcher.stream, 'disp ended'));
|
dispatcher.on('end', () => this.cleanup(dispatcher.stream, 'dispatcher ended'));
|
||||||
dispatcher.on('speaking', value => this.voiceConnection.setSpeaking(value));
|
dispatcher.on('speaking', value => this.voiceConnection.setSpeaking(value));
|
||||||
this.dispatcher = dispatcher;
|
this.dispatcher = dispatcher;
|
||||||
dispatcher.on('debug', m => this.emit('debug', `stream dispatch - ${m}`));
|
dispatcher.on('debug', m => this.emit('debug', `Stream dispatch - ${m}`));
|
||||||
return dispatcher;
|
return dispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,8 +101,7 @@ class VoiceConnectionPlayer extends EventEmitter {
|
|||||||
speaking: true,
|
speaking: true,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
})
|
}).catch(e => {
|
||||||
.catch(e => {
|
|
||||||
this.emit('debug', e);
|
this.emit('debug', e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,17 +25,20 @@ class VoiceReceiver extends EventEmitter {
|
|||||||
this.queues = new Map();
|
this.queues = new Map();
|
||||||
this.pcmStreams = new Map();
|
this.pcmStreams = new Map();
|
||||||
this.opusStreams = new Map();
|
this.opusStreams = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not this receiver has been destroyed.
|
* Whether or not this receiver has been destroyed.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.destroyed = false;
|
this.destroyed = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The VoiceConnection that instantiated this
|
* The VoiceConnection that instantiated this
|
||||||
* @type {VoiceConnection}
|
* @type {VoiceConnection}
|
||||||
*/
|
*/
|
||||||
this.voiceConnection = connection;
|
this.voiceConnection = connection;
|
||||||
this._listener = (msg => {
|
|
||||||
|
this._listener = msg => {
|
||||||
const ssrc = +msg.readUInt32BE(8).toString(10);
|
const ssrc = +msg.readUInt32BE(8).toString(10);
|
||||||
const user = this.voiceConnection.ssrcMap.get(ssrc);
|
const user = this.voiceConnection.ssrcMap.get(ssrc);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -50,7 +53,7 @@ class VoiceReceiver extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.handlePacket(msg, user);
|
this.handlePacket(msg, user);
|
||||||
}
|
}
|
||||||
}).bind(this);
|
};
|
||||||
this.voiceConnection.sockets.udp.socket.on('message', this._listener);
|
this.voiceConnection.sockets.udp.socket.on('message', this._listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ class SecretKey {
|
|||||||
* @type {Uint8Array}
|
* @type {Uint8Array}
|
||||||
*/
|
*/
|
||||||
this.key = new Uint8Array(new ArrayBuffer(key.length));
|
this.key = new Uint8Array(new ArrayBuffer(key.length));
|
||||||
for (const index in key) {
|
for (const index in key) this.key[index] = key[index];
|
||||||
this.key[index] = key[index];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,8 +236,7 @@ class Message {
|
|||||||
*/
|
*/
|
||||||
get cleanContent() {
|
get cleanContent() {
|
||||||
return this.content
|
return this.content
|
||||||
.replace(/@everyone/g, '@\u200Beveryone')
|
.replace(/@(everyone|here)/g, '@\u200b$1')
|
||||||
.replace(/@here/g, '@\u200Bhere')
|
|
||||||
.replace(/<@!?[0-9]+>/g, (input) => {
|
.replace(/<@!?[0-9]+>/g, (input) => {
|
||||||
const id = input.replace(/<|!|>|@/g, '');
|
const id = input.replace(/<|!|>|@/g, '');
|
||||||
if (this.channel.type === 'dm' || this.channel.type === 'group') {
|
if (this.channel.type === 'dm' || this.channel.type === 'group') {
|
||||||
@@ -396,7 +395,7 @@ class Message {
|
|||||||
content = `${prepend}${content}`;
|
content = `${prepend}${content}`;
|
||||||
|
|
||||||
if (options.split) {
|
if (options.split) {
|
||||||
if (typeof options.split !== 'object' && typeof options.split !== 'boolean') options.split = {};
|
if (typeof options.split !== 'object') options.split = {};
|
||||||
if (!options.split.prepend) options.split.prepend = prepend;
|
if (!options.split.prepend) options.split.prepend = prepend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user