/** * Module Name: sstats * Description: Simple Stats, in the absence of good ones. */ var _ = require('underscore')._, async = require('async'); var sstats = function(dbot) { if(!_.has(dbot.db, 'ssinception')) dbot.db.ssinception = new Date().getTime(); // This needs to be somewhere else this.isUpperCase = function(word) { return _.all(word.split(''), function(c) { return c == c.toUpperCase(); }); }; this.internalAPI = { // I'm not a huge fan of this but it's better than all the code // repetition 'highscore': function(key, property, callback) { var pList = {}, pPointer = property; if(!_.isArray(pPointer)) { pPointer = property.split('.'); } this.db.scan(key, function(item) { var id = item.id; for(var i=0;i<pPointer.length;i++) { if(_.has(item, pPointer[i])) { item = item[pPointer[i]]; } else { item = null; break; } } if(item) { pList[id] = item; } }, function() { var pCounts = _.chain(pList) .pairs() .sortBy(function(p) { return p[1]; }) .reverse() .first(10) .value(); async.eachSeries(pCounts, function(pCount, next) { dbot.api.users.getUser(pCount[0], function(err, user) { if(user) { pCount[0] = user.primaryNick; } next(); }); }, function() { callback(pCounts); }.bind(this)); }); }.bind(this), 'channelHighscore': function(key, server, channel, property, callback) { var cId = channel + '.' + server; var newProperty = ['channels', cId, property]; this.internalAPI.highscore(key, newProperty, callback); }.bind(this), 'formatHighscore': function(string, pCounts) { var output = string; for(var i=0;i<pCounts.length;i++) { output += pCounts[i][0] + " (" + pCounts[i][1] + "), "; } return output.slice(0, -2); } }; this.listener = function(event) { if(event.channel == event.user) return; // ignore PMs event.cStats.lines++; event.uStats.lines++; var message = event.message.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`~()]/g, ""); var cId = event.channel + '.' + event.server; var words = message.split(' '), wCount = words.length, capitals = 0, curses = 0; _.each(words, function(word) { if(this.isUpperCase(word)) capitals++; if(_.any(this.config.curses, function(curse) { return word.toLowerCase().indexOf(curse) != -1; })) { curses++; } }, this); event.uStats.words += wCount; event.uStats.capitals += capitals; event.uStats.curses += curses; event.uStats.last = new Date().getTime(); event.cStats.words += wCount; event.cStats.capitals += capitals; event.cStats.curses += curses; if(!_.has(event.uStats.channels, cId)) { event.uStats.channels[cId] = { 'lines': 1, 'words': wCount, 'capitals': capitals, 'curses': curses }; } else { event.uStats.channels[cId].lines++; event.uStats.channels[cId].words += wCount; event.uStats.channels[cId].capitals += capitals; event.uStats.channels[cId].curses += curses; } // Look for tracked words. if(event.message.charAt(0) != '~') { var wMap = {}; // Why reduce isn't working idk _.each(words, function(word) { word = word.toLowerCase(); if(!_.has(wMap, word)) wMap[word] = 0; wMap[word]++; }); _.each(wMap, function(count, word) { this.api.getTrackedWord(word, function(tWord) { if(tWord) { tWord.total += count; if(!_.has(tWord.channels, cId)) tWord.channels[cId] = 0; if(!_.has(tWord.users, event.rUser.id)) tWord.users[event.rUser.id] = 0; tWord.channels[cId] += count; tWord.users[event.rUser.id] += count; this.db.save('tracked_words', word, tWord, function() {}); } }.bind(this)); }, this); } this.db.save('channel_stats', event.cStats.id, event.cStats, function() {}); this.db.save('user_stats', event.uStats.id, event.uStats, function() {}); }.bind(this); this.on = 'PRIVMSG'; this.onLoad = function() { // Preload user stats dbot.instance.addPreEmitHook(function(event, callback) { if(!event.rUser) return callback(); this.api.getUserStats(event.rUser.id, function(uStats) { if(uStats) { event.uStats = uStats; callback(); } else { this.api.createUserStats(event.rUser.id, function(uStats) { event.uStats = uStats; callback(); }); } }.bind(this)); }.bind(this)); // Preload channel stats dbot.instance.addPreEmitHook(function(event, callback) { if(!event.channel) return callback(); var id = event.channel + '.' + event.server; this.api.getChannelStats(id, function(cStats) { if(cStats) { event.cStats = cStats; callback(); } else { this.api.createChannelStats(id, function(cStats) { event.cStats = cStats; callback(); }); } }.bind(this)); }.bind(this)); dbot.api.event.addHook('~mergeusers', function(server, oldUser, newUser) { this.api.getUserStats(oldUser.id, function(ouStats) { this.api.getUserStats(newUser.id, function(nuStats) { _.each(ouStats, function(stat, key) { if(key != 'creation' && key != 'id') { if(_.isObject(stat)) { _.each(ouStats[key], function(stat, sKey) { _.each(ouStats[key][sKey], function(stat, ssKey) { if(_.has(nuStats[key][sKey], ssKey)) { nuStats[key][sKey][ssKey] += stat; } }); }); } else { nuStats[key] += stat; } } }); this.db.del('user_stats', oldUser.id, function() {}); this.db.save('user_stats', newUser.id, nuStats, function() {}); }.bind(this)); }.bind(this)); this.db.scan('tracked_words', function(tWord) { if(_.has(tWord.users, oldUser.id)) { if(_.has(tWord.users, newUser.id)) { tWord.users[newUser.id] += tWord.users[oldUser.id]; } else { tWord.users[newUser.id] = tWord.users[oldUser.id]; } delete tWord.users[oldUser.id]; this.db.save('tracked_words', tWord.word, tWord, function() {}); } }.bind(this), function() {}); }.bind(this)); }.bind(this); }; exports.fetch = function(dbot) { return new sstats(dbot); };