123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- 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<users.length; i++){
- var user = users[i];
- if(user.uplink == me){
- irc.uplink.introduceUser(user);
- }
- }
- for(var i=0; i<channels.length; i++){
- irc.uplink.syncChannel(channels[i]);
- }
- });
- events.newTagMessage.unshift(function(inputTags, newTags, msg){
- if(!newTags){
- var newTags = {};
- }
- if('label' in inputTags) newTags['label'] = inputTags['label']; // labeled-response
- newTags['time'] = new Date().toISOString(); // server-time
- newTags['msgid'] = randomID('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 22);
- return [inputTags, newTags, msg];
- });
- }
- function randomID(characters, length){
- var result = '';
- var charactersLength = characters.length;
- for(var i=0; i<length; i++ ) {
- result += characters.charAt(Math.floor(Math.random() * charactersLength));
- }
- return result;
- }
- 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){
- 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<user.channels.length; i++){
- irc.uplink.joinChannel(user, user.channels[i]);
- }
- return;
- }
- 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,
- '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;
|