Created
March 5, 2012 23:33
-
-
Save sfionov/1982073 to your computer and use it in GitHub Desktop.
OverBot IRC Bot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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