var protocol = { supportsTags: false, connected: function(){ ircSend(handlers.newTags(), settings.ID, 'PASS', [settings.password]); ircSend(handlers.newTags(), settings.ID, 'PROTOCTL', ['NICKv2', 'VHP', 'UMODE2', 'NICKIP', 'SJOIN', 'SJOIN2', 'SJ3', 'NOQUIT', 'TKLEXT', 'MLOCK', 'SID', 'MTAGS']); ircSend(handlers.newTags(), settings.ID, 'PROTOCTL', ['EAUTH=' + settings.name + ',,,' + settings.version]); ircSend(handlers.newTags(), settings.ID, 'PROTOCTL', ['SID=' + settings.ID]); ircSend(handlers.newTags(), settings.ID, 'SERVER', [settings.name, '1', settings.description]); }, processMessage: function(msg){ if(!msg) return; if(msg.command in cmdBinds){ cmdBinds[msg.command](msg); } else { console.log('Unhandled cmd: '+msg.command); console.log(msg); } }, processSender: function(sender){ if(sender.nick){ if(server = handlers.findServer(sender.nick)){ sender.nick = server.name; sender.server = server; } else if(user = handlers.findUser(sender.nick)){ sender.nick = user.name sender.ident = user.ident; sender.host = user.host; sender.user = user; } else { handlers.killUser(sender.nick); return false; } } return sender; }, setHandlers: function(newHandlers){ handlers = newHandlers; }, setSettings: function(newSettings){ settings = newSettings; }, setEvents: function(newEvents){ events = newEvents; }, // IRC actions introduceUser: function(user){ var nick = user.name; var distance = user.distance.toString(); var TS = user.TS.toString(); var ident = user.ident; var host = user.host; var uid = user.uid; var account = '0'; if(user.account){ account = user.account; } var umodes = makeUmodeString(user.umodes, true); var vhost = '*'; if(user.vhost){ vhost = user.vhost; } var cloakedHost = '*'; if(user.cloakedHost){ cloakedHost = user.cloakedHost; } var ip = '*'; if(user.ip){ ip = user.ip; } var realname = user.realname; var args = [nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname]; ircSend(handlers.newTags(), settings.ID, 'UID', args); }, syncChannel: function(channel){ var modes = null; var modeArgs = null; // prepare list modes var listModes = []; for(m in channel.listModes){ var mode = channel.listModes[m]; for(var i=0; i 10) break; if(text.length > 0) text += ' '; text += listModes[listCount]; } for(; userCount < users.length; userCount++){ if(++count > 10) break; if(text.length > 0) text += ' '; text += users[userCount]; } if(text.length == 0){ break; } var args = [Math.floor(new Date() / 1000).toString(10), channel.name]; if(modes) args.push(modes); else args.push('+'); if(modeArgs) args.push(modeArgs); args.push(text); ircSend(handlers.newTags(), settings.ID, 'SJOIN', args); } while(true); }, 'makeUid': function(){ return settings.me.sid + randomID(); }, 'changeUmodes': function(user, umodes){ if(user.uplink == settings.me){ // services bot ircSend(handlers.newTags(), user.uid, 'UMODE2', [makeUmodeString(umodes, true)]); } else { // normal user ircSend(handlers.newTags(), settings.ID, 'SVS2MODE', [user.uid, makeUmodeString(umodes, false)]); } user.changeUmodes(umodes); }, 'joinChannel': function(user, channel){ ircSend(handlers.newTags(), settings.ID, 'SJOIN', [Math.floor(new Date() / 1000).toString(10), channel.name, user.uid]); channel.joinUser(user); } } //>>> @time=2020-03-17T08:14:32.290Z;msgid=uwGzT2VzSTEEGab0demOxX :093 SJOIN 1584303585 #test :0931Y7O8W //>>> @time=2020-03-15T12:17:07.355Z;msgid=FhjgUp20YBKQ32TGPM28tS :143 SJOIN 1580651090 #help +nt :0931Y7O8W 093375CFR 123VTY20R function randomID(){ var length = 6; var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; return handlers.randomID(characters, length); } module.exports = protocol; var handlers = { sendHandler: null, findServer: null, findUser: null, killUser: null, newServer: null, newUser: null, getChannel: null, findChannel: null, quitUser: null, removeServer: null, randomID: null }; var settings = { ID: null, password: null, name: null, description: null, version: null, me: null, maxUsers: null }; var events = {}; var connection = { 'protoctl': {}, 'prefixes': {}, 'statusmodes': [], 'chmodes': [], // 0 - list modes, 1 - argument modes, 2 - argument modes (add only), 3 - standard modes 'umodes': {} }; var chmodeMap = { 'b': { 'name': 'BAN', 'user': true, 'services': true }, 'e': { 'name': 'EXCEPT', 'user': true, 'services': true }, 'I': { 'name': 'INVEX', 'user': true, 'services': true }, 'v': { 'name': 'VOICE', 'user': true, 'services': true }, 'h': { 'name': 'HALFOP', 'user': true, 'services': true }, 'o': { 'name': 'OP', 'user': true, 'services': true }, 'a': { 'name': 'PROTECT', 'user': true, 'services': true }, 'q': { 'name': 'OWNER', 'user': true, 'services': true }, 'k': { 'name': 'KEY', 'user': true, 'services': true }, 'L': { 'name': 'CHANNELLINK', 'user': true, 'services': true }, 'f': { 'name': 'FLOODPROT', 'user': true, 'services': true }, 'l': { 'name': 'LIMIT', 'user': true, 'services': true }, 'H': { 'name': 'HISTORY', 'user': true, 'services': true }, 'p': { 'name': 'PRIVATE', 'user': true, 'services': true }, 's': { 'name': 'SECRET', 'user': true, 'services': true }, 'm': { 'name': 'MODERATED', 'user': true, 'services': true }, 'n': { 'name': 'NOEXTERNAL', 'user': true, 'services': true }, 't': { 'name': 'TOPIC', 'user': true, 'services': true }, 'i': { 'name': 'INVITE', 'user': true, 'services': true }, 'r': { 'name': 'REGISTERED', 'user': false, 'services': true }, 'z': { 'name': 'SECUREONLY', 'user': true, 'services': true }, 'M': { 'name': 'NONREGMODERATED', 'user': true, 'services': true }, 'Q': { 'name': 'NOKICK', 'user': true, 'services': true }, 'N': { 'name': 'NONICK', 'user': true, 'services': true }, 'R': { 'name': 'NONREGINVITE', 'user': true, 'services': true }, 'T': { 'name': 'NOCTCPS', 'user': true, 'services': true }, 'O': { 'name': 'OPERS', 'user': false, 'services': true }, 'V': { 'name': 'NOINVITE', 'user': true, 'services': true }, 'K': { 'name': 'NOKNOCK', 'user': true, 'services': true }, 'D': { 'name': 'OLDDELAYJOIN', 'user': false, 'services': false }, 'd': { 'name': 'DELAYJOIN', 'user': true, 'services': true }, 'G': { 'name': 'CENSOR', 'user': true, 'services': true }, 'P': { 'name': 'PERMANENT', 'user': false, 'services': true }, 'Z': { 'name': 'SECURE', 'user': false, 'services': false }, 'S': { 'name': 'COLORFILTER', 'user': true, 'services': true }, 'C': { 'name': 'NOCTCP', 'user': true, 'services': true }, 'c': { 'name': 'NOCOLOR', 'user': true, 'services': true } }; var umodeMap = { 'i': { 'name': 'INVISIBLE', 'user': true, 'services': true }, 'o': { 'name': 'OPER', 'user': false, 'services': false }, 'w': { 'name': 'WALLOP', 'user': true, 'services': true }, 'r': { 'name': 'REGISTEREDNICK', 'user': false, 'services': true }, 's': { 'name': 'SNOTICE', 'user': true, 'services': true }, 'x': { 'name': 'CLOAK', 'user': true, 'services': true }, 'z': { 'name': 'SECUREUSER', 'user': false, 'services': false }, 'd': { 'name': 'DEAF', 'user': true, 'services': true }, 'H': { 'name': 'HIDEIRCOP', 'user': false, 'services': false }, 't': { 'name': 'VHOST', 'user': false, 'services': true }, 'I': { 'name': 'HIDEIDLE', 'user': false, 'services': false }, 'D': { 'name': 'PRIVDEAF', 'user': true, 'services': true }, 'Z': { 'name': 'SECUREMESSAGES', 'user': true, 'services': true }, 'R': { 'name': 'REGISTEREDMESSAGES', 'user': true, 'services': true }, 'q': { 'name': 'UNKICKABLE', 'user': false, 'services': false }, 'p': { 'name': 'HIDECHANNELS', 'user': true, 'services': true }, 'W': { 'name': 'WHOISNOTICE', 'user': false, 'services': false }, 'G': { 'name': 'CENSORMESSAGES', 'user': true, 'services': true }, 'T': { 'name': 'NOCTCPMESSAGES', 'user': true, 'services': true }, 'S': { 'name': 'SERVICEUSER', 'user': false, 'services': false }, 'B': { 'name': 'BOT', 'user': true, 'services': true } }; function parseChannelModes(modes, args){ var plus = true; var argIndex = 0; var output = {}; output['status'] = []; output['list'] = []; for(var i=0; i= 0){ output['list'].push({ 'name': name, 'value': args[argIndex++], 'status': plus }); } else if(connection.chmodes[1].indexOf(c) >= 0){ if(plus){ output[name] = args[argIndex++]; } else { output[name] = false; argIndex++; } } else if(connection.chmodes[2].indexOf(c) >= 0){ if(plus){ output[name] = args[argIndex++]; } else { output[name] = false; } } else if(connection.statusmodes.indexOf(c) >= 0){ var user = handlers.findUser(args[argIndex++]); if(user){ output['status'].push({ 'name': name, 'user': user, 'status': plus }); } else { throw 'User not found'; } } else { // treat unknown chars as type 3 if(connection.chmodes[3].indexOf(c) < 0){ console.log('Unknown mode '+name); } output[name] = plus; } } return output; } function parseUmodes(modeString){ var plus = true; var modes = {}; for(var i=0; i 0) ip += ':'; ip += ipBinary[i*2].toString(16).padStart(2, '0') + ipBinary[i*2+1].toString(16).padStart(2, '0'); } } else { throw 'unknown IP format'; } } else { ip = null; } if(vhost == '*'){ vhost = null; } if(cloakedHost == '*'){ cloakedHost = null; } if(account == '0'){ account = null; } handlers.newUser(nick, distance, TS, ident, host, uid, account, parseUmodes(umodes), vhost, cloakedHost, ip, realname, msg.sender.server); }, 'SJOIN': function(msg){ var channel = handlers.getChannel(msg.args[1]); channel.setTS(msg.args[0]); if(msg.args.length > 3){ var modeArgs = []; for(var i=3; i 3){ var value = msg.args[3]; } else { var value = null; } var channel = handlers.findChannel(what); if(channel){ channel.setMetadata(name, visibility, value); return; } var user = handlers.findUser(what); if(user){ user.setMetadata(name, visibility, value); } }, 'NETINFO': function(msg){ ircSend(handlers.newTags(), null, 'NETINFO', [settings.maxUsers.toString(10), Math.floor(new Date() / 1000).toString(10), msg.args[2], msg.args[3], "0", "0", "0", msg.args[7]]); }, 'SASL': function(msg){ console.log(msg); //TODO }, 'PING': function(msg){ msg.reply(null, 'PONG', msg.args); }, 'QUIT': function(msg){ handlers.quitUser(msg.sender.user); }, 'PART': function(msg){ var channel = handlers.getChannel(msg.args[0]); channel.removeUser(msg.sender.user); }, 'CHGHOST': function(msg){ var user = handlers.findUser(msg.args[0]); if(!user) return; user.changeVHost(msg.args[1]); }, 'CHGIDENT': function(msg){ var user = handlers.findUser(msg.args[0]); if(!user) return; user.changeVIdent(msg.args[1]); }, 'CHGNAME': function(msg){ var user = handlers.findUser(msg.args[0]); if(!user) return; user.setRealname(msg.args[1]); }, 'SETNAME': function(msg){ msg.sender.user.setRealname(msg.args[0]); }, 'SETHOST': function(msg){ msg.sender.user.changeVHost(msg.args[0]); }, 'SETIDENT': function(msg){ msg.sender.user.changeVIdent(msg.args[0]); }, 'NICK': function(msg){ msg.sender.user.changeNick(msg.args[0]); }, 'SDESC': function(msg){ msg.sender.uplink.setDescription(msg.args[0]); }, 'TOPIC': function(msg){ var channel = handlers.findChannel(msg.args[0]); if(!channel) return; var setter = msg.args[1]; var TS = msg.args[2]; var text = msg.args[3]; channel.setTopic({'setter': setter, 'TS': TS, 'text': text }); }, /* messagedata { text: '0', args: [ '183.89.186.94', '0' ], tags: [], command: 'REPUTATION', sender: { nick: 'test1.pirc.pl', ident: '', host: '', server: server { name: 'test1.pirc.pl', sid: '093', description: 'serwer testowy!', distance: '3', uplink: [Object], introduce: [Function] }, user: false }, time: 2020-03-14T10:48:06.207Z, reply: [Function], originalString: ':093 REPUTATION 183.89.186.94 0' } */ 'REPUTATION': function(msg){ //TODO }, 'UMODE2': function(msg){ msg.sender.user.changeUmodes(parseUmodes(msg.args[0])); }, 'KILL': function(msg){ var user = handlers.findUser(msg.args[0]); if(!user) return; handlers.quitUser(user); }, 'SQUIT': function(msg){ var server = handlers.findServer(msg.args[0]); if(!server) return; handlers.removeServer(server); }, /*messagedata { text: 'TestServ', args: [ '11K3R1VLH', 'TestServ' ], tags: [], command: 'WHOIS', sender: { nick: 'k4be', ident: 'testowy', host: 'localhost', server: false, user: user { name: 'k4be', TS: '1583056052', ident: 'testowy', host: 'localhost', account: null, umodes: [Object], vhost: 'staff:netadmin', cloakedHost: 'ukryty-18D8A42D', ip: '0000:0000:0000:0000:0000:0000:0000:0001', realname: 'realname', uid: '143ZQ0B02', distance: '0', uplink: [server], secure: false, certfp: null, vident: null, metadata: {}, introduce: [Function], setSecure: [Function], setFingerprint: [Function], changeVHost: [Function], changeVIdent: [Function], setRealname: [Function], changeNick: [Function], changeUmodes: [Function], setMetadata: [Function] } }, time: 2020-03-16T14:02:45.252Z, reply: [Function], originalString: ':143ZQ0B02 WHOIS 11K3R1VLH :TestServ' }*/ 'WHOIS': function(msg){ }, 'PRIVMSG': function(msg){ if(msg.args[0].charAt(0) == '#'){ events.doEvent('channelMessage', msg); } else { events.doEvent('botMessage', msg); } }, 'NOTICE': function(msg){ if(msg.args[0].charAt(0) == '#'){ events.doEvent('channelNotice', msg); } else { events.doEvent('botNotice', msg); } } };