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