· 8 years ago · Nov 13, 2017, 01:16 AM
1"use strict";
2(function discordio(Discord){
3var isNode = typeof(window) === "undefined" && typeof(navigator) === "undefined";
4var CURRENT_VERSION = "2.x.x",
5 GATEWAY_VERSION = 5,
6 LARGE_THRESHOLD = 250,
7 CONNECT_WHEN = null,
8 Endpoints, Payloads;
9
10if (isNode) {
11 var Util = require('util'),
12 FS = require('fs'),
13 UDP = require('dgram'),
14 Zlib = require('zlib'),
15 DNS = require('dns'),
16 Stream = require('stream'),
17 BN = require('path').basename,
18 EE = require('events').EventEmitter,
19 requesters = {
20 http: require('http'),
21 https: require('https')
22 },
23 ChildProc = require('child_process'),
24 URL = require('url'),
25 //NPM Modules
26 NACL = require('tweetnacl'),
27 Opus = null;
28}
29
30/* --- Version Check --- */
31try {
32 CURRENT_VERSION = require('../package.json').version;
33} catch(e) {}
34if (!isNode) CURRENT_VERSION = CURRENT_VERSION + "-browser";
35
36/**
37 * Discord Client constructor
38 * @class
39 * @arg {Object} options
40 * @arg {String} options.token - The token of the account you wish to log in with.
41 * @arg {Boolean} [options.autorun] - If true, the client runs when constructed without calling `.connect()`.
42 * @arg {Number} [options.messageCacheLimit] - The amount of messages to cache in memory, per channel. Used for information on deleted/updated messages. The default is 50.
43 * @arg {Array<Number>} [options.shard] - The shard array. The first index is the current shard ID, the second is the amount of shards that should be running.
44 */
45Discord.Client = function DiscordClient(options) {
46 if (!isNode) Emitter.call(this);
47 if (!options || options.constructor.name !== 'Object') return console.error("An Object is required to create the discord.io client.");
48
49 applyProperties(this, [
50 ["_ws", null],
51 ["_uIDToDM", {}],
52 ["_ready", false],
53 ["_vChannels", {}],
54 ["_messageCache", {}],
55 ["_connecting", false],
56 ["_mainKeepAlive", null],
57 ["_req", APIRequest.bind(this)],
58 ["_shard", validateShard(options.shard)],
59 ["_messageCacheLimit", typeof(options.messageCacheLimit) === 'number' ? options.messageCacheLimit : 50],
60 ]);
61
62 this.presenceStatus = "offline";
63 this.connected = false;
64 this.inviteURL = null;
65 this.connect = this.connect.bind(this, options);
66
67 if (options.autorun === true) this.connect();
68};
69if (isNode) Emitter.call(Discord.Client);
70
71/* - DiscordClient - Methods - */
72var DCP = Discord.Client.prototype;
73/**
74 * Manually initiate the WebSocket connection to Discord.
75 */
76DCP.connect = function () {
77 var opts = arguments[0];
78 if (!this.connected && !this._connecting) return setTimeout(function() {
79 init(this, opts);
80 CONNECT_WHEN = Math.max(CONNECT_WHEN, Date.now()) + 6000;
81 }.bind(this), Math.max( 0, CONNECT_WHEN - Date.now() ));
82};
83
84/**
85 * Disconnect the WebSocket connection to Discord.
86 */
87DCP.disconnect = function () {
88 if (this._ws) return this._ws.close(), log(this, "Manual disconnect called, websocket closed");
89 return log(this, Discord.LogLevels.Warn, "Manual disconnect called with no WebSocket active, ignored");
90};
91
92/**
93 * Retrieve a user object from Discord, Bot only endpoint. You don't have to share a server with this user.
94 * @arg {Object} input
95 * @arg {Snowflake} input.userID
96 */
97DCP.getUser = function(input, callback) {
98 if (!this.bot) return handleErrCB("[getUser] This account is a 'user' type account, and cannot use 'getUser'. Only bots can use this endpoint.", callback);
99 this._req('get', Endpoints.USER(input.userID), function(err, res) {
100 handleResCB("Could not get user", err, res, callback);
101 });
102};
103
104/**
105 * Edit the client's user information.
106 * @arg {Object} input
107 * @arg {String<Base64>} input.avatar - The last part of a Base64 Data URI. `fs.readFileSync('image.jpg', 'base64')` is enough.
108 * @arg {String} input.username - A username.
109 * @arg {String} input.email - [User only] An email.
110 * @arg {String} input.password - [User only] Your current password.
111 * @arg {String} input.new_password - [User only] A new password.
112 */
113DCP.editUserInfo = function(input, callback) {
114 var payload = {
115 avatar: this.avatar,
116 email: this.email,
117 new_password: null,
118 password: null,
119 username: this.username
120 },
121 plArr = Object.keys(payload);
122
123 for (var key in input) {
124 if (plArr.indexOf(key) < 0) return handleErrCB(("[editUserInfo] '" + key + "' is not a valid key. Valid keys are: " + plArr.join(", ")), callback);
125 payload[key] = input[key];
126 }
127 if (input.avatar) payload.avatar = "data:image/jpg;base64," + input.avatar;
128
129 this._req('patch', Endpoints.ME, payload, function(err, res) {
130 handleResCB("Unable to edit user information", err, res, callback);
131 });
132};
133
134/**
135 * Change the client's presence.
136 * @arg {Object} input
137 * @arg {String|null} input.status - Used to set the status. online, idle, dnd, invisible, and offline are the possible states.
138 * @arg {Number|null} input.idle_since - Optional, use a Number before the current point in time.
139 * @arg {Boolean|null} input.afk - Optional, changes how Discord handles push notifications.
140 * @arg {Object|null} input.game - Used to set game information.
141 * @arg {String|null} input.game.name - The name of the game.
142 * @arg {Number|null} input.game.type - Activity type, 0 for game, 1 for Twitch.
143 * @arg {String|null} input.game.url - A URL matching the streaming service you've selected.
144 */
145DCP.setPresence = function(input) {
146 var payload = Payloads.STATUS(input);
147 send(this._ws, payload);
148
149 if (payload.d.idle_since === null) return void(this.presenceStatus = payload.d.status);
150 this.presenceStatus = payload.d.status;
151};
152
153/**
154 * Receive OAuth information for the current client.
155 */
156DCP.getOauthInfo = function(callback) {
157 this._req('get', Endpoints.OAUTH, function(err, res) {
158 handleResCB("Error GETing OAuth information", err, res, callback);
159 });
160};
161
162/**
163 * Receive account settings information for the current client.
164 */
165DCP.getAccountSettings = function(callback) {
166 this._req('get', Endpoints.SETTINGS, function(err, res) {
167 handleResCB("Error GETing client settings", err, res, callback);
168 });
169};
170
171/* - DiscordClient - Methods - Content - */
172
173/**
174 * Upload a file to a channel.
175 * @arg {Object} input
176 * @arg {Snowflake} input.to - The target Channel or User ID.
177 * @arg {Buffer|String} input.file - A Buffer containing the file data, or a String that's a path to the file.
178 * @arg {String|null} input.filename - A filename for the uploaded file, required if you provide a Buffer.
179 * @arg {String|null} input.message - An optional message to provide.
180 */
181DCP.uploadFile = function(input, callback) {
182 /* After like 15 minutes of fighting with Request, turns out Discord doesn't allow multiple files in one message...
183 despite having an attachments array.*/
184 var file, client, multi, message, isBuffer, isString;
185
186 client = this;
187 multi = new Multipart();
188 message = generateMessage(input.message || "");
189 isBuffer = (input.file instanceof Buffer);
190 isString = (type(input.file) === 'string');
191
192 if (!isBuffer && !isString) return handleErrCB("uploadFile requires a String or Buffer as the 'file' value", callback);
193 if (isBuffer) {
194 if (!input.filename) return handleErrCB("uploadFile requires a 'filename' value to be set if using a Buffer", callback);
195 file = input.file;
196 }
197 if (isString) try { file = FS.readFileSync(input.file); } catch(e) { return handleErrCB("File does not exist: " + input.file, callback); }
198
199 [
200 ["content", message.content],
201 ["mentions", ""],
202 ["tts", false],
203 ["nonce", message.nonce],
204 ["file", file, input.filename || BN(input.file)]
205 ].forEach(multi.append, multi);
206 multi.finalize();
207
208 resolveID(client, input.to, function(channelID) {
209 client._req('post', Endpoints.MESSAGES(channelID), multi, function(err, res) {
210 handleResCB("Unable to upload file", err, res, callback);
211 });
212 });
213};
214
215/**
216 * Send a message to a channel.
217 * @arg {Object} input
218 * @arg {Snowflake} input.to - The target Channel or User ID.
219 * @arg {String} input.message - The message content.
220 * @arg {Object} [input.embed] - An embed object to include
221 * @arg {Boolean} [input.tts] - Enable Text-to-Speech for this message.
222 * @arg {Number} [input.nonce] - Number-used-only-ONCE. The Discord client uses this to change the message color from grey to white.
223 * @arg {Boolean} [input.typing] - Indicates whether the message should be sent with simulated typing. Based on message length.
224 */
225DCP.sendMessage = function(input, callback) {
226 var message = generateMessage(input.message || '', input.embed);
227 message.tts = (input.tts === true);
228 message.nonce = input.nonce || message.nonce;
229
230 if (typeof input.typing === "boolean" && input.typing === true) {
231 return simulateTyping(
232 this,
233 input.to,
234 message,
235 ( (message.content.length * 0.12) * 1000 ),
236 callback
237 );
238 } else if (typeof input.typing === "number") {
239 return simulateTyping(
240 this,
241 input.to,
242 message,
243 input.typing,
244 callback
245 );
246 }
247
248 sendMessage(this, input.to, message, callback);
249};
250
251/**
252 * Pull a message object from Discord.
253 * @arg {Object} input
254 * @arg {Snowflake} input.channelID - The channel ID that the message is from.
255 * @arg {Snowflake} input.messageID - The ID of the message.
256 */
257DCP.getMessage = function(input, callback) {
258 this._req('get', Endpoints.MESSAGES(input.channelID, input.messageID), function(err, res) {
259 handleResCB("Unable to get message", err, res, callback);
260 });
261};
262
263/**
264 * Pull an array of message objects from Discord.
265 * @arg {Object} input
266 * @arg {Snowflake} input.channelID - The channel ID to pull the messages from.
267 * @arg {Number} [input.limit] - How many messages to pull, defaults to 50.
268 * @arg {Snowflake} [input.before] - Pull messages before this message ID.
269 * @arg {Snowflake} [input.after] - Pull messages after this message ID.
270 */
271DCP.getMessages = function(input, callback) {
272 var client = this, qs = {}, messages = [], lastMessageID = "";
273 var total = typeof(input.limit) !== 'number' ? 50 : input.limit;
274
275 if (input.before) qs.before = input.before;
276 if (input.after) qs.after = input.after;
277
278 (function getMessages() {
279 if (total > 100) {
280 qs.limit = 100;
281 total = total - 100;
282 } else {
283 qs.limit = total;
284 }
285
286 if (messages.length >= input.limit) return call(callback, [null, messages]);
287
288 client._req('get', Endpoints.MESSAGES(input.channelID) + qstringify(qs), function(err, res) {
289 if (err) return handleErrCB("Unable to get messages", callback);
290 messages = messages.concat(res.body);
291 lastMessageID = messages[messages.length - 1] && messages[messages.length - 1].id;
292 if (lastMessageID) qs.before = lastMessageID;
293 if (!res.body.length < qs.limit) return call(callback, [null, messages]);
294 return setTimeout(getMessages, 1000);
295 });
296 })();
297};
298
299/**
300 * Edit a previously sent message.
301 * @arg {Object} input
302 * @arg {Snowflake} input.channelID
303 * @arg {Snowflake} input.messageID
304 * @arg {String} input.message - The new message content
305 * @arg {Object} [input.embed] - The new Discord Embed object
306 */
307DCP.editMessage = function(input, callback) {
308 this._req('patch', Endpoints.MESSAGES(input.channelID, input.messageID), generateMessage(input.message || '', input.embed), function(err, res) {
309 handleResCB("Unable to edit message", err, res, callback);
310 });
311};
312
313/**
314 * Delete a posted message.
315 * @arg {Object} input
316 * @arg {Snowflake} input.channelID
317 * @arg {Snowflake} input.messageID
318 */
319DCP.deleteMessage = function(input, callback) {
320 this._req('delete', Endpoints.MESSAGES(input.channelID, input.messageID), function(err, res) {
321 handleResCB("Unable to delete message", err, res, callback);
322 });
323};
324
325/**
326 * Delete a batch of messages.
327 * @arg {Object} input
328 * @arg {Snowflake} input.channelID
329 * @arg {Array<Snowflake>} input.messageIDs - An Array of message IDs, with a maximum of 100 indexes.
330 */
331DCP.deleteMessages = function(input, callback) {
332 this._req('post', Endpoints.BULK_DELETE(input.channelID), {messages: input.messageIDs.slice(0, 100)}, function(err, res) {
333 handleResCB("Unable to delete messages", err, res, callback);
334 });
335};
336
337/**
338 * Pin a message to the channel.
339 * @arg {Object} input
340 * @arg {Snowflake} input.channelID
341 * @arg {Snowflake} input.messageID
342 */
343DCP.pinMessage = function(input, callback) {
344 this._req('put', Endpoints.PINNED_MESSAGES(input.channelID, input.messageID), function(err, res) {
345 handleResCB("Unable to pin message", err, res, callback);
346 });
347};
348
349/**
350 * Get an array of pinned messages from a channel.
351 * @arg {Object} input
352 * @arg {Snowflake} input.channelID
353 */
354DCP.getPinnedMessages = function(input, callback) {
355 this._req('get', Endpoints.PINNED_MESSAGES(input.channelID), function(err, res) {
356 handleResCB("Unable to get pinned messages", err, res, callback);
357 });
358};
359
360/**
361 * Delete a pinned message from a channel.
362 * @arg {Object} input
363 * @arg {Snowflake} input.channelID
364 * @arg {Snowflake} input.messageID
365 */
366DCP.deletePinnedMessage = function(input, callback) {
367 this._req('delete', Endpoints.PINNED_MESSAGES(input.channelID, input.messageID), function(err, res) {
368 handleResCB("Unable to delete pinned message", err, res, callback);
369 });
370};
371
372/**
373 * Send 'typing...' status to a channel
374 * @arg {Snowflake} channelID
375 */
376DCP.simulateTyping = function(channelID, callback) {
377 this._req('post', Endpoints.TYPING(channelID), function(err, res) {
378 handleResCB("Unable to simulate typing", err, res, callback);
379 });
380};
381
382/**
383 * Replace Snowflakes with the names if applicable.
384 * @arg {String} message - The message to fix.
385 */
386DCP.fixMessage = function(message) {
387 var client = this;
388 return message.replace(/<@&(\d*)>|<@!(\d*)>|<@(\d*)>|<#(\d*)>/g, function(match, RID, NID, UID, CID) {
389 var k, i;
390 if (UID || CID) {
391 if (client.users[UID]) return "@" + client.users[UID].username;
392 if (client.channels[CID]) return "#" + client.channels[CID].name;
393 }
394 if (RID || NID) {
395 k = Object.keys(client.servers);
396 for (i=0; i<k.length; i++) {
397 if (client.servers[k[i]].roles[RID]) return "@" + client.servers[k[i]].roles[RID].name;
398 if (client.servers[k[i]].members[NID]) return "@" + client.servers[k[i]].members[NID].nick;
399 }
400 }
401 });
402};
403
404/**
405 * Add an emoji reaction to a message.
406 * @arg {Object} input
407 * @arg {Snowflake} input.channelID
408 * @arg {Snowflake} input.messageID
409 * @arg {String} input.reaction - Either the emoji unicode or the emoji name:id/object.
410 */
411DCP.addReaction = function(input, callback) {
412 this._req('put', Endpoints.USER_REACTIONS(input.channelID, input.messageID, stringifyEmoji(input.reaction)), function(err, res) {
413 handleResCB("Unable to add reaction", err, res, callback);
414 });
415};
416
417/**
418 * Get an emoji reaction of a message.
419 * @arg {Object} input
420 * @arg {Snowflake} input.channelID
421 * @arg {Snowflake} input.messageID
422 * @arg {String} input.reaction - Either the emoji unicode or the emoji name:id/object.
423 * @arg {String} [input.limit]
424 */
425DCP.getReaction = function(input, callback) {
426 var qs = { limit: (typeof(input.limit) !== 'number' ? 100 : input.limit) };
427 this._req('get', Endpoints.MESSAGE_REACTIONS(input.channelID, input.messageID, stringifyEmoji(input.reaction)) + qstringify(qs), function(err, res) {
428 handleResCB("Unable to get reaction", err, res, callback);
429 });
430};
431
432/**
433 * Remove an emoji reaction from a message.
434 * @arg {Object} input
435 * @arg {Snowflake} input.channelID
436 * @arg {Snowflake} input.messageID
437 * @arg {Snowflake} [input.userID]
438 * @arg {String} input.reaction - Either the emoji unicode or the emoji name:id/object.
439 */
440DCP.removeReaction = function(input, callback) {
441 this._req('delete', Endpoints.USER_REACTIONS(input.channelID, input.messageID, stringifyEmoji(input.reaction), input.userID), function(err, res) {
442 handleResCB("Unable to remove reaction", err, res, callback);
443 });
444};
445
446/**
447 * Remove all emoji reactions from a message.
448 * @arg {Object} input
449 * @arg {Snowflake} input.channelID
450 * @arg {Snowflake} input.messageID
451 */
452DCP.removeAllReactions = function(input, callback) {
453 this._req('delete', Endpoints.MESSAGE_REACTIONS(input.channelID, input.messageID), function(err, res) {
454 handleResCB("Unable to remove reactions", err, res, callback);
455 });
456};
457
458/* - DiscordClient - Methods - Server Management - */
459
460/**
461 * Remove a user from a server.
462 * @arg {Object} input
463 * @arg {Snowflake} input.serverID
464 * @arg {Snowflake} input.userID
465 */
466DCP.kick = function(input, callback) {
467 this._req('delete', Endpoints.MEMBERS(input.serverID, input.userID), function(err, res) {
468 handleResCB("Could not kick user", err, res, callback);
469 });
470};
471
472/**
473 * Remove and ban a user from a server.
474 * @arg {Object} input
475 * @arg {Snowflake} input.serverID
476 * @arg {Snowflake} input.userID
477 * @arg {String} input.reason
478 * @arg {Number} [input.lastDays] - Removes their messages up until this point, either 1 or 7 days.
479 */
480DCP.ban = function(input, callback) {
481 var url = Endpoints.BANS(input.serverID, input.userID);
482 var opts = {};
483
484 if (input.lastDays) {
485 opts.lastDays = Number(input.lastDays);
486 opts.lastDays = Math.min(opts.lastDays, 7);
487 opts.lastDays = Math.max(opts.lastDays, 1);
488 }
489
490 if (input.reason) opts.reason = input.reason;
491
492 url += qstringify(opts);
493
494 this._req('put', url, function(err, res) {
495 handleResCB("Could not ban user", err, res, callback);
496 });
497}
498
499/**
500 * Unban a user from a server.
501 * @arg {Object} input
502 * @arg {Snowflake} input.serverID
503 * @arg {Snowflake} input.userID
504 */
505DCP.unban = function(input, callback) {
506 this._req('delete', Endpoints.BANS(input.serverID, input.userID), function(err, res) {
507 handleResCB("Could not unban user", err, res, callback);
508 });
509};
510
511/**
512 * Move a user between voice channels.
513 * @arg {Object} input
514 * @arg {Snowflake} input.serverID
515 * @arg {Snowflake} input.userID
516 * @arg {Snowflake} input.channelID
517 */
518DCP.moveUserTo = function(input, callback) {
519 this._req('patch', Endpoints.MEMBERS(input.serverID, input.userID), {channel_id: input.channelID}, function(err, res) {
520 handleResCB("Could not move the user", err, res, callback);
521 });
522};
523
524/**
525 * Server-mute the user from speaking in all voice channels.
526 * @arg {Object} input
527 * @arg {Snowflake} input.serverID
528 * @arg {Snowflake} input.userID
529 */
530DCP.mute = function(input, callback) {
531 this._req('patch', Endpoints.MEMBERS(input.serverID, input.userID), {mute: true}, function(err, res) {
532 handleResCB("Could not mute user", err, res, callback);
533 });
534};
535
536/**
537 * Remove the server-mute from a user.
538 * @arg {Object} input
539 * @arg {Snowflake} input.serverID
540 * @arg {Snowflake} input.userID
541 */
542DCP.unmute = function(input, callback) {
543 this._req('patch', Endpoints.MEMBERS(input.serverID, input.userID), {mute: false}, function(err, res) {
544 handleResCB("Could not unmute user", err, res, callback);
545 });
546};
547
548/**
549 * Server-deafen a user.
550 * @arg {Object} input
551 * @arg {Snowflake} input.serverID
552 * @arg {Snowflake} input.userID
553 */
554DCP.deafen = function(input, callback) {
555 this._req('patch', Endpoints.MEMBERS(input.serverID, input.userID), {deaf: true}, function(err, res) {
556 handleResCB("Could not deafen user", err, res, callback);
557 });
558};
559
560/**
561 * Remove the server-deafen from a user.
562 * @arg {Object} input
563 * @arg {Snowflake} input.serverID
564 * @arg {Snowflake} input.userID
565 */
566DCP.undeafen = function(input, callback) {
567 this._req('patch', Endpoints.MEMBERS(input.serverID, input.userID), {deaf: false}, function(err, res) {
568 handleResCB("Could not undeafen user", err, res, callback);
569 });
570};
571
572/**
573 * Self-mute the client from speaking in all voice channels.
574 * @arg {Snowflake} serverID
575 */
576DCP.muteSelf = function(serverID, callback) {
577 var server = this.servers[serverID], channelID, voiceSession;
578 if (!server) return handleErrCB(("Cannot find the server provided: " + serverID), callback);
579
580 server.self_mute = true;
581
582 if (!server.voiceSession) return call(callback, [null]);
583
584 voiceSession = server.voiceSession;
585 voiceSession.self_mute = true;
586 channelID = voiceSession.channelID;
587 if (!channelID) return call(callback, [null]);
588 return call(callback, [send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, true, server.self_deaf))]);
589};
590
591/**
592 * Remove the self-mute from the client.
593 * @arg {Snowflake} serverID
594 */
595DCP.unmuteSelf = function(serverID, callback) {
596 var server = this.servers[serverID], channelID, voiceSession;
597 if (!server) return handleErrCB(("Cannot find the server provided: " + serverID), callback);
598
599 server.self_mute = false;
600
601 if (!server.voiceSession) return call(callback, [null]);
602
603 voiceSession = server.voiceSession;
604 voiceSession.self_mute = false;
605 channelID = voiceSession.channelID;
606 if (!channelID) return call(callback, [null]);
607 return call(callback, [send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, false, server.self_deaf))]);
608};
609
610/**
611 * Self-deafen the client.
612 * @arg {Snowflake} serverID
613 */
614DCP.deafenSelf = function(serverID, callback) {
615 var server = this.servers[serverID], channelID, voiceSession;
616 if (!server) return handleErrCB(("Cannot find the server provided: " + serverID), callback);
617
618 server.self_deaf = true;
619
620 if (!server.voiceSession) return call(callback, [null]);
621
622 voiceSession = server.voiceSession;
623 voiceSession.self_deaf = true;
624 channelID = voiceSession.channelID;
625 if (!channelID) return call(callback, [null]);
626 return call(callback, [send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, server.self_mute, true))]);
627};
628
629/**
630 * Remove the self-deafen from the client.
631 * @arg {Snowflake} serverID
632 */
633DCP.undeafenSelf = function(serverID, callback) {
634 var server = this.servers[serverID], channelID, voiceSession;
635 if (!server) return handleErrCB(("Cannot find the server provided: " + serverID), callback);
636
637 server.self_deaf = false;
638
639 if (!server.voiceSession) return call(callback, [null]);
640
641 voiceSession = server.voiceSession;
642 voiceSession.self_deaf = false;
643 channelID = voiceSession.channelID;
644 if (!channelID) return call(callback, [null]);
645 return call(callback, [send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, server.self_mute, false))]);
646};
647
648/*Bot server management actions*/
649
650/**
651 * Create a server [User only].
652 * @arg {Object} input
653 * @arg {String} input.name - The server's name
654 * @arg {String} [input.region] - The server's region code, check the Gitbook documentation for all of them.
655 * @arg {String<Base64>} [input.icon] - The last part of a Base64 Data URI. `fs.readFileSync('image.jpg', 'base64')` is enough.
656 */
657DCP.createServer = function(input, callback) {
658 var payload, client = this;
659 payload = {icon: null, name: null, region: null};
660 for (var key in input) {
661 if (Object.keys(payload).indexOf(key) < 0) continue;
662 payload[key] = input[key];
663 }
664 if (input.icon) payload.icon = "data:image/jpg;base64," + input.icon;
665
666 client._req('post', Endpoints.SERVERS(), payload, function(err, res) {
667 try {
668 client.servers[res.body.id] = {};
669 copyKeys(res.body, client.servers[res.body.id]);
670 } catch(e) {}
671 handleResCB("Could not create server", err, res, callback);
672 });
673};
674
675/**
676 * Edit server information.
677 * @arg {Object} input
678 * @arg {Snowflake} input.serverID
679 * @arg {String} [input.name]
680 * @arg {String} [input.icon]
681 * @arg {String} [input.region]
682 * @arg {Snowflake} [input.afk_channel_id] - The ID of the voice channel to move a user to after the afk period.
683 * @arg {Number} [input.afk_timeout] - Time in seconds until a user is moved to the afk channel. 60, 300, 900, 1800, or 3600.
684 */
685DCP.editServer = function(input, callback) {
686 var payload, serverID = input.serverID, server, client = this;
687 if (!client.servers[serverID]) return handleErrCB(("[editServer] Server " + serverID + " not found."), callback);
688
689 server = client.servers[serverID];
690 payload = {
691 name: server.name,
692 icon: server.icon,
693 region: server.region,
694 afk_channel_id: server.afk_channel_id,
695 afk_timeout: server.afk_timeout
696 };
697
698 for (var key in input) {
699 if (Object.keys(payload).indexOf(key) < 0) continue;
700 if (key === 'afk_channel_id') {
701 if (server.channels[input[key]] && server.channels[input[key]].type === 'voice') payload[key] = input[key];
702 continue;
703 }
704 if (key === 'afk_timeout') {
705 if ([60, 300, 900, 1800, 3600].indexOf(Number(input[key])) > -1) payload[key] = input[key];
706 continue;
707 }
708 payload[key] = input[key];
709 }
710 if (input.icon) payload.icon = "data:image/jpg;base64," + input.icon;
711
712 client._req('patch', Endpoints.SERVERS(input.serverID), payload, function(err, res) {
713 handleResCB("Unable to edit server", err, res, callback);
714 });
715};
716
717/**
718 * Edit the widget information for a server.
719 * @arg {Object} input
720 * @arg {Snowflake} input.serverID - The ID of the server whose widget you want to edit.
721 * @arg {Boolean} [input.enabled] - Whether or not you want the widget to be enabled.
722 * @arg {Snowflake} [input.channelID] - [Important] The ID of the channel you want the instant invite to point to.
723 */
724DCP.editServerWidget = function(input, callback) {
725 var client = this, payload, url = Endpoints.SERVERS(input.serverID) + "/embed";
726
727 client._req('get', url, function(err, res) {
728 if (err) return handleResCB("Unable to GET server widget settings. Can not edit without retrieving first.", err, res, callback);
729 payload = {
730 enabled: ('enabled' in input ? input.enabled : res.body.enabled),
731 channel_id: ('channelID' in input ? input.channelID : res.body.channel_id)
732 };
733 client._req('patch', url, payload, function(err, res) {
734 handleResCB("Unable to edit server widget", err, res, callback);
735 });
736 });
737};
738
739/**
740 * [User Account] Add an emoji to a server
741 * @arg {Object} input
742 * @arg {Snowflake} input.serverID
743 * @arg {String} input.name - The emoji's name
744 * @arg {String<Base64>} input.image - The emoji's image data in Base64
745 */
746DCP.addServerEmoji = function(input, callback) {
747 var payload = {
748 name: input.name,
749 image: "data:image/png;base64," + input.image
750 };
751 this._req('post', Endpoints.SERVER_EMOJIS(input.serverID), payload, function(err, res) {
752 handleResCB("Unable to add emoji to the server", err, res, callback);
753 });
754}
755
756/**
757 * [User Account] Edit a server emoji data (name only, currently)
758 * @arg {Object} input
759 * @arg {Snowflake} input.serverID
760 * @arg {Snowflake} input.emojiID - The emoji's ID
761 * @arg {String} [input.name]
762 * @arg {Array<Snowflake>} [input.roles] - An array of role IDs you want to limit the emoji's usage to
763 */
764DCP.editServerEmoji = function(input, callback) {
765 var emoji, payload = {};
766 if ( !this.servers[input.serverID] ) return handleErrCB(("[editServerEmoji] Server not available: " + input.serverID), callback);
767 if ( !this.servers[input.serverID].emojis[input.emojiID]) return handleErrCB(("[editServerEmoji] Emoji not available: " + input.emojiID), callback);
768
769 emoji = this.servers[input.serverID].emojis[input.emojiID];
770 payload.name = input.name || emoji.name;
771 payload.roles = input.roles || emoji.roles;
772
773 this._req('patch', Endpoints.SERVER_EMOJIS(input.serverID, input.emojiID), payload, function(err, res) {
774 handleResCB("[editServerEmoji] Could not edit server emoji", err, res, callback);
775 });
776};
777
778/**
779 * [User Account] Remove an emoji from a server
780 * @arg {Object} input
781 * @arg {Snowflake} input.serverID
782 * @arg {Snowflake} input.emojiID
783 */
784DCP.deleteServerEmoji = function(input, callback) {
785 this._req('delete', Endpoints.SERVER_EMOJIS(input.serverID, input.emojiID), function(err, res) {
786 handleResCB("[deleteServerEmoji] Could not delete server emoji", err, res, callback);
787 });
788};
789
790/**
791 * Leave a server.
792 * @arg {Snowflake} serverID
793 */
794DCP.leaveServer = function(serverID, callback) {
795 this._req('delete', Endpoints.SERVERS_PERSONAL(serverID), function(err, res) {
796 handleResCB("Could not leave server", err, res, callback);
797 });
798};
799
800/**
801 * Delete a server owned by the client.
802 * @arg {Snowflake} serverID
803 */
804DCP.deleteServer = function(serverID, callback) {
805 this._req('delete', Endpoints.SERVERS(serverID), function(err, res) {
806 handleResCB("Could not delete server", err, res, callback);
807 });
808};
809
810/**
811 * Transfer ownership of a server to another user.
812 * @arg {Object} input
813 * @arg {Snowflake} input.serverID
814 * @arg {Snowflake} input.userID
815 */
816DCP.transferOwnership = function(input, callback) {
817 this._req('patch', Endpoints.SERVERS(input.serverID), {owner_id: input.userID}, function(err, res) {
818 handleResCB("Could not transfer server ownership", err, res, callback);
819 });
820};
821
822/**
823 * (Used to) Accept an invite to a server [User Only]. Can no longer be used.
824 * @deprecated
825 */
826DCP.acceptInvite = function(NUL, callback) {
827 return handleErrCB("acceptInvite can no longer be used", callback);
828};
829
830/**
831 * (Used to) Generate an invite URL for a channel.
832 * @deprecated
833 */
834DCP.createInvite = function(input, callback) {
835 var payload, client = this;
836
837 payload = {
838 max_age: 0,
839 max_users: 0,
840 temporary: false
841 };
842
843 if ( Object.keys(input).length === 1 && input.channelID ) {
844 payload = {
845 validate: client.internals.lastInviteCode || null
846 };
847 }
848
849 for (var key in input) {
850 if (Object.keys(payload).indexOf(key) < 0) continue;
851 payload[key] = input[key];
852 }
853
854 this._req('post', Endpoints.CHANNEL(input.channelID) + "/invites", payload, function(err, res) {
855 try {client.internals.lastInviteCode = res.body.code;} catch(e) {}
856 handleResCB('Unable to create invite', err, res, callback);
857 });
858};
859
860/**
861 * Delete an invite code.
862 * @arg {String} inviteCode
863 */
864DCP.deleteInvite = function(inviteCode, callback) {
865 this._req('delete', Endpoints.INVITES(inviteCode), function(err, res) {
866 handleResCB('Unable to delete invite', err, res, callback);
867 });
868};
869
870/**
871 * Get information on an invite.
872 * @arg {String} inviteCode
873 */
874DCP.queryInvite = function(inviteCode, callback) {
875 this._req('get', Endpoints.INVITES(inviteCode), function(err, res) {
876 handleResCB('Unable to get information about invite', err, res, callback);
877 });
878};
879
880/**
881 * Get all invites for a server.
882 * @arg {Snowflake} serverID
883 */
884DCP.getServerInvites = function(serverID, callback) {
885 this._req('get', Endpoints.SERVERS(serverID) + "/invites", function(err, res) {
886 handleResCB('Unable to get invite list for server' + serverID, err, res, callback);
887 });
888};
889
890/**
891 * Get all invites for a channel.
892 * @arg {Snowflake} channelID
893 */
894DCP.getChannelInvites = function(channelID, callback) {
895 this._req('get', Endpoints.CHANNEL(channelID) + "/invites", function(err, res) {
896 handleResCB('Unable to get invite list for channel' + channelID, err, res, callback);
897 });
898};
899
900/**
901 * Create a channel.
902 * @arg {Object} input
903 * @arg {Snowflake} input.serverID
904 * @arg {String} input.name
905 * @arg {String} [input.type] - 'text' or 'voice', defaults to 'text.
906 */
907DCP.createChannel = function(input, callback) {
908 var client = this, payload = {
909 name: input.name,
910 type: (['text', 'voice'].indexOf(input.type) < 0) ? 'text' : input.type
911 };
912
913 this._req('post', Endpoints.SERVERS(input.serverID) + "/channels", payload, function(err, res) {
914 try {
915 var serverID = res.body.guild_id;
916 var channelID = res.body.id;
917
918 client.channels[channelID] = new Channel( client, client.servers[serverID], res.body );
919 } catch(e) {}
920 handleResCB('Unable to create channel', err, res, callback);
921 });
922};
923
924/**
925 * Create a Direct Message channel.
926 * @arg {Snowflake} userID
927 */
928DCP.createDMChannel = function(userID, callback) {
929 var client = this;
930 this._req('post', Endpoints.USER(client.id) + "/channels", {recipient_id: userID}, function(err, res) {
931 if (!err && goodResponse(res)) client._uIDToDM[res.body.recipient_id] = res.body.id;
932 handleResCB("Unable to create DM Channel", err, res, callback);
933 });
934};
935
936/**
937 * Delete a channel.
938 * @arg {Snowflake} channelID
939 */
940DCP.deleteChannel = function(channelID, callback) {
941 this._req('delete', Endpoints.CHANNEL(channelID), function(err, res) {
942 handleResCB("Unable to delete channel", err, res, callback);
943 });
944};
945
946/**
947 * Edit a channel's information.
948 * @arg {Object} input
949 * @arg {Snowflake} input.channelID
950 * @arg {String} [input.name]
951 * @arg {String} [input.topic] - The topic of the channel.
952 * @arg {Number} [input.bitrate] - [Voice Only] The bitrate for the channel.
953 * @arg {Number} [input.position] - The channel's position on the list.
954 * @arg {Number} [input.user_limit] - [Voice Only] Imposes a user limit on a voice channel.
955 */
956DCP.editChannelInfo = function(input, callback) {
957 var channel, payload;
958
959 try {
960 channel = this.channels[input.channelID];
961 payload = {
962 name: channel.name,
963 topic: channel.topic,
964 bitrate: channel.bitrate,
965 position: channel.position,
966 user_limit: channel.user_limit
967 };
968
969 for (var key in input) {
970 if (Object.keys(payload).indexOf(key) < 0) continue;
971 if (+input[key]) {
972 if (key === 'bitrate') {
973 payload.bitrate = Math.min( Math.max( input.bitrate, 8000), 96000);
974 continue;
975 }
976 if (key === 'user_limit') {
977 payload.user_limit = Math.min( Math.max( input.user_limit, 0), 99);
978 continue;
979 }
980 }
981 payload[key] = input[key];
982 }
983
984 this._req('patch', Endpoints.CHANNEL(input.channelID), payload, function(err, res) {
985 handleResCB("Unable to edit channel", err, res, callback);
986 });
987 } catch(e) {return handleErrCB(e, callback);}
988};
989
990/**
991 * Edit (or creates) a permission override for a channel.
992 * @arg {Object} input
993 * @arg {Snowflake} input.channelID
994 * @arg {Snowflake} [input.userID]
995 * @arg {Snowflake} [input.roleID]
996 * @arg {Array<Number>} input.allow - An array of permissions to allow. Discord.Permissions.XXXXXX.
997 * @arg {Array<Number>} input.deny - An array of permissions to deny, same as above.
998 * @arg {Array<Number>} input.default - An array of permissions that cancels out allowed and denied permissions.
999 */
1000DCP.editChannelPermissions = function(input, callback) { //Will shrink this up later
1001 var payload, pType, ID, channel, permissions, allowed_values;
1002 if (!input.userID && !input.roleID) return handleErrCB("[editChannelPermissions] No userID or roleID provided", callback);
1003 if (!this.channels[input.channelID]) return handleErrCB(("[editChannelPermissions] No channel found for ID: " + input.channelID), callback);
1004 if (!input.allow && !input.deny && !input.default) return handleErrCB("[editChannelPermissions] No allow, deny or default array provided.", callback);
1005
1006 pType = input.userID ? 'user' : 'role';
1007 ID = input[pType + "ID"];
1008 channel = this.channels[ input.channelID ];
1009 permissions = channel.permissions[pType][ID] || { allow: 0, deny: 0 };
1010 allowed_values = [0, 4, 28].concat((channel.type === 'text' ?
1011 [10, 11, 12, 13, 14, 15, 16, 17, 18] :
1012 [20, 21, 22, 23, 24, 25] ));
1013
1014 //Take care of allow first
1015 if (type(input.allow) === 'array') {
1016 input.allow.forEach(function(perm) {
1017 if (allowed_values.indexOf(perm) < 0) return;
1018 if (hasPermission(perm, permissions.deny)) {
1019 permissions.deny = removePermission(perm, permissions.deny);
1020 }
1021 permissions.allow = givePermission(perm, permissions.allow);
1022 });
1023 }
1024 //Take care of deny second
1025 if (type(input.deny) === 'array') {
1026 input.deny.forEach(function(perm) {
1027 if (allowed_values.indexOf(perm) < 0) return;
1028 if (hasPermission(perm, permissions.allow)) {
1029 permissions.allow = removePermission(perm, permissions.allow);
1030 }
1031 permissions.deny = givePermission(perm, permissions.deny);
1032 });
1033 }
1034 //Take care of defaulting last
1035 if (type(input.default) === 'array') {
1036 input.default.forEach(function(perm) {
1037 if (allowed_values.indexOf(perm) < 0) return;
1038 permissions.allow = removePermission(perm, permissions.allow);
1039 permissions.deny = removePermission(perm, permissions.deny);
1040 });
1041 }
1042
1043 payload = {
1044 type: (pType === 'user' ? 'member' : 'role'),
1045 id: ID,
1046 deny: permissions.deny,
1047 allow: permissions.allow
1048 };
1049
1050 this._req('put', Endpoints.CHANNEL(input.channelID) + "/permissions/" + ID, payload, function(err, res) {
1051 handleResCB('Unable to edit permission', err, res, callback);
1052 });
1053};
1054
1055/**
1056 * Delete a permission override for a channel.
1057 * @arg {Object} input
1058 * @arg {Snowflake} input.channelID
1059 * @arg {Snowflake} [input.userID]
1060 * @arg {Snowflake} [input.roleID]
1061 */
1062DCP.deleteChannelPermission = function(input, callback) {
1063 var payload, pType, ID;
1064 if (!input.userID && !input.roleID) return handleErrCB("[deleteChannelPermission] No userID or roleID provided", callback);
1065 if (!this.channels[input.channelID]) return handleErrCB(("[deleteChannelPermission] No channel found for ID: " + input.channelID), callback);
1066
1067 pType = input.userID ? 'user' : 'role';
1068 ID = input[pType + "ID"];
1069
1070 payload = {
1071 type: (pType === 'user' ? 'member' : 'role'),
1072 id: ID
1073 };
1074
1075 this._req('delete', Endpoints.CHANNEL(input.channelID) + "/permissions/" + ID, payload, function(err, res) {
1076 handleResCB('Unable to delete permission', err, res, callback);
1077 });
1078};
1079
1080/**
1081 * Create a role for a server.
1082 * @arg {Snowflake} serverID
1083 */
1084DCP.createRole = function(serverID, callback) {
1085 var client = this;
1086 this._req('post', Endpoints.ROLES(serverID), function(err, res) {
1087 try {
1088 client.servers[serverID].roles[res.body.id] = new Role(res.body);
1089 } catch(e) {}
1090 handleResCB("Unable to create role", err, res, callback);
1091 });
1092};
1093
1094/**
1095 * Edit a role.
1096 * @arg {Object} input
1097 * @arg {Snowflake} input.serverID
1098 * @arg {Snowflake} input.roleID - The ID of the role.
1099 * @arg {String} [input.name]
1100 * @arg {String} [input.color] - A color value as a number. Recommend using Hex numbers, as they can map to HTML colors (0xF35353 === #F35353).
1101 * @arg {Boolean} [input.hoist] - Separates the users in this role from the normal online users.
1102 * @arg {Object} [input.permissions] - An Object containing the permission as a key, and `true` or `false` as its value. Read the Permissions doc.
1103 * @arg {Boolean} [input.mentionable] - Toggles if users can @Mention this role.
1104 */
1105DCP.editRole = function(input, callback) {
1106 var role, payload;
1107 try {
1108 role = new Role(this.servers[input.serverID].roles[input.roleID]);
1109 payload = {
1110 name: role.name,
1111 color: role.color,
1112 hoist: role.hoist,
1113 permissions: role._permissions,
1114 mentionable: role.mentionable,
1115 position: role.position
1116 };
1117
1118 for (var key in input) {
1119 if (Object.keys(payload).indexOf(key) < 0) continue;
1120 if (key === 'permissions') {
1121 for (var perm in input[key]) {
1122 role[perm] = input[key][perm];
1123 payload.permissions = role._permissions;
1124 }
1125 continue;
1126 }
1127 if (key === 'color') {
1128 if (String(input[key])[0] === '#') payload.color = parseInt(String(input[key]).replace('#', '0x'), 16);
1129 if (Discord.Colors[input[key]]) payload.color = Discord.Colors[input[key]];
1130 if (type(input[key]) === 'number') payload.color = input[key];
1131 continue;
1132 }
1133 payload[key] = input[key];
1134 }
1135 this._req('patch', Endpoints.ROLES(input.serverID, input.roleID), payload, function(err, res) {
1136 handleResCB("Unable to edit role", err, res, callback);
1137 });
1138 } catch(e) {return handleErrCB(('[editRole] ' + e), callback);}
1139};
1140
1141/**
1142 * Move a role up or down relative to it's current position.
1143 * @arg {Object} input
1144 * @arg {Snowflake} input.serverID
1145 * @arg {Snowflake} input.roleID - The ID of the role.
1146 * @arg {Number} input.position - A relative number to move the role up or down.
1147 */
1148DCP.moveRole = function(input,callback){
1149 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
1150
1151 try {
1152 var role = new Role(this.servers[input.serverID].roles[input.roleID]);
1153 var curPos = role.position;
1154 var newPos = curPos + input.position;
1155
1156 if(newPos < 1)
1157 newPos = 1; //make sure it doesn't go under the possible positions
1158
1159 if(newPos > Object.keys(this.servers[input.serverID].roles).length-1)
1160 newPos = Object.keys(this.servers[input.serverID].roles).length-1; //make sure it doesn't go above the possible positions
1161
1162 var currentOrder = [];
1163 for(var roleID in this.servers[input.serverID].roles){
1164 if(roleID == input.serverID) continue; //the everyone role cannot be changed, so best to not try.
1165 var _role = new Role(this.servers[input.serverID].roles[roleID]);
1166 currentOrder[_role.position] = roleID;
1167 }
1168 var payload = [];
1169
1170
1171 for(var pos in currentOrder){
1172 var roleID = currentOrder[pos];
1173 if(newPos>curPos && curPos < pos && pos <= newPos)
1174 payload.push({id:roleID, position:pos-1});
1175 if(curPos>newPos && curPos > pos && pos >= newPos)
1176 payload.push({id:roleID, position:(pos-(-1))});
1177 }
1178 payload.push({id:input.roleID,position:newPos});
1179
1180 this._req('patch', Endpoints.ROLES(input.serverID), payload, function(err, res) {
1181 handleResCB("Unable to move role", err, res, callback);
1182 });
1183 } catch(e) {return handleErrCB(e, callback);}
1184};
1185
1186/**
1187 * Delete a role.
1188 * @arg {Object} input
1189 * @arg {Snowflake} input.serverID
1190 * @arg {Snowflake} input.roleID
1191 */
1192DCP.deleteRole = function(input, callback) {
1193 this._req('delete', Endpoints.ROLES(input.serverID, input.roleID), function(err, res) {
1194 handleResCB("Could not remove role", err, res, callback);
1195 });
1196};
1197
1198/**
1199 * Add a user to a role.
1200 * @arg {Object} input
1201 * @arg {Snowflake} input.serverID
1202 * @arg {Snowflake} input.roleID
1203 * @arg {Snowflake} input.userID
1204 */
1205DCP.addToRole = function(input, callback) {
1206 this._req('put', Endpoints.MEMBER_ROLES(input.serverID, input.userID, input.roleID), function(err, res) {
1207 handleResCB("Could not add role", err, res, callback);
1208 });
1209};
1210
1211/**
1212 * Remove a user from a role.
1213 * @arg {Object} input
1214 * @arg {Snowflake} input.serverID
1215 * @arg {Snowflake} input.roleID
1216 * @arg {Snowflake} input.userID
1217 */
1218DCP.removeFromRole = function(input, callback) {
1219 this._req('delete', Endpoints.MEMBER_ROLES(input.serverID, input.userID, input.roleID), function(err, res) {
1220 handleResCB("Could not remove role", err, res, callback);
1221 });
1222};
1223
1224/**
1225 * Edit a user's nickname.
1226 * @arg {Object} input
1227 * @arg {Snowflake} input.serverID
1228 * @arg {Snowflake} input.userID
1229 * @arg {String} input.nick - The nickname you'd like displayed.
1230 */
1231DCP.editNickname = function(input, callback) {
1232 var payload = {nick: String( input.nick ? input.nick : "" )};
1233 var url = input.userID === this.id ?
1234 Endpoints.MEMBERS(input.serverID) + "/@me/nick" :
1235 Endpoints.MEMBERS(input.serverID, input.userID);
1236
1237 this._req('patch', url, payload, function(err, res) {
1238 handleResCB("Could not change nickname", err, res, callback);
1239 });
1240};
1241
1242/**
1243 * Edit a user's note.
1244 * @arg {Object} input
1245 * @arg {Snowflake} input.userID
1246 * @arg {String} input.note - The note content that you want to use.
1247 */
1248DCP.editNote = function(input, callback) {
1249 this._req('put', Endpoints.NOTE(input.userID), {note: input.note}, function(err, res) {
1250 handleResCB("Could not edit note", err, res, callback);
1251 });
1252};
1253
1254/**
1255 * Retrieve a user object from Discord, the library already caches users, however.
1256 * @arg {Object} input
1257 * @arg {Snowflake} input.serverID
1258 * @arg {Snowflake} input.userID
1259 */
1260DCP.getMember = function(input, callback) {
1261 this._req('get', Endpoints.MEMBERS(input.serverID, input.userID), function(err, res) {
1262 handleResCB("Could not get member", err, res, callback);
1263 });
1264};
1265
1266/**
1267 * Retrieve a group of user objects from Discord.
1268 * @arg {Object} input
1269 * @arg {Number} [input.limit] - The amount of users to pull, defaults to 50.
1270 * @arg {Snowflake} [input.after] - The offset using a user ID.
1271 */
1272DCP.getMembers = function(input, callback) {
1273 var qs = {};
1274 qs.limit = (typeof(input.limit) !== 'number' ? 50 : input.limit);
1275 if (input.after) qs.after = input.after;
1276
1277 this._req('get', Endpoints.MEMBERS(input.serverID) + qstringify(qs), function(err, res) {
1278 handleResCB("Could not get members", err, res, callback);
1279 });
1280};
1281
1282/**
1283 * Get the ban list from a server
1284 * @arg {Snowflake} serverID
1285 */
1286DCP.getBans = function(serverID, callback) {
1287 this._req('get', Endpoints.BANS(serverID), function(err, res) {
1288 handleResCB("Could not get ban list", err, res, callback);
1289 });
1290};
1291
1292/**
1293 * Get all webhooks for a server
1294 * @arg {Snowflake} serverID
1295 */
1296DCP.getServerWebhooks = function(serverID, callback) {
1297 this._req('get', Endpoints.SERVER_WEBHOOKS(serverID), function(err, res) {
1298 handleResCB("Could not get server Webhooks", err, res, callback);
1299 });
1300};
1301
1302/**
1303 * Get webhooks from a channel
1304 * @arg {Snowflake} channelID
1305 */
1306DCP.getChannelWebhooks = function(channelID, callback) {
1307 this._req('get', Endpoints.CHANNEL_WEBHOOKS(channelID), function(err, res) {
1308 handleResCB("Could not get channel Webhooks", err, res, callback);
1309 });
1310};
1311
1312/**
1313 * Create a webhook for a server
1314 * @arg {Snowflake} serverID
1315 */
1316DCP.createWebhook = function(serverID, callback) {
1317 this._req('post', Endpoints.SERVER_WEBHOOKS(serverID), function(err, res) {
1318 handleResCB("Could not create a Webhook", err, res, callback);
1319 });
1320};
1321
1322/**
1323 * Edit a webhook
1324 * @arg {Object} input
1325 * @arg {Snowflake} input.webhookID - The Webhook's ID
1326 * @arg {String} [input.name]
1327 * @arg {String<Base64>} [input.avatar]
1328 * @arg {String} [input.channelID]
1329 */
1330DCP.editWebhook = function(input, callback) {
1331 var client = this, payload = {}, allowed = ['avatar', 'name'];
1332 this._req('get', Endpoints.WEBHOOKS(input.webhookID), function(err, res) {
1333 if (err || !goodResponse(res)) return handleResCB("Couldn't get webhook, do you have permissions to access it?", err, res, callback);
1334 allowed.forEach(function(key) {
1335 payload[key] = (key in input ? input[key] : res.body[key]);
1336 });
1337 payload.channel_id = input.channelID || res.body.channel_id;
1338
1339 client._req('patch', Endpoints.WEBHOOKS(input.webhookID), payload, function(err, res) {
1340 return handleResCB("Couldn't update webhook", err, res, callback);
1341 });
1342 });
1343};
1344
1345/* --- Voice --- */
1346
1347/**
1348 * Join a voice channel.
1349 * @arg {Snowflake} channelID
1350 */
1351DCP.joinVoiceChannel = function(channelID, callback) {
1352 var serverID, server, channel, voiceSession;
1353 try {
1354 serverID = this.channels[channelID].guild_id;
1355 server = this.servers[serverID];
1356 channel = server.channels[channelID];
1357 } catch(e) {}
1358 if (!serverID) return handleErrCB(("Cannot find the server related to the channel provided: " + channelID), callback);
1359 if (channel.type !== 'voice') return handleErrCB(("Selected channel is not a voice channel: " + channelID), callback);
1360 if (this._vChannels[channelID]) return handleErrCB(("Voice channel already active: " + channelID), callback);
1361
1362 voiceSession = getVoiceSession(this, channelID, server);
1363 voiceSession.self_mute = server.self_mute;
1364 voiceSession.self_deaf = server.self_deaf;
1365 checkVoiceReady(voiceSession, callback);
1366 return send(this._ws, Payloads.UPDATE_VOICE(serverID, channelID, server.self_mute, server.self_deaf));
1367};
1368
1369/**
1370 * Leave a voice channel.
1371 * @arg {Snowflake} channelID
1372 */
1373DCP.leaveVoiceChannel = function(channelID, callback) {
1374 if (!this._vChannels[channelID]) return handleErrCB(("Not in the voice channel: " + channelID), callback);
1375 return leaveVoiceChannel(this, channelID, callback);
1376};
1377
1378/**
1379 * Prepare the client for sending/receiving audio.
1380 * @arg {Snowflake|Object} channelObj - Either the channel ID, or an Object with `channelID` as a key and the ID as the value.
1381 * @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.
1382 * @arg {Boolean} [channelObj.stereo] - Sets the audio to be either stereo or mono. Defaults to true.
1383 */
1384DCP.getAudioContext = function(channelObj, callback) {
1385 // #q/qeled gave a proper timing solution. Credit where it's due.
1386 if (!isNode) return handleErrCB("Using audio in the browser is currently not supported.", callback);
1387 var channelID = channelObj.channelID || channelObj, voiceSession = this._vChannels[channelID], encoder = chooseAudioEncoder(['ffmpeg', 'avconv']);
1388
1389 if (!voiceSession) return handleErrCB(("You have not joined the voice channel: " + channelID), callback);
1390 if (voiceSession.ready !== true) return handleErrCB(("The connection to the voice channel " + channelID + " has not been initialized yet."), callback);
1391 if (!encoder) return handleErrCB("You need either 'ffmpeg' or 'avconv' and they need to be added to PATH", callback);
1392
1393 voiceSession.audio = voiceSession.audio || new AudioCB(
1394 voiceSession,
1395 channelObj.stereo === false ? 1 : 2,
1396 encoder,
1397 Math.abs(Number(channelObj.maxStreamSize)));
1398
1399 return call(callback, [null, voiceSession.audio]);
1400};
1401
1402/* --- Misc --- */
1403
1404/**
1405 * Retrieves all offline (and online, if using a user account) users, fires the `allUsers` event when done.
1406 */
1407DCP.getAllUsers = function(callback) {
1408 var servers = Object.keys(this.servers).filter(function(s) {
1409 s = this.servers[s];
1410 if (s.members) return s.member_count !== Object.keys(s.members).length && (this.bot ? s.large : true);
1411 }, this);
1412
1413 if (!servers[0]) {
1414 this.emit('allUsers');
1415 return handleErrCB("There are no users to be collected", callback);
1416 }
1417 if (!this.bot) send(this._ws, Payloads.ALL_USERS(this));
1418
1419 return getOfflineUsers(this, servers, callback);
1420};
1421
1422/* --- Functions --- */
1423function handleErrCB(err, callback) {
1424 if (!err) return false;
1425 return call(callback, [new Error(err)]);
1426}
1427function handleResCB(errMessage, err, res, callback) {
1428 if (typeof(callback) !== 'function') return;
1429 res = res || {};
1430 if (!err && goodResponse(res)) return (callback(null, res.body), true);
1431
1432 var e = new Error( err || errMessage );
1433 e.name = "ResponseError";
1434 e.statusCode = res.statusCode;
1435 e.statusMessage = res.statusMessage;
1436 e.response = res.body;
1437 return (callback(e), false);
1438}
1439function goodResponse(response) {
1440 return (response.statusCode / 100 | 0) === 2;
1441}
1442function stringifyError(response) {
1443 if (!response) return null;
1444 return response.statusCode + " " + response.statusMessage + "\n" + JSON.stringify(response.body);
1445}
1446
1447/* - Functions - Messages - */
1448function sendMessage(client, to, message, callback) {
1449 resolveID(client, to, function(channelID) {
1450 client._req('post', Endpoints.MESSAGES(channelID), message, function(err, res) {
1451 handleResCB("Unable to send messages", err, res, callback);
1452 });
1453 });
1454}
1455function cacheMessage(cache, limit, channelID, message) {
1456 if (!cache[channelID]) cache[channelID] = {};
1457 if (limit === -1) return void(cache[channelID][message.id] = message);
1458 var k = Object.keys(cache[channelID]);
1459 if (k.length > limit) delete(cache[channelID][k[0]]);
1460 cache[channelID][message.id] = message;
1461}
1462function generateMessage(message, embed) {
1463 return {
1464 content: String(message),
1465 nonce: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER),
1466 embed: embed || {}
1467 };
1468}
1469function messageHeaders(client) {
1470 var r = {
1471 "accept": "*/*",
1472 "accept-language": "en-US;q=0.8",
1473 };
1474 if (isNode) {
1475 r["accept-encoding"] = "gzip, deflate";
1476 r.user_agent = "DiscordBot (https://github.com/izy521/discord.io, " + CURRENT_VERSION + ")";
1477 r.dnt = 1;
1478 }
1479 try {
1480 r.authorization = (client.bot ? "Bot " : "") + client.internals.token;
1481 } catch(e) {}
1482 return r;
1483}
1484function simulateTyping(client, to, message, time, callback) {
1485 if (time <= 0) return sendMessage(client, to, message, callback);
1486
1487 client.simulateTyping(to, function() {
1488 setTimeout(simulateTyping, Math.min(time, 5000), client, to, message, time - 5000, callback);
1489 });
1490}
1491function stringifyEmoji(emoji) {
1492 if (typeof emoji === 'object') // if (emoji.name && emoji.id)
1493 return emoji.name + ':' + emoji.id;
1494 if (emoji.indexOf(':') > -1)
1495 return emoji;
1496 return encodeURIComponent(decodeURIComponent(emoji));
1497}
1498
1499/* - Functions - Utils */
1500function APIRequest(method, url) {
1501 var data, callback, opts, req, headers = messageHeaders(this);
1502 callback = ( typeof(arguments[2]) === 'function' ? arguments[2] : (data = arguments[2], arguments[3]) );
1503
1504 if (isNode) {
1505 opts = URL.parse(url);
1506 opts.method = method;
1507 opts.headers = headers;
1508
1509 req = requesters[opts.protocol.slice(0, -1)].request(opts, function(res) {
1510 var chunks = [];
1511 res.on('data', function(c) { chunks[chunks.length] = c; });
1512 res.once('end', function() {
1513 chunks = Buffer.concat(chunks);
1514 Zlib.gunzip(chunks, function(err, uc) {
1515 if (!err) uc = uc.toString();
1516 try { res.body = JSON.parse(uc || chunks); } catch(e) {}
1517 return callback(null, res);
1518 });
1519 });
1520 });
1521 if (typeof(data) == 'object' || method.toLowerCase() === 'get') req.setHeader("Content-Type", "application/json; charset=utf-8");
1522 if (data instanceof Multipart) req.setHeader("Content-Type", "multipart/form-data; boundary=" + data.boundary);
1523 if (data) req.write( data.result || JSON.stringify(data), data.result ? 'binary' : 'utf-8' );
1524 req.end();
1525
1526 return req.once('error', function(e) { return callback(e.message); });
1527 }
1528
1529 req = new XMLHttpRequest();
1530 req.open(method.toUpperCase(), url, true);
1531 for (var key in headers) {
1532 req.setRequestHeader(key, headers[key]);
1533 }
1534 req.onreadystatechange = function() {
1535 if (req.readyState == 4) {
1536 req.statusCode = req.status;
1537 req.statusMessage = req.statusText;
1538 try {req.body = JSON.parse(req.responseText);} catch (e) { return handleErrCB(e, callback); }
1539 callback(null, req);
1540 }
1541 };
1542 if (typeof(data) == 'object' || method.toLowerCase() === 'get') req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
1543 if (data instanceof Multipart) req.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + data.boundary);
1544 if (data) return req.send( data.result ? data.result : JSON.stringify(data) );
1545 req.send(null);
1546}
1547function send(ws, data) {
1548 if (ws && ws.readyState == 1) ws.send(JSON.stringify(data));
1549}
1550function copy(obj) {
1551 try {
1552 return JSON.parse( JSON.stringify( obj ) );
1553 } catch(e) {}
1554}
1555function copyKeys(from, to, omit) {
1556 if (!omit) omit = [];
1557 for (var key in from) {
1558 if (omit.indexOf(key) > -1) continue;
1559 to[key] = from[key];
1560 }
1561}
1562function applyProperties(object, properties) {
1563 properties.forEach(function(t) {
1564 Object.defineProperty(object, t[0], {
1565 configurable: true,
1566 writable: true,
1567 value: t[1]
1568 });
1569 }, object);
1570}
1571function type(v) {
1572 return Object.prototype.toString.call(v).match(/ (.*)]/)[1].toLowerCase();
1573}
1574function call(f, a) {
1575 if (typeof(f) != 'function') return;
1576 return f.apply(null, a);
1577}
1578function qstringify(obj) {
1579 //.map + .join is 7x slower!
1580 var i=0, s = "", k = Object.keys(obj);
1581 for (i;i<k.length;i++) {
1582 s += k[i] + "=" + obj[k[i]] + "&";
1583 }
1584 return "?" + s.slice(0, -1);
1585}
1586function emit(client, message) {
1587 if (!message.t) return;
1588 var t = message.t.split("_"), i = 1, args = [t[0].toLowerCase()];
1589
1590 for (i; i<t.length; i++) {
1591 args[0] += t[i][0] + t[i].slice(1).toLowerCase();
1592 }
1593 for (i=2; i<arguments.length; i++) {
1594 args.push(arguments[i]);
1595 }
1596 args.push(message);
1597 client.emit.apply(client, args);
1598}
1599function decompressWSMessage(m, f) {
1600 f = f || {};
1601 return f.binary ? JSON.parse(Zlib.inflateSync(m).toString()) : JSON.parse(m);
1602}
1603function removeAllListeners(emitter, type) {
1604 if (!emitter) return;
1605 var e = emitter._evts, i, k, o, s, z;
1606 if (isNode) return type ? emitter.removeAllListeners(type) : emitter.removeAllListeners();
1607
1608 if (type && e[type]) {
1609 for (i=0; i<e[type].length; i++) {
1610 emitter.removeListener(type, e[type][i]);
1611 }
1612 }
1613
1614 if (!type) {
1615 k = Object.keys(e);
1616 for (o=0; o<k.length; o++) {
1617 s = e[ k[o] ];
1618 for (z=0; z<s.length; z++) {
1619 emitter.removeListener(k[o], s[z]);
1620 }
1621 }
1622 }
1623}
1624function colorFromRole(server, member) {
1625 return member.roles.reduce(function(array, ID) {
1626 var role = server.roles[ID];
1627 if (!role) return array;
1628 return role.position > array[0] && role.color ? [role.position, role.color] : array;
1629 }, [-1, null])[1];
1630}
1631function log(client, level_message) {
1632 var level, message;
1633 if (arguments.length === 2) {
1634 level = Discord.LogLevels.Info;
1635 message = level_message;
1636 } else {
1637 level = level_message;
1638 message = arguments[2];
1639 }
1640 return client.emit('log', level, message);
1641}
1642
1643function givePermission(bit, permissions) {
1644 return permissions | (1 << bit);
1645}
1646function removePermission(bit, permissions) {
1647 return permissions & ~(1 << bit);
1648}
1649function hasPermission(bit, permissions) {
1650 return ((permissions >> bit) & 1) == 1;
1651}
1652//For the Getters and Setters
1653function getPerm(bit) {
1654 return function() {
1655 return ((this._permissions >> bit) & 1) == 1;
1656 };
1657}
1658function setPerm(bit) {
1659 return function(v) {
1660 if (v === true) return this._permissions |= (1 << (bit));
1661 if (v === false) return this._permissions &= ~(1 << bit);
1662 };
1663}
1664
1665function getServerInfo(client, servArr) {
1666 for (var server=0; server<servArr.length; server++) {
1667 client.servers[servArr[server].id] = new Server(client, servArr[server]);
1668 }
1669}
1670function getDirectMessages(client, DMArray) {
1671 for (var DM=0; DM<DMArray.length; DM++) {
1672 client.directMessages[DMArray[DM].id] = new DMChannel(client._uIDToDM, DMArray[DM]);
1673 }
1674}
1675function resolveID(client, ID, callback) {
1676 /*Get channel from ServerID, ChannelID or UserID.
1677 Only really used for sendMessage and uploadFile.*/
1678 //Callback used instead of return because requesting seems necessary.
1679
1680 if (client._uIDToDM[ID]) return callback(client._uIDToDM[ID]);
1681 //If it's a UserID, and it's in the UserID : ChannelID cache, use the found ChannelID
1682
1683 //If the ID isn't in the UserID : ChannelID cache, let's try seeing if it belongs to a user.
1684 if (client.users[ID]) return client.createDMChannel(ID, function(err, res) {
1685 if (err) return console.log("Internal ID resolver error: " + JSON.stringify(err));
1686 callback(res.id);
1687 });
1688
1689 return callback(ID); //Finally, the ID must not belong to a User, so send the message directly to it, as it must be a Channel's.
1690}
1691function resolveEvent(e) {
1692 return e.detail || ([e.data][0] ? [e.data] : [e.code]);
1693}
1694
1695/* --- Initializing --- */
1696function init(client, opts) {
1697 client.servers = {};
1698 client.channels = {};
1699 client.users = {};
1700 client.directMessages = {};
1701 client.internals = {
1702 oauth: {},
1703 version: CURRENT_VERSION,
1704 settings: {}
1705 };
1706 client._connecting = true;
1707
1708 setupPing(client.internals);
1709 return getToken(client, opts);
1710}
1711function getToken(client, opts) {
1712 if (opts.token) return getGateway(client, opts.token);
1713 if (!isNode) {
1714 //Read from localStorage? Sounds like a bad idea, but I'll leave this here.
1715 }
1716}
1717function getGateway(client, token) {
1718 client.internals.token = token;
1719
1720 return APIRequest('get', Endpoints.GATEWAY, function (err, res) {
1721 if (err || !goodResponse(res)) {
1722 client._connecting = false;
1723 return client.emit("disconnect", "Error GETing gateway:\n" + stringifyError(res), 0);
1724 }
1725 return startConnection(client, (res.body.url + "/?encoding=json&v=" + GATEWAY_VERSION));
1726 });
1727}
1728function startConnection(client, gateway) {
1729 client._ws = new Websocket(gateway);
1730 client.internals.gatewayUrl = gateway;
1731
1732 client._ws.once('close', handleWSClose.bind(client));
1733 client._ws.once('error', handleWSClose.bind(client));
1734 client._ws.on('message', handleWSMessage.bind(client));
1735}
1736function getOfflineUsers(client, servArr, callback) {
1737 if (!servArr[0]) return call(callback);
1738
1739 send(client._ws, Payloads.OFFLINE_USERS(servArr));
1740 return setTimeout( getOfflineUsers, 0, client, servArr, callback );
1741}
1742function checkForAllServers(client, ready, message) {
1743 var all = Object.keys(client.servers).every(function(s) {
1744 return !client.servers[s].unavailable;
1745 });
1746 if (all || ready[0]) return void(client._ready = true && client.emit('ready', message));
1747 return setTimeout(checkForAllServers, 0, client, ready, message);
1748}
1749function setupPing(obj) {
1750 applyProperties(obj, [
1751 ["_pings", []],
1752 ["_lastHB", 0]
1753 ]);
1754 Object.defineProperty(obj, 'ping', {
1755 get: function() {
1756 return ((obj._pings.reduce(function(p, c) { return p + c; }, 0) / obj._pings.length) || 0) | 0;
1757 },
1758 set: function() {}
1759 });
1760}
1761function validateShard(shard) {
1762 return (
1763 type(shard) === 'array' &&
1764 shard.length === 2 &&
1765 shard[0] <= shard[1] &&
1766 shard[1] > 1
1767 ) ? shard : null;
1768}
1769function identifyOrResume(client) {
1770 var payload, internals = client.internals;
1771
1772 if (internals.sequence && internals.token && internals.sessionID) {
1773 payload = Payloads.RESUME(client);
1774 } else {
1775 payload = Payloads.IDENTIFY(client);
1776 if (client._shard) payload.d.shard = client._shard;
1777 }
1778 client._connecting = false;
1779
1780 return send(client._ws, payload);
1781}
1782
1783/* - Functions - Websocket Handling - */
1784function handleWSMessage(data, flags) {
1785 var message = decompressWSMessage(data, flags);
1786 var _data = message.d;
1787 var client = this, user, server, member, old,
1788 userID, serverID, channelID, currentVCID,
1789 mute, deaf, self_mute, self_deaf;
1790
1791 switch (message.op) {
1792 case 0:
1793 //Event dispatched - update our sequence number
1794 client.internals.sequence = message.s;
1795 break;
1796 case 9:
1797 //Disconnect after a Invalid Session
1798 //Disconnect the client if the client has received an invalid session id
1799 delete client.internals.sequence;
1800 delete client.internals.sessionID;
1801 client._ws && client._ws.close(1e3, 'Received an invalid session id');
1802 break;
1803 case 10:
1804 //Start keep-alive interval
1805 //Disconnect the client if no ping has been received
1806 //in 15 seconds (I think that's a decent duration)
1807 //Since v3 you the IDENTIFY/RESUME payload here.
1808 identifyOrResume(client);
1809 client.presenceStatus = 'online';
1810 client.connected = true;
1811
1812 client._mainKeepAlive = setInterval(function() {
1813 client.internals.heartbeat = setTimeout(function() {
1814 client._ws && client._ws.close(1e3, 'No heartbeat received');
1815 }, 15e3);
1816 client.internals._lastHB = Date.now();
1817 send(client._ws, Payloads.HEARTBEAT(client));
1818 }, _data.heartbeat_interval);
1819 break;
1820 case 11:
1821 clearTimeout(client.internals.heartbeat);
1822 client.internals._pings.unshift(Date.now() - client.internals._lastHB);
1823 client.internals._pings = client.internals._pings.slice(0, 10);
1824 break;
1825 }
1826
1827 //Events
1828 client.emit('any', message);
1829 //TODO: Remove in v3
1830 client.emit('debug', message);
1831 switch (message.t) {
1832 case "READY":
1833 copyKeys(_data.user, client);
1834 client.internals.sessionID = _data.session_id;
1835
1836 getServerInfo(client, _data.guilds);
1837 getDirectMessages(client, _data.private_channels);
1838
1839 if (client.bot) client.getOauthInfo(function(err, res) {
1840 if (err) return console.log(err);
1841 client.internals.oauth = res;
1842 client.inviteURL = "https://discordapp.com/oauth2/authorize?client_id=" + res.id + "&scope=bot";
1843 });
1844 if (!client.bot) client.getAccountSettings(function(err, res) {
1845 if (err) return console.log(err);
1846 client.internals.settings = res;
1847 });
1848
1849 return (function() {
1850 if (client._ready) return;
1851 var ready = [false];
1852 setTimeout(function() { ready[0] = true; }, 3500);
1853 checkForAllServers(client, ready, message);
1854 })();
1855 case "MESSAGE_CREATE":
1856 client.emit('message', _data.author.username, _data.author.id, _data.channel_id, _data.content, message);
1857 emit(client, message, _data.author.username, _data.author.id, _data.channel_id, _data.content);
1858 return cacheMessage(client._messageCache, client._messageCacheLimit, _data.channel_id, _data);
1859 case "MESSAGE_UPDATE":
1860 try {
1861 emit(client, message, client._messageCache[_data.channel_id][_data.id], _data);
1862 } catch (e) { emit(client, message, undefined, _data); }
1863 return cacheMessage(client._messageCache, client._messageCacheLimit, _data.channel_id, _data);
1864 case "PRESENCE_UPDATE":
1865 if (!_data.guild_id) break;
1866
1867 serverID = _data.guild_id;
1868 userID = _data.user.id;
1869
1870 if (!client.users[userID]) client.users[userID] = new User(_data.user);
1871
1872 user = client.users[userID];
1873 member = client.servers[serverID].members[userID] || {};
1874
1875 copyKeys(_data.user, user);
1876 user.game = _data.game;
1877
1878 copyKeys(_data, member, ['user', 'guild_id', 'game']);
1879 client.emit('presence', user.username, user.id, member.status, user.game, message);
1880 break;
1881 case "USER_UPDATE":
1882 copyKeys(_data, client);
1883 break;
1884 case "USER_SETTINGS_UPDATE":
1885 copyKeys(_data, client.internals);
1886 break;
1887 case "GUILD_CREATE":
1888 /*The lib will attempt to create the server using the response from the
1889 REST API, if the user using the lib creates the server. There are missing keys, however.
1890 So we still need this GUILD_CREATE event to fill in the blanks.
1891 If It's not our created server, then there will be no server with that ID in the cache,
1892 So go ahead and create one.*/
1893 client.servers[_data.id] = new Server(client, _data);
1894 return emit(client, message, client.servers[_data.id]);
1895 case "GUILD_UPDATE":
1896 old = copy(client.servers[_data.id]);
1897 Server.update(client, _data);
1898 return emit(client, message, old, client.servers[_data.id]);
1899 case "GUILD_DELETE":
1900 emit(client, message, client.servers[_data.id]);
1901 return delete(client.servers[_data.id]);
1902 case "GUILD_MEMBER_ADD":
1903 client.users[_data.user.id] = new User(_data.user);
1904 client.servers[_data.guild_id].members[_data.user.id] = new Member(client, client.servers[_data.guild_id], _data);
1905 client.servers[_data.guild_id].member_count += 1;
1906 return emit(client, message, client.servers[_data.guild_id].members[_data.user.id]);
1907 case "GUILD_MEMBER_UPDATE":
1908 old = copy(client.servers[_data.guild_id].members[_data.user.id]);
1909 Member.update(client, client.servers[_data.guild_id], _data);
1910 return emit(client, message, old, client.servers[_data.guild_id].members[_data.user.id]);
1911 case "GUILD_MEMBER_REMOVE":
1912 if (_data.user && _data.user.id === client.id) return;
1913 client.servers[_data.guild_id].member_count -= 1;
1914 emit(client, message, client.servers[_data.guild_id].members[_data.user.id]);
1915 return delete(client.servers[_data.guild_id].members[_data.user.id]);
1916 case "GUILD_ROLE_CREATE":
1917 client.servers[_data.guild_id].roles[_data.role.id] = new Role(_data.role);
1918 return emit(client, message, client.servers[_data.guild_id].roles[_data.role.id]);
1919 case "GUILD_ROLE_UPDATE":
1920 server = client.servers[_data.guild_id];
1921 old = copy(server.roles[_data.role.id]);
1922 Role.update(server, _data);
1923 Object.keys(server.members).forEach(function(memberID) {
1924 var member = server.members[memberID];
1925 if ( member.roles.indexOf(_data.role.id) < 0 ) return;
1926 member.color = colorFromRole(server, member);
1927 });
1928 return emit(client, message, old, server.roles[_data.role.id]);
1929 case "GUILD_ROLE_DELETE":
1930 emit(client, message, client.servers[_data.guild_id].roles[_data.role_id]);
1931 return delete(client.servers[_data.guild_id].roles[_data.role_id]);
1932 case "CHANNEL_CREATE":
1933 channelID = _data.id;
1934
1935 if (_data.is_private) {
1936 if (client.directMessages[channelID]) return;
1937 client.directMessages[channelID] = new DMChannel(client._uIDToDM, _data);
1938 return emit(client, message, client.directMessages[channelID]);
1939 }
1940
1941 if (client.channels[channelID]) return;
1942 client.channels[channelID] = new Channel(client, client.servers[_data.guild_id], _data);
1943 return emit(client, message, client.channels[channelID]);
1944 case "CHANNEL_UPDATE":
1945 old = copy(client.channels[_data.id]);
1946 Channel.update(client, _data);
1947 return emit(client, message, old, client.channels[_data.id]);
1948 case "CHANNEL_DELETE":
1949 if (_data.is_private === true) {
1950 emit(client, message, client.directMessages[_data.id]);
1951 delete(client.directMessages[_data.id]);
1952 return delete(client._uIDToDM[_data.recipient.id]);
1953 }
1954 emit(client, message, client.servers[_data.guild_id].channels[_data.id]);
1955 delete(client.servers[_data.guild_id].channels[_data.id]);
1956 return delete(client.channels[_data.id]);
1957 case "GUILD_EMOJIS_UPDATE":
1958 old = copy(client.servers[_data.guild_id].emojis);
1959 Emoji.update(client.servers[_data.guild_id], _data.emojis);
1960 return emit(client, message, old, client.servers[_data.guild_id].emojis);
1961 case "VOICE_STATE_UPDATE":
1962 serverID = _data.guild_id;
1963 channelID = _data.channel_id;
1964 userID = _data.user_id;
1965 server = client.servers[serverID];
1966 mute = !!_data.mute;
1967 self_mute = !!_data.self_mute;
1968 deaf = !!_data.deaf;
1969 self_deaf = !!_data.self_deaf;
1970
1971 try {
1972 currentVCID = server.members[userID].voice_channel_id;
1973 if (currentVCID) delete( server.channels[currentVCID].members[userID] );
1974 if (channelID) server.channels[channelID].members[userID] = _data;
1975 server.members[userID].mute = (mute || self_mute);
1976 server.members[userID].deaf = (deaf || self_deaf);
1977 server.members[userID].voice_channel_id = channelID;
1978 } catch(e) {}
1979
1980 if (userID === client.id) {
1981 server.self_mute = self_mute;
1982 server.self_deaf = self_deaf;
1983 if (channelID === null) {
1984 if (server.voiceSession) leaveVoiceChannel(client, server.voiceSession.channelID);
1985 server.voiceSession = null;
1986 } else {
1987 if (!server.voiceSession) {
1988 server.voiceSession = getVoiceSession(client, channelID, server);
1989 }
1990 if (channelID !== server.voiceSession.channelID) {
1991 delete( client._vChannels[server.voiceSession.channelID] );
1992 getVoiceSession(client, channelID, server).channelID = channelID;
1993 }
1994
1995 server.voiceSession.session = _data.session_id;
1996 server.voiceSession.self_mute = self_mute;
1997 server.voiceSession.self_deaf = self_deaf;
1998 }
1999 }
2000 break;
2001 case "VOICE_SERVER_UPDATE":
2002 serverID = _data.guild_id;
2003 server = client.servers[serverID];
2004 server.voiceSession.token = _data.token;
2005 server.voiceSession.severID = serverID;
2006 server.voiceSession.endpoint = _data.endpoint;
2007 joinVoiceChannel(client, server.voiceSession);
2008 break;
2009 case "GUILD_MEMBERS_CHUNK":
2010 serverID = _data.guild_id;
2011 if (!client.servers[serverID].members) client.servers[serverID].members = {};
2012
2013 _data.members.forEach(function(member) {
2014 var uID = member.user.id;
2015 var members = client.servers[serverID].members;
2016 if (members[uID]) return;
2017 if (!client.users[uID]) client.users[uID] = new User(member.user);
2018 members[uID] = new Member(client, client.servers[serverID], member);
2019 });
2020 var all = Object.keys(client.servers).every(function(server) {
2021 server = client.servers[server];
2022 return server.member_count === Object.keys(server.members).length;
2023 });
2024
2025 if (all) return client.emit("allUsers");
2026 break;
2027 case "GUILD_SYNC":
2028 _data.members.forEach(function(member) {
2029 var uID = member.user.id;
2030 if (!client.users[uID]) client.users[uID] = new User(member.user);
2031 client.servers[_data.id].members[uID] = new Member(client, client.servers[_data.id], member);
2032 });
2033
2034 _data.presences.forEach(function(presence) {
2035 var uID = presence.user.id;
2036 var members = client.servers[_data.id].members;
2037 if (!members[uID]) return void(new User(presence.user));
2038 delete(presence.user);
2039 copyKeys(presence, members[uID]);
2040 });
2041 client.servers[_data.id].large = _data.large;
2042 break;
2043 }
2044 return emit(client, message);
2045}
2046function handleWSClose(code, data) {
2047 var client = this;
2048 var eMsg = Discord.Codes.WebSocket[code];
2049
2050 clearInterval(client._mainKeepAlive);
2051 client.connected = false;
2052 client.presenceStatus = "offline";
2053 removeAllListeners(client._ws, 'message');
2054
2055 if ([1001, 1006].indexOf(code) > -1) return getGateway(client, client.internals.token);
2056
2057 client._ready = false;
2058 client._ws = null;
2059
2060 client.emit("disconnect", eMsg, code);
2061}
2062
2063/* - Functions - Voice - */
2064function joinVoiceChannel(client, voiceSession) {
2065 var vWS, vUDP, endpoint = voiceSession.endpoint.split(":")[0];
2066 //handleVoiceChannelChange(client, voiceSession);
2067
2068 voiceSession.ws = {};
2069 voiceSession.udp = {};
2070 voiceSession.members = {};
2071 voiceSession.error = null;
2072 voiceSession.ready = false;
2073 voiceSession.joined = false;
2074 voiceSession.translator = {};
2075 voiceSession.wsKeepAlive = null;
2076 voiceSession.udpKeepAlive = null;
2077 voiceSession.keepAlivePackets = 0;
2078 voiceSession.emitter = new Emitter();
2079 if (isNode) voiceSession.keepAliveBuffer = new Buffer(8).fill(0);
2080 vWS = voiceSession.ws.connection = new Websocket("wss://" + endpoint);
2081
2082 if (isNode) return DNS.lookup(endpoint, function(err, address) {
2083 if (err) return void(voiceSession.error = err);
2084
2085 voiceSession.address = address;
2086 vUDP = voiceSession.udp.connection = UDP.createSocket("udp4");
2087
2088 vUDP.bind({exclusive: true});
2089 vUDP.once('message', handleUDPMessage.bind(client, voiceSession));
2090
2091 vWS.once('open', handlevWSOpen.bind(client, voiceSession));
2092 vWS.on('message', handlevWSMessage.bind(client, voiceSession));
2093 vWS.once('close', handlevWSClose.bind(client, voiceSession));
2094 });
2095
2096 vWS.once('open', handlevWSOpen.bind(client, voiceSession));
2097 vWS.on('message', handlevWSMessage.bind(client, voiceSession));
2098 vWS.once('close', handlevWSClose.bind(client, voiceSession));
2099 return void(voiceSession.joined = true);
2100}
2101
2102function leaveVoiceChannel(client, channelID, callback) {
2103 if (!client._vChannels[channelID]) return;
2104
2105 try {
2106 client._vChannels[channelID].ws.connection.close();
2107 client._vChannels[channelID].udp.connection.close();
2108 } catch(e) {}
2109
2110 send(client._ws, Payloads.UPDATE_VOICE(client.channels[channelID].guild_id, null, false, false));
2111
2112 return call(callback, [null]);
2113}
2114
2115function keepUDPAlive(VS) {
2116 if (!VS.keepAliveBuffer) return;
2117
2118 if (VS.keepAlivePackets > 4294967294) {
2119 VS.keepAlivePackets = 0;
2120 VS.keepAliveBuffer.fill(0);
2121 }
2122 VS.keepAliveBuffer.writeUIntLE(++VS.keepAlivePackets, 0, 6);
2123 try {
2124 return VS.udp.connection.send(VS.keepAliveBuffer, 0, VS.keepAliveBuffer.length, VS.ws.port, VS.address);
2125 } catch(e) {}
2126}
2127
2128function getVoiceSession(client, channelID, server) {
2129 if (!channelID) return null;
2130 return client._vChannels[channelID] ?
2131 client._vChannels[channelID] :
2132 client._vChannels[channelID] = (server && server.voiceSession) || {
2133 serverID: (server && server.id) || null,
2134 channelID: channelID,
2135 token: null,
2136 session: null,
2137 endpoint: null,
2138 self_mute: false,
2139 self_deaf: false
2140 };
2141}
2142
2143function checkVoiceReady(voiceSession, callback) {
2144 return setTimeout(function() {
2145 if (voiceSession.error) return call(callback, [voiceSession.error]);
2146 if (voiceSession.joined) return call(callback, [null, voiceSession.emitter]);
2147 return checkVoiceReady(voiceSession, callback);
2148 }, 1);
2149}
2150
2151/* - Functions - Voice - Handling - */
2152
2153function handlevWSOpen(voiceSession) {
2154 return send(voiceSession.ws.connection, Payloads.VOICE_IDENTIFY(this.id, voiceSession));
2155}
2156function handlevWSMessage(voiceSession, vMessage, vFlags) {
2157 var client = this, vData = decompressWSMessage(vMessage, vFlags);
2158 switch (vData.op) {
2159 case 2: //Ready (Actually means you're READY to initiate the UDP connection)
2160 copyKeys(vData.d, voiceSession.ws);
2161 voiceSession.wsKeepAlive = setInterval(send, vData.d.heartbeat_interval, voiceSession.ws.connection, { "op": 3, "d": null });
2162
2163 if (!isNode) return;
2164
2165 var udpDiscPacket = new Buffer(70);
2166 udpDiscPacket.writeUIntBE(vData.d.ssrc, 0, 4);
2167 voiceSession.udp.connection.send(
2168 udpDiscPacket, 0, udpDiscPacket.length, vData.d.port, voiceSession.address,
2169 function(err) { if (err) {leaveVoiceChannel(client, voiceSession.channelID); handleErrCB("UDP discovery error", callback); } }
2170 );
2171
2172 voiceSession.udpKeepAlive = setInterval(keepUDPAlive, 5000, voiceSession);
2173 break;
2174 case 4: //Session Discription (Actually means you're ready to send audio... stupid Discord Devs :I)
2175 voiceSession.selectedMode = vData.d.mode;
2176 voiceSession.secretKey = vData.d.secret_key;
2177 voiceSession.joined = true;
2178 voiceSession.ready = true;
2179 break;
2180 case 5: //Speaking (At least this isn't confusing!)
2181 voiceSession.emitter.emit('speaking', vData.d.user_id, vData.d.ssrc, vData.d.speaking);
2182 break;
2183 }
2184}
2185function handlevWSClose(voiceSession) {
2186 //Emit the disconnect event first
2187 voiceSession.emitter.emit("disconnect", voiceSession.channelID);
2188 voiceSession.emitter = null
2189 //Kill encoder and decoders
2190 var audio = voiceSession.audio, members = voiceSession.members;
2191 if (audio) {
2192 if (audio._systemEncoder) audio._systemEncoder.kill();
2193 if (audio._mixedDecoder) audio._mixedDecoder.destroy();
2194 }
2195
2196 Object.keys(members).forEach(function(ID) {
2197 var member = members[ID];
2198 if (member.decoder) member.decoder.destroy();
2199 });
2200
2201 //Clear intervals and remove listeners
2202 clearInterval(voiceSession.wsKeepAlive);
2203 clearInterval(voiceSession.udpKeepAlive);
2204 removeAllListeners(voiceSession.emitter);
2205 removeAllListeners(voiceSession.udp.connection, 'message');
2206 removeAllListeners(voiceSession.ws.connection, 'message');
2207
2208 return delete(this._vChannels[voiceSession.channelID]);
2209}
2210
2211function handleUDPMessage(voiceSession, msg, rinfo) {
2212 var buffArr = JSON.parse(JSON.stringify(msg)).data, client = this, vDiscIP = "", vDiscPort;
2213 for (var i=4; i<buffArr.indexOf(0, i); i++) {
2214 vDiscIP += String.fromCharCode(buffArr[i]);
2215 }
2216 vDiscPort = msg.readUIntLE(msg.length - 2, 2).toString(10);
2217 return send(voiceSession.ws.connection, Payloads.VOICE_DISCOVERY(vDiscIP, vDiscPort, 'xsalsa20_poly1305'));
2218}
2219
2220/* - Functions - Voice - AudioCallback - */
2221function AudioCB(voiceSession, audioChannels, encoder, maxStreamSize) {
2222 //With the addition of the new Stream API, `playAudioFile`, `stopAudioFile` and `send`
2223 //will be removed. However they're deprecated for now, hence the code repetition.
2224 if (maxStreamSize && !Opus) Opus = require('cjopus');
2225 Stream.Duplex.call(this);
2226 var ACBI = this;
2227
2228 this.audioChannels = audioChannels;
2229 this.members = voiceSession.members;
2230
2231 applyProperties(this, [
2232 ["_sequence", 0],
2233 ["_timestamp", 0],
2234 ["_readable", false],
2235 ["_streamRef", null],
2236 ["_startTime", null],
2237 ["_systemEncoder", null],
2238 ["_playingAudioFile", false],
2239 ["_voiceSession", voiceSession],
2240 ["_port", voiceSession.ws.port],
2241 ["_address", voiceSession.address],
2242 ["_decodeNonce", new Uint8Array(24)],
2243 ["_vUDP", voiceSession.udp.connection],
2244 ["_secretKey", new Uint8Array(voiceSession.secretKey)],
2245 ["_mixedDecoder", Opus && new Opus.OpusEncoder( 48000, audioChannels ) || null]
2246 ]);
2247
2248 createAudioEncoder(this, encoder);
2249
2250 this._write = function _write(chunk, encoding, callback) {
2251 ACBI._systemEncoder.stdin.write(chunk);
2252 return callback();
2253 };
2254 this._read = function _read() {};
2255 this.stop = function stop() {
2256 return this._systemEncoder.stdout.read = function() { return null };
2257 };
2258
2259 if (maxStreamSize) {
2260 voiceSession.ws.connection.on('message', function(data, flags) {
2261 data = decompressWSMessage(data, flags);
2262
2263 if (data.op !== 5) return;
2264 if (!voiceSession.members[data.d.user_id]) {
2265 voiceSession.members[data.d.user_id] = new Stream.Readable({
2266 highWaterMark: maxStreamSize,
2267 read: function(s) {}
2268 });
2269 voiceSession.members[data.d.user_id].decoder = new Opus.OpusEncoder( 48000, ACBI.audioChannels );
2270 ACBI.emit('newMemberStream', data.d.user_id, voiceSession.members[data.d.user_id]);
2271 }
2272
2273 voiceSession.members[data.d.user_id].ssrc = data.d.ssrc;
2274 voiceSession.translator[data.d.ssrc] = voiceSession.members[data.d.user_id];
2275 });
2276 this._vUDP.on('message', handleIncomingAudio.bind(this));
2277 }
2278}
2279if (isNode) Util.inherits(AudioCB, Stream.Duplex);
2280AudioCB.VoicePacket = (function() {
2281 if (!isNode) return;
2282 var header = new Buffer(12), nonce = new Uint8Array(24), output = new Buffer(2048);
2283
2284 header[0] = 0x80;
2285 header[1] = 0x78;
2286
2287 return function(packet, ssrc, sequence, timestamp, key) {
2288 header.writeUIntBE(sequence, 2, 2);
2289 header.writeUIntBE(timestamp, 4, 4);
2290 header.writeUIntBE(ssrc, 8, 4);
2291 //<Buffer 80 78 00 01 00 00 03 c0 00 00 00 01>
2292 nonce.set(header);
2293 //<Buffer 80 78 00 01 00 00 03 c0 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00>
2294
2295 var encrypted = new Buffer(
2296 NACL.secretbox(
2297 new Uint8Array(packet),
2298 nonce,
2299 key
2300 )
2301 );
2302
2303 header.copy(output);
2304 encrypted.copy(output, 12);
2305
2306 return output.slice(0, header.length + encrypted.length);
2307 };
2308})();
2309var ACBP = AudioCB.prototype;
2310//TODO: Remove in v3
2311ACBP.playAudioFile = function(location, callback) {
2312 if (!this._mixedDecoder) {
2313 if (!Opus) Opus = require('cjopus');
2314 this._mixedDecoder = new Opus.OpusEncoder( 48000, this.audioChannels );
2315 }
2316
2317 if (this._playingAudioFile) return handleErrCB("There is already a file being played.", callback);
2318 var encs = ['ffmpeg', 'avconv'], selection, enc, ACBI = this;
2319
2320 this._playingAudioFile = true;
2321 selection = chooseAudioEncoder(encs);
2322
2323 if (!selection) return console.log("You need either 'ffmpeg' or 'avconv' and they need to be added to PATH");
2324
2325 enc = ChildProc.spawn(selection , [
2326 '-i', location,
2327 '-f', 's16le',
2328 '-ar', '48000',
2329 '-ac', ACBI.audioChannels,
2330 'pipe:1'
2331 ], {stdio: ['pipe', 'pipe', 'ignore']});
2332 enc.stdout.once('end', function() {
2333 enc.kill();
2334 send(ACBI._voiceSession.ws.connection, Payloads.VOICE_SPEAK(0));
2335 ACBI._playingAudioFile = false;
2336 ACBI.emit('fileEnd');
2337 });
2338 enc.stdout.once('error', function(e) {
2339 enc.stdout.emit('end');
2340 });
2341 enc.stdout.once('readable', function() {
2342 send(ACBI._voiceSession.ws.connection, Payloads.VOICE_SPEAK(1));
2343 ACBI._startTime = new Date().getTime();
2344 prepareAudioOld(ACBI, enc.stdout, 1);
2345 });
2346 this._streamRef = enc;
2347};
2348//TODO: Remove in v3
2349ACBP.stopAudioFile = function(callback) {
2350 if (!this._playingAudioFile) return handleErrCB("There is no file being played", callback);
2351
2352 this._streamRef.stdout.end();
2353 this._streamRef.kill();
2354 this._playingAudioFile = false;
2355
2356 call(callback);
2357};
2358//TODO: Remove in v3
2359ACBP.send = function(stream) {
2360 if (!this._mixedDecoder) {
2361 if (!Opus) Opus = require('cjopus');
2362 this._mixedDecoder = new Opus.OpusEncoder( 48000, this.audioChannels );
2363 }
2364 send(this._voiceSession.ws.connection, Payloads.VOICE_SPEAK(1));
2365 this._startTime = new Date().getTime();
2366 prepareAudioOld(this, stream, 1);
2367};
2368
2369function prepareAudio(ACBI, readableStream, cnt) {
2370 var data = readableStream.read( 320 ) || readableStream.read(); //(128 [kb] * 20 [frame_size]) / 8 == 320
2371
2372 if (!data) {
2373 send(ACBI._voiceSession.ws.connection, Payloads.VOICE_SPEAK(0));
2374 ACBI._readable = false;
2375 return readableStream.emit('end');
2376 }
2377
2378 return setTimeout(function() {
2379 sendAudio(ACBI, data);
2380 prepareAudio(ACBI, readableStream, cnt + 1);
2381 }, 20 + ( (ACBI._startTime + cnt * 20) - Date.now() ));
2382}
2383
2384//TODO: Remove in v3
2385function prepareAudioOld(ACBI, readableStream) {
2386 var done = false;
2387
2388 readableStream.on('end', function() {
2389 done = true;
2390 send(ACBI._voiceSession.ws.connection, Payloads.VOICE_SPEAK(0));
2391 });
2392
2393 _prepareAudio(ACBI, readableStream, 1);
2394
2395 function _prepareAudio(ACBI, readableStream, cnt) {
2396 if (done) return;
2397 var buffer, encoded;
2398
2399 buffer = readableStream.read( 1920 * ACBI.audioChannels );
2400 encoded = [0xF8, 0xFF, 0xFE];
2401
2402 if ((buffer && buffer.length === 1920 * ACBI.audioChannels) && !ACBI._mixedDecoder.destroyed) {
2403 encoded = ACBI._mixedDecoder.encode(buffer);
2404 }
2405
2406 return setTimeout(function() {
2407 sendAudio(ACBI, encoded);
2408 _prepareAudio(ACBI, readableStream, cnt + 1);
2409 }, 20 + ( (ACBI._startTime + cnt * 20) - Date.now() ));
2410 }
2411}
2412
2413function sendAudio(ACBI, buffer) {
2414 ACBI._sequence = (ACBI._sequence + 1 ) < 0xFFFF ? ACBI._sequence + 1 : 0;
2415 ACBI._timestamp = (ACBI._timestamp + 960) < 0xFFFFFFFF ? ACBI._timestamp + 960 : 0;
2416 if (ACBI._voiceSession.self_mute) return;
2417 var audioPacket = AudioCB.VoicePacket(buffer, ACBI._voiceSession.ws.ssrc, ACBI._sequence, ACBI._timestamp, ACBI._secretKey);
2418
2419 try {
2420 //It throws a synchronous error if it fails (someone leaves the audio channel while playing audio)
2421 ACBI._vUDP.send(audioPacket, 0, audioPacket.length, ACBI._port, ACBI._address);
2422 } catch(e) { return; }
2423}
2424
2425function handleIncomingAudio(msg) {
2426 //The response from the UDP keep alive ping
2427 if (msg.length === 8 || this._voiceSession.self_deaf) return;
2428
2429 var header = msg.slice(0, 12),
2430 audio = msg.slice(12),
2431 ssrc = header.readUIntBE(8, 4),
2432 member = this._voiceSession.translator[ssrc],
2433 decrypted, decoded;
2434
2435 this._decodeNonce.set(header);
2436
2437 try {
2438 decrypted = new Buffer(
2439 NACL.secretbox.open(
2440 new Uint8Array(audio),
2441 this._decodeNonce,
2442 this._secretKey
2443 )
2444 );
2445
2446 if (member) {
2447 decoded = member.decoder.decode(decrypted);
2448 addToStreamBuffer(member, decoded);
2449 } else {
2450 decoded = this._mixedDecoder.decode(decrypted);
2451 }
2452
2453 addToStreamBuffer(this, decoded);
2454 this.emit('incoming', ssrc, decoded );
2455 } catch(e) {}
2456}
2457function addToStreamBuffer(RStream, data) {
2458 return RStream.push(new Buffer(data)) || !!RStream.read(data.length);
2459}
2460
2461function chooseAudioEncoder(players) {
2462 if (!players[0]) return null;
2463 var s = ChildProc.spawnSync(players.shift());
2464 return s.error ? chooseAudioEncoder(players) : s.file;
2465}
2466function createAudioEncoder(ACBI, encoder) {
2467 var enc = ACBI._systemEncoder;
2468 if (enc) {
2469 enc.stdout.emit('end');
2470 enc.kill();
2471 ACBI._systemEncoder = null;
2472 }
2473
2474 enc = ACBI._systemEncoder = ChildProc.spawn(encoder, [
2475 '-hide_banner',
2476 '-loglevel', 'error',
2477 '-i', 'pipe:0',
2478 '-map', '0:a',
2479 '-acodec', 'libopus',
2480 '-f', 'data',
2481 '-sample_fmt', 's16',
2482 '-vbr', 'off',
2483 '-compression_level', '10',
2484 '-ar', '48000',
2485 '-ac', ACBI.audioChannels,
2486 '-b:a', '128000',
2487 'pipe:1'
2488 ], {stdio: ['pipe', 'pipe', 'pipe']});
2489
2490 enc.stderr.once('data', function(d) {
2491 if (ACBI.listeners('error').length > 0) ACBI.emit('error', d.toString());
2492 });
2493
2494 enc.stdin.once('error', function(e) {
2495 enc.stdout.emit('end');
2496 enc.kill();
2497 });
2498
2499 enc.stdout.once('error', function(e) {
2500 enc.stdout.emit('end');
2501 enc.kill();
2502 });
2503 enc.stdout.once('end', function() {
2504 createAudioEncoder(ACBI, encoder);
2505 ACBI.emit('done');
2506 });
2507 enc.stdout.on('readable', function() {
2508 if (ACBI._readable) return;
2509
2510 ACBI._readable = true;
2511 send(ACBI._voiceSession.ws.connection, Payloads.VOICE_SPEAK(1));
2512 ACBI._startTime = new Date().getTime();
2513 prepareAudio(ACBI, enc.stdout, 1);
2514 });
2515}
2516
2517/* - DiscordClient - Classes - */
2518function Resource() {}
2519Object.defineProperty(Resource.prototype, "creationTime", {
2520 get: function() { return (+this.id / 4194304) + 1420070400000; },
2521 set: function() {}
2522});
2523[Server, Channel, DMChannel, User, Member, Role].forEach(function(p) {
2524 p.prototype = Object.create(Resource.prototype);
2525 Object.defineProperty(p.prototype, 'constructor', {value: p, enumerable: false});
2526});
2527
2528function Server(client, data) {
2529 var server = this;
2530 //Accept everything now and trim what we don't need, manually. Any data left in is fine, any data left out could lead to a broken lib.
2531 copyKeys(data, this);
2532 this.large = this.large || this.member_count > LARGE_THRESHOLD;
2533 this.voiceSession = null;
2534 this.self_mute = !!this.self_mute;
2535 this.self_deaf = !!this.self_deaf;
2536 if (data.unavailable) return;
2537
2538 //Objects so we can use direct property accessing without for loops
2539 this.channels = {};
2540 this.members = {};
2541 this.roles = {};
2542 this.emojis = {};
2543
2544 //Copy the data into the objects using IDs as keys
2545 data.channels.forEach(function(channel) {
2546 client.channels[channel.id] = new Channel(client, server, channel);
2547 });
2548 data.roles.forEach(function(role) {
2549 server.roles[role.id] = new Role(role);
2550 });
2551 data.members.forEach(function(member) {
2552 client.users[member.user.id] = new User(member.user);
2553 server.members[member.user.id] = new Member(client, server, member);
2554 });
2555 data.presences.forEach(function(presence) {
2556 var id = presence.user.id;
2557 if (!client.users[id] || !server.members[id]) return;
2558 delete(presence.user);
2559
2560 client.users[id].game = presence.game;
2561 server.members[id].status = presence.status;
2562 });
2563 data.emojis.forEach(function(emoji) {
2564 server.emojis[emoji.id] = new Emoji(emoji);
2565 });
2566 data.voice_states.forEach(function(vs) {
2567 var cID = vs.channel_id;
2568 var uID = vs.user_id;
2569 if (!server.channels[cID] || !server.members[uID]) return;
2570 server.channels[cID].members[uID] = vs;
2571 server.members[uID].voice_channel_id = cID;
2572 });
2573
2574 //Now we can get rid of any of the things we don't need anymore
2575 delete(this.voice_states);
2576 delete(this.presences);
2577}
2578function Channel(client, server, data) {
2579 var channel = this;
2580 this.members = {};
2581 this.permissions = { user: {}, role: {} };
2582 this.guild_id = server.id;
2583 copyKeys(data, this, ['permission_overwrites', 'emojis']);
2584 Object.defineProperty(server.channels, channel.id, {
2585 get: function() { return client.channels[channel.id]; },
2586 set: function(v) { client.channels[channel.id] = v; },
2587 enumerable: true,
2588 configurable: true
2589 });
2590 data.permission_overwrites.forEach(function(p) {
2591 var type = (p.type === 'member' ? 'user' : 'role');
2592 this.permissions[type][p.id] = {allow: p.allow, deny: p.deny};
2593 }, this);
2594
2595
2596 delete(this.is_private);
2597}
2598function DMChannel(translator, data) {
2599 copyKeys(data, this);
2600 translator[data.recipient.id] = data.id;
2601 delete(this.is_private);
2602}
2603function User(data) {
2604 copyKeys(data, this);
2605 this.bot = this.bot || false;
2606}
2607function Member(client, server, data) {
2608 copyKeys(data, this, ['user', 'joined_at',]);
2609 this.id = data.user.id;
2610 this.joined_at = Date.parse(data.joined_at);
2611 this.color = colorFromRole(server, this);
2612 ['username', 'discriminator', 'bot', 'avatar', 'game'].forEach(function(k) {
2613 if (k in Member.prototype) return;
2614
2615 Object.defineProperty(Member.prototype, k, {
2616 get: function() { return client.users[this.id][k]; },
2617 set: function(v) { client.users[this.id][k] = v; },
2618 enumerable: true,
2619 });
2620 });
2621}
2622function Role(data) {
2623 copyKeys(data, this, ['permissions']);
2624 //Use `permissions` from Discord, or `_permissions` if we're making it out of a cache.
2625 this._permissions = data._permissions || data.permissions;
2626}
2627function Emoji(data) {
2628 copyKeys(data, this);
2629}
2630
2631function Multipart() {
2632 this.boundary =
2633 "NodeDiscordIO" + "-" + CURRENT_VERSION;
2634 this.result = "";
2635}
2636Multipart.prototype.append = function(data) {
2637 /* Header */
2638 var str = "\r\n--";
2639 str += this.boundary + "\r\n";
2640 str += 'Content-Disposition: form-data; name="' + data[0] + '"';
2641 if (data[2]) {
2642 str += '; filename="' + data[2] + '"\r\n';
2643 str += 'Content-Type: application/octet-stream';
2644 }
2645 /* Body */
2646 str += "\r\n\r\n" + ( data[1] instanceof Buffer ? data[1] : new Buffer(String(data[1]), 'utf-8') ).toString('binary');
2647 this.result += str;
2648};
2649Multipart.prototype.finalize = function() {
2650 this.result += "\r\n--" + this.boundary + "--";
2651};
2652
2653Server.update = function(client, data) {
2654 if (!client.servers[data.id]) client.servers[data.id] = {}; // new Server(client, data)?
2655 for (var key in data) {
2656 if (key === 'roles') {
2657 data[key].forEach(function(r) {
2658 client.servers[data.id].roles[r.id] = new Role(r);
2659 });
2660 continue;
2661 }
2662 if (key === 'emojis') continue;
2663 client.servers[data.id][key] = data[key];
2664 }
2665};
2666Channel.update = function(client, data) {
2667 if (!client.channels[data.id]) client.channels[data.id] = {}; // new Channel(client, data)?
2668 for (var key in data) {
2669 if (key === 'permission_overwrites') {
2670 data[key].forEach(function(p) {
2671 var type = (p.type === 'member' ? 'user' : 'role');
2672 client.channels[data.id].permissions[type][p.id] = {
2673 allow: p.allow,
2674 deny: p.deny
2675 };
2676 });
2677 continue;
2678 }
2679 client.channels[data.id][key] = data[key];
2680 }
2681 delete(client.channels[data.id].is_private);
2682};
2683Member.update = function(client, server, data) {
2684 if (!server.members[data.user.id]) return server.members[data.user.id] = new Member(client, server, data);
2685 var member = server.members[data.user.id];
2686 copyKeys(data, member, ['user']);
2687 member.color = colorFromRole(server, member);
2688};
2689Role.update = function(server, data) {
2690 if (!server.roles[data.role.id]) server.roles[data.role.id] = {}; // new Role(data)?
2691 server.roles[data.role.id]._permissions = data.role.permissions;
2692 copyKeys(data.role, server.roles[data.role.id], ['permissions']);
2693};
2694Emoji.update = function(server, data) {
2695 server.emojis = {};
2696 data.forEach(function(emoji) {
2697 server.emojis[emoji.id] = new Emoji(emoji);
2698 });
2699}
2700
2701Object.defineProperty(Role.prototype, "permission_values", {
2702 get: function() { return this; },
2703 set: function() {},
2704 enumerable: true
2705});
2706Object.defineProperty(User.prototype, 'avatarURL', {
2707 get: function() {
2708 if (!this.avatar) return null;
2709 var animated = this.avatar.indexOf("a_") > -1;
2710 return Discord.Endpoints.CDN + "/avatars/" + this.id + "/" + this.avatar + (animated ? ".gif" : ".webp");
2711 },
2712 set: function() {}
2713});
2714
2715//Discord.OAuth;
2716Discord.version = CURRENT_VERSION;
2717Discord.Emitter = Emitter;
2718Discord.Codes = {};
2719Discord.Codes.WebSocket = {
2720 "0" : "Gateway Error",
2721 "4000": "Unknown Error",
2722 "4001": "Unknown Opcode",
2723 "4002": "Decode Error",
2724 "4003": "Not Authenticated",
2725 "4004": "Authentication Failed",
2726 "4005": "Already Authenticated",
2727 "4006": "Session Not Valid",
2728 "4007": "Invalid Sequence Number",
2729 "4008": "Rate Limited",
2730 "4009": "Session Timeout",
2731 "4010": "Invalid Shard"
2732};
2733Discord.Colors = {
2734 DEFAULT: 0,
2735 AQUA: 1752220,
2736 GREEN: 3066993,
2737 BLUE: 3447003,
2738 PURPLE: 10181046,
2739 GOLD: 15844367,
2740 ORANGE: 15105570,
2741 RED: 15158332,
2742 GREY: 9807270,
2743 DARKER_GREY: 8359053,
2744 NAVY: 3426654,
2745 DARK_AQUA: 1146986,
2746 DARK_GREEN: 2067276,
2747 DARK_BLUE: 2123412,
2748 DARK_PURPLE: 7419530,
2749 DARK_GOLD: 12745742,
2750 DARK_ORANGE: 11027200,
2751 DARK_RED: 10038562,
2752 DARK_GREY: 9936031,
2753 LIGHT_GREY: 12370112,
2754 DARK_NAVY: 2899536
2755};
2756Discord.Permissions = {
2757 GENERAL_CREATE_INSTANT_INVITE: 0,
2758 GENERAL_KICK_MEMBERS: 1,
2759 GENERAL_BAN_MEMBERS: 2,
2760 GENERAL_ADMINISTRATOR: 3,
2761 GENERAL_MANAGE_CHANNELS: 4,
2762 GENERAL_MANAGE_GUILD: 5,
2763 GENERAL_MANAGE_ROLES: 28,
2764 GENERAL_MANAGE_NICKNAMES: 27,
2765 GENERAL_CHANGE_NICKNAME: 26,
2766 GENERAL_MANAGE_WEBHOOKS: 29,
2767 GENERAL_MANAGE_EMOJIS: 30,
2768
2769 TEXT_ADD_REACTIONS: 6,
2770 TEXT_READ_MESSAGES: 10,
2771 TEXT_SEND_MESSAGES: 11,
2772 TEXT_SEND_TTS_MESSAGE: 12,
2773 TEXT_MANAGE_MESSAGES: 13,
2774 TEXT_EMBED_LINKS: 14,
2775 TEXT_ATTACH_FILES: 15,
2776 TEXT_READ_MESSAGE_HISTORY: 16,
2777 TEXT_MENTION_EVERYONE: 17,
2778 TEXT_EXTERNAL_EMOJIS: 18,
2779
2780 VOICE_CONNECT: 20,
2781 VOICE_SPEAK: 21,
2782 VOICE_MUTE_MEMBERS: 22,
2783 VOICE_DEAFEN_MEMBERS: 23,
2784 VOICE_MOVE_MEMBERS: 24,
2785 VOICE_USE_VAD: 25,
2786};
2787Discord.LogLevels = {
2788 Verbose: 0,
2789 Info: 1,
2790 Warn: 2,
2791 Error: 3
2792};
2793
2794Object.keys(Discord.Permissions).forEach(function(pn) {
2795 Object.defineProperty(Role.prototype, pn, {
2796 get: getPerm( Discord.Permissions[pn] ),
2797 set: setPerm( Discord.Permissions[pn] ),
2798 enumerable: true
2799 });
2800});
2801
2802/* Wrappers */
2803function Emitter() {
2804 var emt = this;
2805 if (isNode) {
2806 EE.call(this);
2807 if (this.prototype) return Util.inherits(this, EE);
2808 return new EE();
2809 }
2810 //Thank you, http://stackoverflow.com/a/24216547
2811 function _Emitter() {
2812 var eventTarget = document.createDocumentFragment();
2813 ["addEventListener", "dispatchEvent", "removeEventListener"].forEach(function(method) {
2814 if (!this[method]) this[method] = eventTarget[method].bind(eventTarget);
2815 }, this);
2816 }
2817 //But I did the rest myself! D:<
2818 _Emitter.call(this);
2819 this._evts = {};
2820 this.on = function(eName, eFunc) {
2821 if (!emt._evts[eName]) emt._evts[eName] = [];
2822 emt._evts[eName].push(eOn);
2823
2824 return this.addEventListener(eName, eOn);
2825
2826 function eOn(e) {
2827 return eFunc.apply(null, resolveEvent(e));
2828 }
2829 };
2830 this.once = function(eName, eFunc) {
2831 if (!emt._evts[eName]) emt._evts[eName] = [];
2832 emt._evts[eName].push(eOnce);
2833
2834 return this.addEventListener(eName, eOnce);
2835
2836 function eOnce(e) {
2837 eFunc.apply(null, resolveEvent(e));
2838 return emt.removeListener(eName, eOnce);
2839 }
2840 };
2841 this.removeListener = function(eName, eFunc) {
2842 if (emt._evts[eName]) emt._evts[eName].splice(emt._evts[eName].lastIndexOf(eFunc), 1);
2843 return this.removeEventListener(eName, eFunc);
2844 };
2845 this.emit = function(eName) {
2846 return this.dispatchEvent( new CustomEvent(eName, {'detail': Array.prototype.slice.call(arguments, 1) }) );
2847 };
2848 return this;
2849}
2850
2851function Websocket(url, opts) {
2852 if (isNode) return new (require('ws'))(url, opts);
2853 return Emitter.call(new WebSocket(url));
2854}
2855
2856/* Endpoints */
2857(function () {
2858 var API = "https://discordapp.com/api";
2859 var CDN = "http://cdn.discordapp.com";
2860 var ME = API + "/users/@me";
2861 Endpoints = Discord.Endpoints = {
2862 API: API,
2863 CDN: CDN,
2864
2865 ME: ME,
2866 NOTE: function(userID) {
2867 return ME + "/notes/" + userID;
2868 },
2869 LOGIN: API + "/auth/login",
2870 OAUTH: API + "/oauth2/applications/@me",
2871 GATEWAY: API + "/gateway",
2872 SETTINGS: ME + "/settings",
2873
2874 SERVERS: function(serverID) {
2875 return API + "/guilds" + (serverID ? "/" + serverID : "");
2876 },
2877 SERVERS_PERSONAL: function(serverID) {
2878 return this.ME + "/guilds" + (serverID ? "/" + serverID : ""); //Method to list personal servers?
2879 },
2880 SERVER_EMOJIS: function(serverID, emojiID) {
2881 return this.SERVERS(serverID) + "/emojis" + (emojiID ? "/" + emojiID : "");
2882 },
2883
2884 CHANNEL: function(channelID) {
2885 return API + "/channels/" + channelID;
2886 },
2887
2888 MEMBERS: function(serverID, userID) {
2889 return this.SERVERS(serverID) + "/members" + (userID ? "/" + userID : "");
2890 },
2891 MEMBER_ROLES: function(serverID, userID, roleID) {
2892 return this.MEMBERS(serverID, userID) + "/roles" + (roleID ? "/" + roleID : "");
2893 },
2894
2895 USER: function(userID) {
2896 return API + "/users/" + userID;
2897 },
2898
2899 ROLES: function(serverID, roleID) {
2900 return this.SERVERS(serverID) + "/roles" + (roleID ? "/" + roleID : "");
2901 },
2902
2903 BANS: function(serverID, userID) {
2904 return this.SERVERS(serverID) + "/bans" + (userID ? "/" + userID : "");
2905 },
2906
2907 MESSAGES: function(channelID, messageID) {
2908 return this.CHANNEL(channelID) + "/messages" + (messageID ? "/" + messageID : "");
2909 },
2910 PINNED_MESSAGES: function(channelID, messageID) {
2911 return this.CHANNEL(channelID) + "/pins" + (messageID ? "/" + messageID : "");
2912 },
2913
2914 MESSAGE_REACTIONS: function(channelID, messageID, reaction) {
2915 return this.MESSAGES(channelID, messageID) + "/reactions" + ( reaction ? ("/" + reaction) : "" );
2916 },
2917 USER_REACTIONS: function(channelID, messageID, reaction, userID) {
2918 return this.MESSAGE_REACTIONS(channelID, messageID, reaction) + '/' + ( (!userID || userID === this.id) ? '@me' : userID );
2919 },
2920
2921 INVITES: function(inviteCode) {
2922 return API + "/invite/" + inviteCode;
2923 },
2924
2925 SERVER_WEBHOOKS: function(serverID) {
2926 return this.SERVERS(serverID) + "/webhooks";
2927 },
2928 CHANNEL_WEBHOOKS: function(channelID) {
2929 return this.CHANNEL(channelID) +"/webhooks";
2930 },
2931
2932 WEBHOOKS: function(webhookID) {
2933 return API + "/webhooks/" + webhookID;
2934 },
2935
2936 BULK_DELETE: function(channelID) {
2937 return this.CHANNEL(channelID) + "/messages/bulk-delete";
2938 },
2939
2940 TYPING: function(channelID) {
2941 return this.CHANNEL(channelID) + "/typing";
2942 }
2943
2944 };
2945})();
2946
2947/* Payloads */
2948(function() {
2949 Payloads = {
2950 IDENTIFY: function(client) {
2951 return {
2952 op: 2,
2953 d: {
2954 token: client.internals.token,
2955 v: GATEWAY_VERSION,
2956 compress: isNode && !!Zlib.inflateSync,
2957 large_threshold: LARGE_THRESHOLD,
2958 properties: {
2959 $os: isNode ? require('os').platform() : navigator.platform,
2960 $browser:"discord.io",
2961 $device:"discord.io",
2962 $referrer:"",
2963 $referring_domain:""
2964 }
2965 }
2966 };
2967 },
2968 RESUME: function(client) {
2969 return {
2970 op: 6,
2971 d: {
2972 seq: client.internals.s,
2973 token: client.internals.token,
2974 session_id: client.internals.sessionID
2975 }
2976 };
2977 },
2978 HEARTBEAT: function(client) {
2979 return {op: 1, d: client.internals.sequence};
2980 },
2981 ALL_USERS: function(client) {
2982 return {op: 12, d: Object.keys(client.servers)};
2983 },
2984 STATUS: function(input) {
2985 return {
2986 op: 3,
2987 d: {
2988 status: type(input.idle_since) === 'number' ? 'idle' : input.status !== undefined ? input.status : null,
2989 afk: !!input.afk,
2990 since: type(input.idle_since) === 'number' || input.status === 'idle' ? Date.now() : null,
2991 game: type(input.game) === 'object' ?
2992 {
2993 name: input.game.name ? String(input.game.name) : null,
2994 type: input.game.type ? Number(input.game.type) : 0,
2995 url: input.game.url ? String(input.game.url) : null
2996 } :
2997 null
2998 }
2999 };
3000 },
3001 UPDATE_VOICE: function(serverID, channelID, self_mute, self_deaf) {
3002 return {
3003 op: 4,
3004 d: {
3005 guild_id: serverID,
3006 channel_id: channelID,
3007 self_mute: self_mute,
3008 self_deaf: self_deaf
3009 }
3010 };
3011 },
3012 OFFLINE_USERS: function(array) {
3013 return {
3014 op: 8,
3015 d: {
3016 guild_id: array.splice(0, 50),
3017 query: "",
3018 limit: 0
3019 }
3020 };
3021 },
3022 VOICE_SPEAK: function(v) {
3023 return {op:5, d:{ speaking: !!v, delay: 0 }};
3024 },
3025 VOICE_IDENTIFY: function(clientID, voiceSession) {
3026 return {
3027 op: 0,
3028 d: {
3029 server_id: voiceSession.serverID,
3030 user_id: clientID,
3031 session_id: voiceSession.session,
3032 token: voiceSession.token
3033 }
3034 };
3035 },
3036 VOICE_DISCOVERY: function(ip, port, mode) {
3037 return {
3038 op:1,
3039 d:{
3040 protocol:"udp",
3041 data:{
3042 address: ip,
3043 port: Number(port),
3044 mode: mode
3045 }
3046 }
3047 };
3048 }
3049 }
3050})();
3051
3052})(typeof exports === 'undefined'? this.Discord = {} : exports);