/* Polls an icecast2 server for now-playing info, and broadcasts it. * Configure here, and install poll.xsl into icecast http root. */ var _ = require('../lib/underscore'), caps = require('../server/caps'), db = require('../db'), expat = require('node-expat'), request = require('request'), winston = require('winston'); var RADIO_IDENT = {auth: 'Radio', ip: '127.0.0.1'}; var RADIO_MOUNT = '/radio'; var ICECAST_POLL_URL = 'http://localhost:5555/poll.xsl'; var M3U_URL = 'http://doushio.com/radio.m3u'; var SHORT_INTERVAL = 3 * 1000; var LONG_INTERVAL = 30 * 1000; function update_banner(info, cb) { var yaku = new db.Yakusoku(info.board, RADIO_IDENT); yaku.set_banner(info.op, info.msg, function (err, res) { yaku.disconnect(); cb(err, res); }); } function make_monitor(poll) { function monitor(last) { poll(function (err, info) { if (err) winston.error(err); // fall through var clear = false; var interval = SHORT_INTERVAL; if (!info && last) { clear = true; info = last; info.msg = ''; interval = LONG_INTERVAL; } var sameAsLast = _.isEqual(info, last); if (!clear && (!info || sameAsLast)) { if (!info) interval = LONG_INTERVAL; setTimeout(monitor.bind(null, last), interval); } else { update_banner(info, function (err, cb) { if (err) { winston.error(err); interval = LONG_INTERVAL; } if (clear) info = null; setTimeout(monitor.bind(null, info), interval); }); } }); } return monitor; } function poll_icecast(cb) { request.get(ICECAST_POLL_URL, function (err, resp, body) { if (err) return cb(err); if (resp.statusCode != 200) return cb("Got " + resp.statusCode); parse_icecast(body, function (err, mounts) { if (err) cb(err); else cb(null, format_icecast(mounts)); }); }); } function format_icecast(mounts) { var radio = mounts[RADIO_MOUNT]; if (!radio || !radio.url) return null; var info = extract_thread(radio.url); if (!info) return null; var count = parseInt(radio.listeners, 10); count = count + ' listener' + (count == 1 ? '' : 's'); var msg = [{text: count, href: M3U_URL}]; if (radio.title) msg.push(': ' + radio.title); info.msg = msg; return info; } function extract_thread(url) { var m = /\/(\w+)\/(\d+)/.exec(url); if (!m) return; var board = m[1]; if (!db.is_board(board) || !caps.can_access_board(RADIO_IDENT, board)) return; var op = parseInt(m[2], 10); if (!op) return; return {board: board, op: op}; } function parse_icecast(input, cb) { var mounts = {}, curMount, thisKey, thisVal; var parser = new expat.Parser('UTF-8'); parser.on('startElement', function (name, data) { if (name == 'mount') { curMount = {}; return; } if (curMount) { thisKey = name; thisVal = []; } }); parser.on('text', function (val) { if (thisKey) thisVal.push(val); }); parser.on('endElement', function (name) { if (thisKey) { if (name != thisKey) winston.error("Unmatched or nested?!"); curMount[thisKey] = thisVal.join('').trim(); thisKey = thisVal = null; } else if (name == 'mount') { if (curMount.point) mounts[curMount.point] = curMount; else winston.warn("Unknown mount: " + curMount); curMount = null; } else if (name == 'mounts') { parser.removeAllListeners(); cb(null, mounts); } }); parser.on('error', cb); parser.parse(input); } var R_A_D_IO_POLL_URL = 'http://r-a-d.io/api'; function poll_r_a_d_io(cb) { var opts = { url: R_A_D_IO_POLL_URL, json: true, }; request.get(opts, function (err, resp, body) { if (err) return cb(err); if (resp.statusCode != 200) return cb("Got " + resp.statusCode); cb(null, format_r_a_d_io(body)); }); } function format_r_a_d_io(json) { if (!json || !json.main) return null; var station = json.main; var info = extract_thread(station.thread); if (!info) return null; var count = station.listeners || '???'; count = count + ' listener' + (count == 1 ? '' : 's'); var msg = [{text: count, href: 'http://r-a-d.io/'}]; var np = station.np; if (typeof np == 'string' && np) { msg.push(': ' + np.slice(0, 100)); } info.msg = msg; return info; } var reduce_regexp = /&(?:amp|lt|gt|quot);/g; var reductions = {'&' : '&', '<': '<', '>': '>', '"': '"'}; function reduce_entities(html) { return html.replace(reduce_regexp, function (c) { return reductions[c]; }); } if (require.main === module) { var args = process.argv; if (args.length == 2) { winston.info('Polling ' + ICECAST_POLL_URL + '.'); make_monitor(poll_icecast)(); } else if (args.length == 5) { var op = parseInt(args[3], 10); var info = {board: args[2], op: op, msg: args[4]}; update_banner(info, function (err) { if (err) winston.error(err); process.exit(err ? -1 : 0); }); } else if (args[2] == '--r-a-d-io') { winston.info('Polling ' + R_A_D_IO_POLL_URL + '.'); make_monitor(poll_r_a_d_io)(); } }