var net = require('net'); var IRCserver = require('./server'); var IRCuser = require('./user'); var IRCchannel = require('./channel'); 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=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= 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 0) ircMessage(buffer); buffer = ''; break; } } recvData = buffer; } function makeTagsString(tags){ if(!irc.uplink.supportsTags) return ''; if(!tags) return ''; var tagText = ''; for(name in tags){ var value = tags[name].replace(/;/g, '\\:').replace(/ /g, '\\s').replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); if(tagText.length == 0){ tagText += '@'; } else { tagText += ';'; } tagText += name; if(value.length > 0){ tagText += '=' + value; } } if(tagText.length > 0){ tagText += ' '; } return tagText; } function ircMessage(data){ var msg = irc.parseLine(data); irc.uplink.processMessage(msg); } function ircConnectionClosed(e){ events.doEvent('uplinkLost', me); console.log('Closed: '+e); process.exit(1); } function processReplyTags(inputTags, newTags, msg){ newTags = events.doEvent('newTagMessage', inputTags, newTags, msg); return newTags[1]; } function newTags(newTags){ return events.doEvent('newTagMessage', {}, newTags, null)[1]; } function quitUser(user){ if(user.uplink == me){ // out bot was killed? console.log('Tried to kill ' + user.name + '!'); irc.uplink.introduceUser(user); // re-introduce for(var i=0; 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, 'uplink': null, 'setConnection': function(host, port, protocol){ irc.uplink = require('./protocol/' + protocol); me = new IRCserver; me.setEvents(events); me.introduce('serwisy.pirc.pl', '11K', 'Serwisy', 0, null); servers.push(me); irc.uplink.setHandlers({ send: ircSendData, findServer: findServer, findUser: findUser, killUser: killUser, newServer: newServer, newUser: newUser, getChannel: getChannel, quitUser: quitUser, removeServer: removeServer, findChannel: findChannel, randomID: randomID, newTags: newTags }); irc.uplink.setSettings({ ID: me.sid, password: 'myservicespassword', name: me.name, description: me.description, version: 'k4be-services', me: me, maxUsers: 0 }); addInternalEvents(); irc.uplink.setEvents(events); connection = new net.Socket(); connection.connect(port, host, irc.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 = irc.uplink.makeUid(); var umodes = { 'SERVICEUSER': true, 'BOT': true, 'OPER': true }; // TODO make it configurable user.introduce(nick, 0, Math.floor(new Date() / 1000).toString(10), ident, host, uid, null, umodes, 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, this); 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 = irc.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;