Selaa lähdekoodia

[add] IRC handshake
[add] join all channels

B Thibault 8 vuotta sitten
vanhempi
commit
b881fd31ae

+ 1 - 1
srv/src/database.js

@@ -3,7 +3,7 @@ const sqlite3 = require('sqlite3'),
     updateAccountConfigTable = require('./models/accountConfig.js').updateTable;
 
 const DB_PATH = __dirname +"/../database.sqlite",
-    DB_VERSION = 1;
+    DB_VERSION = 2;
 
 function updateMetaTable(dbObj, currentVersion, cb) {
     if (!currentVersion) {

+ 33 - 12
srv/src/httpServ.js

@@ -15,35 +15,56 @@ const http = require("http")
 
 function Server() {
     var ctx = this,
-        errSocks = [];
+        errSocks = [],
+        ircSocks = [],
+        lastSockId = 0;
 
     this.httpServ = http.createServer(function(req, res) {
         res.ended = false;
-        res.on('end', () => {
+        res.once('end', () => {
             res.ended = true;
         });
         ctx.onRequest(req, res);
     });
     this.httpServ.on("connection", (sock) => {
+        sock.id = ++lastSockId;
+        sock.once('close', () => {
+            let i = ircSocks.indexOf(sock.id);
+            if (i !== -1)
+                ircSocks.splice(i, 1);
+            i = errSocks.indexOf(sock.id);
+            if (i !== -1)
+                errSocks.splice(i, 1);
+        });
         sock.on("data", (data) => {
             var dataStrArr = data.toString("utf-8").split(/\r?\n/g);
             for (var i =0, nbLines =dataStrArr.length; i < nbLines; i++)
-                if ((/^NICK /).test(dataStrArr[i])) {
-                    var i = errSocks.indexOf(sock);
-                    errSocks.splice(i, 1);
-                    var con = new IrcConnection(sock);
-                    dataStrArr.forEach((str) => { con.parse(str); });
+                if ((/^(NICK|PASS) /).test(dataStrArr[i])) {
+                    var i = errSocks.indexOf(sock.id);
+                    if (i >= 0) {
+                        errSocks.splice(i, 1);
+                        ircSocks.push(sock.id);
+                        var con = new IrcConnection(sock);
+                        dataStrArr.forEach((str) => { con.parse(str); });
+                    }
                     break;
                 }
         });
     });
+
+    // Default behaviour is to destroy connection, we would like to switch to IRC protocol instead
+    this.httpServ.removeAllListeners("clientError");
     this.httpServ.on("clientError", (err, sock) => {
-        errSocks.push(sock);
-        setTimeout(() => {
-            if (errSocks.indexOf(sock) !== -1)
-                sock.end();
-        }, 1);
+        if (ircSocks.indexOf(sock.id) === -1 && errSocks.indexOf(sock.id) === -1) {
+            errSocks.push(sock.id);
+            setTimeout(() => {
+                if (errSocks.indexOf(sock.id) !== -1) {
+                    sock.destroy(err);
+                }
+            }, 1);
+        }
     });
+
     this.httpServ.listen(config.port, ctx.onListen);
 }
 

+ 2 - 0
srv/src/ircServer/ircCmd.js

@@ -12,6 +12,8 @@ function IrcCmd(regResult) {
     this.command = regResult[2];
     this.args = parseParams(regResult[3]);
     this.trailing = regResult[4];
+    if (this.trailing)
+        this.args.push(this.trailing);
 }
 
 IrcCmd.parse = function(str) {

+ 140 - 8
srv/src/ircServer/ircConnection.js

@@ -2,41 +2,173 @@
 const parseIrcCmd = require('./ircCmd.js').parse
     ,accountManager = require("../models/accounts.js").accountManager
 
+    ,MultiChatManager = require("../multichatManager.js").MultiChatManager
+    ,slackManager = require("../slackManager.js").SlackManager
+
+    ,CONFIG = require("../../config.js");
+
+const SERVER_CONFIG = {
+    port: CONFIG.port,
+    hostname: CONFIG.hostname,
+    created: (new Date()).toString()
+};
+
 IrcConnection = module.exports.IrcConnection = function(sock) {
     var _this = this;
 
     this.account = null;
-    this.username = "mimou";
+    this.nickname;
+    this.username;
+    this.hostname;
+    this.servername;
+    this.realname;
+
+    this.polling = false;
+    this.joinedChannels = {};
 
     this.sock = sock;
     this.sock.on("data", (data) => {
         data.toString("utf-8").split(/\r?\n/g).forEach((str) => { _this.parse(str); });
     });
+    this.sock.once("close", () => {
+        console.log("[IRC] closed connection" +(this.account ? (" for user #" +this.account.id) : ""));
+        this.polling = false;
+    });
 }
 
 IrcConnection.prototype.write = function(line) {
+    console.log("[IRC] >>> " +line);
     this.sock.write(line +"\r\n");
 };
 
+IrcConnection.prototype.isAuthed = function() {
+    return this.account && this.nickname && this.username;
+};
+
+IrcConnection.prototype.checkConnectionDone = function(account, nickname, username, hostname, servername, realname) {
+    var authed = this.isAuthed();
+
+    if (account)
+        this.account = account;
+    if (nickname)
+        this.nickname = nickname;
+    if (username)
+        this.username = username;
+    if (hostname)
+        this.hostname = hostname;
+    if (servername)
+        this.servername = servername;
+    if (realname)
+        this.realname = realname;
+
+    if (!authed && this.isAuthed()) {
+        console.log("Authed new IRC client for user #" +this.account.id);
+        this.write(":" +SERVER_CONFIG.hostname +" 001 " +this.nickname +" :Welcome");
+        this.write(":" +SERVER_CONFIG.hostname +" 002 " +this.nickname +" :Your host is " +SERVER_CONFIG.hostname +"[" +this.sock.localAddress +"/" +SERVER_CONFIG.port +"], running version mimouchat");
+        this.write(":" +SERVER_CONFIG.hostname +" 003 " +this.nickname +" :This server was created " +SERVER_CONFIG.created);
+        this.write(":" +SERVER_CONFIG.hostname +" 004 " +this.nickname +" " +SERVER_CONFIG.hostname +" mimouchat-irc DOQRSZaghilopswz CFILMPQSbcefgijklmnopqrstvz bkloveqjfI");
+        this.write(":" +SERVER_CONFIG.hostname +" 375 " +this.nickname +" :- Mimouchat Message of the Day -");
+        this.write(":" +SERVER_CONFIG.hostname +" 372 " +this.nickname +" :- Welcome to mimouchat -");
+        this.write(":" +SERVER_CONFIG.hostname +" 376 " +this.nickname +" :End of /MOTD command.");
+        this.write(":" +this.nickname +" MODE " +this.nickname +" :+i");
+
+        this.chatContext = new MultiChatManager();
+        var services = this.account.getServices(),
+            now = Date.now();
+        for (var serviceId in services) {
+            switch (services[serviceId].type) {
+                case "Slack":
+                    this.chatContext.push(slackManager.lazyGet(serviceId, services[serviceId].oauthParam, now));
+                break;
+
+                default:
+                    console.error("Unknown service type for ", services[serviceId], " with account #" +req.account.id);
+            }
+        }
+        this.polling = true;
+        this.poll();
+        return true;
+    }
+    return false;
+};
+
+IrcConnection.prototype.sendJoin = function(channel) {
+    var res = this.joinedChannels[channel.id] = { topic: "", purpose: "" };
+    this.write(":" +this.nickname +" JOIN #" +channel.id);
+    return res;
+};
+
+IrcConnection.prototype.sendTopic = function(channel) {
+    this.joinedChannels[channel.id].topic = channel.topic.value;
+    this.write(":" +(channel.topic.creator ? this.chatContext.getUser(channel.topic.creator).getName() : SERVER_CONFIG.hostname) +" TOPIC #" +channel.id +" :" +channel.topic.value);
+};
+
+IrcConnection.prototype.sendPurpose = function(channel) {
+    this.joinedChannels[channel.id].purpose = channel.purpose.value;
+    //this.write(":" +this.nickname +" JOIN #" +channel.id);
+};
+
+IrcConnection.prototype.sendStaticUpdate = function(static) {
+    for (var team in static) {
+        if (static[team].channels)
+            static[team].channels.forEach((channel) => {
+                var jChannel = this.joinedChannels[channel.id];
+
+                if (!jChannel)
+                    jChannel = this.sendJoin(channel);
+                if (channel.purpose && jChannel.purpose !== channel.purpose.value)
+                    this.sendPurpose(channel);
+                if (jChannel.topic !== (channel.topic ? channel.topic.value : ""))
+                    this.sendTopic(channel);
+            });
+    }
+};
+
+IrcConnection.prototype.sendLiveUpdate = function(live) {
+};
+
+IrcConnection.prototype.poll = function() {
+    var now = Date.now();
+    var _this = this;
+    this.chatContext.poll(this.version || 0, now, (data) => {
+        if (_this.polling) {
+            if (data.static)
+                _this.sendStaticUpdate(data.static);
+            if (data.live)
+                _this.sendLiveUpdate(data.live);
+            if (data.v)
+                _this.version = data.v;
+            _this.poll();
+        }
+    }, null, false);
+};
+
 IrcConnection.prototype.parse = function(str) {
     var cmd = parseIrcCmd(str),
         _this = this;
     if (cmd) {
-        if (!this.account) {
+        if (!this.isAuthed()) {
             if (cmd.command === "PASS") {
                 accountManager.fromIrcPass(cmd.args[0], (account) => {
                     if (account) {
-                        _this.account = account;
-                        _this.write("PING :mzRH@PKG?l");
-                        _this.write(":mimouchat.knacki.info 001 " +_this.username +" :Welcome");
-                        _this.write(":mimouchat.knacki.info 001 " +_this.username +" :Welcome");
-                    }
-                    else
+                        _this.checkConnectionDone(account);
+                    } else {
+                        console.log("Invalid password from IRC gateway");
                         _this.sock.end();
+                    }
                 });
             }
+            else if (cmd.command === "NICK") {
+                this.checkConnectionDone(null, cmd.args[0]);
+            }
+            else if (cmd.command === "USER") {
+                this.checkConnectionDone(null, null, cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]);
+            }
         } else {
             switch (cmd.command) {
+                case "PING":
+                    this.write(":" +SERVER_CONFIG.hostname +" PONG :" +cmd.args[0]);
+                break;
                 default:
                     console.log(cmd);
                 break;

+ 4 - 1
srv/src/models/accounts.js

@@ -168,12 +168,15 @@ module.exports.updateTable = function(dbObject, currentVersion, cb) {
             +"`authGoogleUserId` STRING UNIQUE,"
             +"`authFacebookUserId` STRING UNIQUE,"
             +"`authSlackUserEmailAndTeam` STRING UNIQUE,"
-            +"`authIrcPass` STRING UNIQUE,"
             +"`certificates` STRING,"
             +"`cguReadAndAccepted` BOOLEAN NOT NULL DEFAULT FALSE,"
             +"`services` STRING NOT NULL"
             +')', cb);
         //TODO permanent login token array
+    } else if (currentVersion === 1) {
+        console.info("Updating table " +TABLE_NAME);
+        dbObject.run('ALTER TABLE ' +TABLE_NAME +' ADD COLUMN `authIrcPass` STRING;'+
+            'CREATE UNIQUE INDEX authIrcPassUniq ON ' +TABLE_NAME +'(authIrcPass)', cb);
     } else {
         cb(null);
     }

+ 11 - 10
srv/src/multichatManager.js

@@ -114,9 +114,10 @@ ChatSystem.prototype.sendCommand = function(chan, cmd, args) {};
 /**
  * @param {number} knownVersion
  * @param {number} nowTs
+ * @param {boolean} withTyping
  * @return {Object|null}
 **/
-ChatSystem.prototype.poll = function(knownVersion, nowTs) {};
+ChatSystem.prototype.poll = function(knownVersion, nowTs, withTyping) {};
 
 /**
  * @constructor
@@ -246,7 +247,7 @@ MultiChatManager.prototype.isMe = function(userId) {
     return false;
 };
 
-MultiChatManager.prototype.pollPeriodical = function(knownVersion) {
+MultiChatManager.prototype.pollPeriodical = function(knownVersion, withTyping) {
     var liveFeed
         ,staticFeed
         ,allTyping
@@ -257,7 +258,7 @@ MultiChatManager.prototype.pollPeriodical = function(knownVersion) {
     this.contexts.forEach(function(ctx) {
         var id = ctx.getId();
         if (id) {
-            var res = ctx.poll(knownVersion, now);
+            var res = ctx.poll(knownVersion, now, withTyping === undefined ? true : withTyping);
 
             if (res) {
                 if (res["static"]) {
@@ -291,9 +292,9 @@ MultiChatManager.prototype.pollPeriodical = function(knownVersion) {
         });
 };
 
-MultiChatManager.prototype.startPolling = function(knownVersion, reqT, callback) {
+MultiChatManager.prototype.startPolling = function(knownVersion, reqT, callback, withTyping) {
     var _this = this;
-    var res = _this.pollPeriodical(knownVersion),
+    var res = _this.pollPeriodical(knownVersion, withTyping),
         now = Date.now();
     if (res) {
         callback(res);
@@ -302,7 +303,7 @@ MultiChatManager.prototype.startPolling = function(knownVersion, reqT, callback)
             "v": knownVersion
         });
     } else {
-        setTimeout(function() { _this.startPolling(knownVersion, reqT, callback); }, 2000);
+        setTimeout(function() { _this.startPolling(knownVersion, reqT, callback, withTyping); }, 2000);
     }
 };
 
@@ -311,7 +312,7 @@ MultiChatManager.prototype.startPolling = function(knownVersion, reqT, callback)
  * @param {Function} callback
  * @param {(function(number, function((Array<{expose:Function, modified: number}>|null))))=} checkConfigUpdate
 **/
-MultiChatManager.prototype.poll = function(knownVersion, reqT, callback, checkConfigUpdate) {
+MultiChatManager.prototype.poll = function(knownVersion, reqT, callback, checkConfigUpdate, withTyping) {
     this.contexts.forEach(function(ctx) {
         ctx.onRequest();
     });
@@ -327,7 +328,7 @@ MultiChatManager.prototype.poll = function(knownVersion, reqT, callback, checkCo
                     v = Math.max(v, conf.modified);
                 });
 
-                var res = _this.pollPeriodical(knownVersion);
+                var res = _this.pollPeriodical(knownVersion, withTyping);
                 if (res) {
                     res["config"] = exposedConfig;
                     callback(res);
@@ -340,13 +341,13 @@ MultiChatManager.prototype.poll = function(knownVersion, reqT, callback, checkCo
             } else {
                 // FIXME start Polling now (actually some loop appens because of typing conception)
                 //_this.startPolling(knownVersion, reqT, callback);
-                setTimeout(function() { _this.startPolling(knownVersion, reqT, callback); }, 1000);
+                setTimeout(function() { _this.startPolling(knownVersion, reqT, callback, withTyping); }, 1000);
             }
         });
     } else {
         // FIXME start Polling now (actually some loop appens because of typing conception)
         //_this.startPolling(knownVersion, reqT, callback);
-        setTimeout(function() { _this.startPolling(knownVersion, reqT, callback); }, 1000);
+        setTimeout(function() { _this.startPolling(knownVersion, reqT, callback, withTyping); }, 1000);
     }
 };
 

+ 2 - 2
srv/src/slack.js

@@ -190,10 +190,10 @@ Slack.prototype.getEmojis = function(cb) {
     });
 };
 
-Slack.prototype.poll = function(knownVersion, now) {
+Slack.prototype.poll = function(knownVersion, now, withTyping) {
     if (this.connected) {
         var updatedCtx = this.data.getUpdates(knownVersion)
-            ,updatedTyping = this.data.getWhoIsTyping(now)
+            ,updatedTyping = withTyping ? this.data.getWhoIsTyping(now) : undefined
             ,updatedLive = this.getLiveUpdates(knownVersion);
 
         if (updatedCtx || updatedLive || updatedTyping) {