diff --git a/.gitignore b/.gitignore
index 8b137891..360ea127 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1 @@
-
+.vs
diff --git a/README.md b/README.md
index f81be44c..5843e698 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,13 @@

discord.io
+
A small, single-file, fully featured [Discordapp](https://discordapp.com) library for Node.js and browsers.
[](https://discord.gg/0MvHMfHcTKVVmIGP) [](https://img.shields.io/npm/v/gh-badges.svg)
+**You are probably here for the gateway_v6 branch. (https://github.com/Woor/discord.io/tree/gateway_v6) **
+
### Requirements
**Required**:
* **Node.js 0.10.x** or greater
diff --git a/lib/index.js b/lib/index.js
index 45d682f9..7f4752d3 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -73,21 +73,20 @@ var DCP = Discord.Client.prototype;
/**
* Manually initiate the WebSocket connection to Discord.
*/
-DCP.connect = function() {
- if (!this.connected && !this._connecting) return setTimeout(
- init,
- Math.max(0, CONNECT_WHEN - Date.now()),
- this,
- arguments[0],
- void( CONNECT_WHEN = Math.max(CONNECT_WHEN, Date.now()) + 6000 )
- );
+DCP.connect = function () {
+ var opts = arguments[0];
+ if (!this.connected && !this._connecting) return setTimeout(function() {
+ init(this, opts);
+ CONNECT_WHEN = Math.max(CONNECT_WHEN, Date.now()) + 6000;
+ }.bind(this), Math.max( 0, CONNECT_WHEN - Date.now() ));
};
/**
* Disconnect the WebSocket connection to Discord.
*/
-DCP.disconnect = function() {
- if (this._ws) this._ws.close();
+DCP.disconnect = function () {
+ if (this._ws) return this._ws.close(), log(this, "Manual disconnect called, websocket closed");
+ return log(this, Discord.LogLevels.Warn, "Manual disconnect called with no WebSocket active, ignored");
};
/**
@@ -135,18 +134,20 @@ DCP.editUserInfo = function(input, callback) {
/**
* Change the client's presence.
* @arg {Object} input
- * @arg {Number|null} input.idle_since - Use a Number before the current point in time.
+ * @arg {String|null} input.status - Used to set the status. online, idle, dnd, invisible, and offline are the possible states.
+ * @arg {Number|null} input.idle_since - Optional, use a Number before the current point in time.
+ * @arg {Boolean|null} input.afk - Optional, changes how Discord handles push notifications.
* @arg {Object|null} input.game - Used to set game information.
* @arg {String|null} input.game.name - The name of the game.
- * @arg {Number|null} input.game.type - Streaming activity, 0 for nothing, 1 for Twitch.
+ * @arg {Number|null} input.game.type - Activity type, 0 for game, 1 for Twitch.
* @arg {String|null} input.game.url - A URL matching the streaming service you've selected.
*/
DCP.setPresence = function(input) {
var payload = Payloads.STATUS(input);
send(this._ws, payload);
- if (payload.d.idle_since === null) return void(this.presenceStatus = 'online');
- this.presenceStatus = 'idle';
+ if (payload.d.idle_since === null) return void(this.presenceStatus = payload.d.status);
+ this.presenceStatus = payload.d.status;
};
/**
@@ -180,13 +181,20 @@ DCP.getAccountSettings = function(callback) {
DCP.uploadFile = function(input, callback) {
/* After like 15 minutes of fighting with Request, turns out Discord doesn't allow multiple files in one message...
despite having an attachments array.*/
- var file,
- client = this, multi = new Multipart(), message = generateMessage(input.message || ""),
- isBuffer = (input.file instanceof Buffer), isString = (type(input.file) === 'string');
-
- if (!isBuffer && !isString) return handleErrCB("[uploadFile] uploadFile requires a String or Buffer as the 'file' value", callback);
- if (isBuffer) if (input.filename) file = input.file; else return handleErrCB("[uploadFile] uploadFile requires a 'filename' value to be set if using a Buffer", callback);
- if (isString) try { file = FS.readFileSync(input.file); } catch(e) { return handleErrCB("[uploadFile] File does not exist: " + input.file, callback); }
+ var file, client, multi, message, isBuffer, isString;
+
+ client = this;
+ multi = new Multipart();
+ message = generateMessage(input.message || "");
+ isBuffer = (input.file instanceof Buffer);
+ isString = (type(input.file) === 'string');
+
+ if (!isBuffer && !isString) return handleErrCB("uploadFile requires a String or Buffer as the 'file' value", callback);
+ if (isBuffer) {
+ if (!input.filename) return handleErrCB("uploadFile requires a 'filename' value to be set if using a Buffer", callback);
+ file = input.file;
+ }
+ if (isString) try { file = FS.readFileSync(input.file); } catch(e) { return handleErrCB("File does not exist: " + input.file, callback); }
[
["content", message.content],
@@ -219,7 +227,7 @@ DCP.sendMessage = function(input, callback) {
message.tts = (input.tts === true);
message.nonce = input.nonce || message.nonce;
- if (input.typing === true) {
+ if (typeof input.typing === "boolean" && input.typing === true) {
return simulateTyping(
this,
input.to,
@@ -227,6 +235,14 @@ DCP.sendMessage = function(input, callback) {
( (message.content.length * 0.12) * 1000 ),
callback
);
+ } else if (typeof input.typing === "number") {
+ return simulateTyping(
+ this,
+ input.to,
+ message,
+ input.typing,
+ callback
+ );
}
sendMessage(this, input.to, message, callback);
@@ -285,7 +301,7 @@ DCP.getMessages = function(input, callback) {
* @arg {Object} input
* @arg {Snowflake} input.channelID
* @arg {Snowflake} input.messageID
- * @arg {Snowflake} input.message - The new message content
+ * @arg {String} input.message - The new message content
* @arg {Object} [input.embed] - The new Discord Embed object
*/
DCP.editMessage = function(input, callback) {
@@ -458,19 +474,27 @@ DCP.kick = function(input, callback) {
* @arg {Object} input
* @arg {Snowflake} input.serverID
* @arg {Snowflake} input.userID
+ * @arg {String} input.reason
* @arg {Number} [input.lastDays] - Removes their messages up until this point, either 1 or 7 days.
*/
DCP.ban = function(input, callback) {
- if (input.lastDays) {
- input.lastDays = Number(input.lastDays);
- input.lastDays = Math.min(input.lastDays, 7);
- input.lastDays = Math.max(input.lastDays, 1);
+ var url = Endpoints.BANS(input.serverID, input.userID);
+ var opts = {};
+
+ if (input.lastDays) {
+ opts.lastDays = Number(input.lastDays);
+ opts.lastDays = Math.min(opts.lastDays, 7);
+ opts.lastDays = Math.max(opts.lastDays, 1);
}
- this._req('put', Endpoints.BANS(input.serverID, input.userID) + (input.lastDays ? "?delete-message-days=" + input.lastDays : ""), function(err, res) {
- handleResCB("Could not ban user", err, res, callback);
- });
-};
+ if (input.reason) opts.reason = input.reason;
+
+ url += qstringify(opts);
+
+ this._req('put', url, function(err, res) {
+ handleResCB("Could not ban user", err, res, callback);
+ });
+}
/**
* Unban a user from a server.
@@ -522,7 +546,7 @@ DCP.unmute = function(input, callback) {
};
/**
- * Server-deafan a user.
+ * Server-deafen a user.
* @arg {Object} input
* @arg {Snowflake} input.serverID
* @arg {Snowflake} input.userID
@@ -534,7 +558,7 @@ DCP.deafen = function(input, callback) {
};
/**
- * Remove the server-deafan from a user.
+ * Remove the server-deafen from a user.
* @arg {Object} input
* @arg {Snowflake} input.serverID
* @arg {Snowflake} input.userID
@@ -545,6 +569,82 @@ DCP.undeafen = function(input, callback) {
});
};
+/**
+ * Self-mute the client from speaking in all voice channels.
+ * @arg {Snowflake} serverID
+ */
+DCP.muteSelf = function(serverID, callback) {
+ var server = this.servers[serverID], channelID, voiceSession;
+ if (!server) return handleErrCB(("Cannot find the server provided: " + serverID), callback);
+
+ server.self_mute = true;
+
+ if (!server.voiceSession) return call(callback, [null]);
+
+ voiceSession = server.voiceSession;
+ voiceSession.self_mute = true;
+ channelID = voiceSession.channelID;
+ if (!channelID) return call(callback, [null]);
+ return call(callback, [send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, true, server.self_deaf))]);
+};
+
+/**
+ * Remove the self-mute from the client.
+ * @arg {Snowflake} serverID
+ */
+DCP.unmuteSelf = function(serverID, callback) {
+ var server = this.servers[serverID], channelID, voiceSession;
+ if (!server) return handleErrCB(("Cannot find the server provided: " + serverID), callback);
+
+ server.self_mute = false;
+
+ if (!server.voiceSession) return call(callback, [null]);
+
+ voiceSession = server.voiceSession;
+ voiceSession.self_mute = false;
+ channelID = voiceSession.channelID;
+ if (!channelID) return call(callback, [null]);
+ return call(callback, [send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, false, server.self_deaf))]);
+};
+
+/**
+ * Self-deafen the client.
+ * @arg {Snowflake} serverID
+ */
+DCP.deafenSelf = function(serverID, callback) {
+ var server = this.servers[serverID], channelID, voiceSession;
+ if (!server) return handleErrCB(("Cannot find the server provided: " + serverID), callback);
+
+ server.self_deaf = true;
+
+ if (!server.voiceSession) return call(callback, [null]);
+
+ voiceSession = server.voiceSession;
+ voiceSession.self_deaf = true;
+ channelID = voiceSession.channelID;
+ if (!channelID) return call(callback, [null]);
+ return call(callback, [send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, server.self_mute, true))]);
+};
+
+/**
+ * Remove the self-deafen from the client.
+ * @arg {Snowflake} serverID
+ */
+DCP.undeafenSelf = function(serverID, callback) {
+ var server = this.servers[serverID], channelID, voiceSession;
+ if (!server) return handleErrCB(("Cannot find the server provided: " + serverID), callback);
+
+ server.self_deaf = false;
+
+ if (!server.voiceSession) return call(callback, [null]);
+
+ voiceSession = server.voiceSession;
+ voiceSession.self_deaf = false;
+ channelID = voiceSession.channelID;
+ if (!channelID) return call(callback, [null]);
+ return call(callback, [send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, server.self_mute, false))]);
+};
+
/*Bot server management actions*/
/**
@@ -720,38 +820,16 @@ DCP.transferOwnership = function(input, callback) {
};
/**
- * Accept an invite to a server [User Only]
- * @arg {String} inviteCode - The code part of an invite URL (e.g. 0MvHMfHcTKVVmIGP)
+ * (Used to) Accept an invite to a server [User Only]. Can no longer be used.
+ * @deprecated
*/
-DCP.acceptInvite = function(inviteCode, callback) {
- if (this.bot) return handleErrCB("[acceptInvite] This account is a 'bot' type account, and cannot use 'acceptInvite'. Please use the client's inviteURL property instead.", callback);
- var client = this, joinedServers = Object.keys(client.servers);
- this._req('post', Endpoints.INVITES(inviteCode), function(err, res) {
- try {
- //Try to create the server with the small amount of data
- //that Discord provides directly from the HTTP response
- //since the websocket event may take a second to show.
- if (!client.servers[res.body.guild.id]) {
- client.servers[res.body.guild.id] = res.body.guild;
- client.servers[res.body.guild.id].channels = {};
- client.servers[res.body.guild.id].channels[res.body.channel.id] = res.body.channel;
- } else {
- if (joinedServers.indexOf(res.body.guild.id) > -1) {
- return handleErrCB(("Already joined server: " + res.body.guild.id), callback);
- }
- }
- } catch(e) {}
- handleResCB(("The invite code provided " + inviteCode + " is incorrect."), err, res, callback);
- });
+DCP.acceptInvite = function(NUL, callback) {
+ return handleErrCB("acceptInvite can no longer be used", callback);
};
/**
- * Generate an invite URL for a channel.
- * @arg {Object} input
- * @arg {Snowflake} input.channelID
- * @arg {Number} [input.max_age] - Time in seconds.
- * @arg {Number} [input.max_users] - The amount of times the invite code can be used.
- * @arg {Boolean} [input.temporary] - Any users who use this invite will be removed when they disconnect, unless given a role.
+ * (Used to) Generate an invite URL for a channel.
+ * @deprecated
*/
DCP.createInvite = function(input, callback) {
var payload, client = this;
@@ -850,7 +928,7 @@ DCP.createChannel = function(input, callback) {
DCP.createDMChannel = function(userID, callback) {
var client = this;
this._req('post', Endpoints.USER(client.id) + "/channels", {recipient_id: userID}, function(err, res) {
- if (!err && goodResponse(res)) client._uIDToDM[res.body.recipient.id] = res.body.id;
+ if (!err && goodResponse(res)) client._uIDToDM[res.body.recipient_id] = res.body.id;
handleResCB("Unable to create DM Channel", err, res, callback);
});
};
@@ -912,12 +990,12 @@ DCP.editChannelInfo = function(input, callback) {
/**
* Edit (or creates) a permission override for a channel.
* @arg {Object} input
- * @arg {Snowflake} channelID
- * @arg {Snowflake} [userID]
- * @arg {Snowflake} [roleID]
- * @arg {Array} allow - An array of permissions to allow. Discord.Permissions.XXXXXX.
- * @arg {Array} deny - An array of permissions to deny, same as above.
- * @arg {Array} default - An array of permissions that cancels out allowed and denied permissions.
+ * @arg {Snowflake} input.channelID
+ * @arg {Snowflake} [input.userID]
+ * @arg {Snowflake} [input.roleID]
+ * @arg {Array} input.allow - An array of permissions to allow. Discord.Permissions.XXXXXX.
+ * @arg {Array} input.deny - An array of permissions to deny, same as above.
+ * @arg {Array} input.default - An array of permissions that cancels out allowed and denied permissions.
*/
DCP.editChannelPermissions = function(input, callback) { //Will shrink this up later
var payload, pType, ID, channel, permissions, allowed_values;
@@ -930,8 +1008,8 @@ DCP.editChannelPermissions = function(input, callback) { //Will shrink this up l
channel = this.channels[ input.channelID ];
permissions = channel.permissions[pType][ID] || { allow: 0, deny: 0 };
allowed_values = [0, 4, 28].concat((channel.type === 'text' ?
- [10, 11, 12, 13, 14, 15, 16, 17, 18] :
- [20, 21, 22, 23, 24, 25] ));
+ [6, 10, 11, 12, 13, 14, 15, 16, 17, 18] :
+ [20, 21, 22, 23, 24, 25, 29] ));
//Take care of allow first
if (type(input.allow) === 'array') {
@@ -977,9 +1055,9 @@ DCP.editChannelPermissions = function(input, callback) { //Will shrink this up l
/**
* Delete a permission override for a channel.
* @arg {Object} input
- * @arg {Snowflake} channelID
- * @arg {Snowflake} [userID]
- * @arg {Snowflake} [roleID]
+ * @arg {Snowflake} input.channelID
+ * @arg {Snowflake} [input.userID]
+ * @arg {Snowflake} [input.roleID]
*/
DCP.deleteChannelPermission = function(input, callback) {
var payload, pType, ID;
@@ -1059,7 +1137,52 @@ DCP.editRole = function(input, callback) {
});
} catch(e) {return handleErrCB(('[editRole] ' + e), callback);}
};
+
+/**
+ * Move a role up or down relative to it's current position.
+ * @arg {Object} input
+ * @arg {Snowflake} input.serverID
+ * @arg {Snowflake} input.roleID - The ID of the role.
+ * @arg {Number} input.position - A relative number to move the role up or down.
+ */
+DCP.moveRole = function(input,callback){
+ if(input.position===0)return handleErrCB("Desired role position is same as current", callback); //Don't do anything if they dont want it to move
+ try {
+ var role = new Role(this.servers[input.serverID].roles[input.roleID]);
+ var curPos = role.position;
+ var newPos = curPos + input.position;
+
+ if(newPos < 1)
+ newPos = 1; //make sure it doesn't go under the possible positions
+
+ if(newPos > Object.keys(this.servers[input.serverID].roles).length-1)
+ newPos = Object.keys(this.servers[input.serverID].roles).length-1; //make sure it doesn't go above the possible positions
+
+ var currentOrder = [];
+ for(var roleID in this.servers[input.serverID].roles){
+ if(roleID == input.serverID) continue; //the everyone role cannot be changed, so best to not try.
+ var _role = new Role(this.servers[input.serverID].roles[roleID]);
+ currentOrder[_role.position] = roleID;
+ }
+ var payload = [];
+
+
+ for(var pos in currentOrder){
+ var roleID = currentOrder[pos];
+ if(newPos>curPos && curPos < pos && pos <= newPos)
+ payload.push({id:roleID, position:pos-1});
+ if(curPos>newPos && curPos > pos && pos >= newPos)
+ payload.push({id:roleID, position:(pos-(-1))});
+ }
+ payload.push({id:input.roleID,position:newPos});
+
+ this._req('patch', Endpoints.ROLES(input.serverID), payload, function(err, res) {
+ handleResCB("Unable to move role", err, res, callback);
+ });
+ } catch(e) {return handleErrCB(e, callback);}
+};
+
/**
* Delete a role.
* @arg {Object} input
@@ -1237,8 +1360,10 @@ DCP.joinVoiceChannel = function(channelID, callback) {
if (this._vChannels[channelID]) return handleErrCB(("Voice channel already active: " + channelID), callback);
voiceSession = getVoiceSession(this, channelID, server);
+ voiceSession.self_mute = server.self_mute;
+ voiceSession.self_deaf = server.self_deaf;
checkVoiceReady(voiceSession, callback);
- return send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID));
+ return send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, server.self_mute, server.self_deaf));
};
/**
@@ -1393,7 +1518,7 @@ function APIRequest(method, url) {
});
});
});
- if (type(data) === 'object' || method.toLowerCase() === 'get') req.setHeader("Content-Type", "application/json; charset=utf-8");
+ if (typeof(data) == 'object' || method.toLowerCase() === 'get') req.setHeader("Content-Type", "application/json; charset=utf-8");
if (data instanceof Multipart) req.setHeader("Content-Type", "multipart/form-data; boundary=" + data.boundary);
if (data) req.write( data.result || JSON.stringify(data), data.result ? 'binary' : 'utf-8' );
req.end();
@@ -1414,9 +1539,9 @@ function APIRequest(method, url) {
callback(null, req);
}
};
- if (type(data) === 'object' || method.toLowerCase() === 'get') req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
+ if (typeof(data) == 'object' || method.toLowerCase() === 'get') req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
if (data instanceof Multipart) req.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + data.boundary);
- if (data) return req[ (data.result ? "sendAsBinary" : "send") ]( data.result ? data.result : JSON.stringify(data) );
+ if (data) return req.send( data.result ? data.result : JSON.stringify(data) );
req.send(null);
}
function send(ws, data) {
@@ -1503,6 +1628,17 @@ function colorFromRole(server, member) {
return role.position > array[0] && role.color ? [role.position, role.color] : array;
}, [-1, null])[1];
}
+function log(client, level_message) {
+ var level, message;
+ if (arguments.length === 2) {
+ level = Discord.LogLevels.Info;
+ message = level_message;
+ } else {
+ level = level_message;
+ message = arguments[2];
+ }
+ return client.emit('log', level, message);
+}
function givePermission(bit, permissions) {
return permissions | (1 << bit);
@@ -1649,10 +1785,21 @@ function handleWSMessage(data, flags) {
var message = decompressWSMessage(data, flags);
var _data = message.d;
var client = this, user, server, member, old,
- userID, serverID, channelID, currentVCID;
- client.internals.sequence = message.s;
+ userID, serverID, channelID, currentVCID,
+ mute, deaf, self_mute, self_deaf;
switch (message.op) {
+ case 0:
+ //Event dispatched - update our sequence number
+ client.internals.sequence = message.s;
+ break;
+ case 9:
+ //Disconnect after a Invalid Session
+ //Disconnect the client if the client has received an invalid session id
+ delete client.internals.sequence;
+ delete client.internals.sessionID;
+ client._ws && client._ws.close(1e3, 'Received an invalid session id');
+ break;
case 10:
//Start keep-alive interval
//Disconnect the client if no ping has been received
@@ -1663,7 +1810,9 @@ function handleWSMessage(data, flags) {
client.connected = true;
client._mainKeepAlive = setInterval(function() {
- client.internals.heartbeat = setTimeout(client._ws.close.bind(client._ws, 1e3, 'No heartbeat received'), 15e3);
+ client.internals.heartbeat = setTimeout(function() {
+ client._ws && client._ws.close(1e3, 'No heartbeat received');
+ }, 15e3);
client.internals._lastHB = Date.now();
send(client._ws, Payloads.HEARTBEAT(client));
}, _data.heartbeat_interval);
@@ -1814,28 +1963,39 @@ function handleWSMessage(data, flags) {
channelID = _data.channel_id;
userID = _data.user_id;
server = client.servers[serverID];
+ mute = !!_data.mute;
+ self_mute = !!_data.self_mute;
+ deaf = !!_data.deaf;
+ self_deaf = !!_data.self_deaf;
try {
currentVCID = server.members[userID].voice_channel_id;
if (currentVCID) delete( server.channels[currentVCID].members[userID] );
if (channelID) server.channels[channelID].members[userID] = _data;
+ server.members[userID].mute = (mute || self_mute);
+ server.members[userID].deaf = (deaf || self_deaf);
server.members[userID].voice_channel_id = channelID;
} catch(e) {}
if (userID === client.id) {
+ server.self_mute = self_mute;
+ server.self_deaf = self_deaf;
if (channelID === null) {
if (server.voiceSession) leaveVoiceChannel(client, server.voiceSession.channelID);
- return void(server.voiceSession = null);
- }
- if (!server.voiceSession) {
- server.voiceSession = getVoiceSession(client, channelID, server);
- }
- if (channelID !== server.voiceSession.channelID) {
- delete( client._vChannels[server.voiceSession.channelID] );
- getVoiceSession(client, channelID, server).channelID = channelID;
- }
+ server.voiceSession = null;
+ } else {
+ if (!server.voiceSession) {
+ server.voiceSession = getVoiceSession(client, channelID, server);
+ }
+ if (channelID !== server.voiceSession.channelID) {
+ delete( client._vChannels[server.voiceSession.channelID] );
+ getVoiceSession(client, channelID, server).channelID = channelID;
+ }
- server.voiceSession.session = _data.session_id;
+ server.voiceSession.session = _data.session_id;
+ server.voiceSession.self_mute = self_mute;
+ server.voiceSession.self_deaf = self_deaf;
+ }
}
break;
case "VOICE_SERVER_UPDATE":
@@ -1947,7 +2107,7 @@ function leaveVoiceChannel(client, channelID, callback) {
client._vChannels[channelID].udp.connection.close();
} catch(e) {}
- send(client._ws, Payloads.UPDATE_VOICE(client.channels[channelID].guild_id, null));
+ send(client._ws, Payloads.UPDATE_VOICE(client.channels[channelID].guild_id, null, false, false));
return call(callback, [null]);
}
@@ -1974,13 +2134,15 @@ function getVoiceSession(client, channelID, server) {
channelID: channelID,
token: null,
session: null,
- endpoint: null
+ endpoint: null,
+ self_mute: false,
+ self_deaf: false
};
}
function checkVoiceReady(voiceSession, callback) {
return setTimeout(function() {
- if (voiceSession.error) return call(callback, [error]);
+ if (voiceSession.error) return call(callback, [voiceSession.error]);
if (voiceSession.joined) return call(callback, [null, voiceSession.emitter]);
return checkVoiceReady(voiceSession, callback);
}, 1);
@@ -2082,7 +2244,7 @@ function AudioCB(voiceSession, audioChannels, encoder, maxStreamSize) {
["_secretKey", new Uint8Array(voiceSession.secretKey)],
["_mixedDecoder", Opus && new Opus.OpusEncoder( 48000, audioChannels ) || null]
]);
-
+
createAudioEncoder(this, encoder);
this._write = function _write(chunk, encoding, callback) {
@@ -2251,6 +2413,7 @@ function prepareAudioOld(ACBI, readableStream) {
function sendAudio(ACBI, buffer) {
ACBI._sequence = (ACBI._sequence + 1 ) < 0xFFFF ? ACBI._sequence + 1 : 0;
ACBI._timestamp = (ACBI._timestamp + 960) < 0xFFFFFFFF ? ACBI._timestamp + 960 : 0;
+ if (ACBI._voiceSession.self_mute) return;
var audioPacket = AudioCB.VoicePacket(buffer, ACBI._voiceSession.ws.ssrc, ACBI._sequence, ACBI._timestamp, ACBI._secretKey);
try {
@@ -2261,7 +2424,7 @@ function sendAudio(ACBI, buffer) {
function handleIncomingAudio(msg) {
//The response from the UDP keep alive ping
- if (msg.length === 8) return;
+ if (msg.length === 8 || this._voiceSession.self_deaf) return;
var header = msg.slice(0, 12),
audio = msg.slice(12),
@@ -2368,6 +2531,8 @@ function Server(client, data) {
copyKeys(data, this);
this.large = this.large || this.member_count > LARGE_THRESHOLD;
this.voiceSession = null;
+ this.self_mute = !!this.self_mute;
+ this.self_deaf = !!this.self_deaf;
if (data.unavailable) return;
//Objects so we can use direct property accessing without for loops
@@ -2539,10 +2704,10 @@ Object.defineProperty(Role.prototype, "permission_values", {
enumerable: true
});
Object.defineProperty(User.prototype, 'avatarURL', {
- get: function() {
+ get: function() {
if (!this.avatar) return null;
var animated = this.avatar.indexOf("a_") > -1;
- return Discord.Endpoints.CDN + "/avatars/" + this.id + "/" + this.avatar + (animated ? ".gif" : ".jpg");
+ return Discord.Endpoints.CDN + "/avatars/" + this.id + "/" + this.avatar + (animated ? ".gif" : ".webp");
},
set: function() {}
});
@@ -2619,6 +2784,12 @@ Discord.Permissions = {
VOICE_MOVE_MEMBERS: 24,
VOICE_USE_VAD: 25,
};
+Discord.LogLevels = {
+ Verbose: 0,
+ Info: 1,
+ Warn: 2,
+ Error: 3
+};
Object.keys(Discord.Permissions).forEach(function(pn) {
Object.defineProperty(Role.prototype, pn, {
@@ -2814,25 +2985,27 @@ function Websocket(url, opts) {
return {
op: 3,
d: {
- idle_since: input.idle_since || null,
+ status: type(input.idle_since) === 'number' ? 'idle' : input.status !== undefined ? input.status : null,
+ afk: !!input.afk,
+ since: type(input.idle_since) === 'number' || input.status === 'idle' ? Date.now() : null,
game: type(input.game) === 'object' ?
{
name: input.game.name ? String(input.game.name) : null,
- type: input.game.type ? Number(input.game.type) : null,
+ type: input.game.type ? Number(input.game.type) : 0,
url: input.game.url ? String(input.game.url) : null
} :
null
}
};
},
- UPDATE_VOICE: function(serverID, channelID) {
+ UPDATE_VOICE: function(serverID, channelID, self_mute, self_deaf) {
return {
op: 4,
d: {
guild_id: serverID,
channel_id: channelID,
- self_mute: false,
- self_deaf: false
+ self_mute: self_mute,
+ self_deaf: self_deaf
}
};
},
diff --git a/package.json b/package.json
index c2ff8452..c1176136 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "discord.io",
- "version": "2.5.1",
+ "version": "2.5.3",
"description": "JavaScript interface for Discord.",
"main": "./lib/index.js",
"typings": "./typings/index.d.ts",
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 5f1b000e..e1860d9b 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -97,7 +97,9 @@ declare interface permissions {
GENERAL_MANAGE_ROLES?: boolean;
GENERAL_MANAGE_NICKNAMES?: boolean;
GENERAL_CHANGE_NICKNAME?: boolean;
+ GENERAL_MANAGE_WEBHOOKS?: boolean;
+ TEXT_ADD_REACTIONS?: boolean;
TEXT_READ_MESSAGES?: boolean;
TEXT_SEND_MESSAGES?: boolean;
TEXT_SEND_TTS_MESSAGE?: boolean;
@@ -120,15 +122,41 @@ declare interface permissions {
*/
declare type sendMessageOpts = {
to: string,
- message: string,
+ message?: string,
tts?: boolean,
nonce?: string,
- typing?: boolean
+ typing?: boolean,
+ embed?: embedMessageOpts
+}
+
+declare type embedMessageOpts = {
+ author?: {
+ icon_url?: string,
+ name: string,
+ url?: string
+ },
+ color?: number,
+ description?: string,
+ fields?: [{
+ name: string,
+ value?: string,
+ inline?: boolean
+ }],
+ thumbnail?: {
+ url: string
+ },
+ title: string,
+ timestamp?: Date
+ url?: string,
+ footer?: {
+ icon_url?: string,
+ text: string
+ }
}
declare type uploadFileOpts = {
to: string,
- file: string,
+ file: string|Buffer,
filename?: string,
message?: string
}
@@ -325,6 +353,12 @@ declare type getMembersOpts = {
after: string
}
+declare type reactionOpts = {
+ channelID: string,
+ messageID: string,
+ reaction: string
+}
+
/**
* CLASSES
*/
@@ -373,11 +407,18 @@ declare namespace Discord {
}
export class DMChannel extends Resource {
- recipient: Object;
+ recipient: DMRecipient;
last_message_id: string;
id: string;
}
+ export class DMRecipient extends Resource {
+ username: string;
+ id: string;
+ discriminator: string;
+ avatar: string;
+ }
+
export class User extends Resource {
username: string;
id: string;
@@ -506,7 +547,7 @@ declare namespace Discord {
pinMessage(options: pinMessageOpts, callback?: callbackFunc): void
deletePinnedMessage(options: deletePinnedMessageOpts, callback?: callbackFunc): void
getPinnedMessages(options: getPinnedMessagesOpts, callback?: callbackFunc): void
-
+ addReaction(options: reactionOpts, callback?: callbackFunc): void
/**
* VOICE CHANNELS
*/