summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2012-06-01 19:29:08 +0200
committerTrygve Laugstøl <trygvis@inamo.no>2012-06-01 19:29:08 +0200
commita105b34ab53963ab651810f33caa2ee51869b1c7 (patch)
treebe49b089e1d4a81d6b327c6370afcc14c60ac865
downloaddynobot-irc-a105b34ab53963ab651810f33caa2ee51869b1c7.tar.gz
dynobot-irc-a105b34ab53963ab651810f33caa2ee51869b1c7.tar.bz2
dynobot-irc-a105b34ab53963ab651810f33caa2ee51869b1c7.tar.xz
dynobot-irc-a105b34ab53963ab651810f33caa2ee51869b1c7.zip
o Initial import.
-rw-r--r--.gitignore1
-rw-r--r--Makefile7
-rw-r--r--lib/arktekk-bot.js165
-rw-r--r--lib/irc.js12
-rw-r--r--main.js75
-rw-r--r--package.json36
-rw-r--r--test/feed.xml297
-rw-r--r--test/irc.test.js11
8 files changed, 604 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..eb73cae
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+PATH := $(PATH):node_modules/.bin
+all: test
+
+test:
+ mocha test/*.test.js
+
+.PHONY: all test
diff --git a/lib/arktekk-bot.js b/lib/arktekk-bot.js
new file mode 100644
index 0000000..3a00c7f
--- /dev/null
+++ b/lib/arktekk-bot.js
@@ -0,0 +1,165 @@
+/*
+ * 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/lib/irc.js b/lib/irc.js
new file mode 100644
index 0000000..935d2b3
--- /dev/null
+++ b/lib/irc.js
@@ -0,0 +1,12 @@
+module.exports.nickGenerator = function(nick) {
+ var nickCount = 0;
+ var nickAdditions = [ '', '^', '-', '_', '\\', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ];
+
+ return function() {
+ if (nickCount == nickAdditions.length) {
+ nickCount = 0;
+ }
+
+ return nick + nickAdditions[nickCount++];
+ }
+}
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..700bd5f
--- /dev/null
+++ b/main.js
@@ -0,0 +1,75 @@
+var cmdopt = require('cmdopt')
+ , os = require('os')
+ , repl = require('repl')
+ , _ = require('underscore');
+
+var parser = new cmdopt.Parser();
+parser.option("-h, --help", "show help");
+parser.option("-c, --channel=CHANNEL", "IRC channel to join");
+parser.option("-n, --nick=NICK", "Nickname of the bot");
+parser.option("--connect=[yes,no]", "Should the bot connect to IRC").action(function(opts, val) {
+ opts.connect = val === "yes"
+});
+parser.option("--feed=FEED", "A feed to watch").action(function(opts, val) {
+ if (!opts.feeds)
+ opts.feeds = [];
+ opts.feeds.push(val);
+});
+
+var args = process.argv.slice(2);
+var defaults = {
+ channel: "#hoonk",
+ feeds: [
+// "http://search.twitter.com/search.atom?q=arktekk",
+// "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=awesome",
+ "http://search.twitter.com/search.atom?q=eurovision",
+ ],
+ nick: os.hostname(),
+ host: "gibson.freenode.net",
+ port: 6667,
+ connect: true
+};
+
+var bots = [{
+ name: 'arktekk',
+ js: './lib/arktekk-bot.js'
+}];
+
+try {
+ var config = parser.parse(args);
+ config = _.defaults(config, defaults);
+ if (config.help) {
+ console.log(parser.help());
+ console.log("Config:");
+ console.log(config);
+ return;
+ }
+
+ _.each(bots, function(bot) {
+ var instance = require(bot.js)
+ instance.start(config);
+ bot.instance = instance;
+ });
+ var r = repl.start('irc> ');
+ r.context.bots = bots;
+ r.context.config = JSON.parse(JSON.stringify(config));
+ // TODO: Make the config per-plugin.
+ r.context.setConfig = function(c) {
+ // TODO: store configuration
+ console.log("Setting config");
+// console.log(c);
+ _.each(bots, function(bot) { bot.instance.emit("configUpdated", config); });
+ config = c;
+ r.context.config = JSON.parse(JSON.stringify(c));
+ return "Config updated";
+ }
+} catch (ex) {
+ if (ex instanceof cmdopt.ParseError) {
+ process.stderr.write(ex.message + "\n");
+ process.exit(1);
+ }
+ throw ex;
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d45104d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,36 @@
+{
+ "author": {
+ "name": "Trygve Laugstøl",
+ "email": "trygvis@inamo.no",
+ "url": "https://github.com/trygvis"
+ },
+ "name": "bitraf-irc-client",
+ "description": "Bitraf's IRC client",
+ "keywords": [
+ "irc",
+ "chat"
+ ],
+ "version": "0.0.1",
+ "main": "main.js",
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "dependencies": {
+ "blindparser": "0.0.13"
+ "cmdopt": "0.2.0",
+ "cron": "~0.3.2",
+ "date": "1.0.2",
+ "getit": "~0.1.7",
+ "mocha": "~1.0.3",
+ "node-irc": "https://github.com/einaros/node-irc/tarball/master",
+ "underscore": "1.3.3",
+ },
+ "devDependencies": {},
+ "optionalDependencies": {},
+ "licenses": [
+ {
+ "type": "Apache Software License, version 2",
+ "url": "http://apache.org/licenses/LICENSE-2.0"
+ }
+ ]
+}
diff --git a/test/feed.xml b/test/feed.xml
new file mode 100644
index 0000000..113c438
--- /dev/null
+++ b/test/feed.xml
@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xmlns:google="http://base.google.com/ns/1.0" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns="http://www.w3.org/2005/Atom" xmlns:twitter="http://api.twitter.com/" xmlns:georss="http://www.georss.org/georss" xml:lang="en-US">
+ <id>tag:search.twitter.com,2005:search/from:AgileBorat OR from:KongenDin</id>
+ <link type="text/html" href="http://search.twitter.com/search?q=from%3AAgileBorat+OR+from%3AKongenDin" rel="alternate"/>
+ <link type="application/atom+xml" href="http://search.twitter.com/search.atom?q=from%3AAgileBorat+OR+from%3AKongenDin" rel="self"/>
+ <title>from:AgileBorat OR from:KongenDin - Twitter Search</title>
+ <link type="application/opensearchdescription+xml" href="http://twitter.com/opensearch.xml" rel="search"/>
+ <link type="application/atom+xml" href="http://search.twitter.com/search.atom?since_id=206355859759046656&amp;q=from%3AAgileBorat%20OR%20from%3AKongenDin" rel="refresh"/>
+ <updated>2012-05-26T12:07:25Z</updated>
+ <openSearch:itemsPerPage>15</openSearch:itemsPerPage>
+ <link type="application/atom+xml" href="http://search.twitter.com/search.atom?page=2&amp;max_id=206355859759046656&amp;q=from%3AAgileBorat%20OR%20from%3AKongenDin" rel="next"/>
+ <entry>
+ <id>tag:search.twitter.com,2005:206355859759046656</id>
+ <published>2012-05-26T12:07:25Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/206355859759046656" rel="alternate"/>
+ <title>i dag skal jeg ikke kjøpe noe som det ikke står "tutti frutti" på</title>
+ <content type="html">i dag skal jeg ikke kjøpe noe som det ikke står "tutti frutti" på</content>
+ <updated>2012-05-26T12:07:25Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>no</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:206304970952278017</id>
+ <published>2012-05-26T08:45:12Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/206304970952278017" rel="alternate"/>
+ <title>klarer aldri å huske hvem som er knoll og hvem som er tott, men det er ikke akkurat noe stort problem</title>
+ <content type="html">klarer aldri å huske hvem som er knoll og hvem som er tott, men det er ikke akkurat noe stort problem</content>
+ <updated>2012-05-26T08:45:12Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>no</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:206274812157505537</id>
+ <published>2012-05-26T06:45:22Z</published>
+ <link type="text/html" href="http://twitter.com/AgileBorat/statuses/206274812157505537" rel="alternate"/>
+ <title>Svetlana is offer to explain term of "counter rucking". But Azamat is quite sure she is only hear wrong.</title>
+ <content type="html">Svetlana is offer to explain term of "counter rucking". But Azamat is quite sure she is only hear wrong.</content>
+ <updated>2012-05-26T06:45:22Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1327024640/borat_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://itunes.apple.com/us/app/twitter/id409789998?mt=12" rel="nofollow"&gt;Twitter for Mac&lt;/a&gt;</twitter:source>
+ <twitter:lang>en</twitter:lang>
+ <author>
+ <name>AgileBorat (Agile Borat)</name>
+ <uri>http://twitter.com/AgileBorat</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:206273546287194112</id>
+ <published>2012-05-26T06:40:20Z</published>
+ <link type="text/html" href="http://twitter.com/AgileBorat/statuses/206273546287194112" rel="alternate"/>
+ <title>Vlad is introduce more of rugby term in Scrum team. But is not sure of how do proper "counter rucking".</title>
+ <content type="html">Vlad is introduce more of rugby term in Scrum team. But is not sure of how do proper "counter rucking".</content>
+ <updated>2012-05-26T06:40:20Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1327024640/borat_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://itunes.apple.com/us/app/twitter/id409789998?mt=12" rel="nofollow"&gt;Twitter for Mac&lt;/a&gt;</twitter:source>
+ <twitter:lang>en</twitter:lang>
+ <author>
+ <name>AgileBorat (Agile Borat)</name>
+ <uri>http://twitter.com/AgileBorat</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:206050172109721600</id>
+ <published>2012-05-25T15:52:43Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/206050172109721600" rel="alternate"/>
+ <title>de spanske s-ene er så vriene at jeg sier bare Barfelona jeg</title>
+ <content type="html">de spanske s-ene er så vriene at jeg sier bare Barfelona jeg</content>
+ <updated>2012-05-25T15:52:43Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>da</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:206012501576523778</id>
+ <published>2012-05-25T13:23:02Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/206012501576523778" rel="alternate"/>
+ <title>kongen befaler at man kan ha pinseegg i pinsen. De ligner på påskeegg, men er større.</title>
+ <content type="html">kongen befaler at man kan ha pinseegg i pinsen. De ligner på påskeegg, men er større.</content>
+ <updated>2012-05-25T13:23:02Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>no</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:205652909323591682</id>
+ <published>2012-05-24T13:34:09Z</published>
+ <link type="text/html" href="http://twitter.com/AgileBorat/statuses/205652909323591682" rel="alternate"/>
+ <title>Vlad is introduce more of rugby term in Scrum team. When Azamat is do "dump tackle", database is stay dumped! Is nice.</title>
+ <content type="html">Vlad is introduce more of rugby term in Scrum team. When Azamat is do "dump tackle", database is stay dumped! Is nice.</content>
+ <updated>2012-05-24T13:34:09Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1327024640/borat_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://itunes.apple.com/us/app/twitter/id409789998?mt=12" rel="nofollow"&gt;Twitter for Mac&lt;/a&gt;</twitter:source>
+ <twitter:lang>en</twitter:lang>
+ <author>
+ <name>AgileBorat (Agile Borat)</name>
+ <uri>http://twitter.com/AgileBorat</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:205627784033345536</id>
+ <published>2012-05-24T11:54:18Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/205627784033345536" rel="alternate"/>
+ <title>jeg har blitt rød i nakken!!! Har antagelig fått jentelus.</title>
+ <content type="html">jeg har blitt rød i nakken!!! Har antagelig fått jentelus.</content>
+ <updated>2012-05-24T11:54:18Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>no</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:205584907840393216</id>
+ <published>2012-05-24T09:03:56Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/205584907840393216" rel="alternate"/>
+ <title>hold båtisene deres unna badekaret. de kantrer veldig lett.</title>
+ <content type="html">hold båtisene deres unna badekaret. de kantrer veldig lett.</content>
+ <updated>2012-05-24T09:03:56Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>no</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:205397941488336896</id>
+ <published>2012-05-23T20:41:00Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/205397941488336896" rel="alternate"/>
+ <title>ett sted går grensen, og denne "Biber" er på feil side av den. HVis dere vil se 18-åringer spankulere kan dere nøye dere med garden!!</title>
+ <content type="html">ett sted går grensen, og denne "Biber" er på feil side av den. HVis dere vil se 18-åringer spankulere kan dere nøye dere med garden!!</content>
+ <updated>2012-05-23T20:41:00Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>no</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:205307417414742018</id>
+ <published>2012-05-23T14:41:17Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/205307417414742018" rel="alternate"/>
+ <title>ba slottsfrisøren om å klippe meg som Odd nerdrum, men endte opp som ODd einar dørum igjen :-((</title>
+ <content type="html">ba slottsfrisøren om å klippe meg som Odd nerdrum, men endte opp som ODd einar dørum igjen :-((</content>
+ <updated>2012-05-23T14:41:17Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>no</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:205295504064655360</id>
+ <published>2012-05-23T13:53:57Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/205295504064655360" rel="alternate"/>
+ <title>når jeg ser en dåp liker jeg å tenke at babyen er et glass med melk og at døpevannet er o'boy!!</title>
+ <content type="html">når jeg ser en dåp liker jeg å tenke at babyen er et glass med melk og at døpevannet er o'boy!!</content>
+ <updated>2012-05-23T13:53:57Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>no</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:204881566177050625</id>
+ <published>2012-05-22T10:29:06Z</published>
+ <link type="text/html" href="http://twitter.com/AgileBorat/statuses/204881566177050625" rel="alternate"/>
+ <title>I am wish I have one dollar for every smiling Scrum Master I am meet! Then I would have $0.50.</title>
+ <content type="html">I am wish I have one dollar for every smiling Scrum Master I am meet! Then I would have $0.50.</content>
+ <updated>2012-05-22T10:29:06Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1327024640/borat_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://itunes.apple.com/us/app/twitter/id409789998?mt=12" rel="nofollow"&gt;Twitter for Mac&lt;/a&gt;</twitter:source>
+ <twitter:lang>en</twitter:lang>
+ <author>
+ <name>AgileBorat (Agile Borat)</name>
+ <uri>http://twitter.com/AgileBorat</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:204526346481840128</id>
+ <published>2012-05-21T10:57:35Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/204526346481840128" rel="alternate"/>
+ <title>Ari sier at det er ku og gris inni pølsene!!! ÆÆÆÆSJ!!</title>
+ <content type="html">Ari sier at det er ku og gris inni pølsene!!! ÆÆÆÆSJ!!</content>
+ <updated>2012-05-21T10:57:35Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/"&gt;web&lt;/a&gt;</twitter:source>
+ <twitter:lang>da</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+ <entry>
+ <id>tag:search.twitter.com,2005:204296372051132416</id>
+ <published>2012-05-20T19:43:45Z</published>
+ <link type="text/html" href="http://twitter.com/KongenDin/statuses/204296372051132416" rel="alternate"/>
+ <title>det er ikke vanskelig å dunke i basketball. Med utendørstrampoline</title>
+ <content type="html">det er ikke vanskelig å dunke i basketball. Med utendørstrampoline</content>
+ <updated>2012-05-20T19:43:45Z</updated>
+ <link type="image/png" href="http://a0.twimg.com/profile_images/1133995526/SEILING__kongen_tit_808754x_normal.jpg" rel="image"/>
+ <twitter:geo/>
+ <twitter:metadata>
+ <twitter:result_type>recent</twitter:result_type>
+ </twitter:metadata>
+ <twitter:source>&lt;a href="http://twitter.com/#!/download/iphone" rel="nofollow"&gt;Twitter for iPhone&lt;/a&gt;</twitter:source>
+ <twitter:lang>no</twitter:lang>
+ <author>
+ <name>KongenDin (Harald Rex)</name>
+ <uri>http://twitter.com/KongenDin</uri>
+ </author>
+ </entry>
+</feed>
diff --git a/test/irc.test.js b/test/irc.test.js
new file mode 100644
index 0000000..af89a81
--- /dev/null
+++ b/test/irc.test.js
@@ -0,0 +1,11 @@
+var assert = require('assert'),
+ irc = require('../lib/irc.js');
+
+describe('irc.js', function() {
+ it('work', function() {
+ var nick = irc.nickGenerator("foo");
+ assert.equal("function", typeof nick);
+ assert.equal("foo", nick());
+ assert.equal("foo^", nick());
+ });
+});