Skip to content

Instantly share code, notes, and snippets.

@eeeeeta
Created April 14, 2015 06:54
Show Gist options
  • Save eeeeeta/6feff5a3302d2a2b12a0 to your computer and use it in GitHub Desktop.
Save eeeeeta/6feff5a3302d2a2b12a0 to your computer and use it in GitHub Desktop.

buttond

this is a small daemon I rigged up to collect data from reddit's /r/thebutton social experiment. to run it, you will need:

  • redis running on localhost
  • to npm install needle redis moment ws

it should collect data like this:

  • it will make a redis hash called clicks with participant number (with comma) as key and seconds left as value
  • it will make a redis key called record with the lowest recorded amount of seconds left as value
  • it will publish every click on thebutton.clicks and every tick on thebutton.ticks
  • it will make a redis hash called rectimes with records as keys and unixtime of achievement as value.
  • you can also telnet to it on port 5432 to get the current record, participant amount, and seconds left.
  • sending it a SIGUSR2 will make it verbose; send it another one to toggle off again

the nice scatter graphs are generated using a separate file which takes data from the database. I might post that later; anyway, that part's quite trivial.

Regards,

  • /u/eeeeeta
/*jslint node:true,esnext:true*/
"use strict";
var needle = require('needle'),
redis = require('redis'),
moment = require('moment'),
ws = require('ws'),
net = require('net');
console.log('~ buttond v2.5 by η (eeeeeta) ~');
console.log('INIT: Requesting http://reddit.com/r/thebutton');
needle.get('http://reddit.com/r/thebutton', {follow: 5}, function(err, res) {
if (err) {
console.error('ERROR: Failed to get thebutton html');
throw err;
}
if (res.statusCode != 200) {
console.error('ERROR: Got code ' + res.statusCode + ' instead of 200');
return;
}
let addrRegexp = /"thebutton_websocket": "([^\,]+)",/i;
if (!res.body.match(addrRegexp)) {
console.error('ERROR: Failed to retrieve thebutton websocket URL');
return;
}
let URL = res.body.match(addrRegexp)[1];
console.log('INIT: got button websocket URL as ' + URL);
console.log('INIT: connecting to redis (on localhost)');
var rd = redis.createClient();
rd.on('ready', function() {
console.log('INIT: redis connection successful');
console.log('INIT: getting record from redis');
var record = 60;
rd.get('record', function(err, rec) {
if (err) throw err;
if (!rec) {
rd.set('record', record);
console.log('INIT: no record set, set it to 60');
}
else {
record = Number(rec);
console.log('INIT: got record of ' + record);
}
console.log('INIT: connecting to thebutton websocket');
var btn = new ws(URL);
var cur = 60;
var verbose = false;
var lagms = 0;
var microtime = false;
var participants = 'error';
btn.on('open', function() {
console.log('INIT: connected to thebutton websocket');
var tid = setTimeout(function() {
console.error('ERROR: no message from thebutton since 5 seconds');
process.exit(1);
}, 5000);
btn.on('message', function(data) {
clearTimeout(tid);
tid = setTimeout(function() {
console.error('ERROR: no message from thebutton since 5 seconds');
process.exit(1);
}, 5000);
data = JSON.parse(data);
if (data.type != 'ticking') {
console.log('Unidentified socket object: ');
return console.dir(data);
}
let servertime = moment(data.payload.now_str + " 0000", "YYYY-MM-DD-HH-mm-ss Z"); // shameless ripoff from the button monitor
lagms = moment() - servertime;
if (verbose) console.log('[V] Server lag: ' + lagms + 'ms');
if (data.payload.seconds_left > cur || (cur == 60 && data.payload.seconds_left == 60)) {
// it's a click!
if (verbose) console.log('[V] Click at ' + cur + 's (' + data.payload.participants_text + ' participants)');
rd.publish('thebutton.clicks', cur);
rd.hset('clicks', data.payload.participants_text, cur, function(err, reply) {
if (err) {
return console.warn('WARNING: failed to update database with click');
}
});
}
rd.publish('thebutton.ticks', data.payload.seconds_left);
cur = data.payload.seconds_left;
participants = data.payload.participants_text;
if (data.payload.seconds_left < record) {
record = data.payload.seconds_left;
rd.publish('thebutton.records', record);
rd.set('record', record, function(err, reply) {
if (err) throw err;
if (reply != 'OK') {
return console.error('ERROR: got reply ' + reply + ' instead of "OK" setting new record');
}
console.log('[!] Set new button record: ' + data.payload.seconds_left + 's');
});
rd.hset('rectimes', record, servertime.unix(), function(err, reply) {
if (err) throw err;
console.log('[+] First ' + record + 's: ' + servertime.format("dddd, MMMM Do YYYY, h:mm:ss a"));
});
}
});
process.on('SIGUSR2', function() {
verbose = !verbose;
console.log('[+] Verbose mode ' + (verbose ? 'enabled' : 'disabled'));
});
net.createServer(function(sock) {
console.log('[+] server connection from ' + sock.remoteAddress + ':' + sock.remotePort);
sock.write('\n\nbuttond v2 by η (eeeeeta)\n');
sock.write('\nCurrent record: ' + record + 's');
sock.write('\nSeconds left: ' + cur + 's');
sock.write('\nParticipants: ' + participants + ' filthy pressers');
sock.write('\n\nHave a good day.\n\n');
sock.end();
}).listen(5432);
console.log('INIT: server listening on port 5432');
console.log('Tip: send me a SIGUSR2 to enable verbose mode');
console.log('~ buttond ready ~');
});
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment