diff --git a/hydrabot/commands.js b/hydrabot/commands.js index ee4ecddc8..838c9c386 100644 --- a/hydrabot/commands.js +++ b/hydrabot/commands.js @@ -379,18 +379,13 @@ Commands[ "activity" ] = { var report = "here's a list of activity over the last " + count + " messages :\n\n"; - var users = {}; - - for ( id in activity ) { - users[ id ] = message.channel.server.members.filter( "id", id, true ); + var usernames = {}; + for( id in activity ){ + usernames[id] = bot.getUser(id).username; } - activity = Object.keys( activity ).sort( function( a, b ) { - return activity[ a ] - activity[ b ] - } ); - for ( id in activity ) { - report += id + " | " + activity[ id ] + " | **" + Math.round( ( activity[ id ] / count ) * 100 ) + "%**.\n"; + report += usernames[id] + " | " + activity[ id ] + " | **" + Math.round( ( activity[ id ] / count ) * 100 ) + "%**.\n"; } bot.reply( message, report, false, false ); diff --git a/index.js b/index.js index 5b15ee6f1..e3c5d8ec5 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,13 @@ var PMChannel = require( "./lib/PMChannel.js" ).PMChannel; var WebSocket = require( 'ws' ); var Internal = require( "./lib/internal.js" ).Internal; +/** + * The wrapper module for the Discord Client, also provides some helpful objects. + * + * @module Discord + */ +exports; + exports.Endpoints = Endpoints; exports.Server = Server; exports.Message = Message; @@ -19,55 +26,156 @@ exports.List = List; exports.Invite = Invite; exports.PMChannel = PMChannel; +/** + * The Discord Client used to interface with the Discord API. + * @class Client + * @constructor + * @param {Object} options An object containing configurable options. + * @param {Number} [options.maxmessage=5000] The maximum amount of messages to be stored per channel. + */ exports.Client = function( options ) { + /** + * Contains the options of the client + * @attribute options + * @type {Object} + */ this.options = options || {}; this.options.maxmessage = 5000; + /** + * Contains the token used to authorise HTTP requests and WebSocket connection. Received when login was successful. + * @attribute token + * @readonly + * @type {String} + */ this.token = ""; + /** + * Indicates whether the client is logged in or not. Does not indicate whether the client is ready. + * @attribute loggedIn + * @readonly + * @type {Boolean} + */ this.loggedIn = false; + /** + * The WebSocket used when receiving message and other event updates. + * @type {WebSocket} + * @attribute websocket + * @readonly + */ this.websocket = null; + /** + * An Object containing the functions tied to events. These should be set using Client.on(); + * @type {Object} + * @attribute events + */ this.events = {}; + /** + * The User of the Client class, set when the initial startup is complete. + * @attribute user + * @type {User} + * @readonly + */ this.user = null; + /** + * Indicates whether the Client is ready and has cached all servers it as aware of. + * @type {Boolean} + * @attribute ready + * @readonly + */ this.ready = false; + /** + * A List containing all the Servers the Client has access to. + * @attribute serverList + * @type {List} + * @readonly + */ this.serverList = new List( "id" ); + /** + * A List containing all the PMChannels the Client has access to. + * @attribute PMList + * @type {List} + * @readonly + */ this.PMList = new List( "id" ); } +/** + * Returns a list of all servers that the Discord Client has access to. + * + * @method getServers + * @return {List} ServerList + */ exports.Client.prototype.getServers = function() { return this.serverList; } +/** + * Returns a list of all servers that the Discord Client has access to. + * @method getChannels + * @return {List} Channelist + */ exports.Client.prototype.getChannels = function() { return this.serverList.concatSublists( "channels", "id" ); } +/** + * Returns a Server matching the given id, or false if not found. Will return false if the server is not cached or not available. + * @method getServer + * @param {String/Number} id The ID of the Server + * @return {Server} The Server matching the ID + */ exports.Client.prototype.getServer = function( id ) { return this.getServers().filter( "id", id, true ); } +/** + * Returns a Channel matching the given id, or false if not found. Will return false if the Channel is not cached or not available. + * @method getChannel + * @param {String/Number} id The ID of the Channel + * @return {Server} The Channel matching the ID + */ exports.Client.prototype.getChannel = function( id ) { return this.getChannels().filter( "id", id, true ); } +/** + * Triggers an .on() event. + * @param {String} event The event to be triggered + * @param {Array} args The arguments to be passed onto the method + * @return {Boolean} whether the event was triggered successfully. + * @private + */ exports.Client.prototype.triggerEvent = function( event, args ) { if ( !this.ready && event !== "raw" && event !== "disconnected" && event !== "debug" ) { //if we're not even loaded yet, don't try doing anything because it always ends badly! - return; + return false; } if ( this.events[ event ] ) { this.events[ event ].apply( this, args ); + return true; } else { return false; } } +/** + * Binds a function to an event + * @param {String} name The event name which the function should be bound to. + * @param {Function} fn The function that should be bound to the event. + * @method on + */ exports.Client.prototype.on = function( name, fn ) { this.events[ name ] = fn; } +/** + * Unbinds a function from an event + * @param {String} name The event name which should be cleared + * @method off + */ exports.Client.prototype.off = function( name ) { this.events[ name ] = function() {}; } @@ -132,15 +240,29 @@ exports.Client.prototype.cacheServer = function( id, cb, members ) { } -exports.Client.prototype.login = function( email, password ) { +/** + * Logs the Client in with the specified credentials and begins initialising it. + * @async + * @method login + * @param {String} email The Discord email. + * @param {String} password The Discord password. + * @param {Function} [callback] Called when received reply from authentication server. + * @param {Object} callback.error Set to null if there was no error logging in, otherwise is an Object that + * can be evaluated as true. + * @param {String} callback.error.reason The reason why there was an error + * @param {Object} callback.error.error The raw XHR error. + * @param {String} callback.token The token received when logging in + */ +exports.Client.prototype.login = function( email, password, callback ) { var self = this; + callback = callback || function() {}; self.connectWebsocket(); var time = Date.now(); Internal.XHR.login( email, password, function( err, token ) { - console.log(Date.now() - time); + console.log( Date.now() - time ); if ( err ) { self.triggerEvent( "disconnected", [ { reason: "failed to log in", @@ -151,6 +273,7 @@ exports.Client.prototype.login = function( email, password ) { self.token = token; self.websocket.sendData(); self.loggedIn = true; + callback( null, token ); } } ); @@ -343,11 +466,11 @@ exports.Client.prototype.connectWebsocket = function( cb ) { } this.websocket.onopen = function() { - this.sendData("onopen"); + this.sendData( "onopen" ); } - this.websocket.sendData = function(why){ - if(this.readyState == 1 && !sentInitData && self.token){ + this.websocket.sendData = function( why ) { + if ( this.readyState == 1 && !sentInitData && self.token ) { sentInitData = true; var connDat = { op: 2, @@ -365,7 +488,7 @@ exports.Client.prototype.connectWebsocket = function( cb ) { exports.Client.prototype.logout = function( callback ) { - callback = callback || function(){}; + callback = callback || function() {}; var self = this; @@ -580,18 +703,18 @@ exports.Client.prototype.deleteMessage = function( message, callback ) { Internal.XHR.deleteMessage( self.token, message.channel.id, message.id, callback ); } -exports.Client.prototype.updateMessage = function(oldMessage, newContent, callback, options){ +exports.Client.prototype.updateMessage = function( oldMessage, newContent, callback, options ) { var self = this; var channel = oldMessage.channel; options = options || {}; - Internal.XHR.updateMessage(self.token, channel.id, oldMessage.id, { - content : newContent, - mentions : [] - }, function(err, data){ - if(err){ - callback(err); + Internal.XHR.updateMessage( self.token, channel.id, oldMessage.id, { + content: newContent, + mentions: [] + }, function( err, data ) { + if ( err ) { + callback( err ); return; } var msg = new Message( data, self.getChannel( data.channel_id ) ); @@ -601,7 +724,7 @@ exports.Client.prototype.updateMessage = function(oldMessage, newContent, callba }, options.selfDestruct ); } callback( null, msg ); - }); + } ); } @@ -667,13 +790,6 @@ exports.Client.prototype.deleteServer = function( server, callback ) { } -/** - UTILS - UTILS - UTILS - did I mention UTILS? - */ - exports.Client.prototype.getServers = function() { return this.serverList; } diff --git a/lib/list.js b/lib/list.js index 5de3d955d..7d50db95e 100644 --- a/lib/list.js +++ b/lib/list.js @@ -1,9 +1,39 @@ +/** + * Similar to a Java set. Contains no duplicate elements and includes filter + * functions. Discriminates between elements based on a discriminator passed + * when created. Generally "ID" + * @class List + */ exports.List = function( discriminator, cap ) { + /** + * What to use to distringuish duplicates + * @attribute discriminator + * @type {String} + */ this.discriminator = discriminator; + /** + * The maximum amount of elements allowed in the list. + * @default Infinity + * @attribute cap + * @type {Number} + */ this.cap = cap || Number.MAX_SAFE_INTEGER; + /** + * The Array version of the List. + * @type {Array} + * @attribute contents + */ this.contents = []; } +/** + * Adds an element to the list if it isn't already there. + * @method add + * @param {Object/Array} element The element(s) to add + * @example + * List.add( obj ); + * List.add( [ obj, obj, obj ] ); + */ exports.List.prototype.add = function( child ) { var self = this; @@ -12,17 +42,17 @@ exports.List.prototype.add = function( child ) { children = child; for ( child of children ) { - addChild(child); + addChild( child ); } } else { - addChild(child); + addChild( child ); } - function addChild(child){ + function addChild( child ) { - if(self.length() > self.cap){ - self.splice(0, 1); + if ( self.length() > self.cap ) { + self.splice( 0, 1 ); } if ( self.filter( self.discriminator, child[ self.discriminator ] ).length === 0 ) @@ -30,17 +60,28 @@ exports.List.prototype.add = function( child ) { } } +/** + * Returns the length of the List + * @method length + * @return {Number} + */ exports.List.prototype.length = function() { return this.contents.length; } -exports.List.prototype.getIndex = function( object ){ +/** + * Gets the index of an element in the List or defaults to false + * @param {Object} object The element we want to get the index of + * @return {Number/Boolean} The index if the object is in the list, or false. + * @method getIndex + */ +exports.List.prototype.getIndex = function( object ) { var index = false; - for(elementIndex in this.contents){ - var element = this.contents[elementIndex]; - if( element[this.discriminator] == object[this.discriminator] ){ + for ( elementIndex in this.contents ) { + var element = this.contents[ elementIndex ]; + if ( element[ this.discriminator ] == object[ this.discriminator ] ) { return elementIndex; } @@ -50,28 +91,46 @@ exports.List.prototype.getIndex = function( object ){ } +/** + * Removes an element at the specified index + * @param {Number} index + * @method removeIndex + */ exports.List.prototype.removeIndex = function( index ) { this.contents.splice( index, 1 ); } +/** + * Removes an element from the list + * @param {Object} element + * @method removeElement + * @return {Boolean} whether the operation was successful or not. + */ exports.List.prototype.removeElement = function( child ) { for ( _element in this.contents ) { - var element = this.contents[_element]; + var element = this.contents[ _element ]; if ( child[ this.discriminator ] == element[ this.discriminator ] ) { this.removeIndex( _element, 1 ); + return true; } } return false; } -exports.List.prototype.updateElement = function( child, newChild ){ +/** + * Replaces an element in the list with a specified element + * @param {Object} element Element to update. + * @param {Object} newElement New Element + * @return {Boolean} whether the operation was successful or not. + */ +exports.List.prototype.updateElement = function( child, newChild ) { for ( _element in this.contents ) { - var element = this.contents[_element]; + var element = this.contents[ _element ]; if ( child[ this.discriminator ] == element[ this.discriminator ] ) { - this.contents[_element] = newChild; + this.contents[ _element ] = newChild; return true; } } diff --git a/lib/server.js b/lib/server.js index 5f0c231f5..964993e17 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,15 +1,66 @@ var User = require( "./user.js" ).User; var List = require( "./list.js" ).List; +/** + * A wrapper for Server information, contains channels and users too. Developers should not instantiate the class, instead they should + * manipulate Server objects given to them. + * @class Server + * @param {String} region The region of the server + */ exports.Server = function( region, ownerID, name, id, members, icon, afkTimeout, afkChannelId ) { + /** + * The region of the Server + * @type {String} + * @attribute region + */ this.region = region; + /** + * The ID of the owner of the Server (not a User!) + * @type {String} + * @attribute ownerID + */ this.ownerID = ownerID; + /** + * The name of the Server + * @type {String} + * @attribute name + */ this.name = name; + /** + * The ID of the Server + * @type {String} + * @attribute id + */ this.id = id; + /** + * List containing members of the Server + * @param {List} + * @attribute members + */ this.members = new List( "id" ); + /** + * List containing channelss of the Server + * @param {List} + * @attribute channels + */ this.channels = new List( "id" ); + /** + * ID of the Icon of the Server + * @param {String} + * @attribute icon + */ this.icon = icon; + /** + * The amount of seconds that should pass before the user is + * @type {Number} + * @attribute afkTimeout + */ this.afkTimeout = afkTimeout; + /** + * The ID of the AFK Channel, evaluates to false if doesn't exist. + * @type {String} + * @attribute afkChannelid + */ this.afkChannelId = afkChannelId; for ( x in members ) { @@ -18,12 +69,22 @@ exports.Server = function( region, ownerID, name, id, members, icon, afkTimeout, } } +/** + * Returns a valid URL pointing towards the server's icon if it has one. + * @method getIconURL + * @return {String/Boolean} If there is an icon, a URL is returned. If not, false is returned. + */ exports.Server.prototype.getIconURL = function(){ if(!this.icon) return false; return "https://discordapp.com/api/guilds/"+this.id+"/icons/"+this.icon+".jpg"; } +/** + * Returns the AFK Channel if a server has one + * @method getAFKChannel + * @return {Channel/Boolean} If there is an AFK Channel, a Channel is returned. If not, false is returned. + */ exports.Server.prototype.getAFKChannel = function(){ if(!this.afkChannelId) @@ -33,7 +94,11 @@ exports.Server.prototype.getAFKChannel = function(){ } - +/** + * Returns the #general channel of the server. + * @method getDefaultChannel + * @return {Channel} Returns the #general channel of the Server. + */ exports.Server.prototype.getDefaultChannel = function() { return this.channels.filter( "name", "general", true );