var net = require('net'); var IRCserver = require('./server'); var IRCuser = require('./user'); var IRCchannel = require('./channel'); var uplink = null; var connection = null; var recvData = ''; var users = []; var servers = []; var channels = []; var me = null; var events = {}; function addInternalEvents(){ events.netSynced.unshift(function(){ for(var i=0; i<users.length; i++){ var user = users[i]; if(user.uplink == me){ uplink.introduceUser(user); } } for(var i=0; i<channels.length; i++){ uplink.syncChannel(channels[i]); } }); } function findServer(s){ for(var i=0; i<servers.length; i++){ srv = servers[i]; if(srv.sid == s || srv.name == s) return srv; } return null; } function findUser(u){ for(var i=0; i<users.length; i++){ usr = users[i]; if(usr.uid == u || usr.name == u) return usr; } return null; } function findChannel(c){ for(var i=0; i<channels.length; i++){ usr = channels[i]; if(channels[i].name == c) return channels[i]; } return null; } function removeServer(server){ var length = servers.length; var done = false; do { for(var i=0; i<length; i++){ if(servers.length != length) break; if(servers[i].uplink == server){ removeServer(servers[i]); // remove connected servers recursively } } if(i == length) done = true; } while(!done); for(var i=users.length-1; i>=0; i--){ if(users[i].uplink == server){ quitUser(users[i]); // remove users connected to this server } } for(var i=servers.length-1; i>=0; i--){ // finally, remove the given server if(servers[i] == server){ console.log('Removing server '+servers[i].name); events.doEvent('serverDelete', server); servers.splice(i, 1); delete server; break; } } } function getChannel(name){ var lowerName = name.toLowerCase(); var channel = null; for(var i=0; i<channels.length; i++){ if(channels[i].name == lowerName){ channel = channels[i]; break; } } if(!channel){ console.log('Creating channel '+name); channel = new IRCchannel; channel.setEvents(events); channel.name = name; channels.push(channel); events.doEvent('channelCreate', channel); } return channel; } function killUser(user){ console.log('KILL'); console.log(user); // TODO } 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 var user; if((user = findUser(uid)) || (user = findUser(nick))){ console.log(user); throw 'User already exists'; } console.log('Introducing user '+nick); user = new IRCuser; user.setEvents(events); user.introduce(nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink); users.push(user); events.doEvent('userCreate', user); return user; } function newServer(name, sid, desc, distance, uplink){ if(findServer(sid) || findServer(name)){ throw 'Server already exists'; } console.log('Introducing server '+name+' (SID='+sid+', uplink="'+uplink.name+'")'); server = new IRCserver; server.setEvents(events); server.introduce(name, sid, desc, distance, uplink); servers.push(server); events.doEvent('serverCreate', server); return server; } function ircSendData(tags, from, cmd, args){ var data = makeTagsString(tags); if(from){ data += ':' + from + ' '; } data += cmd; if(args) for(var i=0; i<args.length; i++){ var arg = args[i]; if(arg.indexOf(' ') >= 0){ // valid for last arg only arg = ':' + arg; } data += ' ' + arg; } console.log('<<< '+data); data += "\r\n"; connection.write(data); } function ircDataReceived(data){ data = data.toString('utf8'); recvData += data; var buffer = ''; for(var i=0; i<recvData.length; i++){ var c = recvData.charAt(i); switch(c){ default: buffer += c; break; case '\r': case '\n': if(buffer.length > 0) ircMessage(buffer); buffer = ''; break; } } recvData = buffer; } function makeTagsString(tags){ return ''; //TODO } function ircMessage(data){ var msg = irc.parseLine(data); uplink.processMessage(msg); } function ircConnectionClosed(e){ events.doEvent('uplinkLost', me); console.log('Closed: '+e); process.exit(1); } function processReplyTags(inputTags, newTags){ return newTags; // TODO } function quitUser(user){ console.log('Removing user '+user.name); for(var i=0; i<channels.length; i++){ channels[i].removeUser(user); } for(var i=0; i<users.length; i++){ if(users[i] == user){ users.splice(i, 1); events.doEvent('userDelete', user); delete user; break; } } } function removeEmptyChannels(){ for(var i=channels.length - 1; i >= 0; i--){ if(channels[i].users.length == 0){ storeChannelData(channels[i]); console.log('Removing channel '+channels[i].name); var channel = channels[i]; channels.splice(i, 1); delete channel; } } } function storeChannelData(channel){ // TODO maybe move a level up } setInterval(removeEmptyChannels, 1000); var irc = { 'users': users, 'channels': channels, 'servers': servers, 'getChannel': getChannel, 'setConnection': function(host, port, protocol){ uplink = require('./protocol/' + protocol); me = new IRCserver; me.setEvents(events); me.introduce('serwisy.pirc.pl', '11K', 'Serwisy', 0, null); servers.push(me); uplink.setHandlers({ send: ircSendData, findServer: findServer, findUser: findUser, killUser: killUser, newServer: newServer, newUser: newUser, getChannel: getChannel, quitUser: quitUser, removeServer: removeServer, findChannel: findChannel }); uplink.setSettings({ ID: me.sid, password: 'myservicespassword', name: me.name, description: me.description, version: 'k4be-services', me: me, maxUsers: 0 }); addInternalEvents(); uplink.setEvents(events); connection = new net.Socket(); connection.connect(port, host, uplink.connected.bind(this)); connection.on('data', ircDataReceived); connection.on('close', ircConnectionClosed); }, 'setEvents': function(newEvents){ events = newEvents; }, 'makeUser': function(nick, ident, host, realname){ var user = new IRCuser; user.setEvents(events); var uid = uplink.makeUid(); user.introduce(nick, 0, Math.floor(new Date() / 1000).toString(10), ident, host, uid, null, '+SoB'/*TODO*/, null, null, null, realname, me); users.push(user); return user; }, 'messagedata': function() { this.text = ''; this.args = []; this.tags = []; this.command = ''; this.sender = { 'nick': '', 'ident': '', 'host': '', 'server': false, 'user': false }; this.time = new Date(); this.reply = function(tags, cmd, args){ var outTags = processReplyTags(this.tags, tags); ircSendData(outTags, this.sender.nick, cmd, args); } this.originalString = ''; }, 'parseTags': function(tagsLine){ var tags = []; var tagState = 'keyName'; var keyValue; var keyName = ''; for(var i = 0; i < tagsLine.length; i++){ var cchar = tagsLine.charAt(i); switch(tagState){ case 'keyName': switch(cchar){ case '=': tagState = 'keyValue'; keyValue = ''; break; case ';': tags[keyName] = ''; keyName = ''; // staying in tagStateKeyName break; default: keyName += cchar; break; } break; case 'keyValue': switch(cchar){ case '\\': tagState = 'keyValueEscape'; break; case ';': tags[keyName] = keyValue; keyName = ''; tagState = 'keyName'; break; default: keyValue += cchar; break; } break; case 'keyValueEscape': switch(cchar){ case ':': keyValue += ';'; break; case 's': keyValue += ' '; break; case 'r': keyValue += '\r'; break; case 'n': keyValue += '\n'; break; default: keyValue += cchar; break; } tagState = 'keyValue'; break; } } if(keyName.length > 0) tags[keyName] = keyValue; // flush last tag return tags; }, 'parseLine': function(line){ var ircmsg = new irc.messagedata(); var line = line.trim(); line.replace(/^\s+|\s+$/gm,''); if(line == ''){ return; } ircmsg.originalString = line; var msglen = line.length; var pstate = 'start'; var currArg = ''; var tags = ''; var haveText = false; console.log('>>> ' + line); for(var i = 0; i < msglen; i++){ var cchar = line.charAt(i); switch(pstate){ case 'start': switch(cchar){ case '@': pstate = 'tags'; break; case ':': pstate = 'senderNick'; break; default: pstate = 'command'; ircmsg.command += cchar; break; } break; case 'tags': switch(cchar){ case ' ': pstate = 'start'; ircmsg.tags = irc.parseTags(tags); break; default: tags += cchar; break; } break; case 'senderNick': switch(cchar){ case '!': pstate = 'senderUser'; break; case '@': pstate = 'senderHost'; break; case ' ': pstate = 'command'; break; default: ircmsg.sender.nick += cchar; break; } break; case 'senderUser': switch(cchar){ case '@': pstate = 'senderHost'; break; case ' ': pstate = 'command'; break; default: ircmsg.sender.ident += cchar; break; } break; case 'senderHost': switch(cchar){ case ' ': pstate = 'command'; break; default: ircmsg.sender.host += cchar; break; } break; case 'command': switch(cchar){ case ' ': pstate = 'args'; break; default: ircmsg.command += cchar; break; } break; case 'args': switch(cchar){ case ' ': if(currArg != ''){ ircmsg.args.push(currArg); } currArg = ''; break; case ':': if(prevChar == ' '){ pstate = 'message'; haveText = true; } else { currArg += cchar; } break; default: currArg += cchar; break; } break; case 'message': ircmsg.text += cchar; break; } var prevChar = cchar; } if(pstate == 'args'){ ircmsg.args.push(currArg); } ircmsg.sender = uplink.processSender(ircmsg.sender); if(!ircmsg.sender){ return false; } if(!ircmsg.sender.server && !ircmsg.sender.user){ if(ircmsg.sender.ident == '' && ircmsg.sender.host == '' && ircmsg.sender.nick.indexOf('.')!=-1){ ircmsg.sender.server = true; } else { ircmsg.sender.user = true; } } if(!haveText){ ircmsg.text = ircmsg.args[ircmsg.args.length-1]; // handling last argument as text if : is missing } else { ircmsg.args.push(ircmsg.text); // handling text as a last argument as required by the protocol } // add u@h /* if(ircmsg.sender.user){ if(ircmsg.sender.ident) users.getUser(ircmsg.sender.nick).setIdent(ircmsg.sender.ident); if(ircmsg.sender.host) users.getUser(ircmsg.sender.nick).setHost(ircmsg.sender.host); }*/ // process known tags /* if('time' in ircmsg.tags){ ircmsg.time = parseISOString(ircmsg.tags['time']); } if('account' in ircmsg.tags){ users.getUser(ircmsg.sender.nick).setAccount(ircmsg.tags['account']); }*/ // console.log(ircmsg); return ircmsg; } }; module.exports = irc;