diff options
-rw-r--r-- | newsbot.js | 272 | ||||
-rw-r--r-- | package.json | 32 |
2 files changed, 304 insertions, 0 deletions
diff --git a/newsbot.js b/newsbot.js new file mode 100644 index 0000000..99556a7 --- /dev/null +++ b/newsbot.js @@ -0,0 +1,272 @@ +var IRC = require('irc') +var FeedParser = require('feedparser') +var Request = require('request') +var Parse = require('shell-quote').parse +var FS = require('fs') +var HTTP = require('http') +var FormData = require('form-data') +var URL = require('url') + +var irc_server = 'cd.retiolum' +var master_nick = 'knews' +var news_channel = '#news' +var feeds_file = 'new_feeds' +var feedbot_loop_delay = 60 * 1000 // [ms] +var feedbot_create_delay = 200 // [ms] +var url_shortener_host = 'echelon:1337' + +var slaves = {} + +function main () { + var master = new IRC.Client(irc_server, master_nick, { + channels: [ news_channel ], + }) + + master.on('message' + news_channel, function (nick, text, message) { + if (is_talking_to(master_nick, text)) { + var request = parse_request(text) + if (request) { + return run_command(request.method, request.params, function (error, result) { + if (error) { + return master.say(news_channel, '4' + error) + } else { + return master.say(news_channel, result) + } + }) + } + } + }) + + master.once('registered', function () { + // read feeds file and create a feedbot for each entry + FS + .readFileSync(feeds_file) + .toString() + .split('\n') + //.filter((function () { + // var n = 2; + // return function () { + // return n-- > 0 + // } + //})()) + .filter(function (line) { + return line.length > 0 + }) + .forEach(function (line, i) { + var parts = line.split('|') + if (parts.length !== 3) { + console.log('bad new_feeds line ' + lines + ': ' + line) + return + } + + var nick = parts[0] + var uri = parts[1] + var channels = parts[2].split(' ') + + setTimeout(function () { + return create_feedbot(nick, uri, channels) + }, i*feedbot_create_delay) + }) + }) +} + +function create_feedbot (nick, uri, channels) { + var client = new IRC.Client(irc_server, nick, { + channels: channels, + autoRejoin: false, + }) + + slaves[nick] = { + client: client, + nick: nick, + uri: uri, + } + + // say text in every joined channel + function broadcast (text) { + Object.keys(client.chans).forEach(function (channel) { + client.say(channel, text) + }) + } + + function broadcast_new_item (item) { + return getShortLink(item.link, function (error, shortlink) { + return broadcast(item.title + ' ' + shortlink) + }) + } + + client.once('registered', loop_feedparser) + client.once('registered', deaf_myself) + + client.on('invite', function (channel, from, message) { + client.join(channel, null) + }) + + client.on('error', function (error) { + console.log('Error:', error) + }) + + // TODO stopping criteria + function loop_feedparser () { + try { + var request = Request(uri) + var feedparser = new FeedParser() + } catch (error) { + return broadcast('4' + error) + } + + request.on('error', function (error) { + broadcast('4request ' + error) + }) + request.on('response', function (response) { + if (response.statusCode !== 200) { + return this.emit('error', new Error('Bad status code')) + } + var output = response + switch (response.headers['content-encoding']) { + case 'gzip': + output = zlib.createGunzip() + response.pipe(output) + break + case 'deflate': + output = zlib.createInflate() + response.pipe(output) + break + } + this.pipe(feedparser) + }) + + var items = [] + + feedparser.on('error', function (error) { + broadcast('4feedparser ' + error) + return continue_loop() + }) + feedparser.on('readable', function () { + for (var item; item = this.read(); ) { + items.push(item) + } + }) + feedparser.on('end', function () { + + if (client.lastItems) { + items.forEach(function (item) { + if (!client.lastItems.hasOwnProperty(item.title)) { + broadcast_new_item(item) + } + }) + } + + client.lastItems = {} + items.forEach(function (item) { + client.lastItems[item.title] = true + }) + + return continue_loop() + }) + + function continue_loop () { + setTimeout(loop_feedparser, feedbot_loop_delay) + } + } + function deaf_myself () { + client.send('mode', nick, '+D') + } +} + +// return true if text "is talking to" my_nick +function is_talking_to (my_nick, text) { + return text.slice(0, my_nick.length) === my_nick + && text[my_nick.length] === ':' +} + +function parse_request (text) { + var parse = Parse(text) + return { + method: parse[1], + params: parse.slice(2), + } +} + +function run_command (methodname, params, callback) { + var method = methods[methodname] + if (method) { + return method(params, callback) + } else { + return callback(new Error('dunno what ' + methodname + ' is')); + } +} + +function getShortLink (link, callback) { + var form = new FormData() + try { + form.append('uri', link) + } catch (err) { + console.log('link:', link) + throw err + } + + var request = HTTP.request({ + method: 'post', + host: url_shortener_host, + path: '/', + headers: form.getHeaders(), + }) + form.pipe(request) + + request.on('response', function (response) { + var data = '' + response.on('data', function (chunk) { + data += chunk + }) + response.on('end', function () { + callback(null, data.replace(/\r\n$/,'') + '#' + URL.parse(link).host) + }) + }) +} + +var methods = {} +methods.add = function (params, callback) { + if (slaves.hasOwnProperty(params[0])) { + return callback(new Error('name already taken')) + } else { + create_feedbot(params[0], params[1], [news_channel]) + return callback(null) + } +} +methods.del = function (params, callback) { + var nick = params[0] + if (slaves.hasOwnProperty(nick)) { + var slave = slaves[nick] + slave.client.disconnect() + delete slaves[nick] + return callback(null) + } else { + return callback(new Error('botname not found')) + } +} +methods.save = function (params, callback) { + var feeds = Object.keys(slaves) + .map(function (nick) { + return slaves[nick] + }) + .map(function (slave) { + return [ + slave.nick, + slave.uri, + Object.keys(slave.client.chans).join(' '), + ].join('|') + }).join('\n') + '\n' + return FS.writeFile(feeds_file, feeds, function (error) { + if (error) { + return callback(error) + } else { + return callback(null, 'Feeds saved') + } + }) +} + + +if (require.main === module) { + main() +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..52c1917 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "news", + "version": "0.0.0", + "description": "", + "main": "newsbot.js", + "dependencies": { + "feedparser": "*", + "form-data": "*", + "irc": "*", + "request": "*", + "shell-quote": "*" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/krebscode/painload" + }, + "keywords": [ + "irc", + "news", + "feed" + ], + "author": "krebs", + "license": "WTFPLv2", + "bugs": { + "url": "https://github.com/krebscode/painload/issues" + }, + "homepage": "https://github.com/krebscode/painload" +} |