OmnomIRC.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. #!node
  2. process.chdir(__dirname);
  3. var fs = require('fs'),
  4. url = require('url'),
  5. path = require('path'),
  6. vm = require('vm'),
  7. toobusy = function(){return false;},//require('toobusy'),
  8. noop = function(){},
  9. cluster = require('cluster'),
  10. ircClient = require('node-irc'),
  11. logger = {
  12. log: function(msg){
  13. if(options.loglevel > 2){
  14. console.log(msg);
  15. }
  16. },
  17. debug: function(msg){
  18. if(options.loglevel > 2){
  19. console.log('DEBUG - '+msg);
  20. }
  21. },
  22. warn: function(msg){
  23. if(options.loglevel > 1){
  24. console.log('WARN - '+msg);
  25. }
  26. },
  27. info: function(msg){
  28. if(options.loglevel > 1){
  29. console.log('INFO - '+msg);
  30. }
  31. },
  32. error: function(msg){
  33. if(options.loglevel > 0){
  34. console.error(msg);
  35. }
  36. }
  37. },
  38. options = global.options = (function(){
  39. var defaults = {
  40. port: 80,
  41. loglevel: 3,
  42. threads: require('os').cpus().length,
  43. redis: {
  44. port: 6379,
  45. host: 'localhost'
  46. },
  47. debug: false,
  48. paths: {
  49. www: './www/',
  50. api: './api/',
  51. plugins: './plugins/'
  52. },
  53. irc: {
  54. host: 'irp.irc.omnimaga.org',
  55. port: 6667,
  56. nick: 'oirc3',
  57. name: 'OmnomIRC3',
  58. channels: [
  59. '#omnimaga'
  60. ],
  61. messages: {
  62. quit: 'Server closed'
  63. }
  64. }
  65. },
  66. i,
  67. options;
  68. try{
  69. options = JSON.parse(fs.readFileSync('./options.json'));
  70. defaults = (function merge(options,defaults){
  71. for(var i in options){
  72. if(typeof defaults[i] != 'object' || defaults[i] instanceof Array){
  73. defaults[i] = options[i];
  74. }else{
  75. defaults[i] = merge(options[i],defaults[i]);
  76. }
  77. }
  78. return defaults
  79. })(options,defaults);
  80. }catch(e){
  81. logger.warn('Using default settings. Please create options.json');
  82. }
  83. options = {};
  84. for(i in defaults){
  85. Object.defineProperty(options,i,{
  86. value: defaults[i],
  87. enumerable: true,
  88. writable: false
  89. });
  90. }
  91. return options;
  92. })();
  93. if(typeof fs.existsSync == 'undefined') fs.existsSync = path.existsSync; // legacy support
  94. if(cluster.isMaster){
  95. var iWorker;
  96. cluster.on('exit', function(worker, code, signal) {
  97. console.log('worker ' + worker.process.pid + ' died');
  98. });
  99. iWorker = global.iw = cluster.fork();
  100. iWorker.on('online',function(){
  101. logger.info('First worker online');
  102. iWorker.send('S');
  103. });
  104. for(var i=1;i<options.threads;i++){
  105. cluster.fork().on('online',function(){
  106. logger.info('Child socket worker online');
  107. });
  108. }
  109. for(i in cluster.workers){
  110. var worker = cluster.workers[i];
  111. worker.on('message',function(msg){
  112. var c = msg[0];
  113. msg = msg.substr(1);
  114. logger.debug('Parent recieved command '+c+' with message '+msg);
  115. switch(c){
  116. case 'M':
  117. iWorker.send('M'+msg);
  118. break;
  119. }
  120. });
  121. }
  122. if(options.debug){
  123. require('repl').start({
  124. prompt: '> ',
  125. useGlobal: true
  126. }).on('exit',function(){
  127. for(var i in cluster.workers){
  128. cluster.workers[i].send('Q');
  129. }
  130. process.exit();
  131. });
  132. }
  133. }else{
  134. process.on('message',function(msg){
  135. var c = msg[0];
  136. msg = msg.substr(1);
  137. logger.debug('Child recieved command '+c+' with message '+msg);
  138. switch(c){
  139. case 'Q':
  140. if(typeof app != 'undefined' && typeof irc == 'undefined'){
  141. app.close();
  142. }else if(typeof irc != 'undefined'){
  143. irc.quit(options.irc.messages.quit);
  144. }
  145. break;
  146. case 'M':
  147. if(typeof irc != 'undefined'){
  148. msg = JSON.parse(msg);
  149. if(msg.message){
  150. irc.say(msg.room,'<'+msg.from+'> '+msg.message);
  151. }
  152. }
  153. break;
  154. case 'S':
  155. logger.info('Child starting irc');
  156. irc = new ircClient(options.irc.host,options.irc.port,options.irc.nick,options.irc.name);
  157. irc.on('ready',function(){
  158. logger.info('Connected to IRC');
  159. for(var i in options.irc.channels){
  160. irc.join(options.irc.channels[i]);
  161. }
  162. });
  163. irc.on('CHANMSG',function(d){
  164. console.log(d);
  165. message(d.reciever,d.sender,d.message);
  166. });
  167. irc.connect();
  168. logger.debug('Connecting to IRC');
  169. break;
  170. }
  171. });
  172. logger.info('Child starting socket.io');
  173. var RedisStore = require('socket.io/lib/stores/redis'),
  174. redis = require('socket.io/node_modules/redis'),
  175. pub = redis.createClient(options.redis.port,options.redis.host),
  176. sub = redis.createClient(options.redis.port,options.redis.host),
  177. client = redis.createClient(options.redis.port,options.redis.host),
  178. mimeTypes = {
  179. 'html': 'text/html',
  180. 'js': 'text/javascript',
  181. 'css': 'text/css',
  182. 'png': 'image/png',
  183. 'jpg': 'image/jpeg'
  184. },
  185. app = require('http').createServer(function(req,res){
  186. if(toobusy()){
  187. res.writeHead(503,{
  188. 'Content-type': 'text/plain'
  189. });
  190. res.write('503 Server busy.\n');
  191. res.end();
  192. return;
  193. }
  194. req.addListener('end',function(){
  195. logger.debug('served static content for '+req.url);
  196. var uri = url.parse(req.url).pathname,
  197. serveFile = function(filename,req,res){
  198. try{
  199. stats = fs.lstatSync(filename);
  200. }catch(e){
  201. res.writeHead(404,{
  202. 'Content-type': 'text/plain'
  203. });
  204. res.write('404 Not Found.\n');
  205. res.end();
  206. return;
  207. }
  208. if(stats.isFile()){
  209. var fileStream,
  210. mimetype = mimeTypes[path.extname(filename).split('.')[1]];
  211. res.writeHead(200,{
  212. 'Content-Type': mimetype
  213. });
  214. fileStream = fs.createReadStream(filename);
  215. fileStream.pipe(res);
  216. }else if(stats.isDirectory()){
  217. if(fs.existsSync(path.join(filename,'index.html'))){
  218. serveFile(path.join(filename,'index.html'),req,res);
  219. }else if(fs.existsSync(path.join(filename,'index.htm'))){
  220. serveFile(path.join(filename,'index.htm'),req,res);
  221. }else if(fs.existsSync(path.join(filename,'index.txt'))){
  222. serveFile(path.join(filename,'index.txt'),req,res);
  223. }else{
  224. res.writeHead(200,{
  225. 'Content-Type': 'text/plain'
  226. });
  227. res.write('Index of '+url+'\n');
  228. res.write('TODO, show index');
  229. res.end();
  230. }
  231. }else{
  232. res.writeHead(500,{
  233. 'Content-Type': 'text/plain'
  234. });
  235. res.write('500 Internal server error\n');
  236. res.end();
  237. }
  238. },
  239. filepath = unescape(uri);
  240. if(filepath.substr(0,5) == '/api/'){
  241. filepath = path.join(options.paths.api,filepath.substr(5));
  242. logger.debug('Attempting to run api script '+filepath);
  243. if(fs.existsSync(filepath)){
  244. fs.readFile(filepath,function(e,data){
  245. if(e){
  246. logger.error(e);
  247. res.end('null;');
  248. }else{
  249. var output = '',
  250. sandbox = {
  251. log: function(text){
  252. output += text;
  253. },
  254. error: function(msg){
  255. logger.error(msg);
  256. },
  257. info: function(msg){
  258. logger.info(msg);
  259. },
  260. debug: function(msg){
  261. logger.debug(msg);
  262. },
  263. head: {
  264. 'Content-Type': 'text/javascript'
  265. },
  266. returnCode: 200,
  267. vm: vm,
  268. fs: fs
  269. };
  270. vm.runInNewContext(data,sandbox,filepath);
  271. res.writeHead(sandbox.returnCode,sandbox.head);
  272. res.end(output);
  273. }
  274. });
  275. }else{
  276. res.writeHead(404,{
  277. 'Content-Type': 'text/javascript'
  278. });
  279. res.end('null;');
  280. }
  281. }else{
  282. serveFile(path.join(options.paths.www,filepath),req,res);
  283. }
  284. }).resume();
  285. }).listen(options.port),
  286. io = require('socket.io').listen(app);
  287. io.set('log level',options.loglevel);
  288. io.log = logger;
  289. if(typeof options.redis.password != 'undefined'){
  290. var eh = function(e){
  291. throw e;
  292. };
  293. pub.auth(options.redis.ppassword,eh);
  294. sub.auth(options.redis.ppassword,eh);
  295. client.auth(options.redis.ppassword,eh);
  296. }
  297. io.set('store', new RedisStore({
  298. redisPub : pub,
  299. redisSub : sub,
  300. redisClient : client
  301. }));
  302. io.sockets.on('connection',function(socket){
  303. socket.on('join',function(data){
  304. socket.join(data.name);
  305. data.title = data.name;
  306. socket.emit('join',{
  307. name: data.name
  308. });
  309. sendUserList(data.name);
  310. socket.get('nick',function(e,nick){
  311. logger.debug(nick+' joined '+data.name);
  312. fromServer(data.name,nick+' joined the channel');
  313. });
  314. });
  315. socket.on('part',function(data){
  316. socket.leave(data.name);
  317. socket.get('nick',function(e,nick){
  318. logger.debug(nick+' left '+data.name);
  319. sendUserList(data.name);
  320. });
  321. });
  322. socket.on('disconnect',function(data){
  323. var rooms = io.sockets.manager.rooms,
  324. i,
  325. room;
  326. for(i in rooms){
  327. if(rooms[i] != '' && typeof rooms[i] == 'string'){
  328. try{
  329. room = rooms[i].substr(1);
  330. }catch(e){}
  331. sendUserList(room);
  332. }
  333. }
  334. });
  335. socket.on('message',function(data){
  336. logger.debug('message sent to '+data.room);
  337. io.sockets.in(data.room).emit('message',data);
  338. process.send('M'+JSON.stringify(data));
  339. });
  340. socket.on('echo',function(data){
  341. logger.debug('echoing to '+data.room);
  342. socket.emit('message',data);
  343. });
  344. socket.on('names',function(data){
  345. var sockets = io.sockets.clients(data.name),
  346. i;
  347. runWithUserList(data.name,function(users){
  348. var temp = [],i;
  349. for(i in users) i && i != null && temp.push(users[i]);
  350. users = temp;
  351. fromServer(data.name,data.name+" users:\n\t\t"+users.join("\n\t\t"),socket);
  352. sendUserList(data.name);
  353. });
  354. });
  355. socket.on('auth',function(data){
  356. logger.info(data.nick+' registered');
  357. // TODO - authorize
  358. socket.set('nick',data.nick.substr(0,12));
  359. socket.emit('authorized',{
  360. nick: data.nick.substr(0,12)
  361. });
  362. });
  363. var runWithUserList = function(room,callback){
  364. var sockets = io.sockets.clients(room),
  365. i = 0,
  366. ret = [],
  367. getNext = function(){
  368. if(i < sockets.length){
  369. sockets[i].get('nick',function(e,nick){
  370. if(e){
  371. logger.error(e);
  372. }else if(!inArray(ret,nick)){
  373. logger.debug(room+' '+nick);
  374. ret.push(nick);
  375. }
  376. i++;
  377. getNext();
  378. });
  379. }else{
  380. callback(ret);
  381. }
  382. };
  383. getNext();
  384. },
  385. inArray = function(arr,val){
  386. for(var i in arr){
  387. if(arr[i] == val){
  388. return true;
  389. }
  390. }
  391. return false;
  392. },
  393. sendUserList = function(room){
  394. if(typeof room != 'undefined'){
  395. runWithUserList(room,function(users){
  396. io.sockets.in(room).emit('names',{
  397. room: room,
  398. names: users
  399. });
  400. });
  401. }
  402. },
  403. message = function(room,from,message,socket){
  404. if(typeof socket == 'undefined'){
  405. socket = io.sockets.in(room);
  406. }
  407. socket.emit('message',{
  408. message: message,
  409. room: room,
  410. from: from
  411. })
  412. },
  413. fromServer = function(room,message,socket){
  414. if(typeof socket == 'undefined'){
  415. socket = io.sockets.in(room);
  416. }
  417. socket.emit('message',{
  418. message: message,
  419. room: room,
  420. from: 0
  421. });
  422. };
  423. });
  424. }
  425. process.on('uncaughtException',function(e){
  426. if(typeof logger != 'undefined'){
  427. logger.error(e);
  428. }else{
  429. console.error(e);
  430. }
  431. });