#!node process.chdir(__dirname); var fs = require('fs'), url = require('url'), path = require('path'), vm = require('vm'), toobusy = function(){return false;},//require('toobusy'), noop = function(){}, cluster = require('cluster'), ircClient = require('node-irc'), logger = { log: function(msg){ if(options.loglevel > 2){ console.log(msg); } }, debug: function(msg){ if(options.loglevel > 2){ console.log('DEBUG - '+msg); } }, warn: function(msg){ if(options.loglevel > 1){ console.log('WARN - '+msg); } }, info: function(msg){ if(options.loglevel > 1){ console.log('INFO - '+msg); } }, error: function(msg){ if(options.loglevel > 0){ console.error(msg); } } }, options = global.options = (function(){ var defaults = { port: 80, loglevel: 3, threads: require('os').cpus().length, redis: { port: 6379, host: 'localhost' }, debug: false, paths: { www: './www/', api: './api/', plugins: './plugins/' }, irc: { host: 'irp.irc.omnimaga.org', port: 6667, nick: 'oirc3', name: 'OmnomIRC3', channels: [ '#omnimaga' ], messages: { quit: 'Server closed' } }, origins: [ ['O','OmnomIRC'], ['#','IRC'] ] }, i, options; try{ options = JSON.parse(fs.readFileSync('./options.json')); defaults = (function merge(options,defaults){ for(var i in options){ if(typeof defaults[i] != 'object' || defaults[i] instanceof Array){ defaults[i] = options[i]; }else{ defaults[i] = merge(options[i],defaults[i]); } } return defaults })(options,defaults); }catch(e){ console.warn('Using default settings. Please create options.json'); console.error(e); } defaults.origins.unshift(['S','Server'],['?','Unknown']); options = {}; for(i in defaults){ Object.defineProperty(options,i,{ value: defaults[i], enumerable: true, writable: false }); } return options; })(), origin = function(name){ for(var i in options.origins){ if(options.origins[i][1] == name){ return i; } } return 1; }; if(typeof fs.existsSync == 'undefined') fs.existsSync = path.existsSync; // legacy support if(cluster.isMaster){ var iWorker; cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); }); iWorker = global.iw = cluster.fork(); iWorker.on('online',function(){ logger.info('First worker online'); iWorker.send('S'); }); for(var i=1;i ', useGlobal: true }).on('exit',function(){ for(var i in cluster.workers){ cluster.workers[i].send('Q'); } process.exit(); }); } }else{ process.on('message',function(msg){ var c = msg[0]; msg = msg.substr(1); switch(c){ case 'Q': if(typeof app != 'undefined' && typeof irc == 'undefined'){ app.close(); }else if(typeof irc != 'undefined'){ irc.quit(options.irc.messages.quit); } break; case 'M': if(typeof irc != 'undefined'){ msg = JSON.parse(msg); if(msg.message){ irc.say(msg.room,'('+options.origins[msg.origin][0]+')'+'<'+msg.from+'> '+msg.message); } } break; case 'S': logger.info('Child starting irc'); irc = new ircClient(options.irc.host,options.irc.port,options.irc.nick,options.irc.name); irc.on('ready',function(){ logger.info('Connected to IRC'); for(var i in options.irc.channels){ irc.join(options.irc.channels[i]); //irc.client.send('WHO %s\n',options.irc.channels[i]); } }); irc.on('CHANMSG',function(d){ console.log(d); message(d.reciever,d.sender,d.message,origin('IRC')); }); // Beginnings of names handler /*irc.on('names',function(chan,nicks){ for(var i in nicks){ logger.debug('[NICKS] Channel '+chan+' '+nicks[i]); } });*/ irc.connect(); logger.debug('Connecting to IRC'); break; } }); logger.info('Child starting socket.io'); var RedisStore = require('socket.io/lib/stores/redis'), redis = require('socket.io/node_modules/redis'), pub = redis.createClient(options.redis.port,options.redis.host), sub = redis.createClient(options.redis.port,options.redis.host), client = redis.createClient(options.redis.port,options.redis.host), mimeTypes = { 'html': 'text/html', 'js': 'text/javascript', 'css': 'text/css', 'png': 'image/png', 'jpg': 'image/jpeg' }, message = function(room,from,message,origin,socket){ if(typeof socket == 'undefined'){ socket = io.sockets.in(room); } socket.emit('message',{ message: message, room: room, from: from, origin: origin }) }, app = require('http').createServer(function(req,res){ if(toobusy()){ res.writeHead(503,{ 'Content-type': 'text/plain' }); res.write('503 Server busy.\n'); res.end(); return; } req.addListener('end',function(){ logger.debug('served static content for '+req.url); var uri = url.parse(req.url).pathname, serveFile = function(filename,req,res){ try{ stats = fs.lstatSync(filename); }catch(e){ res.writeHead(404,{ 'Content-type': 'text/plain' }); res.write('404 Not Found.\n'); res.end(); return; } if(stats.isFile()){ var fileStream, mimetype = mimeTypes[path.extname(filename).split('.')[1]]; res.writeHead(200,{ 'Content-Type': mimetype }); fileStream = fs.createReadStream(filename); fileStream.pipe(res); }else if(stats.isDirectory()){ if(fs.existsSync(path.join(filename,'index.html'))){ serveFile(path.join(filename,'index.html'),req,res); }else if(fs.existsSync(path.join(filename,'index.htm'))){ serveFile(path.join(filename,'index.htm'),req,res); }else if(fs.existsSync(path.join(filename,'index.txt'))){ serveFile(path.join(filename,'index.txt'),req,res); }else{ res.writeHead(200,{ 'Content-Type': 'text/plain' }); res.write('Index of '+url+'\n'); res.write('TODO, show index'); res.end(); } }else{ res.writeHead(500,{ 'Content-Type': 'text/plain' }); res.write('500 Internal server error\n'); res.end(); } }, filepath = unescape(uri); if(filepath.substr(0,5) == '/api/'){ filepath = path.join(options.paths.api,filepath.substr(5)); logger.debug('Attempting to run api script '+filepath); if(fs.existsSync(filepath)){ fs.readFile(filepath,function(e,data){ if(e){ logger.error(e); res.end('null;'); }else{ var output = '', sandbox = { log: function(text){ output += text; }, error: function(msg){ logger.error(msg); }, info: function(msg){ logger.info(msg); }, debug: function(msg){ logger.debug(msg); }, head: { 'Content-Type': 'text/javascript' }, returnCode: 200, vm: vm, fs: fs, options: options }; vm.runInNewContext(data,sandbox,filepath); res.writeHead(sandbox.returnCode,sandbox.head); res.end(output); } }); }else{ res.writeHead(404,{ 'Content-Type': 'text/javascript' }); res.end('null;'); } }else{ serveFile(path.join(options.paths.www,filepath),req,res); } }).resume(); }).listen(options.port), io = require('socket.io').listen(app); io.set('log level',options.loglevel); io.log = logger; if(typeof options.redis.password != 'undefined'){ var eh = function(e){ throw e; }; pub.auth(options.redis.ppassword,eh); sub.auth(options.redis.ppassword,eh); client.auth(options.redis.ppassword,eh); } io.set('store', new RedisStore({ redisPub : pub, redisSub : sub, redisClient : client })); io.sockets.on('connection',function(socket){ socket.on('join',function(data){ socket.join(data.name); data.title = data.name; socket.emit('join',{ name: data.name }); sendUserList(data.name); socket.get('nick',function(e,nick){ logger.debug(nick+' joined '+data.name); fromServer(data.name,nick+' joined the channel'); }); }); socket.on('part',function(data){ socket.leave(data.name); socket.get('nick',function(e,nick){ logger.debug(nick+' left '+data.name); sendUserList(data.name); }); }); socket.on('disconnect',function(data){ var rooms = io.sockets.manager.rooms, i, room; for(i in rooms){ if(rooms[i] != '' && typeof rooms[i] == 'string'){ try{ room = rooms[i].substr(1); }catch(e){} sendUserList(room); } } }); socket.on('message',function(data){ logger.debug('message sent to '+data.room); io.sockets.in(data.room).emit('message',data); process.send('M'+JSON.stringify(data)); }); socket.on('echo',function(data){ logger.debug('echoing to '+data.room); socket.emit('message',data); }); socket.on('names',function(data){ var sockets = io.sockets.clients(data.name), i; runWithUserList(data.name,function(users){ var temp = [],i; for(i in users) i && i != null && temp.push(users[i]); users = temp; fromServer(data.name,data.name+" users:\n\t\t"+users.join("\n\t\t"),socket); sendUserList(data.name); }); }); socket.on('auth',function(data){ logger.info(data.nick+' registered'); // TODO - authorize socket.set('nick',data.nick.substr(0,12)); socket.emit('authorized',{ nick: data.nick.substr(0,12) }); }); var runWithUserList = function(room,callback){ var sockets = io.sockets.clients(room), i = 0, ret = [], getNext = function(){ if(i < sockets.length){ sockets[i].get('nick',function(e,nick){ if(e){ logger.error(e); }else if(!inArray(ret,nick)){ logger.debug(room+' '+nick); ret.push(nick); } i++; getNext(); }); }else{ callback(ret); } }; getNext(); }, inArray = function(arr,val){ for(var i in arr){ if(arr[i] == val){ return true; } } return false; }, sendUserList = function(room){ if(typeof room != 'undefined'){ runWithUserList(room,function(users){ io.sockets.in(room).emit('names',{ room: room, names: users }); }); } }, fromServer = function(room,message,socket){ if(typeof socket == 'undefined'){ socket = io.sockets.in(room); } socket.emit('message',{ message: message, room: room, from: 0, origin: 2 }); }; }); } process.on('uncaughtException',function(e){ if(typeof logger != 'undefined'){ logger.error(e); }else{ console.error(e); } });