├── .gitignore ├── modules ├── git.js ├── fortune.js ├── links.js ├── seen.js ├── stalk.js ├── url.js └── notes.js ├── README.md ├── config.js.example ├── package.json ├── lib └── pretty.js └── bot.js /.gitignore: -------------------------------------------------------------------------------- 1 | config.js 2 | node_modules 3 | -------------------------------------------------------------------------------- /modules/git.js: -------------------------------------------------------------------------------- 1 | // ~r 2 | irc_client.addListener('message', function(from, to, text) { 3 | var matches = text.match(/^~r\s?([0-9a-f]{6,})/i); 4 | if (matches) { 5 | irc_client.say_in_response(from, to, 'http://github.com/gallery/gallery3/commit/' + matches[1]); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /modules/fortune.js: -------------------------------------------------------------------------------- 1 | irc_client.addListener('message', function(from, to, text) { 2 | return; // Temporarily disable 3 | if (text.match(/^~fortune/i)) { 4 | var child_process = require('child_process'); 5 | 6 | child_process.exec('fortune -s', function(err, stdout, stderr) { 7 | irc_client.say_in_response(from, to, stdout.replace(/[\t\r\n]/g, ' ')); 8 | }); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gallery\_bot 2 | 3 | ## Requirements 4 | 5 | * [node.js](http://nodejs.org/) 6 | * [Redis](http://redis.io/) 7 | 8 | ## Installation 9 | 10 | Create a configuration file based on the example: 11 | 12 | ``` 13 | cp config.js.example config.js 14 | vim config.js # and edit to your needs 15 | ``` 16 | 17 | Install the dependencies: 18 | 19 | ``` 20 | npm install 21 | ``` 22 | 23 | ## Running 24 | 25 | ``` 26 | node bot.js 27 | ``` 28 | -------------------------------------------------------------------------------- /config.js.example: -------------------------------------------------------------------------------- 1 | options = { 2 | server : 'chat.freenode.net', 3 | nickName : 'gallery_bot', 4 | userName : 'gbot', 5 | realName : 'Bot for #gallery', 6 | channels : ['#gallery-test'], 7 | port : 6667, 8 | debug : true, 9 | showErrors : true, 10 | autoRejoin : true, 11 | autoConnect : true, 12 | secure : false 13 | }; 14 | 15 | redis_options = { 16 | port: 6379, 17 | host: '127.0.0.1' 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andy Lindeman (http://andylindeman.com/)", 3 | "name": "gallery_bot", 4 | "description": "Bot for #gallery on irc.freeenode.net", 5 | "version": "0.0.0", 6 | "homepage": "http://github.com/alindeman/gallery_bot", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/alindeman/gallery_bot.git" 10 | }, 11 | "engines": { 12 | "node": "~v0.4.7" 13 | }, 14 | "dependencies": { 15 | "irc": "~>0.2.1", 16 | "redis": "~>0.7.2", 17 | "underscore": "~>1.3.3" 18 | }, 19 | "devDependencies": {} 20 | } 21 | -------------------------------------------------------------------------------- /modules/links.js: -------------------------------------------------------------------------------- 1 | // ~pastebin 2 | irc_client.addListener('message', function(from, to, text) { 3 | if (text.match(/^~pastebin/i)) { 4 | irc_client.say_in_response(from, to, 'Paste stuff here (rather than in the channel): http://tools.gallery2.org/pastebin/?sesame'); 5 | } 6 | }); 7 | 8 | // ~code 9 | irc_client.addListener('message', function(from, to, text) { 10 | var matches = text.match(/^~code (\S+)/i); 11 | if (matches) { 12 | irc_client.say_in_response(from, to, 'https://github.com/gallery/gallery3/blob/master/' + matches[1]); 13 | } 14 | }); 15 | 16 | // ~trac or ~ticket 17 | irc_client.addListener('message', function(from, to, text) { 18 | var matches = text.match(/^~(?:trac|ticket) (\S+)/i); 19 | if (matches) { 20 | irc_client.say_in_response(from, to, 'https://sourceforge.net/apps/trac/gallery/ticket/' + matches[1]); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /modules/seen.js: -------------------------------------------------------------------------------- 1 | // logs time a user says anything 2 | irc_client.addListener('message', function(from, to, text) { 3 | if (to.is_channel()) { // sent to a channel 4 | var seen_object = [new Date().valueOf(), text]; 5 | redis_client.set('seen:' + from.toLowerCase(), JSON.stringify(seen_object)); 6 | } 7 | }); 8 | 9 | // ~seen 10 | irc_client.addListener('message', function(from, to, text) { 11 | var matches = text.match(/^~seen (\S+)/i); 12 | if (matches) { 13 | redis_client.get('seen:' + matches[1].toLowerCase(), function(err, obj) { 14 | if (obj) { 15 | var seen_object = JSON.parse(obj); 16 | irc_client.say_in_response(from, to, 'I saw ' + matches[1] + ' ' + pretty_date.prettyDate(new Date(seen_object[0])) + ' saying "' + seen_object[1] + '"'); 17 | } else { 18 | irc_client.say_in_response(from, to, 'I have never seen ' + matches[1]); 19 | } 20 | }); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /lib/pretty.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript Pretty Date 3 | * Copyright (c) 2008 John Resig (jquery.com) 4 | * Licensed under the MIT license. 5 | */ 6 | 7 | // Takes an ISO time and returns a string representing how 8 | // long ago the date represents. 9 | exports.prettyDate = function(date){ 10 | var diff = (((new Date()).getTime() - date.getTime()) / 1000), 11 | day_diff = Math.floor(diff / 86400); 12 | 13 | if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 ) 14 | return; 15 | 16 | return day_diff == 0 && ( 17 | diff < 60 && "just now" || 18 | diff < 120 && "1 minute ago" || 19 | diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || 20 | diff < 7200 && "1 hour ago" || 21 | diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || 22 | day_diff == 1 && "Yesterday" || 23 | day_diff < 7 && day_diff + " days ago" || 24 | day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago"; 25 | } 26 | -------------------------------------------------------------------------------- /modules/stalk.js: -------------------------------------------------------------------------------- 1 | // ~stalk 2 | irc_client.addListener('message', function(from, to, text) { 3 | var matches = text.match(/^~stalk (\S+)/i); 4 | if (matches) { 5 | redis_client.rpush('stalks:' + matches[1].toLowerCase(), from, function(err, res) { 6 | irc_client.say(to, 'OK, I will notify you when ' + matches[1] + ' next speaks'); 7 | }); 8 | } 9 | }); 10 | 11 | // Notify on join or message 12 | function notify_stalks(channel, nick) { 13 | redis_client.rpop('stalks:' + nick.toLowerCase(), function(err, obj) { 14 | if (obj) { 15 | var stalker = obj; 16 | irc_client.say(stalker, 'I just saw ' + nick + ' in ' + channel); 17 | 18 | // Recurse to get all stalkers 19 | notify_stalks(channel, nick); 20 | } 21 | }); 22 | } 23 | 24 | irc_client.addListener('join', notify_stalks); 25 | irc_client.addListener('message', function(from, to, text) { 26 | if (to.is_channel()) { 27 | notify_stalks(to, from); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | irc = require('irc'); 2 | redis = require('redis'); 3 | 4 | require('./config.js'); 5 | 6 | redis_client = redis.createClient(redis_options.port, redis_options.host); 7 | irc_client = new irc.Client(options.server, options.nickName, options); 8 | 9 | // Various support methods 10 | // TODO: Move to separate files? 11 | 12 | // Determines whether a string represents a channel name 13 | String.prototype.is_channel = function() { 14 | // Something that starts with a letter is _not_ a channel 15 | return this.match(/^[a-z]/i) ? false : true; 16 | }; 17 | 18 | // Responds in kind via the channel or private message 19 | irc.Client.prototype.say_in_response = function(from, to, text) { 20 | dest = to.is_channel() ? to : from; 21 | this.say(dest, text); 22 | }; 23 | 24 | // Libraries/support 25 | pretty_date = require('./lib/pretty.js'); 26 | 27 | // TODO: Autoload everything in modules/ directory? 28 | require('./modules/notes.js'); 29 | require('./modules/git.js'); 30 | require('./modules/links.js'); 31 | require('./modules/seen.js'); 32 | require('./modules/stalk.js'); 33 | require('./modules/url.js'); 34 | require('./modules/fortune.js'); 35 | -------------------------------------------------------------------------------- /modules/url.js: -------------------------------------------------------------------------------- 1 | irc_client.addListener('message', function(from, to, text) { 2 | var matches = text.match(/^~reg(?:ister[-_]?)?url (\S+) (\S+)/i); 3 | if (matches) { 4 | redis_client.set('url:' + matches[1].toLowerCase(), matches[2], function(err, res) { 5 | irc_client.say_in_response(from, to, 'URL ' + matches[1] + ' registered!'); 6 | }); 7 | } 8 | }); 9 | 10 | irc_client.addListener('message', function(from, to, text) { 11 | var matches = text.match(/^~unreg(?:ister[-_]?)?url (\S+)/i); 12 | if (matches) { 13 | redis_client.del('url:' + matches[1].toLowerCase(), function(err, res) { 14 | irc_client.say_in_response(from, to, 'URL ' + matches[1] + ' unregistered!'); 15 | }); 16 | } 17 | }); 18 | 19 | irc_client.addListener('message', function(from, to, text) { 20 | var matches = text.match(/^~(?:url )?(\S+)\s?(.*)$/i); 21 | if (matches) { 22 | var url_id = matches[1].toLowerCase(); 23 | var positional_matches = matches[2].split(/\s+/); 24 | 25 | redis_client.get('url:' + url_id, function(err, obj) { 26 | if (obj) { 27 | url_with_args = obj.replace(/%s/g, function() { return positional_matches.shift(); }); 28 | irc_client.say_in_response(from, to, url_with_args); 29 | } 30 | }); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /modules/notes.js: -------------------------------------------------------------------------------- 1 | function list_notes(nick, num_notes) { 2 | if (!num_notes) { 3 | num_notes = 0; 4 | } 5 | 6 | redis_client.rpop('notes:' + nick.toLowerCase(), function(err, obj) { 7 | if (obj === null && num_notes === 0) { 8 | irc_client.say(nick, '[No Notes]'); 9 | } else if (obj !== null) { 10 | note_object = JSON.parse(obj); 11 | irc_client.say(nick, '[From: ' + note_object[0] + '] ' + note_object[1]); 12 | 13 | // Recurse to list all notes 14 | num_notes += 1; 15 | list_notes(nick, num_notes); 16 | } 17 | }); 18 | } 19 | 20 | // callback is function(num_notes) 21 | function num_notes_available(nick, callback) { 22 | redis_client.llen('notes:' + nick.toLowerCase(), function(err, obj) { callback(obj); }); 23 | } 24 | 25 | function notify_user_of_available_notes(nick) { 26 | num_notes_available(nick, function(num_notes) { 27 | if (num_notes > 0) { 28 | irc_client.send('NOTICE', nick, 'Notes are available for you. Send ~list-notes in the channel or via /msg to view them'); 29 | } 30 | }); 31 | } 32 | 33 | // note to : ... 34 | irc_client.addListener('message', function(from, to, text) { 35 | var matches = text.match(/^note to (\S+): (.*)$/i); 36 | if (matches) { 37 | redis_client.rpush('notes:' + matches[1].toLowerCase(), JSON.stringify([from, matches[2]]), function(err, res) { 38 | irc_client.send('NOTICE', from, 'Note Recorded!'); 39 | }); 40 | } 41 | }); 42 | 43 | // ~list-notes 44 | irc_client.addListener('message', function(from, to, text) { 45 | var matches = text.match(/^~list-notes/i); 46 | if (matches) { 47 | list_notes(from); 48 | } 49 | }); 50 | 51 | // notification of available notes when user joins channel 52 | irc_client.addListener('join', function(channel, nick) { 53 | notify_user_of_available_notes(nick); 54 | }); 55 | 56 | // notification of available notes when user changes nick 57 | irc_client.addListener('nick', function(oldnick, newnick, channels) { 58 | notify_user_of_available_notes(newnick); 59 | }); 60 | --------------------------------------------------------------------------------