From 18c5b3ceac115200f994c440b78ceccda9a9862d Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Mon, 4 Jun 2012 00:08:25 +0200 Subject: wip --- atom-bot/index.js | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++ atom-bot/package.json | 27 ++++++++ dynobot-irc.js | 61 +++++++++++++------ echo-bot.js | 17 ------ echo-bot/index.js | 22 +++++++ echo-bot/package.json | 27 ++++++++ irc-client.js | 103 +++++++++++++++++++++++++++++++ lib/arktekk-bot.js | 165 ------------------------------------------------- package.json | 8 ++- 9 files changed, 394 insertions(+), 202 deletions(-) create mode 100644 atom-bot/index.js create mode 100644 atom-bot/package.json delete mode 100644 echo-bot.js create mode 100644 echo-bot/index.js create mode 100644 echo-bot/package.json create mode 100644 irc-client.js delete mode 100644 lib/arktekk-bot.js diff --git a/atom-bot/index.js b/atom-bot/index.js new file mode 100644 index 0000000..37d073c --- /dev/null +++ b/atom-bot/index.js @@ -0,0 +1,166 @@ +/* + * Possible strategies for updating the topic: + * + * o Set the topic unconditionally when the feed changes. This makes + * it possible for users to change the topic and it won't be + * overridden until the feed changes. + * + * o Set the topic on any topic change (making the feed control the + * entire topic) + * + * o Support a delimiter so it can control only a part of the topic, + * like "<>". Example + * + * Next meeting, sat 1900 <> DATA FROM FEED. + * + * A regexp selecting the are to be updated might conver it. + * + * o If the bot changed the topic the last time, it's probably safe to + * just update it. + */ + +require('tinycolor'); +var node_irc = require('../node_modules/node-irc/IRC.js') + , cron = require('cron').CronJob + , parser = require('blindparser') + , events = require('events') + , _ = require('underscore'); + +var parserOptions = {}; + +var config; +var cronJobs = []; + +var state = { + channelTopic: undefined, + irc: undefined, + updatingFeed: false, + feeds: [], + newest: { + timestamp: 0, + text: undefined + } +}; +module.exports.state = state; + +var eventEmitter = new events.EventEmitter; + +function startIrc() { + irc = new node_irc.IRC(config.host, config.port); + irc.on('raw', function(data) { console.log(data) }); + irc.on('connected', function(server) { + console.log('Connected to ' + server); + irc.join(config.channel, function(error) { + irc.notice(config.channel, 'well hello yall'); + }); + }); + irc.topic = function(channel, topic) { + irc._socket.write('topic ' + channel + ' :' + topic + '\r\n'); + }; + + irc.on('topic', function(channel, topic) { + console.log("new topic: " + topic); + /* If we're not storing this, it is possible for people to set + * the topic after the bot has set it and it will persist (until + * next update from the feed). + topic = t; + */ + + state.channelTopic = topic; + }); +} + +function updateFeed(feedState) { + console.log("Fetching " + feedState.url); + if(feedState.updatingFeed) + console.log("Already working"); + feedState.updatingFeed = true; + + parser.parseURL(feedState.url, parserOptions, function(err, feed) { +// console.log("Fetched " + feedState.url + ", status=" + (err ? "failure" : "success")); + if(err) { + console.log(err); + return; + } + var newest = processFeed(feed); + if(typeof newest == "object") { + eventEmitter.emit("feedChanged", feedState.url, newest); + } + feedState.updatingFeed = false; + }); +} + +function processFeed(feed) { + // Extracts the username from the feed. + // TODO: Use something better than blindparser to parse atom so that + // each entry has an author too to get the full name. + if(typeof feed.items[0] == "undefined") { + console.log("feed does not contain any items", feed); + return undefined; + } + var match = /^http:\/\/twitter.com\/([a-zA-Z0-9_]+)\/.*$/.exec(feed.items[0].link) + if(match.length != 2) { + return undefined; + } + return { + text: feed.items[0].title, + author: match[1], + timestamp: feed.items[0].date + }; +} +module.exports.processFeed = processFeed; + +eventEmitter.on("feedChanged", function(url, newest) { + state.feeds[url] = newest; + + if(state.newest.timestamp >= newest.timestamp) { +// console.log("oold: " + newest.text); + return; + } + + var text = newest.author + ": " + newest.text; + console.log("New topic", newest.timestamp, url, text); + state.newest = newest; + if(config.connect && state.topic != text) { + irc.topic(config.channel, text); + } +}); + +eventEmitter.on("configUpdated", function(c) { + setup(); +}); + +function setup() { + console.log("Stopping " + cronJobs.length + " cron jobs"); + _.each(cronJobs, function(job) { job.stop(); }); + cronJobs = []; + _.each(config.feeds, function(feed) { + var state = { + url: feed, + updatingFeed: false, + last: undefined + }; + var job = new cron("*/10 * * * *", function() { + updateFeed(state); + }, function() {}, true); + cronJobs.push(job); + }); +} + +function start(c) { + config = c; + startIrc(); + setup(); + if(config.connect) { + console.log('Connecting to ' + config.host + ':' + config.port); + irc.connect(config.nick); + } else { + console.log('Not connecting to IRC'); + } +} + +module.exports.start = start; + +module.exports.emit = function(config) { + eventEmitter.emit("configUpdated", config); +} diff --git a/atom-bot/package.json b/atom-bot/package.json new file mode 100644 index 0000000..a060290 --- /dev/null +++ b/atom-bot/package.json @@ -0,0 +1,27 @@ +{ + "author": { + "name": "Trygve Laugstøl", + "email": "trygvis@inamo.no", + "url": "https://github.com/trygvis" + }, + "name": "atom-bot", + "description": "IRC Bot that changes topic to the latest in a set of atom feeds", + "keywords": [ + "irc", + "chat" + ], + "version": "0.0.1", + "engines": { + "node": ">=0.6.18" + }, + "dependencies": { + "tinycolor": "0.0.1", + "underscore": "1.3.3" + }, + "licenses": [ + { + "type": "Apache Software License, version 2", + "url": "http://apache.org/licenses/LICENSE-2.0" + } + ] +} diff --git a/dynobot-irc.js b/dynobot-irc.js index 705e534..20f6f79 100644 --- a/dynobot-irc.js +++ b/dynobot-irc.js @@ -1,15 +1,19 @@ +require('tinycolor'); var EventEmitter = require('events').EventEmitter; var util = require('util'); var cp = require('child_process'); -var IRC = require('./node_modules/node-irc/irc').IRC; +var IrcClient = require('./irc-client').IrcClient; var Service = require('dynobot/service.js'); var Channel = require('dynobot/channel.js'); var _ = require('underscore'); var rl = require('readline'); -function FakeSocket(stdin, stdout) { +/************************************************************/ + +function FakeSocket(stdin, stdout, msgHandler) { this.stdin = stdin; this.stdout = stdout; + this.msgHandler = msgHandler; this.connectHandler = undefined; this.errorHandler = undefined; } @@ -45,9 +49,12 @@ FakeSocket.prototype.connect = function(server, port) { } FakeSocket.prototype.write = function(chunk) { - this.stdout.write(chunk); + this.stdout.write(chunk.red); + this.msgHandler && this.msgHandler(chunk); } +/************************************************************/ + var StdinPipe = function() { EventEmitter.call(this); } @@ -55,29 +62,38 @@ util.inherits(StdinPipe, EventEmitter); StdinPipe.prototype.setEncoding = function() {} StdinPipe.prototype.send = function(msg) { - console.log('outgoing: ' + msg); + console.log(msg.blue); stdinPipe.emit('data', msg + '\r\n'); } +/************************************************************/ + var stdinPipe = new StdinPipe(); var irc; +var nick = 'dynobot'; +var realName = 'bots\'r\'us'; +var ident = 'ident'; if(false) { - irc = new IRC('irc.freenode.net', 6667); + irc = new IrcClient(nick, realName, ident, 'irc.freenode.net', 6667); } else { - irc = new IRC(new FakeSocket(stdinPipe, process.stdout)); + var msgHandler = function(line) { + line = line.trim(); + var prefix = ':' + nick + '!' + ident + '@example.org'; + var parts = line.split(' '); + switch(parts[0]) { + case 'JOIN': + stdinPipe.send(prefix + ' JOIN ' + parts[1]); + } + } + irc = new IrcClient(nick, realName, ident,new FakeSocket(stdinPipe, process.stdout, msgHandler)); process.stdin.resume(); } var ircService = new Service('irc', irc); irc.setDebugLevel(3); -irc.on('connected', function() { - irc.join('#bitraf2', function(channel) { - irc.notice(channel, 'well hello yall'); - }); -}); function Plugin(name, script) { this.name = name; @@ -86,13 +102,18 @@ function Plugin(name, script) { } var plugins = [ - new Plugin('arktekk', './echo-bot.js') + new Plugin('echo', './echo-bot/index.js') ]; _.each(plugins, function(plugin) { console.log('Starting ' + plugin.name); // TODO: Resolve plugin.script - var child = cp.fork(plugin.script); + // TODO: set cwd option to the plugin's directory. + var args = []; + var options = { + silent: true + }; + var child = cp.fork(plugin.script, args, options); var channel = new Channel(child); ircService.handle(channel); child.on('exit', function() { @@ -104,8 +125,6 @@ _.each(plugins, function(plugin) { // piping to process.stdout }); -irc.connect('dynobot', 'real name', 'ident'); - process.stdin.resume(); var i = rl.createInterface(process.stdin, process.stdout, null); @@ -176,7 +195,15 @@ function setupRepl() { updatePrompt(); } -stdinPipe.emit('data', ':irc.foo.bar 001 this :Welcome to Some Internet Relay Chat Network this'); +irc.join('#bitraf2', function(name) { + console.log('joined ' + name + ' from dynobot.'); +}); + +console.log('colors: ' + 'from bot'.red + ', ' + 'to bot'.blue); +irc.connect(); + setupRepl(i); -// setInterval(function() {gc();}, 2000); +setTimeout(function() { + stdinPipe.send(':irc.foo.bar 001 this :Welcome to Some Internet Relay Chat Network this'); +}, 1000); diff --git a/echo-bot.js b/echo-bot.js deleted file mode 100644 index 2d721e4..0000000 --- a/echo-bot.js +++ /dev/null @@ -1,17 +0,0 @@ -var irc = function() { - var Proxy = require('dynobot/proxy'); - var Channel = require('dynobot/channel'); - var IRC = require('./node_modules/node-irc/irc').IRC; - - var channel = new Channel(); - return new Proxy(IRC.prototype, 'irc', channel); -}(); - -irc.on('privmsg', function(nick, channel, message) { - console.log('privmsg: ', arguments); - irc.notice(channel, nick + ': ' + message); -}); - -console.log("echo bot started"); - -// process.exit(0); diff --git a/echo-bot/index.js b/echo-bot/index.js new file mode 100644 index 0000000..8c932a8 --- /dev/null +++ b/echo-bot/index.js @@ -0,0 +1,22 @@ +require('tinycolor'); +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 channel = new Channel(); + return new Proxy(IrcClient.prototype, 'irc', channel); +}(); + +irc.on('privmsg', function(nick, channel, message) { +// console.log('privmsg: ', arguments); + irc.notice(channel, nick + ': ' + message); +}); + +irc.join('#bitraf2', function() { + console.log('joined channel!'.green); +}); + +console.log("echo bot started".green); + +// process.exit(0); diff --git a/echo-bot/package.json b/echo-bot/package.json new file mode 100644 index 0000000..6ca0096 --- /dev/null +++ b/echo-bot/package.json @@ -0,0 +1,27 @@ +{ + "author": { + "name": "Trygve Laugstøl", + "email": "trygvis@inamo.no", + "url": "https://github.com/trygvis" + }, + "name": "echo-bot", + "description": "Example bot that sends a NOTICE for each PRIVMSG", + "keywords": [ + "irc", + "chat" + ], + "version": "0.0.1", + "engines": { + "node": ">=0.6.18" + }, + "dependencies": { + "tinycolor": "0.0.1", + "underscore": "1.3.3" + }, + "licenses": [ + { + "type": "Apache Software License, version 2", + "url": "http://apache.org/licenses/LICENSE-2.0" + } + ] +} diff --git a/irc-client.js b/irc-client.js new file mode 100644 index 0000000..bd8edb8 --- /dev/null +++ b/irc-client.js @@ -0,0 +1,103 @@ +var IRC = require('./node_modules/node-irc/irc').IRC; +var util = require('util'); +var events = require('events'); +var _ = require('underscore'); + +function info() { + var copy = [].slice.apply(arguments) + console.log('IrcClient: ' + copy.join(' ')); +} + +function Channel(name) { + this.name = name; + this.listeners = []; +} + +Channel.prototype.addListener = function(f) { + this.listeners.push(f); +}; + +Channel.prototype.fire = function(event) { + _.each(this.listeners, function(f) { + f(event); + }); +}; + +function IrcClient(nick, realName, ident, server, port, password) { + this.nick = nick; + this.realName = realName; + this.ident = ident; + this.server = server; + this.port = port; + this.password = password; + + this.channels = {}; + this.connected = false; + this.debugLevel = undefined; + this.irc = undefined; +} +util.inherits(IrcClient, events.EventEmitter); + +IrcClient.prototype.init = function(irc) { + // TODO: hmm.. + var client = this; + this.debugLevel && irc.setDebugLevel(this.debugLevel); + irc.on('connected', function() { + info('Connected, channels: ' + _.size(client.channels)); + _.each(client.channels, function(channel) { + info('JOINing ' + channel.name); + irc.join(channel.name); + }); + }); + irc.on('disconnected', function() { + info('Disconnected, reconnecting...'); + client.connect(); + }); + irc.on('join', function(nick, channelName) { + _.each(client.channels, function(channel) { + info('JOINed ' + channel.name); + channel.fire(channelName); + }); + }); + irc.on('privmsg', function(nick, channel, message) { + client.emit('privmsg', nick, channel, message); + }); + return irc; +} + +IrcClient.prototype.getChannel = function(name) { + var c = this.channels[name]; + + if(typeof c == "undefined") { + c = new Channel(name); + this.channels[name] = c; + } + + return c; +} + +IrcClient.prototype.setDebugLevel = function(level) { + this.debugLevel = level; + this.irc && this.irc.setDebugLevel(level); +} + +IrcClient.prototype.connect = function() { + this.irc = this.init(new IRC(this.server, this.port, this.password)); + this.irc.connect(this.nick, this.realName, this.ident); +} + +IrcClient.prototype.join = function(name, cb) { + var c = this.getChannel(name); + if(typeof cb != "undefined") { + c.addListener(cb); + } + if(this.connected) { + c.fire(name); + } +} + +IrcClient.prototype.notice = function(to, message) { + this.irc.notice(to, message); +} + +module.exports.IrcClient = IrcClient; diff --git a/lib/arktekk-bot.js b/lib/arktekk-bot.js deleted file mode 100644 index 3a00c7f..0000000 --- a/lib/arktekk-bot.js +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Possible strategies for updating the topic: - * - * o Set the topic unconditionally when the feed changes. This makes - * it possible for users to change the topic and it won't be - * overridden until the feed changes. - * - * o Set the topic on any topic change (making the feed control the - * entire topic) - * - * o Support a delimiter so it can control only a part of the topic, - * like "<>". Example - * - * Next meeting, sat 1900 <> DATA FROM FEED. - * - * A regexp selecting the are to be updated might conver it. - * - * o If the bot changed the topic the last time, it's probably safe to - * just update it. - */ - -var node_irc = require('../node_modules/node-irc/IRC.js') - , cron = require('cron').CronJob - , parser = require('blindparser') - , events = require('events') - , _ = require('underscore'); - -var parserOptions = {}; - -var config; -var cronJobs = []; - -var state = { - channelTopic: undefined, - irc: undefined, - updatingFeed: false, - feeds: [], - newest: { - timestamp: 0, - text: undefined - } -}; -module.exports.state = state; - -var eventEmitter = new events.EventEmitter; - -function startIrc() { - irc = new node_irc.IRC(config.host, config.port); - irc.on('raw', function(data) { console.log(data) }); - irc.on('connected', function(server) { - console.log('Connected to ' + server); - irc.join(config.channel, function(error) { - irc.notice(config.channel, 'well hello yall'); - }); - }); - irc.topic = function(channel, topic) { - irc._socket.write('topic ' + channel + ' :' + topic + '\r\n'); - }; - - irc.on('topic', function(channel, topic) { - console.log("new topic: " + topic); - /* If we're not storing this, it is possible for people to set - * the topic after the bot has set it and it will persist (until - * next update from the feed). - topic = t; - */ - - state.channelTopic = topic; - }); -} - -function updateFeed(feedState) { - console.log("Fetching " + feedState.url); - if(feedState.updatingFeed) - console.log("Already working"); - feedState.updatingFeed = true; - - parser.parseURL(feedState.url, parserOptions, function(err, feed) { -// console.log("Fetched " + feedState.url + ", status=" + (err ? "failure" : "success")); - if(err) { - console.log(err); - return; - } - var newest = processFeed(feed); - if(typeof newest == "object") { - eventEmitter.emit("feedChanged", feedState.url, newest); - } - feedState.updatingFeed = false; - }); -} - -function processFeed(feed) { - // Extracts the username from the feed. - // TODO: Use something better than blindparser to parse atom so that - // each entry has an author too to get the full name. - if(typeof feed.items[0] == "undefined") { - console.log("feed does not contain any items", feed); - return undefined; - } - var match = /^http:\/\/twitter.com\/([a-zA-Z0-9_]+)\/.*$/.exec(feed.items[0].link) - if(match.length != 2) { - return undefined; - } - return { - text: feed.items[0].title, - author: match[1], - timestamp: feed.items[0].date - }; -} -module.exports.processFeed = processFeed; - -eventEmitter.on("feedChanged", function(url, newest) { - state.feeds[url] = newest; - - if(state.newest.timestamp >= newest.timestamp) { -// console.log("oold: " + newest.text); - return; - } - - var text = newest.author + ": " + newest.text; - console.log("New topic", newest.timestamp, url, text); - state.newest = newest; - if(config.connect && state.topic != text) { - irc.topic(config.channel, text); - } -}); - -eventEmitter.on("configUpdated", function(c) { - setup(); -}); - -function setup() { - console.log("Stopping " + cronJobs.length + " cron jobs"); - _.each(cronJobs, function(job) { job.stop(); }); - cronJobs = []; - _.each(config.feeds, function(feed) { - var state = { - url: feed, - updatingFeed: false, - last: undefined - }; - var job = new cron("*/10 * * * *", function() { - updateFeed(state); - }, function() {}, true); - cronJobs.push(job); - }); -} - -function start(c) { - config = c; - startIrc(); - setup(); - if(config.connect) { - console.log('Connecting to ' + config.host + ':' + config.port); - irc.connect(config.nick); - } else { - console.log('Not connecting to IRC'); - } -} - -module.exports.start = start; - -module.exports.emit = function(config) { - eventEmitter.emit("configUpdated", config); -} diff --git a/package.json b/package.json index d45104d..948dea7 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "email": "trygvis@inamo.no", "url": "https://github.com/trygvis" }, - "name": "bitraf-irc-client", - "description": "Bitraf's IRC client", + "name": "arktekk-irc-client", + "description": "Arktekk's IRC client", "keywords": [ "irc", "chat" @@ -16,13 +16,15 @@ "node": ">=0.4.7" }, "dependencies": { - "blindparser": "0.0.13" + "blindparser": "0.0.13", "cmdopt": "0.2.0", "cron": "~0.3.2", "date": "1.0.2", + "dynobot": "https://github.com/einaros/dynobot/tarball/master", "getit": "~0.1.7", "mocha": "~1.0.3", "node-irc": "https://github.com/einaros/node-irc/tarball/master", + "tinycolor": "0.0.1" "underscore": "1.3.3", }, "devDependencies": {}, -- cgit v1.2.3