diff --git a/README.md b/README.md index c76d8f14..f8a9f0af 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ A small, single-file, fully featured [Discordapp](https://discordapp.com) librar [![Discord](https://discordapp.com/api/guilds/66192955777486848/widget.png)](https://discord.gg/0MvHMfHcTKVVmIGP) [![NPM](https://img.shields.io/npm/v/discord.io.svg)](https://img.shields.io/npm/v/gh-badges.svg) +**With V5 gateway getting deprecated on Oct. 16, this is a first step at getting V6 to work.** + ### Requirements **Required**: * **Node.js 0.10.x** or greater @@ -21,11 +23,8 @@ A small, single-file, fully featured [Discordapp](https://discordapp.com) librar ### Getting Started: #### Installing -**[Stable](https://www.npmjs.com/package/discord.io)** -`npm install discord.io` - -**[Latest](https://github.com/izy521/discord.io)** -`npm install izy521/discord.io` +**[Latest](https://github.com/Woor/discord.io/tree/gateway_v6)** +`npm install Woor/discord.io#gateway_v6` #### Example ```javascript diff --git a/lib/index.js b/lib/index.js index b692f0df..52ab6047 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,7 +2,7 @@ (function discordio(Discord){ var isNode = typeof(window) === "undefined" && typeof(navigator) === "undefined"; var CURRENT_VERSION = "2.x.x", - GATEWAY_VERSION = 5, + GATEWAY_VERSION = 6, LARGE_THRESHOLD = 250, CONNECT_WHEN = null, Endpoints, Payloads; @@ -135,7 +135,7 @@ DCP.editUserInfo = function(input, callback) { * Change the client's presence. * @arg {Object} input * @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 {Number|null} input.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. @@ -146,7 +146,7 @@ DCP.setPresence = function(input) { var payload = Payloads.STATUS(input); send(this._ws, payload); - if (payload.d.idle_since === null) return void(this.presenceStatus = payload.d.status); + if (payload.d.since === null) return void(this.presenceStatus = payload.d.status); this.presenceStatus = payload.d.status; }; @@ -409,8 +409,11 @@ DCP.fixMessage = function(message) { * @arg {String} input.reaction - Either the emoji unicode or the emoji name:id/object. */ DCP.addReaction = function(input, callback) { - this._req('put', Endpoints.USER_REACTIONS(input.channelID, input.messageID, stringifyEmoji(input.reaction)), function(err, res) { - handleResCB("Unable to add reaction", err, res, callback); + var client = this; + resolveID(client, input.channelID, function(channelID) { + client._req('put', Endpoints.USER_REACTIONS(channelID, input.messageID, stringifyEmoji(input.reaction)), function(err, res) { + handleResCB("Unable to add reaction", err, res, callback); + }); }); }; @@ -424,8 +427,11 @@ DCP.addReaction = function(input, callback) { */ DCP.getReaction = function(input, callback) { var qs = { limit: (typeof(input.limit) !== 'number' ? 100 : input.limit) }; - this._req('get', Endpoints.MESSAGE_REACTIONS(input.channelID, input.messageID, stringifyEmoji(input.reaction)) + qstringify(qs), function(err, res) { - handleResCB("Unable to get reaction", err, res, callback); + var client = this; + resolveID(client, input.channelID, function(channelID) { + client._req('get', Endpoints.MESSAGE_REACTIONS(channelID, input.messageID, stringifyEmoji(input.reaction)) + qstringify(qs), function(err, res) { + handleResCB("Unable to get reaction", err, res, callback); + }); }); }; @@ -438,8 +444,11 @@ DCP.getReaction = function(input, callback) { * @arg {String} input.reaction - Either the emoji unicode or the emoji name:id/object. */ DCP.removeReaction = function(input, callback) { - this._req('delete', Endpoints.USER_REACTIONS(input.channelID, input.messageID, stringifyEmoji(input.reaction), input.userID), function(err, res) { - handleResCB("Unable to remove reaction", err, res, callback); + var client = this; + resolveID(client, input.channelID, function(channelID) { + client._req('delete', Endpoints.USER_REACTIONS(channelID, input.messageID, stringifyEmoji(input.reaction), input.userID), function(err, res) { + handleResCB("Unable to remove reaction", err, res, callback); + }); }); }; @@ -450,8 +459,11 @@ DCP.removeReaction = function(input, callback) { * @arg {Snowflake} input.messageID */ DCP.removeAllReactions = function(input, callback) { - this._req('delete', Endpoints.MESSAGE_REACTIONS(input.channelID, input.messageID), function(err, res) { - handleResCB("Unable to remove reactions", err, res, callback); + var client = this; + resolveID(client, input.channelID, function(channelID) { + client._req('delete', Endpoints.MESSAGE_REACTIONS(channelID, input.messageID), function(err, res) { + handleResCB("Unable to remove reactions", err, res, callback); + }); }); }; @@ -482,9 +494,10 @@ DCP.ban = function(input, callback) { var opts = {}; if (input.lastDays) { - opts.lastDays = Number(input.lastDays); - opts.lastDays = Math.min(opts.lastDays, 7); - opts.lastDays = Math.max(opts.lastDays, 1); + input.lastDays = Number(input.lastDays); + input.lastDays = Math.min(input.lastDays, 7); + input.lastDays = Math.max(input.lastDays, 1); + opts["delete-message-days"] = input.lastDays; } if (input.reason) opts.reason = input.reason; @@ -576,11 +589,11 @@ DCP.undeafen = function(input, callback) { 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; @@ -595,11 +608,11 @@ DCP.muteSelf = function(serverID, callback) { 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; @@ -614,11 +627,11 @@ DCP.unmuteSelf = function(serverID, callback) { 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; @@ -633,11 +646,11 @@ DCP.deafenSelf = function(serverID, callback) { 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; @@ -903,11 +916,13 @@ DCP.getChannelInvites = function(channelID, callback) { * @arg {Snowflake} input.serverID * @arg {String} input.name * @arg {String} [input.type] - 'text' or 'voice', defaults to 'text. + * @arg {Snowflake} [input.parentID] */ DCP.createChannel = function(input, callback) { var client = this, payload = { name: input.name, - type: (['text', 'voice'].indexOf(input.type) < 0) ? 'text' : input.type + type: (['text', 'voice'].indexOf(input.type) < 0) ? 'text' : input.type, + parent_id: input.parentID }; this._req('post', Endpoints.SERVERS(input.serverID) + "/channels", payload, function(err, res) { @@ -928,7 +943,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.recipients[0].id] = res.body.id; handleResCB("Unable to create DM Channel", err, res, callback); }); }; @@ -1007,7 +1022,7 @@ DCP.editChannelPermissions = function(input, callback) { //Will shrink this up l ID = input[pType + "ID"]; channel = this.channels[ input.channelID ]; permissions = channel.permissions[pType][ID] || { allow: 0, deny: 0 }; - allowed_values = [0, 4, 28].concat((channel.type === 'text' ? + allowed_values = [0, 4, 28].concat(((channel.type === 'text' || channel.type === 0) ? [10, 11, 12, 13, 14, 15, 16, 17, 18] : [20, 21, 22, 23, 24, 25] )); @@ -1137,7 +1152,7 @@ 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 @@ -1155,7 +1170,7 @@ DCP.moveRole = function(input,callback){ 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 @@ -1182,7 +1197,7 @@ DCP.moveRole = function(input,callback){ }); } catch(e) {return handleErrCB(e, callback);} }; - + /** * Delete a role. * @arg {Object} input @@ -1356,7 +1371,7 @@ DCP.joinVoiceChannel = function(channelID, callback) { channel = server.channels[channelID]; } catch(e) {} if (!serverID) return handleErrCB(("Cannot find the server related to the channel provided: " + channelID), callback); - if (channel.type !== 'voice') return handleErrCB(("Selected channel is not a voice channel: " + channelID), callback); + if (channel.type !== 'voice' && channel.type !== 2) return handleErrCB(("Selected channel is not a voice channel: " + channelID), callback); if (this._vChannels[channelID]) return handleErrCB(("Voice channel already active: " + channelID), callback); voiceSession = getVoiceSession(this, channelID, server); @@ -1381,19 +1396,22 @@ DCP.leaveVoiceChannel = function(channelID, callback) { * @arg {Number} [channelObj.maxStreamSize] - The size in KB that you wish to receive before pushing out earlier data. Required if you want to store or receive incoming audio. * @arg {Boolean} [channelObj.stereo] - Sets the audio to be either stereo or mono. Defaults to true. */ -DCP.getAudioContext = function(channelObj, callback) { +DCP.getAudioContext = function(channelObj, volume, callback) { // #q/qeled gave a proper timing solution. Credit where it's due. if (!isNode) return handleErrCB("Using audio in the browser is currently not supported.", callback); + var callback = (typeof(arguments[1]) === 'function') ? arguments[1] : arguments[2]; var channelID = channelObj.channelID || channelObj, voiceSession = this._vChannels[channelID], encoder = chooseAudioEncoder(['ffmpeg', 'avconv']); + if (typeof(volume) !== 'number') volume = 1.0 if (!voiceSession) return handleErrCB(("You have not joined the voice channel: " + channelID), callback); if (voiceSession.ready !== true) return handleErrCB(("The connection to the voice channel " + channelID + " has not been initialized yet."), callback); if (!encoder) return handleErrCB("You need either 'ffmpeg' or 'avconv' and they need to be added to PATH", callback); - voiceSession.audio = voiceSession.audio || new AudioCB( + voiceSession.audio = voiceSession.audio && voiceSession.audio.volume === volume || new AudioCB( voiceSession, channelObj.stereo === false ? 1 : 2, encoder, + volume, Math.abs(Number(channelObj.maxStreamSize))); return call(callback, [null, voiceSession.audio]); @@ -1461,6 +1479,7 @@ function cacheMessage(cache, limit, channelID, message) { } function generateMessage(message, embed) { return { + type: 0, content: String(message), nonce: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER), embed: embed || {} @@ -1932,7 +1951,7 @@ function handleWSMessage(data, flags) { case "CHANNEL_CREATE": channelID = _data.id; - if (_data.is_private) { + if (_data.type == 1) { if (client.directMessages[channelID]) return; client.directMessages[channelID] = new DMChannel(client._uIDToDM, _data); return emit(client, message, client.directMessages[channelID]); @@ -1946,10 +1965,10 @@ function handleWSMessage(data, flags) { Channel.update(client, _data); return emit(client, message, old, client.channels[_data.id]); case "CHANNEL_DELETE": - if (_data.is_private === true) { + if (_data.type == 1) { emit(client, message, client.directMessages[_data.id]); delete(client.directMessages[_data.id]); - return delete(client._uIDToDM[_data.recipient.id]); + return delete(client._uIDToDM[_data.recipients[0].id]); } emit(client, message, client.servers[_data.guild_id].channels[_data.id]); delete(client.servers[_data.guild_id].channels[_data.id]); @@ -2218,7 +2237,7 @@ function handleUDPMessage(voiceSession, msg, rinfo) { } /* - Functions - Voice - AudioCallback - */ -function AudioCB(voiceSession, audioChannels, encoder, maxStreamSize) { +function AudioCB(voiceSession, audioChannels, encoder, volume, maxStreamSize) { //With the addition of the new Stream API, `playAudioFile`, `stopAudioFile` and `send` //will be removed. However they're deprecated for now, hence the code repetition. if (maxStreamSize && !Opus) Opus = require('cjopus'); @@ -2227,6 +2246,7 @@ function AudioCB(voiceSession, audioChannels, encoder, maxStreamSize) { this.audioChannels = audioChannels; this.members = voiceSession.members; + this.volume = volume applyProperties(this, [ ["_sequence", 0], @@ -2480,6 +2500,7 @@ function createAudioEncoder(ACBI, encoder) { '-f', 'data', '-sample_fmt', 's16', '-vbr', 'off', + '-filter:a', 'volume=' + ACBI.volume, '-compression_level', '10', '-ar', '48000', '-ac', ACBI.audioChannels, @@ -2591,14 +2612,13 @@ function Channel(client, server, data) { var type = (p.type === 'member' ? 'user' : 'role'); this.permissions[type][p.id] = {allow: p.allow, deny: p.deny}; }, this); - - - delete(this.is_private); } function DMChannel(translator, data) { copyKeys(data, this); - translator[data.recipient.id] = data.id; - delete(this.is_private); + this.recipient = data.recipients[0]; + data.recipients.forEach(function(recipient) { + translator[recipient.id] = data.id; + }); } function User(data) { copyKeys(data, this); @@ -2678,7 +2698,6 @@ Channel.update = function(client, data) { } client.channels[data.id][key] = data[key]; } - delete(client.channels[data.id].is_private); }; Member.update = function(client, server, data) { if (!server.members[data.user.id]) return server.members[data.user.id] = new Member(client, server, data); @@ -2760,6 +2779,7 @@ Discord.Permissions = { GENERAL_ADMINISTRATOR: 3, GENERAL_MANAGE_CHANNELS: 4, GENERAL_MANAGE_GUILD: 5, + GENERAL_AUDIT_LOG: 7, GENERAL_MANAGE_ROLES: 28, GENERAL_MANAGE_NICKNAMES: 27, GENERAL_CHANGE_NICKNAME: 26, @@ -2985,9 +3005,9 @@ function Websocket(url, opts) { return { op: 3, d: { - status: type(input.idle_since) === 'number' ? 'idle' : input.status !== undefined ? input.status : null, + status: type(input.since) === 'number' ? 'idle' : input.status !== undefined ? input.status : null, afk: !!input.afk, - since: type(input.idle_since) === 'number' || input.status === 'idle' ? Date.now() : null, + since: type(input.since) === 'number' || input.status === 'idle' ? Date.now() : null, game: type(input.game) === 'object' ? { name: input.game.name ? String(input.game.name) : null,