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