diff --git a/modules/event/README.md b/modules/event/README.md new file mode 100644 index 0000000..7474288 --- /dev/null +++ b/modules/event/README.md @@ -0,0 +1,30 @@ +## Event + +Emit events for whatever you want man idk. + +### Description + +This is a library module designed for other modules to use to emit various +events at any point, and also to attach functions to said events. These are +similar to command hooks, however their advantage is that they may be called +anywhere in your code; they are particularly useful when you want to attach a +callback to a listener. + +### API + +#### addHook(eventName, callback) +This function will set a given callback to be executed every time the +emit API function is executed with the given event name. The arguments of your +callback are defined as an array in the emit call. + +The best place to add hooks to commands is in the 'onLoad' function of your +module, as this ensures it will be run while all other modules are loaded so +nothing will be missed. + +#### emit(eventName, [ arguments ]) +This function executes all of the functions associated with the given eventName, +passing your given array of arguments. + +For example, to emit an event when you detect a nick change: + + dbot.api.event.emit('nick_changed', [ event.server, newNick ]); diff --git a/modules/event/event.js b/modules/event/event.js new file mode 100644 index 0000000..83fb822 --- /dev/null +++ b/modules/event/event.js @@ -0,0 +1,28 @@ +/** + * Module Name: event + * Description: Allow other modules to emit events and that + */ +var _ = require('underscore')._; + +var event = function(dbot) { + this.dbot = dbot; + this.hooks = {}; + this.api = { + 'addHook': function(eventName, callback) { + if(!_.has(this.hooks, eventName)) this.hooks[eventName] = []; + this.hooks[eventName].push(callback); + }, + + 'emit': function(eventName, args) { + if(_.has(this.hooks, eventName)) { + _.each(this.hooks[eventName], function(callback) { + callback.apply(callback.module, args); + }); + } + } + }; +} + +exports.fetch = function(dbot) { + return new event(dbot); +}; diff --git a/modules/link/link.js b/modules/link/link.js index 0efbd02..dfe5f70 100644 --- a/modules/link/link.js +++ b/modules/link/link.js @@ -34,17 +34,19 @@ var link = function(dbot) { }, '~ud': function(event) { - var reqUrl = 'http://api.urbandictionary.com/v0/define?term=' + event.params[1]; + var query = event.input[1]; + var reqUrl = 'http://api.urbandictionary.com/v0/define?term=' + encodeURI(query); request(reqUrl, function(error, response, body) { var result = JSON.parse(body); if(_.has(result, 'result_type') && result.result_type != 'no_results') { - event.reply(event.params[1] + ': ' + result.list[0].definition); + event.reply(query + ': ' + result.list[0].definition.split('\n')[0]); } else { event.reply(event.user + ': No definition found.'); } }); } }; + commands['~ud'].regex = [/~ud (.+)/, 2]; this.commands = commands; this.listener = function(event) { diff --git a/modules/stats b/modules/stats index 616cc9f..33886df 160000 --- a/modules/stats +++ b/modules/stats @@ -1 +1 @@ -Subproject commit 616cc9f8f23afa6941c1c130626aeb851716c5aa +Subproject commit 33886df41d84c160270b581f072e710e4c9d00e8 diff --git a/modules/users/README.md b/modules/users/README.md index 5bc713e..0a11e5b 100644 --- a/modules/users/README.md +++ b/modules/users/README.md @@ -46,3 +46,11 @@ Return a list of aliases for a given primary user. #### isOnline(server, user, channel, useLowerCase) Return whether a user is online in a given channel on the given server. + +### Events + +#### nick_changed(server, newNick) +This is executed when a new alias is added for a user. + +#### new_user(server, nick) +This is executed when a new primary user is added to the known users DB. diff --git a/modules/users/config.json b/modules/users/config.json index 0e947cb..866deb7 100644 --- a/modules/users/config.json +++ b/modules/users/config.json @@ -1,5 +1,6 @@ { "ignorable": false, + "dependencies": [ "command", "event" ], "dbKeys": [ "knownUsers" ], "help": "https://github.com/reality/depressionbot/blob/master/modules/users/README.md" } diff --git a/modules/users/pages.js b/modules/users/pages.js index c3fad15..386f96b 100644 --- a/modules/users/pages.js +++ b/modules/users/pages.js @@ -26,8 +26,15 @@ var pages = function(dbot) { if(connections.hasOwnProperty(connection) && connections[connection].channels.hasOwnProperty(channel)) { + var chanData = dbot.api.stats.getChanStats(connection, channel, ["freq"]); + var chanFreq = []; + for(var i=0; i <= 6; i++){ + for(var j=0; j <= 23; j++){ + chanFreq.push(chanData.fields.freq.raw[i][j]); + } + } + var userData = { "active": [], "inactive": [], "offline": []}; - var reply = dbot.api.stats.getChanUsersStats(connection, channel, [ "lines", "words", "lincent", "wpl", "in_mentions" ]); @@ -60,8 +67,14 @@ var pages = function(dbot) { var userDataSorted = (userData.active.concat(userData.inactive)).concat(userData.offline); - res.render('users', { 'name': dbot.config.name, 'connection': connection, - 'channel': channel, 'nicks': userDataSorted }); + res.render('users', { + 'name': dbot.config.name, + 'connection': connection, + 'channel': channel, + 'userStats': userDataSorted, + 'chanFreq': chanFreq, + 'chanFreqLen': chanFreq.length }); + } else { res.render_core('error', { 'name': dbot.config.name, 'message': 'No such connection or channel.' }); } diff --git a/modules/users/users.js b/modules/users/users.js index d4ad44a..205eb76 100644 --- a/modules/users/users.js +++ b/modules/users/users.js @@ -48,6 +48,7 @@ var users = function(dbot) { nick = this.api.resolveUser(event.server, nick); } else { knownUsers.users.push(nick); + dbot.api.emit('new_user', [ event.server, nick ]); } if(!_.include(channelUsers, nick)) { @@ -57,7 +58,7 @@ var users = function(dbot) { var newNick = event.params.substr(1); if(!this.api.isKnownUser(newNick)) { knownUsers.aliases[newNick] = this.api.resolveUser(event.server, event.user); - if(_.has(dbot.modules, 'stats')) dbot.api.stats.renameStats(event.server, newNick); + dbot.api.event.emit('nick_change', [ event.server, newNick ]); } } }.bind(this); @@ -78,6 +79,7 @@ var users = function(dbot) { nick = this.api.resolveUser(event.server, nick); } else { knownUsers.users.push(nick); + dbot.api.emit('new_user', [ event.server, nick ]); } if(!_.include(channelUsers, nick)) { channelUsers.push(nick); diff --git a/run.js b/run.js index 1807d04..a32c869 100644 --- a/run.js +++ b/run.js @@ -5,32 +5,36 @@ var fs = require('fs'), require('./snippets'); var DBot = function(timers) { - // Load DB - var rawDB; - try { - var rawDB = fs.readFileSync('db.json', 'utf-8'); - } catch(err) { - this.db = {}; // If no db file, make empty one + + /*** Load the DB ***/ + + if(fs.existsSync('db.json')) { + try { + this.db = JSON.parse(fs.readFileSync('db.json', 'utf-8')); + } catch(err) { + console.log('Error loading db.json. Stopping: ' + err); + process.exit(); + } + } else { + this.db = {}; } - try { - if(!this.db) { // If it wasn't empty - this.db = JSON.parse(rawDB); - } - if(!_.has(this.db, 'config')) { - this.db.config = {}; - } - } catch(err) { - console.log('Syntax error in db.json. Stopping: ' + err); + if(!_.has(this.db, 'config')) { + this.db.config = {}; + } + + /*** Load the Config ***/ + + if(!fs.existsSync('config.json')) { + console.log('Error: config.json file does not exist. Stopping'); process.exit(); } - - // Load config + this.config = _.clone(this.db.config); try { _.defaults(this.config, JSON.parse(fs.readFileSync('config.json', 'utf-8'))); } catch(err) { - console.log('Config file is invalid. Stopping'); + console.log('Config file is invalid. Stopping: ' + err); process.exit(); } @@ -44,7 +48,8 @@ var DBot = function(timers) { // Load missing config directives from sample file _.defaults(this.config, defaultConfig); - // Load Strings file + /*** Load main strings ***/ + try { this.strings = JSON.parse(fs.readFileSync('strings.json', 'utf-8')); } catch(err) { @@ -220,13 +225,17 @@ DBot.prototype.reloadModules = function() { // Load the module data _.each([ 'commands', 'pages', 'api' ], function(property) { var propertyObj = {}; - try { - var propertyKey = require.resolve(moduleDir + property); - if(propertyKey) delete require.cache[propertyKey]; - propertyObj = require(moduleDir + property).fetch(this); - } catch(err) { - console.log('Module error (' + module.name + ') in ' + property + ': ' + err); - } + + if(fs.existsSync(moduleDir + property + '.js')) { + try { + var propertyKey = require.resolve(moduleDir + property); + if(propertyKey) delete require.cache[propertyKey]; + propertyObj = require(moduleDir + property).fetch(this); + } catch(err) { + this.status[name] = 'Error loading ' + propertyKey + ': ' + err + ' - ' + err.stack.split('\n')[1].trim(); + console.log('Module error (' + module.name + ') in ' + property + ': ' + err); + } + } if(!_.has(module, property)) module[property] = {}; _.extend(module[property], propertyObj); @@ -286,7 +295,11 @@ DBot.prototype.reloadModules = function() { _.each(this.modules, function(module, name) { if(module.onLoad) { - module.onLoad(); + try { + module.onLoad(); + } catch(err) { + this.status[name] = 'Error in onLoad: ' + err + ' ' + err.stack.split('\n')[1].trim(); + } } }, this); diff --git a/views/profile/profile.jade b/views/profile/profile.jade index 414dee5..a719833 100644 --- a/views/profile/profile.jade +++ b/views/profile/profile.jade @@ -43,7 +43,10 @@ block content h1 #{primary} small - "#{profile.tagline}" + if(profile.tagline) + "#{profile.tagline}" + div#backlink + a(href='/profile/'+connection) « Profiles div.row.profile_row#profile_data div.span3 @@ -92,6 +95,3 @@ block content #{chan.fields.wpl.data} td #{chan.fields.in_mentions.data} - ul.pager - li.previous - a(href='/profile/'+connection) ← Back to #{connection}. diff --git a/views/profile/profile_grid.jade b/views/profile/profile_grid.jade index 2436b04..74a1693 100644 --- a/views/profile/profile_grid.jade +++ b/views/profile/profile_grid.jade @@ -14,6 +14,9 @@ block content div.page-header.profile_page-header h1 #{connection} + div#backlink + a(href='../connections') « Connections + ul.thumbnails each profile, key in profiles if profile.hasOwnProperty('profile') && profile.profile.avatar diff --git a/views/users/users.jade b/views/users/users.jade index de9dae5..3f9b3bc 100644 --- a/views/users/users.jade +++ b/views/users/users.jade @@ -2,9 +2,50 @@ extends ../layout block content script(type="text/javascript", src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js") + style + .chart rect { + fill: steelblue; + } + .chart rect:hover { + fill: #000; + } script $(document).ready(function(){ + // d3.js Graph + var w = 5.595; + var h = 120; + + var x = d3.scale.linear() + .domain([0,1]) + .range([0,w]); + + var y = d3.scale.linear() + .domain([0,100]) + .rangeRound([0,h]); + + var chart = d3.select($("#chanFreqChart")[0]).append("svg") + .attr("class", "chart") + .attr("width", w * #{chanFreqLen} - 1) + .attr("height", h); + + chart.selectAll("rect") + .data([#{chanFreq}]) + .enter().append("rect") + .attr("x", function(d, i) { return x(i) - .5; }) + .attr("y", function(d) { return h - y(d) - .5; }) + .attr("width", w) + .attr("height", function(d) { return y(d); }) + .attr("title", function(d){ return y(d); }); + + chart.append("line") + .attr("x1", 0) + .attr("x2", w * #{chanFreqLen}) + .attr("y1", h - .5) + .attr("y2", h - .5) + .style("stroke", "#000"); + + // Allowing forcing of string stats data to sort as numeric jQuery.extend( jQuery.fn.dataTableExt.oSort, { "forcenum-pre": function ( a ) { @@ -22,6 +63,8 @@ block content } ); $('.tip').tooltip(); + $('rect').tooltip({ + }); $('.data').dataTable({ "aoColumnDefs": [ { "aDataSort": [ 1, 0 ], "asSorting": [ "asc" ], "aTargets": [ 0 ] }, @@ -40,9 +83,16 @@ block content }); }); - h3 Users of #{channel} on #{connection} + div.page-header.profile_page-header + h1 + #{channel} + small + #{connection} div#backlink a(href='/channels/'+connection) « Channel List + div#row + div.barchart#chanFreqChart + hr div#row table.table.table-hover.data thead @@ -55,7 +105,7 @@ block content th Verbosity th Mentions tbody - -each nick in nicks + -each nick in userStats tr td a(href='/profile/'+connection+'/'+nick.primary)