OmnomIRC.js 10 KB

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