mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
452 lines
12 KiB
JavaScript
452 lines
12 KiB
JavaScript
"use strict";
|
|
/*
|
|
Major credit to izy521 who is the creator of
|
|
https://github.com/izy521/discord.io,
|
|
|
|
without his help voice chat in discord.js would not have
|
|
been possible!
|
|
*/
|
|
|
|
exports.__esModule = true;
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
|
|
|
var _ws = require("ws");
|
|
|
|
var _ws2 = _interopRequireDefault(_ws);
|
|
|
|
var _dns = require("dns");
|
|
|
|
var _dns2 = _interopRequireDefault(_dns);
|
|
|
|
var _dgram = require("dgram");
|
|
|
|
var _dgram2 = _interopRequireDefault(_dgram);
|
|
|
|
var _AudioEncoder = require("./AudioEncoder");
|
|
|
|
var _AudioEncoder2 = _interopRequireDefault(_AudioEncoder);
|
|
|
|
var _VoicePacket = require("./VoicePacket");
|
|
|
|
var _VoicePacket2 = _interopRequireDefault(_VoicePacket);
|
|
|
|
var _StreamIntent = require("./StreamIntent");
|
|
|
|
var _StreamIntent2 = _interopRequireDefault(_StreamIntent);
|
|
|
|
var _events = require("events");
|
|
|
|
var _events2 = _interopRequireDefault(_events);
|
|
|
|
var _unpipe = require("unpipe");
|
|
|
|
var _unpipe2 = _interopRequireDefault(_unpipe);
|
|
|
|
var VoiceConnection = (function (_EventEmitter) {
|
|
_inherits(VoiceConnection, _EventEmitter);
|
|
|
|
function VoiceConnection(channel, client, session, token, server, endpoint) {
|
|
_classCallCheck(this, VoiceConnection);
|
|
|
|
_EventEmitter.call(this);
|
|
this.id = channel.id;
|
|
this.voiceChannel = channel;
|
|
this.client = client;
|
|
this.session = session;
|
|
this.token = token;
|
|
this.server = server;
|
|
this.endpoint = endpoint.split(":")[0];
|
|
this.vWS = null; // vWS means voice websocket
|
|
this.ready = false;
|
|
this.vWSData = {};
|
|
this.encoder = new _AudioEncoder2["default"]();
|
|
this.udp = null;
|
|
this.playingIntent = null;
|
|
this.playing = false;
|
|
this.streamTime = 0;
|
|
this.streamProc = null;
|
|
this.KAI = null;
|
|
this.timestamp = 0;
|
|
this.sequence = 0;
|
|
this.init();
|
|
}
|
|
|
|
VoiceConnection.prototype.destroy = function destroy() {
|
|
this.stopPlaying();
|
|
if (this.KAI) {
|
|
clearInterval(this.KAI);
|
|
}
|
|
this.vWS.close();
|
|
this.udp.close();
|
|
this.client.internal.sendWS({
|
|
op: 4,
|
|
d: {
|
|
guild_id: null,
|
|
channel_id: null,
|
|
self_mute: true,
|
|
self_deaf: false
|
|
}
|
|
});
|
|
};
|
|
|
|
VoiceConnection.prototype.stopPlaying = function stopPlaying() {
|
|
this.playing = false;
|
|
this.playingIntent = null;
|
|
if (this.instream) {
|
|
//not all streams implement these...
|
|
//and even file stream don't seem to implement them properly...
|
|
_unpipe2["default"](this.instream);
|
|
if (this.instream.end) {
|
|
this.instream.end();
|
|
}
|
|
if (this.instream.destroy) {
|
|
this.instream.destroy();
|
|
}
|
|
this.instream = null;
|
|
}
|
|
if (this.streamProc) {
|
|
this.streamProc.stdin.pause();
|
|
this.streamProc.kill("SIGINT");
|
|
this.streamProc = null;
|
|
}
|
|
};
|
|
|
|
VoiceConnection.prototype.playStream = function playStream(stream) {
|
|
var channels = arguments.length <= 1 || arguments[1] === undefined ? 2 : arguments[1];
|
|
|
|
var self = this,
|
|
startTime = Date.now(),
|
|
count = 0,
|
|
length = 20,
|
|
retStream = new _StreamIntent2["default"](),
|
|
onWarning = false,
|
|
lastVolume = this.volume !== undefined ? this.volume.get() : 1;
|
|
|
|
this.playing = true;
|
|
this.playingIntent = retStream;
|
|
|
|
this.setVolume(lastVolume);
|
|
|
|
function send() {
|
|
if (!self.playingIntent || !self.playing) {
|
|
self.setSpeaking(false);
|
|
retStream.emit("end");
|
|
//console.log("ending 1");
|
|
return;
|
|
}
|
|
try {
|
|
|
|
var buffer = stream.read(1920 * channels);
|
|
|
|
if (!buffer) {
|
|
if (onWarning) {
|
|
retStream.emit("end");
|
|
self.setSpeaking(false);
|
|
//console.log("ending 2");
|
|
return;
|
|
} else {
|
|
onWarning = true;
|
|
setTimeout(send, length * 10); // give chance for some data in 200ms to appear
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (buffer.length !== 1920 * channels) {
|
|
var newBuffer = new Buffer(1920 * channels).fill(0);
|
|
buffer.copy(newBuffer);
|
|
buffer = newBuffer;
|
|
}
|
|
|
|
count++;
|
|
self.sequence + 1 < 65535 ? self.sequence += 1 : self.sequence = 0;
|
|
self.timestamp + 960 < 4294967295 ? self.timestamp += 960 : self.timestamp = 0;
|
|
|
|
self.sendBuffer(buffer, self.sequence, self.timestamp, function (e) {});
|
|
|
|
var nextTime = startTime + count * length;
|
|
|
|
self.streamTime = count * length;
|
|
|
|
setTimeout(send, length + (nextTime - Date.now()));
|
|
|
|
if (!self.playing) self.setSpeaking(true);
|
|
|
|
retStream.emit("time", self.streamTime);
|
|
} catch (e) {
|
|
retStream.emit("error", e);
|
|
}
|
|
}
|
|
self.setSpeaking(true);
|
|
send();
|
|
|
|
return retStream;
|
|
};
|
|
|
|
VoiceConnection.prototype.setSpeaking = function setSpeaking(value) {
|
|
this.playing = value;
|
|
if (this.vWS.readyState === _ws2["default"].OPEN) this.vWS.send(JSON.stringify({
|
|
op: 5,
|
|
d: {
|
|
speaking: value,
|
|
delay: 0
|
|
}
|
|
}));
|
|
};
|
|
|
|
VoiceConnection.prototype.sendPacket = function sendPacket(packet) {
|
|
var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err) {} : arguments[1];
|
|
|
|
var self = this;
|
|
self.playing = true;
|
|
try {
|
|
if (self.vWS.readyState === _ws2["default"].OPEN) self.udp.send(packet, 0, packet.length, self.vWSData.port, self.endpoint, callback);
|
|
} catch (e) {
|
|
self.playing = false;
|
|
callback(e);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
VoiceConnection.prototype.sendBuffer = function sendBuffer(rawbuffer, sequence, timestamp, callback) {
|
|
var self = this;
|
|
self.playing = true;
|
|
try {
|
|
if (!self.encoder.opus) {
|
|
self.playing = false;
|
|
self.emit("error", "No Opus!");
|
|
self.client.emit("debug", "Tried to use node-opus, but opus not available - install it!");
|
|
return;
|
|
}
|
|
|
|
if (!self.encoder.sanityCheck()) {
|
|
self.playing = false;
|
|
self.emit("error", "Opus sanity check failed!");
|
|
self.client.emit("debug", "Opus sanity check failed - opus is installed but not correctly! Please reinstall opus and make sure it's installed correctly.");
|
|
return;
|
|
}
|
|
|
|
var buffer = self.encoder.opusBuffer(rawbuffer);
|
|
var packet = new _VoicePacket2["default"](buffer, sequence, timestamp, self.vWSData.ssrc);
|
|
return self.sendPacket(packet, callback);
|
|
} catch (e) {
|
|
self.playing = false;
|
|
self.emit("error", e);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
VoiceConnection.prototype.playFile = function playFile(stream) {
|
|
var _this = this;
|
|
|
|
var options = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
|
|
var callback = arguments.length <= 2 || arguments[2] === undefined ? function (err, str) {} : arguments[2];
|
|
|
|
var self = this;
|
|
self.stopPlaying();
|
|
if (typeof options === "function") {
|
|
// options is the callback
|
|
callback = options;
|
|
options = {};
|
|
}
|
|
if (options.volume != null) {
|
|
this.setVolume(options.volume);
|
|
}
|
|
return new Promise(function (resolve, reject) {
|
|
_this.encoder.encodeFile(stream, options)["catch"](error).then(function (data) {
|
|
self.streamProc = data.proc;
|
|
var intent = self.playStream(data.stream, 2);
|
|
resolve(intent);
|
|
callback(null, intent);
|
|
});
|
|
function error() {
|
|
var e = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
|
|
|
|
reject(e);
|
|
callback(e);
|
|
}
|
|
});
|
|
};
|
|
|
|
VoiceConnection.prototype.playRawStream = function playRawStream(stream) {
|
|
var _this2 = this;
|
|
|
|
var options = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
|
|
var callback = arguments.length <= 2 || arguments[2] === undefined ? function (err, str) {} : arguments[2];
|
|
|
|
var self = this;
|
|
self.stopPlaying();
|
|
if (typeof options === "function") {
|
|
// options is the callback
|
|
callback = options;
|
|
options = {};
|
|
}
|
|
if (options.volume != null) {
|
|
this.setVolume(options.volume);
|
|
}
|
|
return new Promise(function (resolve, reject) {
|
|
_this2.encoder.encodeStream(stream, options)["catch"](error).then(function (data) {
|
|
self.streamProc = data.proc;
|
|
self.instream = data.instream;
|
|
var intent = self.playStream(data.stream);
|
|
resolve(intent);
|
|
callback(null, intent);
|
|
});
|
|
function error() {
|
|
var e = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
|
|
|
|
reject(e);
|
|
callback(e);
|
|
}
|
|
});
|
|
};
|
|
|
|
VoiceConnection.prototype.playArbitraryFFmpeg = function playArbitraryFFmpeg(ffmpegOptions) {
|
|
var _this3 = this;
|
|
|
|
var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, str) {} : arguments[1];
|
|
|
|
var self = this;
|
|
self.stopPlaying();
|
|
if (typeof options === "function") {
|
|
// options is the callback
|
|
callback = options;
|
|
options = {};
|
|
}
|
|
if (options.volume != null) {
|
|
this.setVolume(options.volume);
|
|
}
|
|
return new Promise(function (resolve, reject) {
|
|
_this3.encoder.encodeArbitraryFFmpeg(ffmpegOptions)["catch"](error).then(function (data) {
|
|
self.streamProc = data.proc;
|
|
self.instream = data.instream;
|
|
var intent = self.playStream(data.stream);
|
|
resolve(intent);
|
|
callback(null, intent);
|
|
});
|
|
function error() {
|
|
var e = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
|
|
|
|
reject(e);
|
|
callback(e);
|
|
}
|
|
});
|
|
};
|
|
|
|
VoiceConnection.prototype.init = function init() {
|
|
var _this4 = this;
|
|
|
|
var self = this;
|
|
_dns2["default"].lookup(this.endpoint, function (err, address, family) {
|
|
var vWS = self.vWS = new _ws2["default"]("wss://" + _this4.endpoint, null, { rejectUnauthorized: false });
|
|
_this4.endpoint = address;
|
|
var udpClient = self.udp = _dgram2["default"].createSocket("udp4");
|
|
|
|
var firstPacket = true;
|
|
|
|
var discordIP = "",
|
|
discordPort = "";
|
|
|
|
udpClient.bind({ exclusive: true });
|
|
udpClient.on('message', function (msg, rinfo) {
|
|
var buffArr = JSON.parse(JSON.stringify(msg)).data;
|
|
if (firstPacket === true) {
|
|
for (var i = 4; i < buffArr.indexOf(0, i); i++) {
|
|
discordIP += String.fromCharCode(buffArr[i]);
|
|
}
|
|
discordPort = msg.readUIntLE(msg.length - 2, 2).toString(10);
|
|
|
|
vWS.send(JSON.stringify({
|
|
"op": 1,
|
|
"d": {
|
|
"protocol": "udp",
|
|
"data": {
|
|
"address": discordIP,
|
|
"port": Number(discordPort),
|
|
"mode": self.vWSData.modes[0] //Plain
|
|
}
|
|
}
|
|
}));
|
|
firstPacket = false;
|
|
}
|
|
});
|
|
|
|
vWS.on("open", function () {
|
|
vWS.send(JSON.stringify({
|
|
op: 0,
|
|
d: {
|
|
server_id: self.server.id,
|
|
user_id: self.client.internal.user.id,
|
|
session_id: self.session,
|
|
token: self.token
|
|
}
|
|
}));
|
|
});
|
|
|
|
var KAI;
|
|
|
|
vWS.on("message", function (msg) {
|
|
var data = JSON.parse(msg);
|
|
switch (data.op) {
|
|
case 2:
|
|
self.vWSData = data.d;
|
|
|
|
self.KAI = KAI = self.client.internal.intervals.misc["voiceKAI"] = setInterval(function () {
|
|
if (vWS && vWS.readyState === _ws2["default"].OPEN) vWS.send(JSON.stringify({
|
|
op: 3,
|
|
d: null
|
|
}));
|
|
}, data.d.heartbeat_interval);
|
|
|
|
var udpPacket = new Buffer(70);
|
|
udpPacket.writeUIntBE(data.d.ssrc, 0, 4);
|
|
udpClient.send(udpPacket, 0, udpPacket.length, data.d.port, self.endpoint, function (err) {
|
|
if (err) self.emit("error", err);
|
|
});
|
|
break;
|
|
case 4:
|
|
|
|
self.ready = true;
|
|
self.mode = data.d.mode;
|
|
self.emit("ready", self);
|
|
|
|
break;
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
VoiceConnection.prototype.wrapVolume = function wrapVolume(stream) {
|
|
stream.pipe(this.volume);
|
|
|
|
return this.volume;
|
|
};
|
|
|
|
VoiceConnection.prototype.setVolume = function setVolume(volume) {
|
|
this.volume.set(volume);
|
|
};
|
|
|
|
VoiceConnection.prototype.getVolume = function getVolume() {
|
|
return this.volume.get();
|
|
};
|
|
|
|
VoiceConnection.prototype.mute = function mute() {
|
|
this.lastVolume = this.volume.get();
|
|
this.setVolume(0);
|
|
};
|
|
|
|
VoiceConnection.prototype.unmute = function unmute() {
|
|
this.setVolume(this.lastVolume);
|
|
this.lastVolume = undefined;
|
|
};
|
|
|
|
return VoiceConnection;
|
|
})(_events2["default"]);
|
|
|
|
exports["default"] = VoiceConnection;
|
|
module.exports = exports["default"];
|