You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
4.8 KiB
209 lines
4.8 KiB
5 years ago
|
/* 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)();
|
||
|
}
|
||
|
}
|