irc.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. var net = require('net');
  2. var IRCserver = require('./server');
  3. var IRCuser = require('./user');
  4. var IRCchannel = require('./channel');
  5. var uplink = null;
  6. var connection = null;
  7. var recvData = '';
  8. var users = [];
  9. var servers = [];
  10. var channels = [];
  11. var me = null;
  12. var events = {};
  13. function addInternalEvents(){
  14. events.netSynced.unshift(function(){
  15. for(var i=0; i<users.length; i++){
  16. var user = users[i];
  17. if(user.uplink == me){
  18. uplink.introduceUser(user);
  19. }
  20. }
  21. for(var i=0; i<channels.length; i++){
  22. uplink.syncChannel(channels[i]);
  23. }
  24. });
  25. }
  26. function findServer(s){
  27. for(var i=0; i<servers.length; i++){
  28. srv = servers[i];
  29. if(srv.sid == s || srv.name == s) return srv;
  30. }
  31. return null;
  32. }
  33. function findUser(u){
  34. for(var i=0; i<users.length; i++){
  35. usr = users[i];
  36. if(usr.uid == u || usr.name == u) return usr;
  37. }
  38. return null;
  39. }
  40. function findChannel(c){
  41. for(var i=0; i<channels.length; i++){
  42. usr = channels[i];
  43. if(channels[i].name == c) return channels[i];
  44. }
  45. return null;
  46. }
  47. function removeServer(server){
  48. var length = servers.length;
  49. var done = false;
  50. do {
  51. for(var i=0; i<length; i++){
  52. if(servers.length != length) break;
  53. if(servers[i].uplink == server){
  54. removeServer(servers[i]); // remove connected servers recursively
  55. }
  56. }
  57. if(i == length) done = true;
  58. } while(!done);
  59. for(var i=users.length-1; i>=0; i--){
  60. if(users[i].uplink == server){
  61. quitUser(users[i]); // remove users connected to this server
  62. }
  63. }
  64. for(var i=servers.length-1; i>=0; i--){ // finally, remove the given server
  65. if(servers[i] == server){
  66. console.log('Removing server '+servers[i].name);
  67. events.doEvent('serverDelete', server);
  68. servers.splice(i, 1);
  69. delete server;
  70. break;
  71. }
  72. }
  73. }
  74. function getChannel(name){
  75. var lowerName = name.toLowerCase();
  76. var channel = null;
  77. for(var i=0; i<channels.length; i++){
  78. if(channels[i].name == lowerName){
  79. channel = channels[i];
  80. break;
  81. }
  82. }
  83. if(!channel){
  84. console.log('Creating channel '+name);
  85. channel = new IRCchannel;
  86. channel.name = name;
  87. channels.push(channel);
  88. events.doEvent('channelCreate', channel);
  89. }
  90. return channel;
  91. }
  92. function killUser(user){
  93. console.log('KILL');
  94. console.log(user);
  95. // TODO
  96. }
  97. function newUser(nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink){ // nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink
  98. var user;
  99. if((user = findUser(uid)) || (user = findUser(nick))){
  100. console.log(user);
  101. throw 'User already exists';
  102. }
  103. console.log('Introducing user '+nick);
  104. user = new IRCuser;
  105. user.introduce(nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink);
  106. users.push(user);
  107. events.doEvent('userCreate', user);
  108. return user;
  109. }
  110. function newServer(name, sid, desc, distance, uplink){
  111. if(findServer(sid) || findServer(name)){
  112. throw 'Server already exists';
  113. }
  114. console.log('Introducing server '+name+' (SID='+sid+', uplink="'+uplink.name+'")');
  115. server = new IRCserver;
  116. server.introduce(name, sid, desc, distance, uplink);
  117. servers.push(server);
  118. events.doEvent('serverCreate', server);
  119. return server;
  120. }
  121. function ircSendData(tags, from, cmd, args){
  122. var data = makeTagsString(tags);
  123. if(from){
  124. data += ':' + from + ' ';
  125. }
  126. data += cmd;
  127. if(args) for(var i=0; i<args.length; i++){
  128. var arg = args[i];
  129. if(arg.indexOf(' ') >= 0){ // valid for last arg only
  130. arg = ':' + arg;
  131. }
  132. data += ' ' + arg;
  133. }
  134. console.log('<<< '+data);
  135. data += "\r\n";
  136. connection.write(data);
  137. }
  138. function ircDataReceived(data){
  139. data = data.toString('utf8');
  140. recvData += data;
  141. var buffer = '';
  142. for(var i=0; i<recvData.length; i++){
  143. var c = recvData.charAt(i);
  144. switch(c){
  145. default: buffer += c; break;
  146. case '\r': case '\n':
  147. if(buffer.length > 0)
  148. ircMessage(buffer);
  149. buffer = '';
  150. break;
  151. }
  152. }
  153. recvData = buffer;
  154. }
  155. function makeTagsString(tags){
  156. return '';
  157. //TODO
  158. }
  159. function ircMessage(data){
  160. var msg = irc.parseLine(data);
  161. uplink.processMessage(msg);
  162. }
  163. function ircConnectionClosed(e){
  164. events.doEvent('uplinkLost', me);
  165. console.log('Closed: '+e);
  166. process.exit(1);
  167. }
  168. function processReplyTags(inputTags, newTags){
  169. return newTags;
  170. // TODO
  171. }
  172. function quitUser(user){
  173. console.log('Removing user '+user.name);
  174. for(var i=0; i<channels.length; i++){
  175. channels[i].removeUser(user);
  176. }
  177. for(var i=0; i<users.length; i++){
  178. if(users[i] == user){
  179. users.splice(i, 1);
  180. events.doEvent('userDelete', user);
  181. delete user;
  182. break;
  183. }
  184. }
  185. }
  186. function removeEmptyChannels(){
  187. for(var i=channels.length - 1; i >= 0; i--){
  188. if(channels[i].users.length == 0){
  189. storeChannelData(channels[i]);
  190. console.log('Removing channel '+channels[i].name);
  191. var channel = channels[i];
  192. channels.splice(i, 1);
  193. delete channel;
  194. }
  195. }
  196. }
  197. function storeChannelData(channel){
  198. // TODO maybe move a level up
  199. }
  200. setInterval(removeEmptyChannels, 1000);
  201. var irc = {
  202. 'users': users,
  203. 'channels': channels,
  204. 'servers': servers,
  205. 'getChannel': getChannel,
  206. 'setConnection': function(host, port, protocol){
  207. uplink = require('./protocol/' + protocol);
  208. me = new IRCserver;
  209. me.introduce('serwisy.pirc.pl', '11K', 'Serwisy', 0, null);
  210. servers.push(me);
  211. uplink.setHandlers({
  212. send: ircSendData,
  213. findServer: findServer,
  214. findUser: findUser,
  215. killUser: killUser,
  216. newServer: newServer,
  217. newUser: newUser,
  218. getChannel: getChannel,
  219. quitUser: quitUser,
  220. removeServer: removeServer,
  221. findChannel: findChannel
  222. });
  223. uplink.setSettings({
  224. ID: me.sid,
  225. password: 'myservicespassword',
  226. name: me.name,
  227. description: me.description,
  228. version: 'k4be-services',
  229. me: me,
  230. maxUsers: 0
  231. });
  232. addInternalEvents();
  233. uplink.setEvents(events);
  234. connection = new net.Socket();
  235. connection.connect(port, host, uplink.connected.bind(this));
  236. connection.on('data', ircDataReceived);
  237. connection.on('close', ircConnectionClosed);
  238. },
  239. 'setEvents': function(newEvents){
  240. events = newEvents;
  241. },
  242. 'makeUser': function(nick, ident, host, realname){
  243. var user = new IRCuser;
  244. var uid = uplink.makeUid();
  245. user.introduce(nick, 0, Math.floor(new Date() / 1000).toString(10), ident, host, uid, null, '+SoB'/*TODO*/, null, null, null, realname, me);
  246. users.push(user);
  247. return user;
  248. },
  249. 'messagedata': function() {
  250. this.text = '';
  251. this.args = [];
  252. this.tags = [];
  253. this.command = '';
  254. this.sender = {
  255. 'nick': '',
  256. 'ident': '',
  257. 'host': '',
  258. 'server': false,
  259. 'user': false
  260. };
  261. this.time = new Date();
  262. this.reply = function(tags, cmd, args){
  263. var outTags = processReplyTags(this.tags, tags);
  264. ircSendData(outTags, this.sender.nick, cmd, args);
  265. }
  266. this.originalString = '';
  267. },
  268. 'parseTags': function(tagsLine){
  269. var tags = [];
  270. var tagState = 'keyName';
  271. var keyValue;
  272. var keyName = '';
  273. for(var i = 0; i < tagsLine.length; i++){
  274. var cchar = tagsLine.charAt(i);
  275. switch(tagState){
  276. case 'keyName':
  277. switch(cchar){
  278. case '=':
  279. tagState = 'keyValue';
  280. keyValue = '';
  281. break;
  282. case ';':
  283. tags[keyName] = '';
  284. keyName = ''; // staying in tagStateKeyName
  285. break;
  286. default: keyName += cchar; break;
  287. }
  288. break;
  289. case 'keyValue':
  290. switch(cchar){
  291. case '\\': tagState = 'keyValueEscape'; break;
  292. case ';':
  293. tags[keyName] = keyValue;
  294. keyName = '';
  295. tagState = 'keyName';
  296. break;
  297. default: keyValue += cchar; break;
  298. }
  299. break;
  300. case 'keyValueEscape':
  301. switch(cchar){
  302. case ':': keyValue += ';'; break;
  303. case 's': keyValue += ' '; break;
  304. case 'r': keyValue += '\r'; break;
  305. case 'n': keyValue += '\n'; break;
  306. default: keyValue += cchar; break;
  307. }
  308. tagState = 'keyValue';
  309. break;
  310. }
  311. }
  312. if(keyName.length > 0) tags[keyName] = keyValue; // flush last tag
  313. return tags;
  314. },
  315. 'parseLine': function(line){
  316. var ircmsg = new irc.messagedata();
  317. var line = line.trim();
  318. line.replace(/^\s+|\s+$/gm,'');
  319. if(line == ''){
  320. return;
  321. }
  322. ircmsg.originalString = line;
  323. var msglen = line.length;
  324. var pstate = 'start';
  325. var currArg = '';
  326. var tags = '';
  327. var haveText = false;
  328. console.log('>>> ' + line);
  329. for(var i = 0; i < msglen; i++){
  330. var cchar = line.charAt(i);
  331. switch(pstate){
  332. case 'start':
  333. switch(cchar){
  334. case '@': pstate = 'tags'; break;
  335. case ':': pstate = 'senderNick'; break;
  336. default:
  337. pstate = 'command';
  338. ircmsg.command += cchar;
  339. break;
  340. }
  341. break;
  342. case 'tags':
  343. switch(cchar){
  344. case ' ':
  345. pstate = 'start';
  346. ircmsg.tags = irc.parseTags(tags);
  347. break;
  348. default: tags += cchar; break;
  349. }
  350. break;
  351. case 'senderNick':
  352. switch(cchar){
  353. case '!': pstate = 'senderUser'; break;
  354. case '@': pstate = 'senderHost'; break;
  355. case ' ': pstate = 'command'; break;
  356. default: ircmsg.sender.nick += cchar; break;
  357. }
  358. break;
  359. case 'senderUser':
  360. switch(cchar){
  361. case '@': pstate = 'senderHost'; break;
  362. case ' ': pstate = 'command'; break;
  363. default: ircmsg.sender.ident += cchar; break;
  364. }
  365. break;
  366. case 'senderHost':
  367. switch(cchar){
  368. case ' ': pstate = 'command'; break;
  369. default: ircmsg.sender.host += cchar; break;
  370. }
  371. break;
  372. case 'command':
  373. switch(cchar){
  374. case ' ': pstate = 'args'; break;
  375. default: ircmsg.command += cchar; break;
  376. }
  377. break;
  378. case 'args':
  379. switch(cchar){
  380. case ' ':
  381. if(currArg != ''){
  382. ircmsg.args.push(currArg);
  383. }
  384. currArg = '';
  385. break;
  386. case ':':
  387. if(prevChar == ' '){
  388. pstate = 'message';
  389. haveText = true;
  390. } else {
  391. currArg += cchar;
  392. }
  393. break;
  394. default: currArg += cchar; break;
  395. }
  396. break;
  397. case 'message':
  398. ircmsg.text += cchar;
  399. break;
  400. }
  401. var prevChar = cchar;
  402. }
  403. if(pstate == 'args'){
  404. ircmsg.args.push(currArg);
  405. }
  406. ircmsg.sender = uplink.processSender(ircmsg.sender);
  407. if(!ircmsg.sender){
  408. return false;
  409. }
  410. if(!ircmsg.sender.server && !ircmsg.sender.user){
  411. if(ircmsg.sender.ident == '' && ircmsg.sender.host == '' && ircmsg.sender.nick.indexOf('.')!=-1){
  412. ircmsg.sender.server = true;
  413. } else {
  414. ircmsg.sender.user = true;
  415. }
  416. }
  417. if(!haveText){
  418. ircmsg.text = ircmsg.args[ircmsg.args.length-1]; // handling last argument as text if : is missing
  419. } else {
  420. ircmsg.args.push(ircmsg.text); // handling text as a last argument as required by the protocol
  421. }
  422. // add u@h
  423. /* if(ircmsg.sender.user){
  424. if(ircmsg.sender.ident) users.getUser(ircmsg.sender.nick).setIdent(ircmsg.sender.ident);
  425. if(ircmsg.sender.host) users.getUser(ircmsg.sender.nick).setHost(ircmsg.sender.host);
  426. }*/
  427. // process known tags
  428. /* if('time' in ircmsg.tags){
  429. ircmsg.time = parseISOString(ircmsg.tags['time']);
  430. }
  431. if('account' in ircmsg.tags){
  432. users.getUser(ircmsg.sender.nick).setAccount(ircmsg.tags['account']);
  433. }*/
  434. // console.log(ircmsg);
  435. return ircmsg;
  436. }
  437. };
  438. module.exports = irc;