From 8f926d24d474da2ba5c7bc98090014278e87d4c0 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Thu, 28 Jun 2012 02:56:41 +0200 Subject: o Making the atom bot useful. --- atom-bot/index.js | 217 +++++++++++++++++++++++++++++++++++++++++++----------- dynobot-irc.js | 53 ++++++++----- irc-client.js | 1 + package.json | 5 +- 4 files changed, 213 insertions(+), 63 deletions(-) diff --git a/atom-bot/index.js b/atom-bot/index.js index 7de99bc..18eaf14 100644 --- a/atom-bot/index.js +++ b/atom-bot/index.js @@ -21,57 +21,55 @@ require('tinycolor'); var cron = require('cron').CronJob - , parser = require('blindparser') , events = require('events') + , fs = require('fs') + , parser = require('blindparser') + , url = require('url') , _ = require('underscore'); -var irc = function() { - var Proxy = require('../node_modules/dynobot/proxy'); - var Channel = require('../node_modules/dynobot/channel'); - var IrcClient = require('../irc-client.js').IrcClient; +var Proxy = require('../node_modules/dynobot/proxy'); +var Channel = require('../node_modules/dynobot/channel'); - var channel = new Channel(); - return new Proxy(IrcClient.prototype, 'irc', channel); -}(); +var irc = new Proxy(require('../irc-client.js').IrcClient.prototype, 'irc', new Channel()); function log(message) { console.log(('log ' + message).green); } -var parserOptions = {}; - -// Config -var channel = '#dynobot'; -var feeds = [ -// "http://search.twitter.com/search.atom?q=from:AgileBorat", -// "http://search.twitter.com/search.atom?q=from:KongenDin", -// "http://search.twitter.com/search.atom?q=from:NestenSivJensen", - "http://search.twitter.com/search.atom?q=ndc2012" -// "http://search.twitter.com/search.atom?q=awesome", -// "http://search.twitter.com/search.atom?q=eurovision", -]; -var cronJobs = []; +var config; var state = { channelTopic: undefined, topic: undefined, - updatingFeed: false, - feeds: [], newest: { timestamp: 0, text: undefined } }; +var feeds = {}; + +function FeedState(url) { + this.url = url; + this.updatingFeed = false; + this.last = undefined; + this.job = undefined; +} + var eventEmitter = new events.EventEmitter; +function parseFeed(url, cb) { + var parserOptions = {}; + parser.parseURL(url, parserOptions, cb); +} + function updateFeed(feedState) { log("Fetching " + feedState.url); if(feedState.updatingFeed) log("Already working"); feedState.updatingFeed = true; - parser.parseURL(feedState.url, parserOptions, function(err, feed) { + parseFeed(feedState.url, function(err, feed) { log("Fetched " + feedState.url + ", status=" + (err ? "failure" : "success")); if(err) { log(err); @@ -106,7 +104,7 @@ function processFeed(feed) { } eventEmitter.on("feedChanged", function(url, newest) { - state.feeds[url] = newest; + feeds[url].newest = newest; if(state.newest.timestamp >= newest.timestamp) { log("oold: " + newest.text); @@ -120,33 +118,55 @@ eventEmitter.on("feedChanged", function(url, newest) { state.newest = newest; /* if(state.channelTopic != text) { - irc.topic(channel, text); + irc.topic(config.channel, text); } */ - irc.notice(channel, text); + log(config.channel); + irc.notice(config.channel, text); }); +function appendFeed(feedUrl) { + var feedState = new FeedState(feedUrl); + log("Job starting for " + feedUrl); + feedState.job = new cron("*/10 * * * *", function() { + updateFeed(feedState); + }, function() { + log("Job stopping for " + feedUrl); + }, true); + feeds[feedUrl] = feedState; + log("appendFeed: keys=" + _.keys(feeds)); +} + +function removeFeed(feedUrl) { + log("removeFeed: feedUrl=" + feedUrl); + log("removeFeed: keys=" + _.keys(feeds)); + var feedState = feeds[feedUrl]; + log("removeFeed: feedState=" + typeof feedState); + if(_.isObject(feedState)) { + log("removeFeed: keys feedState=" + _.keys(feedState)); + feedState.job.stop(); + } + else { + log("removeFeed: feed not found"); + } + delete feeds[feedUrl]; + log("removeFeed: keys=" + _.keys(feeds)); +} + function setup() { - log("Stopping " + cronJobs.length + " cron jobs"); - _.each(cronJobs, function(job) { job.stop(); }); - cronJobs = []; - _.each(feeds, function(feed) { - var state = { - url: feed, - updatingFeed: false, - last: undefined - }; - var job = new cron("*/10 * * * *", function() { - updateFeed(state); - }, function() {}, true); - cronJobs.push(job); + log('Starting..'); + + loadConfig(function(err, config) { + log('config: ' + JSON.stringify(config)); + irc.join(config.channel, function(c) { + irc.notice(config.channel, 'Atom plugin online. Monitoring ' + config.feeds.length + ' feeds.'); + }); + _.each(config.feeds, function(feed) { + appendFeed(feed); + }); }); } -irc.join(channel, function(c) { - irc.notice(channel, 'well hello yall: ' + c); -}); - irc.on('topic', function(channel, topic) { log("new topic: " + topic); /* If we're not storing this, it is possible for people to set @@ -158,4 +178,113 @@ irc.on('topic', function(channel, topic) { state.channelTopic = topic; }); +irc.on('privmsg', function(nick, channel, message) { + irc.whoami(function(whoami) { + // Hm, what happens if a nick contain funny characters like '. + var reg = new RegExp('^' + whoami + ': atom '); + if(!reg.test(message)) { + return; + } + var args = message.substring(whoami.length + 7).split(' '); + + if(args.length < 1) { + usage(channel); + return; + } + var rest = _.rest(args); + switch(args[0]) { + case 'list-feeds': onListFeeds(channel); break; + case 'add-feed': onAddFeed(channel, rest); break; + case 'remove-feed': onRemoveFeed(channel, rest); break; + case 'load-config': onLoadConfig(channel); break; + } + }); +}); + +function usage(channel) { +} + +function onListFeeds(channel) { + _.each(config.feeds, function(feed, i) { + irc.notice(channel, 'feed #' + i + ': ' + feed); + }); +} + +function onAddFeed(channel, urls) { + if(urls.length != 1) { + return; + } + var u = urls[0]; + log('Adding feed: ' + u); + url.parse(u); // TODO: Add error handling + parseFeed(u, function(err, feed) { + if(err) { + log('Unable to fetch feed'); + log(util.format(err)); + return; + } + config.feeds.push(u); + appendFeed(u); + saveConfig(function(err) { + if(err) throw err; + irc.notice(channel, 'Feed added'); + }); + }); +} + +function onRemoveFeed(channel, args) { + if(args.length != 1) { + return; + } + log("onRemoveFeed: args[0]=" + args[0]); + var i = parseInt(args[0]); + log("onRemoveFeed: i=" + i); + var feedUrl = config.feeds[i]; + log("onRemoveFeed: config=" + JSON.stringify(config)); + config.feeds.splice(i, 1); + log("onRemoveFeed: config=" + JSON.stringify(config)); + log("onRemoveFeed: feedUrl=" + feedUrl); + removeFeed(feedUrl); + saveConfig(function(err) { + if(err) { + log(err); + irc.notice(channel, 'Error removing feed'); + return; + } + irc.notice(channel, 'Feed removed'); + }); +} + +function onLoadConfig(channel) { + loadConfig(function(err, config) { + if(err) { + irc.notice(channel, 'Unable to load config'); + } + else { + irc.notice(channel, 'Loaded configuration, has ' + config.feeds.length + ' feeds.'); + } + }); +} + +function loadConfig(cb) { + fs.readFile('config.json', function(err, data) { + if(err) return cb(err, undefined); + + try { + config = JSON.parse(data); + } catch(e) { + log('Unable to parse config.json, using defaults.'); + } + + config.channel = _.isString(config.channel) ? config.channel : '#dynobot'; + config.feeds = _.isArray(config.feeds) ? config.feeds : []; + cb(undefined, config); + }); +} + +function saveConfig(cb) { + log('Saving config: ' + JSON.stringify(config)); + fs.writeFile('config.json', JSON.stringify(config), cb); +} + setup(); diff --git a/dynobot-irc.js b/dynobot-irc.js index 9cae258..a942883 100644 --- a/dynobot-irc.js +++ b/dynobot-irc.js @@ -1,14 +1,15 @@ require('tinycolor'); var Channel = require('dynobot/channel.js'); -var cmdopt = require('cmdopt') -var cp = require('child_process'); var EventEmitter = require('events').EventEmitter; var FakeSocket = require('./FakeSocket.js'); var IrcClient = require('./irc-client').IrcClient; -var os = require('os') -var _ = require('underscore'); var Service = require('dynobot/service.js'); +var _ = require('underscore'); +var cmdopt = require('cmdopt') +var cp = require('child_process'); +var os = require('os') var util = require('util'); +var watchTree = require("fs-watch-tree").watchTree; var parser = new cmdopt.Parser(); parser.option("-h, --help", "Show this help"); @@ -82,35 +83,53 @@ var ircService = new Service('irc', irc); irc.setDebugLevel(3); -function Plugin(name, script) { - this.name = name; - this.script = script; +function Plugin(dir, config) { + this.dir = dir; + this.config = config; this.channel = undefined; +// this.watch = undefined; + + // TODO: Resolve plugin.script. Dir is relative to the plugin's + // installation directory. + this.script = 'index.js'; } -var plugins = [ -// new Plugin('echo', './echo-bot/index.js') - new Plugin('atom', './atom-bot/index.js') -]; +Plugin.prototype.start = function(name) { + info('Starting ' + name); -_.each(plugins, function(plugin) { - info('Starting ' + plugin.name); - // TODO: Resolve plugin.script // TODO: set cwd option to the plugin's directory. var args = []; var options = { - silent: true + silent: true, + cwd: this.dir }; - var child = cp.fork(plugin.script, args, options); + var child = cp.fork(this.script, args, options); var channel = new Channel(child); ircService.handle(channel); child.on('exit', function() { - info(plugin.name + ' exited..'); + info(name + ' exited..'); ircService.release(channel); channel.close(); }); // TODO: set up consumers for child.stdout and color it before // piping to process.stdout + +// this.watch = watchTree(this.dir); + info(name + ' started..'); +} + +Plugin.prototype.stop = function(name) { + log("Stooping pulugin " + name); +// this.watch && this.watch.end(); +} + +var plugins = { +// new Plugin('echo', './echo-bot') + atom: new Plugin('./atom-bot') +}; + +_.each(plugins, function(plugin, name) { + plugin.start(name); }); irc.join(config.channel, function(name) { diff --git a/irc-client.js b/irc-client.js index 1be71bf..c94d8e5 100644 --- a/irc-client.js +++ b/irc-client.js @@ -136,5 +136,6 @@ function dispatch(name) { IrcClient.prototype.notice = function() { this.irc.notice.apply(this.irc, arguments); } IrcClient.prototype.privmsg = function() { this.irc.privmsg.apply(this.irc, arguments); } IrcClient.prototype.topic = function() { this.irc.topic.apply(this.irc, arguments); } +IrcClient.prototype.whoami = function(cb) { cb(this.irc.whoami()); } module.exports.IrcClient = IrcClient; diff --git a/package.json b/package.json index 4a8d8fc..0cfef1c 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,12 @@ "cron": "~0.3.2", "date": "1.0.2", "dynobot": "https://github.com/einaros/dynobot/tarball/master", + "fs-watch-tree": "~0.2.0" "getit": "~0.1.7", - "mocha": "~1.0.3", "irc.js": "0.1.0", + "mocha": "~1.0.3", "tinycolor": "0.0.1", - "underscore": "1.3.3" + "underscore": "1.3.3", }, "devDependencies": {}, "optionalDependencies": {}, -- cgit v1.2.3