Merge pull request #50 from hydrabolt/voice-rewrite

Added basic voice capabilities
This commit is contained in:
Amish Shah
2015-11-08 15:57:41 +00:00
18 changed files with 1195 additions and 147 deletions

View File

@@ -532,6 +532,24 @@ var Client = (function (_EventEmitter) {
});
};
//def joinVoiceChannel
Client.prototype.joinVoiceChannel = function joinVoiceChannel(channel) {
var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err) {} : arguments[1];
var self = this;
return new Promise(function (resolve, reject) {
self.internal.joinVoiceChannel(channel).then(function (chan) {
callback(null, chan);
resolve(chan);
})["catch"](function (err) {
callback(err);
reject(err);
});
});
};
_createClass(Client, [{
key: "users",
get: function get() {

View File

@@ -22,7 +22,8 @@ var User = require("../Structures/User.js"),
Server = require("../Structures/Server.js"),
Message = require("../Structures/Message.js"),
Role = require("../Structures/Role.js"),
Invite = require("../Structures/Invite.js");
Invite = require("../Structures/Invite.js"),
VoiceConnection = require("../Voice/VoiceConnection.js");
var zlib;
@@ -43,9 +44,76 @@ var InternalClient = (function () {
this.channels = new Cache();
this.servers = new Cache();
this.private_channels = new Cache();
this.voiceConnection = null;
this.resolver = new Resolver(this);
}
//def leaveVoiceChannel
InternalClient.prototype.leaveVoiceChannel = function leaveVoiceChannel() {
var self = this;
return new Promise(function (resolve, reject) {
if (self.voiceConnection) {
self.voiceConnection.destroy();
self.voiceConnection = null;
resolve();
} else {
resolve();
}
});
};
//def joinVoiceChannel
InternalClient.prototype.joinVoiceChannel = function joinVoiceChannel(chann) {
var self = this;
return new Promise(function (resolve, reject) {
var channel = self.resolver.resolveVoiceChannel(chann);
if (channel) {
var next = function next() {
var session,
token,
server = channel.server,
endpoint;
var check = function check(m) {
var data = JSON.parse(m);
if (data.t === "VOICE_STATE_UPDATE") {
session = data.d.session_id;
} else if (data.t === "VOICE_SERVER_UPDATE") {
token = data.d.token;
endpoint = data.d.endpoint;
var chan = self.voiceConnection = new VoiceConnection(channel, self.client, session, token, server, endpoint);
chan.on("ready", resolve);
chan.on("error", reject);
self.client.emit("debug", "removed temporary voice websocket listeners");
self.websocket.removeListener("message", check);
}
};
self.websocket.on("message", check);
self.sendWS({
op: 4,
d: {
"guild_id": server.id,
"channel_id": channel.id,
"self_mute": false,
"self_deaf": false
}
});
};
self.leaveVoiceChannel().then(next);
} else {
reject(new Error("voice channel does not exist"));
}
});
};
// def createServer
InternalClient.prototype.createServer = function createServer(name) {
@@ -718,7 +786,6 @@ var InternalClient = (function () {
}
request.put(Endpoints.CHANNEL_PERMISSIONS(channel.id) + "/" + data.id).set("authorization", self.token).send(data).end(function (err) {
console.log(err);
if (err) {
reject(err);
} else {

View File

@@ -116,6 +116,14 @@ var Resolver = (function () {
return found;
};
Resolver.prototype.resolveVoiceChannel = function resolveVoiceChannel(resource) {
// resolveChannel will also work but this is more apt
if (resource instanceof VoiceChannel) {
return resource;
}
return null;
};
Resolver.prototype.resolveChannel = function resolveChannel(resource) {
/*
accepts a Message, Channel, Server, String ID, User

98
lib/Voice/AudioEncoder.js Normal file
View File

@@ -0,0 +1,98 @@
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var cpoc = require("child_process");
var opus;
try {
opus = require("node-opus");
} catch (e) {
// no opus!
}
var VoicePacket = require("./VoicePacket.js");
var AudioEncoder = (function () {
function AudioEncoder() {
_classCallCheck(this, AudioEncoder);
if (opus) {
this.opus = new opus.OpusEncoder(48000, 1);
}
}
AudioEncoder.prototype.opusBuffer = function opusBuffer(buffer) {
return this.opus.encode(buffer, 1920);
};
AudioEncoder.prototype.encodeStream = function encodeStream(stream) {
var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, buffer) {} : arguments[1];
var self = this;
return new Promise(function (resolve, reject) {
var enc = cpoc.spawn("ffmpeg", ["-f", "s16le", "-ar", "48000", "-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth.
"-af", "volume=1", "pipe:1", "-i", "-"]);
stream.pipe(enc.stdin);
enc.stdout.once("readable", function () {
callback(null, {
proc: enc,
stream: enc.stdout,
instream: stream
});
resolve({
proc: enc,
stream: enc.stdout,
instream: stream
});
});
enc.stdout.on("end", function () {
callback("end");
reject("end");
});
enc.stdout.on("close", function () {
callback("close");
reject("close");
});
});
};
AudioEncoder.prototype.encodeFile = function encodeFile(file) {
var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, buffer) {} : arguments[1];
var self = this;
return new Promise(function (resolve, reject) {
var enc = cpoc.spawn("ffmpeg", ["-f", "s16le", "-ar", "48000", "-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth.
"-af", "volume=1", "pipe:1", "-i", file]);
enc.stdout.once("readable", function () {
callback(null, {
proc: enc,
stream: enc.stdout
});
resolve({
proc: enc,
stream: enc.stdout
});
});
enc.stdout.on("end", function () {
callback("end");
reject("end");
});
enc.stdout.on("close", function () {
callback("close");
reject("close");
});
});
};
return AudioEncoder;
})();
module.exports = AudioEncoder;

22
lib/Voice/StreamIntent.js Normal file
View File

@@ -0,0 +1,22 @@
"use strict";
// represents an intent of streaming music
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 EventEmitter = require("events");
var StreamIntent = (function (_EventEmitter) {
_inherits(StreamIntent, _EventEmitter);
function StreamIntent() {
_classCallCheck(this, StreamIntent);
_EventEmitter.call(this);
}
return StreamIntent;
})(EventEmitter);
module.exports = StreamIntent;

View File

@@ -0,0 +1,323 @@
"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!
*/
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 WebSocket = require("ws");
var dns = require("dns");
var udp = require("dgram");
var fs = require("fs");
var AudioEncoder = require("./AudioEncoder.js");
var VoicePacket = require("./VoicePacket.js");
var StreamIntent = require("./StreamIntent.js");
var EventEmitter = require("events");
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.replace(":80", "");
this.vWS = null; // vWS means voice websocket
this.ready = false;
this.vWSData = {};
this.encoder = new AudioEncoder();
this.udp = null;
this.playingIntent = null;
this.playing = false;
this.streamTime = 0;
this.streamProc = null;
this.KAI = null;
this.init();
}
VoiceConnection.prototype.destroy = function destroy() {
this.stopPlaying();
if (this.KAI) clearInterval(this.KAI);
this.vWS.close();
this.udp.close();
};
VoiceConnection.prototype.stopPlaying = function stopPlaying() {
this.playing = false;
this.playingIntent = null;
if (this.instream) {
this.instream.end();
this.instream.destroy();
}
};
VoiceConnection.prototype.playStream = function playStream(stream) {
var self = this;
var startTime = Date.now();
var sequence = 0;
var time = 0;
var count = 0;
var length = 20;
if (self.playingIntent) {
self.stopPlaying();
}
self.playing = true;
var retStream = new StreamIntent();
var onWarning = false;
self.playingIntent = retStream;
function send() {
if (!self.playingIntent || !self.playing) {
self.setSpeaking(false);
retStream.emit("end");
self;
return;
}
try {
var buffer = stream.read(1920);
if (!buffer) {
setTimeout(send, length * 10); // give chance for some data in 200ms to appear
return;
}
if (buffer.length !== 1920) {
if (onWarning) {
retStream.emit("end");
stream.destroy();
self.setSpeaking(false);
return;
} else {
onWarning = true;
setTimeout(send, length * 10); // give chance for some data in 200ms to appear
return;
}
}
count++;
sequence + 10 < 65535 ? sequence += 1 : sequence = 0;
time + 9600 < 4294967295 ? time += 960 : time = 0;
self.sendBuffer(buffer, sequence, time, 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 === WebSocket.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 === WebSocket.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;
}
var buffer = self.encoder.opusBuffer(rawbuffer);
var packet = new VoicePacket(buffer, sequence, timestamp, self.vWSData.ssrc);
return self.sendPacket(packet, callback);
} catch (e) {
self.playing = false;
self.emit("error", e);
return false;
}
};
VoiceConnection.prototype.test = function test() {
this.playFile("C:/users/amish/desktop/audio.mp3").then(function (stream) {
stream.on("time", function (time) {
console.log("Time", time);
});
});
};
VoiceConnection.prototype.playFile = function playFile(stream) {
var _this = this;
var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, str) {} : arguments[1];
var self = this;
return new Promise(function (resolve, reject) {
_this.encoder.encodeFile(stream)["catch"](error).then(function (data) {
self.streamProc = data.proc;
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.playRawStream = function playRawStream(stream) {
var _this2 = this;
var callback = arguments.length <= 1 || arguments[1] === undefined ? function (err, str) {} : arguments[1];
var self = this;
return new Promise(function (resolve, reject) {
_this2.encoder.encodeStream(stream)["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 _this3 = this;
var self = this;
dns.lookup(this.endpoint, function (err, address, family) {
self.endpoint = address;
var vWS = self.vWS = new WebSocket("wss://" + _this3.endpoint, null, { rejectUnauthorized: false });
var udpClient = self.udp = udp.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);
var wsDiscPayload = {
"op": 1,
"d": {
"protocol": "udp",
"data": {
"address": discordIP,
"port": Number(discordPort),
"mode": self.vWSData.modes[0] //Plain
}
}
};
vWS.send(JSON.stringify(wsDiscPayload));
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;
KAI = setInterval(function () {
if (vWS.readyState === WebSocket.OPEN) vWS.send(JSON.stringify({
op: 3,
d: null
}));
}, data.d.heartbeat_interval);
self.KAI = KAI;
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;
}
});
});
};
return VoiceConnection;
})(EventEmitter);
module.exports = VoiceConnection;

26
lib/Voice/VoicePacket.js Normal file
View File

@@ -0,0 +1,26 @@
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var VoicePacket = function VoicePacket(data, sequence, time, ssrc) {
_classCallCheck(this, VoicePacket);
var audioBuffer = data,
returnBuffer = new Buffer(audioBuffer.length + 12);
returnBuffer.fill(0);
returnBuffer[0] = 0x80;
returnBuffer[1] = 0x78;
returnBuffer.writeUIntBE(sequence, 2, 2);
returnBuffer.writeUIntBE(time, 4, 4);
returnBuffer.writeUIntBE(ssrc, 8, 4);
for (var i = 0; i < audioBuffer.length; i++) {
returnBuffer[i + 12] = audioBuffer[i];
}
return returnBuffer;
};
module.exports = VoicePacket;

View File

@@ -1,54 +1,17 @@
"use strict";
module.exports = {
Client: require("./Client/Client.js")
};
var a = new module.exports.Client();
a.on("debug", function (m) {
return console.log("[debug]", m);
});
a.on("warn", function (m) {
return console.log("[warn]", m);
});
var start = Date.now();
a.on("message", function (m) {
if (m.content === "$$$") {
a.internal.setTopic(m.channel, "a channel topic!");
}
});
a.on("userTypingStart", function (user, chan) {
console.log(user.username + " typing");
});
a.on("userTypingStop", function (user, chan) {
console.log(user.username + " stopped typing");
});
a.on("ready", function () {
for (var _iterator = a.internal.servers, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var server = _ref;
if (server.name === "craptown") {
a.leaveServer(server);
}
}
});
function error(e) {
throw e;
process.exit(0);
}
a.login(process.env["discordEmail"], process.env["discordPass"])["catch"](function (e) {
return console.log(e);
});
Client: require("./Client/Client"),
Channel: require("./Structures/Channel"),
ChannelPermissions: require("./Structures/ChannelPermissions"),
Invite: require("./Structures/Invite"),
Message: require("./Structures/Message"),
PermissionOverwrite: require("./Structures/PermissionOverwrite"),
PMChannel: require("./Structures/PMChannel"),
Role: require("./Structures/Role"),
Server: require("./Structures/Server"),
ServerChannel: require("./Structures/ServerChannel"),
TextChannel: require("./Structures/TextChannel"),
User: require("./Structures/User"),
VoiceChannel: require("./Structures/VoiceChannel")
};

View File

@@ -34,5 +34,8 @@
"grunt-browserify": "^4.0.0",
"grunt-contrib-uglify": "^0.9.2",
"load-grunt-tasks": "^3.2.0"
},
"optionalDependencies": {
"node-opus": "^0.1.11"
}
}

View File

@@ -513,6 +513,24 @@ class Client extends EventEmitter {
})
}
//def joinVoiceChannel
joinVoiceChannel(channel, callback=function(err){}){
var self = this;
return new Promise((resolve, reject)=>{
self.internal.joinVoiceChannel(channel)
.then(chan => {
callback(null, chan);
resolve(chan);
})
.catch(err => {
callback(err);
reject(err);
});
});
}
}
module.exports = Client;

View File

@@ -20,7 +20,8 @@ var User = require("../Structures/User.js"),
Server = require("../Structures/Server.js"),
Message = require("../Structures/Message.js"),
Role = require("../Structures/Role.js"),
Invite = require("../Structures/Invite.js");
Invite = require("../Structures/Invite.js"),
VoiceConnection = require("../Voice/VoiceConnection.js");
var zlib;
@@ -39,8 +40,74 @@ class InternalClient {
this.channels = new Cache();
this.servers = new Cache();
this.private_channels = new Cache();
this.voiceConnection = null;
this.resolver = new Resolver(this);
}
//def leaveVoiceChannel
leaveVoiceChannel(){
var self = this;
return new Promise((resolve, reject) => {
if(self.voiceConnection){
self.voiceConnection.destroy();
self.voiceConnection = null;
resolve();
}else{
resolve();
}
});
}
//def joinVoiceChannel
joinVoiceChannel(chann){
var self = this;
return new Promise((resolve, reject) => {
var channel = self.resolver.resolveVoiceChannel(chann);
if(channel){
self.leaveVoiceChannel().then(next);
function next(){
var session, token, server = channel.server, endpoint;
var check = (m) => {
var data = JSON.parse(m);
if(data.t === "VOICE_STATE_UPDATE"){
session = data.d.session_id;
}else if(data.t === "VOICE_SERVER_UPDATE"){
token = data.d.token;
endpoint = data.d.endpoint;
var chan = self.voiceConnection = new VoiceConnection(channel, self.client, session, token, server, endpoint);
chan.on("ready", resolve);
chan.on("error", reject);
self.client.emit("debug", "removed temporary voice websocket listeners");
self.websocket.removeListener("message", check);
}
};
self.websocket.on("message", check);
self.sendWS({
op : 4,
d : {
"guild_id" : server.id,
"channel_id" : channel.id,
"self_mute" : false,
"self_deaf" : false
}
});
}
}else{
reject(new Error("voice channel does not exist"));
}
});
}
// def createServer
createServer(name, region = "london") {
var self = this;
@@ -766,7 +833,6 @@ class InternalClient {
.set("authorization", self.token)
.send(data)
.end(function (err) {
console.log(err);
if (err) {
reject(err);
} else {

View File

@@ -98,6 +98,14 @@ class Resolver {
return found;
}
resolveVoiceChannel(resource) {
// resolveChannel will also work but this is more apt
if(resource instanceof VoiceChannel){
return resource;
}
return null;
}
resolveChannel(resource) {
/*

101
src/Voice/AudioEncoder.js Normal file
View File

@@ -0,0 +1,101 @@
"use strict";
var cpoc = require("child_process");
var opus;
try{
opus = require("node-opus");
}catch(e){
// no opus!
}
var VoicePacket = require("./VoicePacket.js");
class AudioEncoder{
constructor(){
if(opus){
this.opus = new opus.OpusEncoder(48000, 1);
}
}
opusBuffer(buffer){
return this.opus.encode(buffer, 1920);
}
encodeStream(stream, callback=function(err, buffer){}){
var self = this;
return new Promise((resolve, reject) => {
var enc = cpoc.spawn("ffmpeg" , [
"-f", "s16le",
"-ar", "48000",
"-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth.
"-af", "volume=1",
"pipe:1",
"-i", "-"
]);
stream.pipe(enc.stdin);
enc.stdout.once("readable", function() {
callback(null, {
proc : enc,
stream : enc.stdout,
instream : stream
});
resolve({
proc : enc,
stream : enc.stdout,
instream : stream
});
});
enc.stdout.on("end", function() {
callback("end");
reject("end");
});
enc.stdout.on("close", function() {
callback("close");
reject("close");
});
});
}
encodeFile(file, callback=function(err, buffer){}){
var self = this;
return new Promise((resolve, reject) => {
var enc = cpoc.spawn("ffmpeg" , [
"-f", "s16le",
"-ar", "48000",
"-ac", "1", // this can be 2 but there's no point, discord makes it mono on playback, wasted bandwidth.
"-af", "volume=1",
"pipe:1",
"-i", file
]);
enc.stdout.once("readable", function() {
callback(null, {
proc : enc,
stream : enc.stdout
});
resolve({
proc : enc,
stream : enc.stdout
});
});
enc.stdout.on("end", function() {
callback("end");
reject("end");
});
enc.stdout.on("close", function() {
callback("close");
reject("close");
});
});
}
}
module.exports = AudioEncoder;

11
src/Voice/StreamIntent.js Normal file
View File

@@ -0,0 +1,11 @@
"use strict";
// represents an intent of streaming music
var EventEmitter = require("events");
class StreamIntent extends EventEmitter{
constructor(){
super();
}
}
module.exports = StreamIntent;

View File

@@ -0,0 +1,315 @@
"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!
*/
var WebSocket = require("ws");
var dns = require("dns");
var udp = require("dgram");
var fs = require("fs");
var AudioEncoder = require("./AudioEncoder.js");
var VoicePacket = require("./VoicePacket.js");
var StreamIntent = require("./StreamIntent.js");
var EventEmitter = require("events");
class VoiceConnection extends EventEmitter {
constructor(channel, client, session, token, server, endpoint) {
super();
this.id = channel.id;
this.voiceChannel = channel;
this.client = client;
this.session = session;
this.token = token;
this.server = server;
this.endpoint = endpoint.replace(":80", "");
this.vWS = null; // vWS means voice websocket
this.ready = false;
this.vWSData = {};
this.encoder = new AudioEncoder();
this.udp = null;
this.playingIntent = null;
this.playing = false;
this.streamTime = 0;
this.streamProc = null;
this.KAI = null;
this.init();
}
destroy() {
this.stopPlaying();
if(this.KAI)
clearInterval(this.KAI);
this.vWS.close();
this.udp.close();
}
stopPlaying() {
this.playing = false;
this.playingIntent = null;
if(this.instream){
this.instream.end();
this.instream.destroy();
}
}
playStream(stream) {
var self = this;
var startTime = Date.now();
var sequence = 0;
var time = 0;
var count = 0;
var length = 20;
if (self.playingIntent) {
self.stopPlaying();
}
self.playing = true;
var retStream = new StreamIntent();
var onWarning = false;
self.playingIntent = retStream;
function send() {
if (!self.playingIntent || !self.playing) {
self.setSpeaking(false);
retStream.emit("end");
self
return;
}
try {
var buffer = stream.read(1920);
if (!buffer) {
setTimeout(send, length * 10); // give chance for some data in 200ms to appear
return;
}
if (buffer.length !== 1920) {
if (onWarning) {
retStream.emit("end");
stream.destroy();
self.setSpeaking(false);
return;
} else {
onWarning = true;
setTimeout(send, length * 10); // give chance for some data in 200ms to appear
return;
}
}
count++;
sequence + 10 < 65535 ? sequence += 1 : sequence = 0;
time + 9600 < 4294967295 ? time += 960 : time = 0;
self.sendBuffer(buffer, sequence, time, (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;
}
setSpeaking(value) {
this.playing = value;
if (this.vWS.readyState === WebSocket.OPEN)
this.vWS.send(JSON.stringify({
op: 5,
d: {
speaking: value,
delay: 0
}
}));
}
sendPacket(packet, callback = function (err) { }) {
var self = this;
self.playing = true;
try {
if (self.vWS.readyState === WebSocket.OPEN)
self.udp.send(packet, 0, packet.length, self.vWSData.port, self.endpoint, callback);
} catch (e) {
self.playing = false;
callback(e);
return false;
}
}
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;
}
var buffer = self.encoder.opusBuffer(rawbuffer);
var packet = new VoicePacket(buffer, sequence, timestamp, self.vWSData.ssrc);
return self.sendPacket(packet, callback);
} catch (e) {
self.playing = false;
self.emit("error", e);
return false;
}
}
test() {
this.playFile("C:/users/amish/desktop/audio.mp3")
.then(stream => {
stream.on("time", time => {
console.log("Time", time);
})
})
}
playFile(stream, callback = function (err, str) { }) {
var self = this;
return new Promise((resolve, reject) => {
this.encoder
.encodeFile(stream)
.catch(error)
.then(data => {
self.streamProc = data.proc;
var intent = self.playStream(data.stream);
resolve(intent);
callback(null, intent);
});
function error(e = true) {
reject(e);
callback(e);
}
})
}
playRawStream(stream, callback = function (err, str) { }) {
var self = this;
return new Promise((resolve, reject) => {
this.encoder
.encodeStream(stream)
.catch(error)
.then(data => {
self.streamProc = data.proc;
self.instream = data.instream;
var intent = self.playStream(data.stream);
resolve(intent);
callback(null, intent);
});
function error(e = true) {
reject(e);
callback(e);
}
})
}
init() {
var self = this;
dns.lookup(this.endpoint, (err, address, family) => {
self.endpoint = address;
var vWS = self.vWS = new WebSocket("wss://" + this.endpoint, null, { rejectUnauthorized: false });
var udpClient = self.udp = udp.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);
var wsDiscPayload = {
"op": 1,
"d": {
"protocol": "udp",
"data": {
"address": discordIP,
"port": Number(discordPort),
"mode": self.vWSData.modes[0] //Plain
}
}
}
vWS.send(JSON.stringify(wsDiscPayload));
firstPacket = false;
}
});
vWS.on("open", () => {
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", (msg) => {
var data = JSON.parse(msg);
switch (data.op) {
case 2:
self.vWSData = data.d;
KAI = setInterval(() => {
if (vWS.readyState === WebSocket.OPEN)
vWS.send(JSON.stringify({
op: 3,
d: null
}));
}, data.d.heartbeat_interval);
self.KAI = KAI;
var udpPacket = new Buffer(70);
udpPacket.writeUIntBE(data.d.ssrc, 0, 4);
udpClient.send(udpPacket, 0, udpPacket.length, data.d.port, self.endpoint, err => {
if (err)
self.emit("error", err)
});
break;
case 4:
self.ready = true;
self.mode = data.d.mode;
self.emit("ready", self);
break;
}
});
});
}
}
module.exports = VoiceConnection;

26
src/Voice/VoicePacket.js Normal file
View File

@@ -0,0 +1,26 @@
"use strict";
class VoicePacket{
constructor(data, sequence, time, ssrc){
var audioBuffer = data,
returnBuffer = new Buffer(audioBuffer.length + 12);
returnBuffer.fill(0);
returnBuffer[0] = 0x80;
returnBuffer[1] = 0x78;
returnBuffer.writeUIntBE(sequence, 2, 2);
returnBuffer.writeUIntBE(time, 4, 4);
returnBuffer.writeUIntBE(ssrc, 8, 4);
for (var i=0; i<audioBuffer.length; i++) {
returnBuffer[i + 12] = audioBuffer[i];
}
return returnBuffer;
}
}
module.exports = VoicePacket;

View File

@@ -1,34 +1,15 @@
module.exports = {
Client : require("./Client/Client.js")
}
var a = new module.exports.Client();
a.on("debug", (m) => console.log("[debug]",m));
a.on("warn", (m) => console.log("[warn]", m));
var start = Date.now();
a.on("message", m => {
if(m.content === "$$$"){
a.internal.setTopic(m.channel, "a channel topic!");
}
});
a.on("userTypingStart", (user, chan) => {
console.log(user.username + " typing");
});
a.on("userTypingStop", (user, chan) => {
console.log(user.username + " stopped typing");
});
a.on("ready", () => {
for(var server of a.internal.servers){
if(server.name === "craptown"){
a.leaveServer(server);
}
}
});
function error(e){
throw e;
process.exit(0);
}
a.login(process.env["discordEmail"], process.env["discordPass"]).catch((e)=>console.log(e));
Client : require("./Client/Client"),
Channel : require("./Structures/Channel"),
ChannelPermissions : require("./Structures/ChannelPermissions"),
Invite : require("./Structures/Invite"),
Message : require("./Structures/Message"),
PermissionOverwrite : require("./Structures/PermissionOverwrite"),
PMChannel : require("./Structures/PMChannel"),
Role : require("./Structures/Role"),
Server : require("./Structures/Server"),
ServerChannel : require("./Structures/ServerChannel"),
TextChannel : require("./Structures/TextChannel"),
User : require("./Structures/User"),
VoiceChannel : require("./Structures/VoiceChannel"),
}

View File

@@ -1,69 +1,63 @@
var Discord = require("../");
var Member = require("../lib/Member.js");
var mybot = new Discord.Client({
compress : true,
catchup : "all"
});
var fs = require("fs");
var request = require("request").defaults({ encoding: null });
var client = new Discord.Client();
var request = require("superagent");
client.on("debug", (m) => console.log("[debug]", m));
client.on("warn", (m) => console.log("[warn]", m));
var start = Date.now();
Discord.patchStrings();
var server, channel, message, sentMessage = false;
mybot.on("message", function (message) {
console.log("Everyone mentioned? " + doned);
doned++;
if (message.content.substr(0, 3) !== "$$$") {
return;
}
// we can go ahead :)
var user;
if (message.mentions.length > 0) {
user = message.mentions[0];
} else {
user = message.sender;
}
mybot.reply(message, "Hello! It has been " + ((Date.now() - message.timestamp) - this.timeoffset) + "ms since you sent that.");
});
var doned = 0;
mybot.once("ready", function () {
console.log("im ready");
for (var server of mybot.servers) {
if (server.name === "test-server") {
mybot.leaveServer(server);
client.on("message", m => {
if (m.content === "&init") {
for (var channel of m.channel.server.channels) {
if (channel instanceof Discord.VoiceChannel) {
client.reply(m, channel.name + " - " + channel.id);
client.joinVoiceChannel(channel).catch(error);
break;
}
}
}
if (m.content.startsWith("$$$ stop")) {
if (client.internal.voiceConnection) {
client.internal.voiceConnection.stopPlaying();
}
return;
}
if (m.content.startsWith("$$$")) {
var chan;
var rest = m.content.split(" ");
rest.splice(0, 1);
rest = rest.join(" ");
if (client.internal.voiceConnection) {
client.reply(m, "ok, I'll play that for you");
var connection = client.internal.voiceConnection;
connection.playFile("C:/users/amish/desktop/" + rest);
}
} if (m.content.startsWith("pipeit")) {
var chan;
var rest = m.content.split(" ");
rest.splice(0, 1);
rest = rest.join(" ");
if (client.internal.voiceConnection) {
var connection = client.internal.voiceConnection;
connection.playFile(rest).then(intent => {
client.reply(m, "playing!").then((msg) => {
intent.on("end", () => {
client.updateMessage(msg, "that song has finished now.");
});
});
});
}
}
});
mybot.on("messageUpdate", function(newMessage, oldMessage){
mybot.reply(newMessage, JSON.stringify(newMessage.embeds));
})
mybot.on("serverUpdate", function (oldserver, newserver) {
console.log("server changed! " + mybot.servers.length);
})
mybot.on("channelUpdate", function (oldChan, newChan) {
});
function dump(msg) {
console.log("dump", msg);
function error(e) {
console.log(e.stack);
process.exit(0);
}
function error(err) {
console.log(err);
}
mybot.login(process.env["ds_email"], process.env["ds_password"]).catch(error);
client.login(process.env["discordEmail"], process.env["discordPass"]).catch((e) => console.log(e));