irc.js 9.9 KB

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