Skip to content

Instantly share code, notes, and snippets.

@sfionov
Created March 5, 2012 23:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sfionov/1982073 to your computer and use it in GitHub Desktop.
Save sfionov/1982073 to your computer and use it in GitHub Desktop.
OverBot IRC Bot
/**
* @author sig_wall
* @date 2011/01/03
*/
// Includes
var util = require('util');
var request = require('request');
var htmlparser = require('htmlparser');
var _u = require('underscore');
var irc = require('irc');
var bitly = require('bitly');
var fs = require('fs');
var crypto = require('crypto');
var im = require('imagemagick');
var iconv = require('iconv');
var jsdom = require('jsdom');
// Settings
var ircOptions = {
server: "localhost",
port: 6667,
nick: "overbot",
userName: "overbot",
realName: "NodeJS",
channels: [ "#botters" ],
debug: true
};
var configFile = "overbot.cfg";
var bitlyLogin = "";
var bitlyKey = "";
var nickServPassword = "";
var defaultTimeout = 600000;
var feedsInfo = [
{
name: 'lab',
channel: '#botters',
url: 'http://www.overclockers.ru/rss/lab.rss',
timeout: defaultTimeout
},
{
name: 'hw',
channel: '#botters',
url: 'http://www.overclockers.ru/rss/hardnews.rss',
timeout: defaultTimeout
}
];
var reportError = function(e) {
if (e) {
if (e.stack) {
util.log(e.message + '\n' + e.stack);
} else {
util.log(e);
console.trace();
}
}
}
// Classes definition
var Config = _u.extend(function() {
Config.prototype.initialize.apply(this, arguments);
}, {
prototype: {
initialize: function(fileName) {
this.attributes = {};
this.fileName = fileName;
var self = this;
fs.readFile(this.fileName, 'utf8', function(err, data) {
try {
if (err) {
throw err;
}
self.attributes = JSON.parse(data);
} catch (e) {
reportError(e);
}
});
},
set: function(key, value) {
this.attributes[key] = value;
fs.writeFile(this.fileName, JSON.stringify(this.attributes, null, ' '));
},
get: function(key) {
return this.attributes[key];
}
}
});
var Feed = _u.extend(function() {
Feed.prototype.initialize.apply(this, arguments);
}, {
prototype: {
initialize: function(options) {
this.name = options.name;
this.channel = options.channel;
this.url = options.url;
this.timeout = options.timeout;
},
fetch: function() {
util.log('Fetching feed: ' + this.url);
var self = this;
var handler = new htmlparser.RssHandler(function (error, dom) {
_u.each(dom.items, function(param){
var hash = crypto.createHash('md5').update(param.id).digest('base64').substr(0,5);
var saidIds = config.get('saidIds') || [];
if (!_u(saidIds).include(hash)) {
request.head(param.link, function(err, response) {
bitlyApi.shorten(response.request.href, function(err, response) {
bot.say(self.channel, self.name + ' ' + response.data.url + ' \u00033' + param.title + '\u0003');
});
});
util.log('new item: ' + param.title);
saidIds = _u(saidIds).push(hash);
config.set('saidIds', saidIds);
}
});
});
var parser = new htmlparser.Parser(handler);
request(this.url, function (error, response, body) {
if (!error && response.statusCode == 200) {
parser.parseComplete(body)
} else {
reportError(new String(error));
}
});
},
start: function() {
var last = new Date();
var self = this;
this.last = last;
var fetch = function() {
if (self.last == last) {
try {
self.fetch();
} catch (e) {
reportError(e);
}
setTimeout(fetch, self.timeout);
}
};
fetch();
},
stop: function() {
this.last = new Date();
}
}
});
var Feeds = _u.extend(function() {
Feeds.prototype.initialize.apply(this, arguments);
}, {
prototype: {
initialize: function(feedsInfo) {
var self = this;
this.feeds = [];
feedsInfo && _u(feedsInfo).each(function(info) {
self.add(new Feed(info));
});
},
add: function(feed) {
this.feeds.push(feed);
},
start: function(channel) {
_u(this.feeds).each(function(feed){
if (feed.channel == channel) {
feed.start();
}
});
},
stop: function(channel) {
_u(this.feeds).each(function(feed){
if (channel == undefined || feed.channel == channel) {
feed.stop();
}
});
}
}
});
var Link = function(url) {
this.url = url;
}
Link.prototype = {
maxPageSize: 1048576,
maxImageSize: 50000000,
identify: function(say) {
var message = "";
var self = this;
request.get({uri: this.url, onResponse: true}, function(error, response) {
if (error) return;
self.type = response.headers['content-type'];
if (self.type && self.type.split(';')[0].trim() == 'text/html') {
var body = '';
var size = 0;
response.on('data', function(data) {
body += data.toString('binary');
size += data.length;
if (size >= self.maxPageSize) {
response.request.abort();
}
});
response.on('end', function() {
try {
jsdom.env({
html: body,
done: function(error, win) {
if (error) return;
self.document = win.document;
var headers = self.getHttpEquivHeaders();
if (headers.hasOwnProperty('content-type')) {
self.type = headers['content-type'];
}
}
});
jsdom.env({
html: new iconv.Iconv(self.getCharset(),'utf-8//ignore').convert(new Buffer(body, 'binary')),
done: function(error, win) {
self.document = win.document;
var title = self.getTitle();
if (title) {
say("^= " + title);
}
}
});
} catch(e) {
reportError(e);
}
});
} else if (self.type && self.type.split('/')[0] == 'image') {
try {
var tempdir = ['TMPDIR','TMP','TERM']
.map(function(i){
return process.env[i];
})
.concat(['/tmp'])
.map(function(path) {
try {
return path ? fs.realpathSync(path) : undefined;
} catch (e) {
return undefined;
}
})
.filter(Boolean)
.shift();
var tempFile = new fs.WriteStream(tempdir + '/' + Math.random().toString(36));
var realSize = response.headers['content-length'];
var size = 0;
response.on('data', function(data) {
tempFile.write(data);
size += data.length;
if (size >= self.maxImageSize) {
response.request.abort();
}
});
response.on('end', function() {
try {
tempFile.flush();
tempFile.end();
im.identify(['-format', '%m %w %h %b %n\n', tempFile.path], function(error,info){
if (!error) {
var infos = '';
try {
infos = info.split('\n').filter(String).slice(-1)[0];
} catch (e) {}
if (infos) {
var attrs = infos.split(' ');
var format = attrs[0];
var width = parseInt(attrs[1], 10);
var height = parseInt(attrs[2], 10);
var kbytes = Math.round(parseInt(attrs[3], 10) / 100) / 10;
var frames = parseInt(attrs[4], 10);
if (frames > 1) {
say("^= анимированный " + format + ", " + width + "x" + height + ", " + kbytes + " КБ.");
} else {
say("^= " + format + ", " + width + "x" + height + ", " + kbytes + " КБ.");
}
}
fs.unlinkSync(tempFile.path);
} else {
console.log(error);
fs.unlinkSync(tempFile.path);
}
});
} catch(e) {
if (e) console.log(e.toString());
}
});
} catch (e) {
reportError(e);
}
} else {
console.log('invalid link');
response.request.abort();
}
});
},
getHttpEquivHeaders: function() {
var equivNodes = [].filter.call(this.document.getElementsByTagName('meta'),
function(item){
return [].reduce.call(item.attributes,
function(a,b){
return a || b.name=='http-equiv';
},
false);
}
);
var equivs = equivNodes.map(function(node) {
var attrs = {};
[].map.call(node.attributes, function(attr) {
attrs[attr.name] = attr.nodeValue;
});
return attrs;
});
var headers = {};
for (i in equivs) {
headers[String(i['http-equiv']).toLowerCase()] = i.content;
}
return headers;
},
getTitle: function() {
var titles = [];
var headers = {};
var text = [].map.call(this.document.getElementsByTagName('title'), function(item) {return item.text;}).join('');
console.log(text);
return text ? text.replace('\n',' ').replace(/\s+/g,' ').replace(/ /g,'').trim() : undefined;
},
getCharset: function() {
var type = this.type ? this.type.toLowerCase().split('charset=')[1] : 'utf-8';
return (type || 'utf-8').replace('windows-', 'cp');
}
};
// Overbot
var bitlyApi = new bitly(bitlyLogin, bitlyKey);
var config = new Config(configFile);
var feeds = new Feeds(feedsInfo);
var bot = new irc.Client(null, null, ircOptions);
bot.addListener('error', function(error) {
reportError(e);
});
bot.addListener('notice', function(from, to, message) {
if (/nickserv/.test(String(from).toLowerCase()) &&
/IDENTIFY/.test(String(message))) {
bot.send('NICKSERV IDENTIFY ' + nickServPassword);
}
});
bot.addListener('message', function(from, to, message) {
console.log(message);
if (message == 'ping') {
bot.say(to, 'pong');
}
var url = message.match(/((http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/);
if (url && url.length > 0) {
var link = new Link(url[0]);
link.identify(function(message) {
bot.say(to, message);
});
}
});
bot.addListener('join', function(channel, nick, message) {
if (nick == bot.nick) {
feeds.start(channel);
}
});
bot.addListener('part', function(channel, nick, message) {
if (nick == bot.nick) {
feeds.stop(channel);
}
});
bot.addListener('kick', function(channel, nick, message) {
if (nick == bot.nick) {
feeds.stop(channel);
}
});
bot.addListener('disconnect', function() {
feeds.stop();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment