├── .gitignore ├── Procfile ├── public ├── favicon.ico ├── wikipulse.png ├── js │ └── wikipulse.js └── index.html ├── package.json ├── README.md ├── config.json └── wikipulse.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node wikipulse.js 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edsu/wikipulse/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/wikipulse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edsu/wikipulse/HEAD/public/wikipulse.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": "0.8.x", 4 | "npm": "1.1.x" 5 | }, 6 | "name": "wikipedia-console", 7 | "version": "0.0.1", 8 | "private": true, 9 | "dependencies": { 10 | "express": "2.5.x", 11 | "redis": "0.8.x", 12 | "redis-url": "0.0.1", 13 | "underscore": "1.2.x", 14 | "wikichanges": ">0.2.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wikipulse is a simple visualization of edits to various major language 2 | wikipedias using node.js. The app connects to mediawiki IRC chatrooms 3 | where article edits are announced by a bot, and keeps track of the edits 4 | in redis. It also runs a webserver on the port specified in config.json. 5 | 6 | 1. `sudo aptitude install redis-server nodejs npm` 7 | 1. `npm install` 8 | 1. `node wikipulse.js` 9 | 1. point your browser at localhost:3000 10 | 11 | Thanks to [Dario Taraborelli](http://nitens.org/taraborelli/home) 12 | of the [Wikimedia Foundation](http://wikimediafoundation.org/) for the idea. 13 | 14 | License: Public Domain 15 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ircNick": "wikipulse-1", 3 | "ircUserName": "wikipulse-1", 4 | "ircRealName": "http://github.com/edsu/wikipulse", 5 | "webServerPort": 9999, 6 | "wikipedias": [ 7 | "#ar.wikipedia", 8 | "#bg.wikipedia", 9 | "#ca.wikipedia", 10 | "#cs.wikipedia", 11 | "#da.wikipedia", 12 | "#de.wikipedia", 13 | "#el.wikipedia", 14 | "#en.wikipedia", 15 | "#eo.wikipedia", 16 | "#es.wikipedia", 17 | "#eu.wikipedia", 18 | "#fa.wikipedia", 19 | "#fi.wikipedia", 20 | "#fr.wikipedia", 21 | "#he.wikipedia", 22 | "#hu.wikipedia", 23 | "#id.wikipedia", 24 | "#it.wikipedia", 25 | "#ja.wikipedia", 26 | "#ko.wikipedia", 27 | "#lt.wikipedia", 28 | "#ms.wikipedia", 29 | "#nl.wikipedia", 30 | "#no.wikipedia", 31 | "#pl.wikipedia", 32 | "#pt.wikipedia", 33 | "#ro.wikipedia", 34 | "#ru.wikipedia", 35 | "#sk.wikipedia", 36 | "#sl.wikipedia", 37 | "#sv.wikipedia", 38 | "#tr.wikipedia", 39 | "#uk.wikipedia", 40 | "#vi.wikipedia", 41 | "#vo.wikipedia", 42 | "#zh.wikipedia", 43 | "#commons.wikimedia", 44 | "#wikidata.wikipedia" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /public/js/wikipulse.js: -------------------------------------------------------------------------------- 1 | var charts = {}; 2 | 3 | function drawChart(wikipedia, range, combined) { 4 | var url = "/stats/" + wikipedia + "/" + range + ".json"; 5 | $.getJSON(url, function(edits) { 6 | var data = new google.visualization.DataTable(); 7 | data.addColumn('string', 'Label'); 8 | data.addColumn('number', 'Value'); 9 | data.addRows(1); 10 | data.setValue(0, 0, shortName(wikipedia)); 11 | data.setValue(0, 1, edits); 12 | 13 | var size = (combined ? 500 : 120), 14 | max = (combined ? 1000 : 300), 15 | endGreen = (combined ? 600 : 180), // green = <10 edits/second (global gauge), <3 ed/s (single gauges) 16 | endYellow = (combined ? 900 : 240); // yellow = 10-15 ed/s (global gauge), 3-5 ed/s (single gauges) 17 | // red = >15 ed/s (global gauge), >5 ed/s (single gauges) 18 | var options = { 19 | width: size, 20 | height: size, 21 | max: max, 22 | minorTicks: (combined ? 2 : 5), 23 | majorTicks: (combined ? ["0","200","","","",max] : ["0","50","","","","",max]), 24 | greenColor: "GreenYellow", 25 | greenFrom: 0, greenTo: endGreen, 26 | yellowColor: "Gold", 27 | yellowFrom: endGreen, yellowTo: endYellow, 28 | redColor: "Tomato", 29 | redFrom: endYellow, redTo: max 30 | }; 31 | 32 | var chart = null; 33 | if (charts[wikipedia]) { 34 | chart = charts[wikipedia]; 35 | } else { 36 | chart = new google.visualization.Gauge($("#" + wikipedia)[0]); 37 | charts[wikipedia] = chart; 38 | } 39 | chart.draw(data, options); 40 | var draw = 'drawChart("' + wikipedia + '",' + range + "," + combined + ")"; 41 | setTimeout(draw, 1000); 42 | }); 43 | } 44 | 45 | function shortName(wikipedia) { 46 | return wikipedia.split("-")[0]; 47 | } 48 | -------------------------------------------------------------------------------- /wikipulse.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | express = require('express'), 4 | _ = require('underscore')._, 5 | redis = require('redis-url'), 6 | wikichanges = require('wikichanges'); 7 | 8 | var db = redis.createClient(process.env.REDISTOGO_URL || "redis://localhost:6379"); 9 | db.flushall(); 10 | 11 | function main() { 12 | purgeOld(); 13 | startMonitoring(); 14 | startWebApp(); 15 | } 16 | 17 | function getConfig() { 18 | configFile = path.join(__dirname, 'config.json'); 19 | return JSON.parse(fs.readFileSync(configFile)); 20 | } 21 | 22 | function startMonitoring() { 23 | var w = new wikichanges.WikiChanges({wikipedias: config.wikipedias}); 24 | w.listen(function(change) { 25 | processUpdate(change); 26 | }); 27 | } 28 | 29 | function startWebApp() { 30 | var app = module.exports = express.createServer(); 31 | app.configure(function(){ 32 | app.use(express.methodOverride()); 33 | app.use(express.bodyParser()); 34 | app.use(app.router); 35 | app.use(express.logger()); 36 | app.use(express.static(__dirname + '/public')); 37 | }); 38 | app.get('/stats/:wikipedia/:range.json', getStats); 39 | app.listen(process.env.PORT || config.webServerPort); 40 | } 41 | 42 | function processUpdate(msg) { 43 | var wikipedia = msg.channel; 44 | t = new Date().getTime(); 45 | console.log("zadd " + wikipedia + " " + t) 46 | db.zadd(wikipedia, t, t, function (e, r) { 47 | if (e) console.log("error on zadd: " + e); 48 | }); 49 | db.zadd('#wikipedia', t, t, function (e, r) { 50 | if (e) console.log("error on zadd #wikipedia" + e); 51 | }); 52 | } 53 | 54 | function getStats(req, res) { 55 | wikipedia = req.params.wikipedia.replace("-", "."); 56 | range = req.params.range; 57 | t = new Date().getTime(); 58 | db.zrangebyscore('#' + wikipedia, t - range, t, function(e, r) { 59 | if (e) { 60 | console.log(e); 61 | } else { 62 | res.send(r.length.toString()); 63 | } 64 | }); 65 | } 66 | 67 | function purgeOld() { 68 | var t = new Date().getTime(); 69 | var maxTime = 1000 * 60 * 1; // 60 seconds 70 | var cutoff = t - maxTime; 71 | db.zremrangebyscore('#wikipedia', 0, cutoff, function (e,r) { 72 | if (e) console.log("unable to purge old for #wikipedia: " + e ); 73 | }); 74 | _.each(config.wikipedias, function(wikipedia) { 75 | console.log("zremrangebyscore " + wikipedia + " " + cutoff) 76 | db.zremrangebyscore(wikipedia, 0, cutoff, function (e, r) { 77 | if (e) console.log("unable to purge old for " + wikipedia + ": " + e); 78 | }); 79 | }); 80 | setTimeout(purgeOld, maxTime); 81 | } 82 | 83 | var config = getConfig(); 84 | main(); 85 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
82 |
83 |