From bd11d55a3323857fdddd7fa4665dadd3d90dff53 Mon Sep 17 00:00:00 2001 From: Aidan Gauland Date: Wed, 7 Feb 2018 22:17:38 +1300 Subject: [PATCH 001/162] Improve !help message Add the missing server argument to the help message for !whois, and make this argument consistent across all command help messages. (The change from irc.example.com to irc.example.net is simply because that is a more common top-level-domain for IRC networks, and it reads almost as "example net".) --- lib/bridge/MatrixHandler.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index dbf50e549..de6464fa3 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -203,33 +203,33 @@ MatrixHandler.prototype._onAdminMessage = Promise.coroutine(function*(req, event if (cmd === "!help") { let helpCommands = { "!join": { - example: `!join irc.example.com #channel [key]`, + example: `!join [irc.example.net] #channel [key]`, summary: `Join a channel (with optional channel key)`, }, "!nick": { - example: `!nick irc.example.com DesiredNick`, + example: `!nick [irc.example.net] DesiredNick`, summary: "Change your nick. If no arguments are supplied, " + "your current nick is shown.", }, "!whois": { - example: `!whois NickName|@alice:matrix.org`, + example: `!whois [irc.example.net] NickName|@alice:matrix.org`, summary: "Do a /whois lookup. If a Matrix User ID is supplied, " + "return information about that user's IRC connection.", }, "!storepass": { - example: `!storepass [irc.example.com] passw0rd`, + example: `!storepass [irc.example.net] passw0rd`, summary: `Store a NickServ password (server password)`, }, "!removepass": { - example: `!removepass [irc.example.com]`, + example: `!removepass [irc.example.net]`, summary: `Remove a previously stored NickServ password`, }, "!quit": { example: `!quit`, - summary: `Leave all bridged channels and remove your connection to IRC`, + summary: `Leave all bridged channels, on all networks, and remove your connections to all networks.`, }, "!cmd": { - example: `!cmd [irc.server] COMMAND [arg0 [arg1 [...]]]`, + example: `!cmd [irc.example.net] COMMAND [arg0 [arg1 [...]]]`, summary: `Issue a raw IRC command. These will not produce a reply.`, }, }; From 56e16db02e1a53f446ff6ba67f1bfffdf7754b8e Mon Sep 17 00:00:00 2001 From: Aidan Gauland Date: Wed, 7 Feb 2018 22:35:34 +1300 Subject: [PATCH 002/162] Split very long line Wrap a line that is longer than the maximum allowed length of 100 characters. --- lib/bridge/MatrixHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index de6464fa3..cdf75d372 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -226,7 +226,8 @@ MatrixHandler.prototype._onAdminMessage = Promise.coroutine(function*(req, event }, "!quit": { example: `!quit`, - summary: `Leave all bridged channels, on all networks, and remove your connections to all networks.`, + summary: "Leave all bridged channels, on all networks, and remove your " + + "connections to all networks.", }, "!cmd": { example: `!cmd [irc.example.net] COMMAND [arg0 [arg1 [...]]]`, From 5d578e07f3d4c452c64d7f64c76a798cc1f227b1 Mon Sep 17 00:00:00 2001 From: Aidan Gauland Date: Sat, 10 Feb 2018 11:09:39 +1300 Subject: [PATCH 003/162] Add note about !cmd being case-sensitive. --- lib/bridge/MatrixHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index cdf75d372..2200c0f34 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -231,7 +231,8 @@ MatrixHandler.prototype._onAdminMessage = Promise.coroutine(function*(req, event }, "!cmd": { example: `!cmd [irc.example.net] COMMAND [arg0 [arg1 [...]]]`, - summary: `Issue a raw IRC command. These will not produce a reply.`, + summary: "Issue a raw IRC command. These will not produce a reply." + + "(Note that the command must be all uppercase.)", }, }; From 79d5c222366ec4aed01ae41d2e6552dcdf63caef Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 11 Jul 2018 11:00:53 +0100 Subject: [PATCH 004/162] Bump jasmine to 3.X --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f4d4aacc..b8fd76374 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "devDependencies": { "eslint": "^1.10.3", "istanbul": "^0.4.5", - "jasmine": "^2.5.2", + "jasmine": "^3.1.0", "proxyquire": "^1.4.0" } } From 5137d65b2cb580e471ee60e56d332e1d77abd7e3 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 11 Jul 2018 11:01:08 +0100 Subject: [PATCH 005/162] Replace the deprecated istanbul utility with nyc --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b8fd76374..aa42f4625 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test": "BLUEBIRD_DEBUG=1 jasmine --stop-on-failure=true", "lint": "eslint --max-warnings 0 lib spec", "check": "npm test && npm run lint", - "ci-test": "BLUEBIRD_DEBUG=1 istanbul cover -x \"**/spec/**\" --report text jasmine", + "ci-test": "BLUEBIRD_DEBUG=1 nyc --report text jasmine", "ci": "npm run lint && npm run ci-test" }, "repository": { @@ -40,8 +40,8 @@ }, "devDependencies": { "eslint": "^1.10.3", - "istanbul": "^0.4.5", "jasmine": "^3.1.0", + "nyc": "^12.0.2", "proxyquire": "^1.4.0" } } From bb8649f244661039045f2272f849dacac65c81a6 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 25 Jul 2018 18:09:15 +0100 Subject: [PATCH 006/162] Bump matrix-appservice-bridge to 1.5.0a --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 594fce769..2453007eb 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "extend": "^2.0.0", "irc": "matrix-org/node-irc#b1614bc784200c65247784d7b9e9ab867140412d", "js-yaml": "^3.2.7", - "matrix-appservice-bridge": "1.4.0a", + "matrix-appservice-bridge": "1.5.0a", "nedb": "^1.1.2", "nopt": "^3.0.1", "prom-client": "^6.3.0", From 03e78089a61a4b9014311aaccfcfb87d1eef9a4f Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 14:37:05 +0100 Subject: [PATCH 007/162] DataStore functions for storing mode across restarts --- lib/DataStore.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/DataStore.js b/lib/DataStore.js index a46feec44..255cf468d 100644 --- a/lib/DataStore.js +++ b/lib/DataStore.js @@ -311,6 +311,46 @@ DataStore.prototype.getMappingsForChannelByOrigin = function(server, channel, or }); }; +DataStore.prototype.getModesForChannel = function (server, channel) { + log.info("getModesForChannel (server=%s, channel=%s)", + server.domain, channel + ); + let remoteId = IrcRoom.createId(server, channel); + return this._roomStore.getEntriesByRemoteId(remoteId).then((entries) => { + const mapping = {}; + entries.forEach((entry) => { + mapping[entry.matrix.getId()] = entry.remote.get("modes") || []; + }); + return mapping; + }); +}; + +DataStore.prototype.setModeForRoom = Promise.coroutine(function*(roomId, mode, remove=False) { + log.info("setModeForRoom (mode=%s, roomId=%s, remove=%s)", + mode, roomId, remove + ); + this._roomStore.getEntriesByMatrixId(roomId).then((entries) => { + entries.forEach((entry) => { + const modes = entry.remote.get("modes") || []; + const hasMode = modes.includes(mode); + + if ((!hasMode && remove) || (hasMode && !remove)) { + return; + } + + if (remove) { + modes.splice(modes.indexOf(mode), 1); + } else { + modes.push(mode); + } + + entry.remote.set("modes", modes); + + return this._roomStore.upsertEntry(entry); + }); + }); +}); + DataStore.prototype.setPmRoom = function(ircRoom, matrixRoom, userId, virtualUserId) { log.info("setPmRoom (id=%s, addr=%s chan=%s real=%s virt=%s)", matrixRoom.getId(), ircRoom.server.domain, ircRoom.channel, userId, From dbeb12f9e2f6a514304dda247c7bbd5fac2e76e5 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 14:37:46 +0100 Subject: [PATCH 008/162] Fix bad log line --- lib/bridge/IrcHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index 5ab6c8d01..2f3c6f15f 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -882,7 +882,7 @@ IrcHandler.prototype._onPrivateMode = Promise.coroutine(function*(req, server, c return this._setMatrixRoomAsInviteOnly(room, enabled); default: // Not reachable, but warn anyway in case of future additions - log.warn(`onMode: Unhandled channel mode ${mode}`); + req.log.warn(`onMode: Unhandled channel mode ${mode}`); return Promise.resolve(); } }); From 7ebd891e98a5ddd858faeeaaa58f4a05c48bba70 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 14:43:17 +0100 Subject: [PATCH 009/162] Emit a disabled if a cached mode is no longer present. --- lib/bridge/IrcHandler.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index 2f3c6f15f..bbf043294 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -911,10 +911,25 @@ IrcHandler.prototype.onModeIs = Promise.coroutine(function*(req, server, channel } ); - // If the channel does not have 's' as part of its mode, trigger the equivalent of '-s' - if (mode.indexOf('s') === -1) { - promises.push(this.onMode(req, server, channel, 'onModeIs function', 's', false)); - } + // We cache modes per room, so extract the set of modes for all these rooms. + const roomModeMap = yield this.ircBridge.getStore().getModesForChannel(server, channel); + const oldModes = new Set(); + Object.values(roomModeMap).forEach((mode) => { + mode.forEach((m) => {oldModes.add(m)}); + }); + req.log.debug(`Got cached mode for ${channel} ${[...oldModes]}`); + + // For each cached mode we have for the room, that is no longer set: emit a disabled mode. + promises.concat([...oldModes].map((oldModeChar) => { + if (!MODES_TO_WATCH.includes(oldModeChar)) { + return Promise.resolve(); + } + req.log.debug(`${server.domain} ${channel}: Checking if cached mode '${oldModeChar}' is still set.`); + if (!mode.includes(oldModeChar)) { // If the mode is no longer here. + req.log.debug(`${oldModeChar} has been unset, disabling.`); + return this.onMode(req, server, channel, 'onModeIs function', oldModeChar, false); + } + })); yield Promise.all(promises); }); From 20ba573f3b1ca4fa3e5c417dc1e06c495b21bc03 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 14:44:32 +0100 Subject: [PATCH 010/162] Cache 'm' mode state --- lib/bridge/IrcHandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index bbf043294..a335dbb09 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -824,6 +824,7 @@ IrcHandler.prototype._onModeratedChannelToggle = Promise.coroutine(function*(req "onModeratedChannelToggle: (channel=%s,enabled=%s) power levels updated in room %s", channel, enabled, roomId ); + this.ircBridge.getStore().setModeForRoom(roomId, "m", !enabled); } catch (err) { req.log.error("Failed to alter power level in room %s : %s", roomId, err); From e3b3d08c682207a2dd6b3fca18679af453f33232 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 14:45:21 +0100 Subject: [PATCH 011/162] List of modes we need to watch for --- lib/bridge/IrcHandler.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index a335dbb09..baca06831 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -14,6 +14,12 @@ var QuitDebouncer = require("./QuitDebouncer.js"); const JOIN_DELAY_MS = 250; const JOIN_DELAY_CAP_MS = 30 * 60 * 1000; // 30 mins +const MODES_TO_WATCH = [ + "m", // We want to ensure we do not miss rooms that get unmoderated. + "k", + "i", + "s" +]; function IrcHandler(ircBridge) { this.ircBridge = ircBridge; From 173f6b152449405db30260af56e79d0b41f56636 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 14:45:48 +0100 Subject: [PATCH 012/162] Cache 'k','i','s' for rooms --- lib/bridge/IrcHandler.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index baca06831..0fb36ab5e 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -866,6 +866,9 @@ IrcHandler.prototype._onPrivateMode = Promise.coroutine(function*(req, server, c } const key = this.ircBridge.publicitySyncer.getIRCVisMapKey(server.getNetworkId(), channel); + matrixRooms.map((room) => { + this.ircBridge.getStore().setModeForRoom(room.getId(), "s", enabled); + }); // Update the visibility for all rooms connected to this channel return this.ircBridge.publicitySyncer.updateVisibilityMap( true, key, enabled @@ -880,6 +883,7 @@ IrcHandler.prototype._onPrivateMode = Promise.coroutine(function*(req, server, c "Reverting %s back to default join_rule"), room.getId() ); + this.ircBridge.getStore().setModeForRoom(room.getId(), mode, enabled); if (enabled) { return this._setMatrixRoomAsInviteOnly(room, true); } From 123a39429b57588ec5fb0945ef1fc7a2cc51c69f Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 15:15:34 +0100 Subject: [PATCH 013/162] removed -> enabled is less confusing --- lib/DataStore.js | 15 +++++++-------- lib/bridge/IrcHandler.js | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/DataStore.js b/lib/DataStore.js index 255cf468d..ca9fa3505 100644 --- a/lib/DataStore.js +++ b/lib/DataStore.js @@ -325,23 +325,22 @@ DataStore.prototype.getModesForChannel = function (server, channel) { }); }; -DataStore.prototype.setModeForRoom = Promise.coroutine(function*(roomId, mode, remove=False) { - log.info("setModeForRoom (mode=%s, roomId=%s, remove=%s)", - mode, roomId, remove +DataStore.prototype.setModeForRoom = Promise.coroutine(function*(roomId, mode, enabled=True) { + log.info("setModeForRoom (mode=%s, roomId=%s, enabled=%s)", + mode, roomId, enabled ); this._roomStore.getEntriesByMatrixId(roomId).then((entries) => { entries.forEach((entry) => { const modes = entry.remote.get("modes") || []; const hasMode = modes.includes(mode); - if ((!hasMode && remove) || (hasMode && !remove)) { + if ((!hasMode && !enabled) || (hasMode && enabled)) { return; } - - if (remove) { - modes.splice(modes.indexOf(mode), 1); - } else { + if (enabled) { modes.push(mode); + } else { + modes.splice(modes.indexOf(mode), 1); } entry.remote.set("modes", modes); diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index 0fb36ab5e..f54cbace5 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -830,7 +830,7 @@ IrcHandler.prototype._onModeratedChannelToggle = Promise.coroutine(function*(req "onModeratedChannelToggle: (channel=%s,enabled=%s) power levels updated in room %s", channel, enabled, roomId ); - this.ircBridge.getStore().setModeForRoom(roomId, "m", !enabled); + this.ircBridge.getStore().setModeForRoom(roomId, "m", enabled); } catch (err) { req.log.error("Failed to alter power level in room %s : %s", roomId, err); From 10f92e7562fed10328d7d246098cff5b6dda2baa Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 15:15:52 +0100 Subject: [PATCH 014/162] Make setting modes for rooms less racy --- lib/bridge/IrcHandler.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index f54cbace5..1cf83d8ed 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -883,7 +883,6 @@ IrcHandler.prototype._onPrivateMode = Promise.coroutine(function*(req, server, c "Reverting %s back to default join_rule"), room.getId() ); - this.ircBridge.getStore().setModeForRoom(room.getId(), mode, enabled); if (enabled) { return this._setMatrixRoomAsInviteOnly(room, true); } @@ -898,7 +897,11 @@ IrcHandler.prototype._onPrivateMode = Promise.coroutine(function*(req, server, c } }); - yield Promise.all(promises); + yield Promise.all(promises).then(() => { + matrixRooms.forEach((room) => { + this.ircBridge.getStore().setModeForRoom(room.getId(), mode, enabled); + }); + }); }); /** From d2c220102bf387e3f7fda5031943e27956802270 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 16:32:53 +0100 Subject: [PATCH 015/162] Boolean properly --- lib/DataStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DataStore.js b/lib/DataStore.js index ca9fa3505..df29eb97f 100644 --- a/lib/DataStore.js +++ b/lib/DataStore.js @@ -334,7 +334,7 @@ DataStore.prototype.setModeForRoom = Promise.coroutine(function*(roomId, mode, e const modes = entry.remote.get("modes") || []; const hasMode = modes.includes(mode); - if ((!hasMode && !enabled) || (hasMode && enabled)) { + if (hasMode === enabled) { return; } if (enabled) { From 85e5fa896e079c3fa4042c1953dbbd644244041e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 16:33:13 +0100 Subject: [PATCH 016/162] Store mode earlier --- lib/bridge/IrcHandler.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index 1cf83d8ed..45a85db5a 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -873,6 +873,9 @@ IrcHandler.prototype._onPrivateMode = Promise.coroutine(function*(req, server, c return this.ircBridge.publicitySyncer.updateVisibilityMap( true, key, enabled ); + } else { + // "k" and "i" + this.ircBridge.getStore().setModeForRoom(room.getId(), mode, enabled); } var promises = matrixRooms.map((room) => { @@ -897,11 +900,7 @@ IrcHandler.prototype._onPrivateMode = Promise.coroutine(function*(req, server, c } }); - yield Promise.all(promises).then(() => { - matrixRooms.forEach((room) => { - this.ircBridge.getStore().setModeForRoom(room.getId(), mode, enabled); - }); - }); + yield Promise.all(promises); }); /** From 7951064c47ae7e13424a3b613b3eba70b69e9f77 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 16:45:13 +0100 Subject: [PATCH 017/162] Fix moaning tests --- lib/bridge/IrcHandler.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index 45a85db5a..39dbc760c 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -875,7 +875,9 @@ IrcHandler.prototype._onPrivateMode = Promise.coroutine(function*(req, server, c ); } else { // "k" and "i" - this.ircBridge.getStore().setModeForRoom(room.getId(), mode, enabled); + matrixRooms.map((room) => { + this.ircBridge.getStore().setModeForRoom(room.getId(), mode, enabled); + }); } var promises = matrixRooms.map((room) => { From c6b1d74baa5c981cd367d9ba707bc23962fe3abf Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 30 Jul 2018 16:52:40 +0100 Subject: [PATCH 018/162] Linting --- lib/DataStore.js | 9 +++++---- lib/bridge/IrcHandler.js | 15 +++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/DataStore.js b/lib/DataStore.js index df29eb97f..864bb763f 100644 --- a/lib/DataStore.js +++ b/lib/DataStore.js @@ -329,8 +329,8 @@ DataStore.prototype.setModeForRoom = Promise.coroutine(function*(roomId, mode, e log.info("setModeForRoom (mode=%s, roomId=%s, enabled=%s)", mode, roomId, enabled ); - this._roomStore.getEntriesByMatrixId(roomId).then((entries) => { - entries.forEach((entry) => { + return this._roomStore.getEntriesByMatrixId(roomId).then((entries) => { + entries.map((entry) => { const modes = entry.remote.get("modes") || []; const hasMode = modes.includes(mode); @@ -339,13 +339,14 @@ DataStore.prototype.setModeForRoom = Promise.coroutine(function*(roomId, mode, e } if (enabled) { modes.push(mode); - } else { + } + else { modes.splice(modes.indexOf(mode), 1); } entry.remote.set("modes", modes); - return this._roomStore.upsertEntry(entry); + this._roomStore.upsertEntry(entry); }); }); }); diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index 39dbc760c..152191408 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -873,12 +873,11 @@ IrcHandler.prototype._onPrivateMode = Promise.coroutine(function*(req, server, c return this.ircBridge.publicitySyncer.updateVisibilityMap( true, key, enabled ); - } else { - // "k" and "i" - matrixRooms.map((room) => { - this.ircBridge.getStore().setModeForRoom(room.getId(), mode, enabled); - }); } + // "k" and "i" + matrixRooms.map((room) => { + this.ircBridge.getStore().setModeForRoom(room.getId(), mode, enabled); + }); var promises = matrixRooms.map((room) => { switch (mode) { @@ -929,8 +928,8 @@ IrcHandler.prototype.onModeIs = Promise.coroutine(function*(req, server, channel // We cache modes per room, so extract the set of modes for all these rooms. const roomModeMap = yield this.ircBridge.getStore().getModesForChannel(server, channel); const oldModes = new Set(); - Object.values(roomModeMap).forEach((mode) => { - mode.forEach((m) => {oldModes.add(m)}); + Object.values(roomModeMap).forEach((roomMode) => { + roomMode.forEach((m) => {oldModes.add(m)}); }); req.log.debug(`Got cached mode for ${channel} ${[...oldModes]}`); @@ -939,7 +938,7 @@ IrcHandler.prototype.onModeIs = Promise.coroutine(function*(req, server, channel if (!MODES_TO_WATCH.includes(oldModeChar)) { return Promise.resolve(); } - req.log.debug(`${server.domain} ${channel}: Checking if cached mode '${oldModeChar}' is still set.`); + req.log.debug(`${server.domain} ${channel}: Checking if '${oldModeChar}' is still set.`); if (!mode.includes(oldModeChar)) { // If the mode is no longer here. req.log.debug(`${oldModeChar} has been unset, disabling.`); return this.onMode(req, server, channel, 'onModeIs function', oldModeChar, false); From 9159ed547049ffc525225fd6b99ee1394e0015a3 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 1 Aug 2018 11:25:57 +0100 Subject: [PATCH 019/162] presenceEnabled => enablePresence --- config.sample.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.sample.yaml b/config.sample.yaml index 86a8fecc8..9b6db9bd6 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -30,7 +30,7 @@ homeserver: # Should presence be enabledfor matrix clients on this bridge. If disabled on the # homeserver then it should also be disabled here to avoid excess traffic. # Default: true - presenceEnabled: true + enablePresence: true # Configuration specific to the IRC service ircService: From af1d3c5debb9925c1592e9fa9f37e6dee63d377e Mon Sep 17 00:00:00 2001 From: Sven Anderson Date: Mon, 30 Oct 2017 18:56:55 +0100 Subject: [PATCH 020/162] Track nick changes by parting and joining a new user The only way to track nick changes on the IRC side at the moment is to let the user leave the channel and letting a new user with the new nick join. This looks weird but still is better than many zombie nicks and hidden users in bridged channels. Fixes: #71 --- lib/irc/IrcEventBroker.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/irc/IrcEventBroker.js b/lib/irc/IrcEventBroker.js index baef33445..aa4d92688 100644 --- a/lib/irc/IrcEventBroker.js +++ b/lib/irc/IrcEventBroker.js @@ -347,6 +347,18 @@ IrcEventBroker.prototype.addHooks = function(client, connInst) { req, server, createUser(nick), chan, "join" )); }); + this._hookIfClaimed(client, connInst, "nick", function(oldNick, newNick, chans, msg) { + chans = chans || []; + chans.forEach((chan) => { + const req = createRequest(); + complete(req, ircHandler.onPart( + req, server, createUser(oldNick), chan, "nick" + )); + complete(req, ircHandler.onJoin( + req, server, createUser(newNick), chan, "nick" + )); + }); + }); // bucket names and drain them one at a time to avoid flooding // the matrix side with registrations / joins var namesBucket = [ From 1424b6c36071940639326725646a013457934e87 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 7 Aug 2018 15:44:32 +0100 Subject: [PATCH 021/162] Detect "Too many host connections" --- lib/irc/ConnectionInstance.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/irc/ConnectionInstance.js b/lib/irc/ConnectionInstance.js index abaa3fe95..fd4466a86 100644 --- a/lib/irc/ConnectionInstance.js +++ b/lib/irc/ConnectionInstance.js @@ -93,7 +93,7 @@ ConnectionInstance.prototype.connect = function() { /** * Blow away the connection. You MUST destroy this object afterwards. * @param {string} reason - Reason to reject with. One of: - * throttled|irc_error|net_error|timeout|raw_error + * throttled|irc_error|net_error|timeout|raw_error|toomanyconns */ ConnectionInstance.prototype.disconnect = function(reason) { if (this.dead) { @@ -118,7 +118,7 @@ ConnectionInstance.prototype.disconnect = function(reason) { } if (this.state !== "connected") { // we never resolved this defer, so reject it. - this._connectDefer.reject(new Error(reason)); + this.reject(new Error(reason)); } if (this.state === "connected" && this.onDisconnect) { // we only invoke onDisconnect once we've had a successful connect. @@ -215,6 +215,11 @@ ConnectionInstance.prototype._listenForErrors = function() { self.disconnect("banned").catch(logError); return; } + const tooManyHosts = errText.startsWith("Too many host connections"); + if (tooManyHosts) { + self.disconnect("toomanyconns").catch(logError); + return; + } } if (!wasThrottled) { self.disconnect("raw_error").catch(logError); @@ -369,6 +374,13 @@ ConnectionInstance.create = Promise.coroutine(function*(server, opts, onCreatedC yield Promise.delay(BANNED_TIME_MS); } + if (err.message === "toomanyconns") { + log.error( + `User ${opts.nick} as ILINED. This may be the network limiting us!` + ); + throw new Error("Connection was ILINED. We cannot retry this."); + } + // always set a staggered delay here to avoid thundering herd // problems on mass-disconnects let delay = (BASE_RETRY_TIME_MS * Math.random())+ retryTimeMs + From cdfb9ca5243a62317b6bd7234212e10f6cf8956c Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 7 Aug 2018 15:44:47 +0100 Subject: [PATCH 022/162] Kick if we failed to get a bridged irc user for whatever reason. --- lib/bridge/MatrixHandler.js | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index ab215b657..907677a39 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -17,6 +17,8 @@ const MSG_PMS_DISABLED = "[Bridge] Sorry, PMs are disabled on this bridge."; const MSG_PMS_DISABLED_FEDERATION = "[Bridge] Sorry, PMs are disabled on " + "this bridge over federation."; +const KICK_RETRY_DELAY_MS = 15000; + function MatrixHandler(ircBridge) { this.ircBridge = ircBridge; // maintain a list of room IDs which are being processed invite-wise. This is @@ -853,9 +855,32 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) } // get the virtual IRC user for this user promises.push(Promise.coroutine(function*() { - let bridgedClient = yield self.ircBridge.getBridgedClient( - room.server, user.getId(), (event.content || {}).displayname - ); + let bridgedClient; + let kickUser = false; + try { + bridgedClient = yield self.ircBridge.getBridgedClient( + room.server, user.getId(), (event.content || {}).displayname + ); + } catch (e) { + // We need to kick on failure to get a client. + req.log.info(`${user.getId()} failed to get a IRC connection. Kicking from room.`); + kickUser = true; + + } + + const intent = this.ircBridge.getAppServiceBridge().getIntent(); + while (kickUser) { + try { + yield intent.kick( + event.room_id, user.getId(), + `Connection limit has been reached for ${room.server.domain}. Please try again later.` + ); + break; + } catch (err) { + req.log.warn(`User was not kicked. Retrying in ${KICK_RETRY_DELAY_MS}ms. ${err}`); + yield Promise.delay(KICK_RETRY_DELAY_MS); + } + } // Check for a displayname change and update nick accordingly. if (event.content.displayname !== bridgedClient.displayName) { From 5ec8a4fd1024f1835d6b37cd36975b9de6e08d1b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 7 Aug 2018 16:11:48 +0100 Subject: [PATCH 023/162] use kickIntent --- lib/bridge/MatrixHandler.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 907677a39..021c29818 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -856,7 +856,7 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) // get the virtual IRC user for this user promises.push(Promise.coroutine(function*() { let bridgedClient; - let kickUser = false; + let kickIntent; try { bridgedClient = yield self.ircBridge.getBridgedClient( room.server, user.getId(), (event.content || {}).displayname @@ -864,14 +864,13 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) } catch (e) { // We need to kick on failure to get a client. req.log.info(`${user.getId()} failed to get a IRC connection. Kicking from room.`); - kickUser = true; + kickIntent = self.ircBridge.getAppServiceBridge().getIntent(); } - const intent = this.ircBridge.getAppServiceBridge().getIntent(); - while (kickUser) { + while (kickIntent) { try { - yield intent.kick( + yield kickIntent.kick( event.room_id, user.getId(), `Connection limit has been reached for ${room.server.domain}. Please try again later.` ); From 5a48e6cb68db1746276741d864b29b504bacbcbe Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 7 Aug 2018 16:12:20 +0100 Subject: [PATCH 024/162] Reject if the error *includes* one of these lines --- lib/irc/ConnectionInstance.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/irc/ConnectionInstance.js b/lib/irc/ConnectionInstance.js index fd4466a86..c192030bf 100644 --- a/lib/irc/ConnectionInstance.js +++ b/lib/irc/ConnectionInstance.js @@ -28,6 +28,12 @@ const PING_RATE_MS = 1000 * 60; // String reply of any CTCP Version requests const CTCP_VERSION = 'matrix-appservice-irc, part of the Matrix.org Network'; +const CONN_LIMIT_MESSAGES = [ + "too many host connections", // ircd-seven + "no more connections allowed in your connection class", + "this server is full", // unrealircd +] + // Log an Error object to stderr function logError(err) { if (!err || !err.message) { @@ -118,7 +124,7 @@ ConnectionInstance.prototype.disconnect = function(reason) { } if (this.state !== "connected") { // we never resolved this defer, so reject it. - this.reject(new Error(reason)); + this._connectDefer.reject(new Error(reason)); } if (this.state === "connected" && this.onDisconnect) { // we only invoke onDisconnect once we've had a successful connect. @@ -215,7 +221,9 @@ ConnectionInstance.prototype._listenForErrors = function() { self.disconnect("banned").catch(logError); return; } - const tooManyHosts = errText.startsWith("Too many host connections"); + const tooManyHosts = CONN_LIMIT_MESSAGES.find((connLimitMsg) => { + return errText.includes(connLimitMsg); + }) !== undefined; if (tooManyHosts) { self.disconnect("toomanyconns").catch(logError); return; From db6d696a8caf0516ee303da522ad51fc69e4e351 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 7 Aug 2018 16:15:57 +0100 Subject: [PATCH 025/162] Add some jitter to kick delay --- lib/bridge/MatrixHandler.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 021c29818..63091c825 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -18,6 +18,7 @@ const MSG_PMS_DISABLED_FEDERATION = "[Bridge] Sorry, PMs are disabled on " + "this bridge over federation."; const KICK_RETRY_DELAY_MS = 15000; +const KICK_DELAY_JITTER = 500; function MatrixHandler(ircBridge) { this.ircBridge = ircBridge; @@ -876,8 +877,11 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) ); break; } catch (err) { - req.log.warn(`User was not kicked. Retrying in ${KICK_RETRY_DELAY_MS}ms. ${err}`); - yield Promise.delay(KICK_RETRY_DELAY_MS); + const delay = KICK_RETRY_DELAY_MS + (Math.random() * KICK_DELAY_JITTER); + req.log.warn( + `User was not kicked. Retrying in ${delay}ms. ${err}` + ); + yield Promise.delay(delay); } } From e8809d433f67365f97f5be1a5b4a30e37208a999 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 7 Aug 2018 16:16:07 +0100 Subject: [PATCH 026/162] linting --- lib/bridge/MatrixHandler.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 63091c825..7e6cdeaa0 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -862,7 +862,8 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) bridgedClient = yield self.ircBridge.getBridgedClient( room.server, user.getId(), (event.content || {}).displayname ); - } catch (e) { + } + catch (e) { // We need to kick on failure to get a client. req.log.info(`${user.getId()} failed to get a IRC connection. Kicking from room.`); kickIntent = self.ircBridge.getAppServiceBridge().getIntent(); @@ -873,10 +874,11 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) try { yield kickIntent.kick( event.room_id, user.getId(), - `Connection limit has been reached for ${room.server.domain}. Please try again later.` + `Connection limit reached for ${room.server.domain}. Please try again later.` ); break; - } catch (err) { + } + catch (err) { const delay = KICK_RETRY_DELAY_MS + (Math.random() * KICK_DELAY_JITTER); req.log.warn( `User was not kicked. Retrying in ${delay}ms. ${err}` From d759cdcdfe5e7e14d402681a8d9848e64cb4950e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 7 Aug 2018 16:38:33 +0100 Subject: [PATCH 027/162] was --- lib/irc/ConnectionInstance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irc/ConnectionInstance.js b/lib/irc/ConnectionInstance.js index c192030bf..91e850bc8 100644 --- a/lib/irc/ConnectionInstance.js +++ b/lib/irc/ConnectionInstance.js @@ -384,7 +384,7 @@ ConnectionInstance.create = Promise.coroutine(function*(server, opts, onCreatedC if (err.message === "toomanyconns") { log.error( - `User ${opts.nick} as ILINED. This may be the network limiting us!` + `User ${opts.nick} was ILINED. This may be the network limiting us!` ); throw new Error("Connection was ILINED. We cannot retry this."); } From c8a53d9bea2f3f0775b502f70d109b7b204a82ad Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 7 Aug 2018 17:04:53 +0100 Subject: [PATCH 028/162] Spurious emptyline --- lib/bridge/MatrixHandler.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 7e6cdeaa0..1e8589891 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -867,7 +867,6 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) // We need to kick on failure to get a client. req.log.info(`${user.getId()} failed to get a IRC connection. Kicking from room.`); kickIntent = self.ircBridge.getAppServiceBridge().getIntent(); - } while (kickIntent) { From 06c4530ebb13760d340b568f3bb92870b8f5959e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 7 Aug 2018 17:05:17 +0100 Subject: [PATCH 029/162] Ramp up retry jitter to be 30000, effectively 30+/-15 --- lib/bridge/MatrixHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 1e8589891..5f294c881 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -18,7 +18,7 @@ const MSG_PMS_DISABLED_FEDERATION = "[Bridge] Sorry, PMs are disabled on " + "this bridge over federation."; const KICK_RETRY_DELAY_MS = 15000; -const KICK_DELAY_JITTER = 500; +const KICK_DELAY_JITTER = 30000; function MatrixHandler(ircBridge) { this.ircBridge = ircBridge; From e5516d4462e27ce2855d975aa54e6317766706db Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 10:15:39 +0100 Subject: [PATCH 030/162] Add metric tracking for MatrixHandler --- lib/bridge/MatrixHandler.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 2682e7a8f..840544aa2 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -30,6 +30,9 @@ function MatrixHandler(ircBridge) { }; this._memberTracker = null; + this.metrics = { + //domain => {"metricname" => value} + }; } // ===== Matrix Invite Handling ===== @@ -1481,6 +1484,22 @@ MatrixHandler.prototype._onUserQuery = Promise.coroutine(function*(req, userId) yield this.ircBridge.getMatrixUser(ircUser); }); +MatrixHandler.prototype._incrementMetric = function(serverDomain, metricName) { + let metricSet = this.metrics[serverDomain]; + if (metricSet === undefined) { + metricSet = this.metrics[serverDomain] = { + + }; + } + if (metricSet[metricName] === undefined) { + metricSet[metricName] = 1; + } + else { + metricSet[metricName]++; + } + this.metrics[serverDomain] = metricSet; +} + // EXPORTS MatrixHandler.prototype.onMemberEvent = function(req, event, inviter, invitee) { @@ -1515,6 +1534,13 @@ MatrixHandler.prototype.onUserQuery = function(req, userId) { return reqHandler(req, this._onUserQuery(req, userId)) }; +MatrixHandler.prototype.getMetrics = function(serverDomain) { + if (this.metrics[serverDomain] === undefined) { + return null; + } + return this.metrics[serverDomain]; +} + function reqHandler(req, promise) { return promise.then(function(res) { req.resolve(res); From 84b007b94643fc16c5a39d9e764185c915b29b6c Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 10:16:09 +0100 Subject: [PATCH 031/162] Count kicks from connection failures in metrics --- lib/bridge/IrcBridge.js | 11 +++++++++++ lib/bridge/MatrixHandler.js | 1 + 2 files changed, 12 insertions(+) diff --git a/lib/bridge/IrcBridge.js b/lib/bridge/IrcBridge.js index fba8b2b5c..ce5eda3de 100644 --- a/lib/bridge/IrcBridge.js +++ b/lib/bridge/IrcBridge.js @@ -181,11 +181,22 @@ IrcBridge.prototype._initialiseMetrics = function() { help: "Track calls made to the IRC Handler", labels: ["method"] }); + + const matrixHandlerConnFailureKicks = metrics.addCounter({ + name: "matrixhandler_connection_failure_kicks", + help: "Track IRC connection failures resulting in kicks", + labels: ["server"] + }); + metrics.addCollector(() => { this.ircServers.forEach((server) => { reconnQueue.set({server: server.domain}, this._clientPool.totalReconnectsWaiting(server.domain) ); + matrixHandlerConnFailureKicks.set( + {server: server.domain}, + this.matrixHandler.getMetrics(server.domain)["connection_failure_kicks"] + ); }); Object.keys(this.memberListSyncers).forEach((server) => { diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 840544aa2..061155c5f 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -880,6 +880,7 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) event.room_id, user.getId(), `Connection limit reached for ${room.server.domain}. Please try again later.` ); + this._incrementMetric(room.server.domain, "connection_failure_kicks"); break; } catch (err) { From 031b47fe0268e9981688b5351a0ea95e3d06e9d5 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 10:59:39 +0100 Subject: [PATCH 032/162] Don't answer any msgtypes other than text in a admin room. --- lib/bridge/MatrixHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index ab215b657..4a3419d22 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1115,7 +1115,8 @@ MatrixHandler.prototype._onMessage = Promise.coroutine(function*(req, event) { let ircAction = IrcAction.fromMatrixAction(mxAction); let ircRooms = yield this.ircBridge.getStore().getIrcChannelsForRoomId(event.room_id); - if (ircRooms.length === 0) { + // Sometimes bridge's message each other and get stuck in a silly loop. Ensure it's m.text + if (ircRooms.length === 0 && event.content && event.content.msgtype === "m.text") { // could be an Admin room, so check. let adminRoom = yield this.ircBridge.getStore().getAdminRoomById(event.room_id); if (!adminRoom) { From e905df8df2b4c1bd7157a5e15ed3896c663a1c21 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 11:57:33 +0100 Subject: [PATCH 033/162] Add server option to enable sasl --- lib/irc/ConnectionInstance.js | 1 + lib/irc/IrcServer.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/lib/irc/ConnectionInstance.js b/lib/irc/ConnectionInstance.js index abaa3fe95..31f22b145 100644 --- a/lib/irc/ConnectionInstance.js +++ b/lib/irc/ConnectionInstance.js @@ -317,6 +317,7 @@ ConnectionInstance.create = Promise.coroutine(function*(server, opts, onCreatedC retryCount: 0, family: server.getIpv6Prefix() || server.getIpv6Only() ? 6 : null, bustRfc3484: true, + sasl: opts.password ? server.useSasl() : false, }; if (server.useSsl()) { diff --git a/lib/irc/IrcServer.js b/lib/irc/IrcServer.js index bd8074f44..42f5ced42 100644 --- a/lib/irc/IrcServer.js +++ b/lib/irc/IrcServer.js @@ -201,6 +201,10 @@ IrcServer.prototype.useSslSelfSigned = function() { return Boolean(this.config.sslselfsign); }; +IrcServer.prototype.useSasl = function() { + return Boolean(this.config.sasl); +}; + IrcServer.prototype.allowExpiredCerts = function() { return Boolean(this.config.allowExpiredCerts); }; From df819a10c58f039f406081e5515d5fb9404cc3af Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 11:57:50 +0100 Subject: [PATCH 034/162] Update configs with sasl option --- config.sample.yaml | 3 +++ lib/config/schema.yml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/config.sample.yaml b/config.sample.yaml index 86a8fecc8..4fdff3e51 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -63,6 +63,9 @@ ircService: ssl: true # Whether or not IRC server is using a self-signed cert or not providing CA Chain sslselfsign: false + # Should the connection attempt to identify via SASL (if a server or user password is given?) + # If false, this will use PASS instead. If SASL fails, we do not fallback to PASS. + sasl: false # Whether to allow expired certs when connecting to the IRC server. # Usually this should be off. Default: false. allowExpiredCerts: false diff --git a/lib/config/schema.yml b/lib/config/schema.yml index 7abfa19ab..36eff21e9 100644 --- a/lib/config/schema.yml +++ b/lib/config/schema.yml @@ -94,6 +94,8 @@ properties: type: "boolean" sslselfsign: type: "boolean" + sasl: + type: "boolean" allowExpiredCerts: type: "boolean" password: From 5bfd69df5e71c87cf5c7058b2160ae51edd029e8 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 12:00:39 +0100 Subject: [PATCH 035/162] Update comment about PASS --- config.sample.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.sample.yaml b/config.sample.yaml index 4fdff3e51..309ec82f4 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -76,7 +76,7 @@ ircService: # -----END CERTIFICATE----- # - # The connection password to send for all clients as a PASS command. Optional. + # The connection password to send for all clients as a PASS (or SASL, if enabled above) command. Optional. # password: 'pa$$w0rd' # # Whether or not to send connection/error notices to real Matrix users. Default: true. From fdf5d6eec183c980c71a2d8ca1cd8a48a232e3d5 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 16:35:15 +0100 Subject: [PATCH 036/162] Do not question the config --- config.sample.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.sample.yaml b/config.sample.yaml index 309ec82f4..047f73888 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -63,7 +63,7 @@ ircService: ssl: true # Whether or not IRC server is using a self-signed cert or not providing CA Chain sslselfsign: false - # Should the connection attempt to identify via SASL (if a server or user password is given?) + # Should the connection attempt to identify via SASL (if a server or user password is given) # If false, this will use PASS instead. If SASL fails, we do not fallback to PASS. sasl: false # Whether to allow expired certs when connecting to the IRC server. From 78a12a5747721ef0028d11fc52bc979065d2338e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 19:34:12 +0100 Subject: [PATCH 037/162] Null guarding --- lib/bridge/IrcBridge.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/bridge/IrcBridge.js b/lib/bridge/IrcBridge.js index ce5eda3de..ea567c015 100644 --- a/lib/bridge/IrcBridge.js +++ b/lib/bridge/IrcBridge.js @@ -193,9 +193,14 @@ IrcBridge.prototype._initialiseMetrics = function() { reconnQueue.set({server: server.domain}, this._clientPool.totalReconnectsWaiting(server.domain) ); + let mxMetrics = this.matrixHandler.getMetrics(server.domain); + if (mxMetrics === null) { + mxMetrics = {} + } + matrixHandlerConnFailureKicks.set( {server: server.domain}, - this.matrixHandler.getMetrics(server.domain)["connection_failure_kicks"] + mxMetrics["connection_failure_kicks"] ? mxMetrics["connection_failure_kicks"] : 0 ); }); From f66d2bedefeb35c23b9663bd6651cfd73e5f3fbd Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 19:42:05 +0100 Subject: [PATCH 038/162] Better attempts at guards, and misc things --- lib/bridge/IrcBridge.js | 6 +----- lib/bridge/MatrixHandler.js | 11 +++-------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/bridge/IrcBridge.js b/lib/bridge/IrcBridge.js index ea567c015..79ba07d54 100644 --- a/lib/bridge/IrcBridge.js +++ b/lib/bridge/IrcBridge.js @@ -194,13 +194,9 @@ IrcBridge.prototype._initialiseMetrics = function() { this._clientPool.totalReconnectsWaiting(server.domain) ); let mxMetrics = this.matrixHandler.getMetrics(server.domain); - if (mxMetrics === null) { - mxMetrics = {} - } - matrixHandlerConnFailureKicks.set( {server: server.domain}, - mxMetrics["connection_failure_kicks"] ? mxMetrics["connection_failure_kicks"] : 0 + mxMetrics["connection_failure_kicks"] || 0, ); }); diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 061155c5f..60b1f451f 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1487,10 +1487,8 @@ MatrixHandler.prototype._onUserQuery = Promise.coroutine(function*(req, userId) MatrixHandler.prototype._incrementMetric = function(serverDomain, metricName) { let metricSet = this.metrics[serverDomain]; - if (metricSet === undefined) { - metricSet = this.metrics[serverDomain] = { - - }; + if (!metricSet) { + metricSet = this.metrics[serverDomain] = {}; } if (metricSet[metricName] === undefined) { metricSet[metricName] = 1; @@ -1536,10 +1534,7 @@ MatrixHandler.prototype.onUserQuery = function(req, userId) { }; MatrixHandler.prototype.getMetrics = function(serverDomain) { - if (this.metrics[serverDomain] === undefined) { - return null; - } - return this.metrics[serverDomain]; + return this.metrics[serverDomain] || {}; } function reqHandler(req, promise) { From b7a300731cfd3a6d043c23d3eb2f369cb008b933 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 10 Aug 2018 19:48:22 +0100 Subject: [PATCH 039/162] Seriously, what are these eslint rules --- lib/bridge/IrcBridge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bridge/IrcBridge.js b/lib/bridge/IrcBridge.js index 79ba07d54..1edc88ad1 100644 --- a/lib/bridge/IrcBridge.js +++ b/lib/bridge/IrcBridge.js @@ -196,7 +196,7 @@ IrcBridge.prototype._initialiseMetrics = function() { let mxMetrics = this.matrixHandler.getMetrics(server.domain); matrixHandlerConnFailureKicks.set( {server: server.domain}, - mxMetrics["connection_failure_kicks"] || 0, + mxMetrics["connection_failure_kicks"] || 0 ); }); From 4918c638224b8ecdbc22d0dbd6668f34ce524760 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Sat, 11 Aug 2018 03:11:57 +0100 Subject: [PATCH 040/162] Add err_nononreg --- lib/irc/ConnectionInstance.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/irc/ConnectionInstance.js b/lib/irc/ConnectionInstance.js index 91e850bc8..41e6b3ae1 100644 --- a/lib/irc/ConnectionInstance.js +++ b/lib/irc/ConnectionInstance.js @@ -171,7 +171,7 @@ ConnectionInstance.prototype._listenForErrors = function() { "err_banonchan", "err_nickcollision", "err_nicknameinuse", "err_erroneusnickname", "err_nonicknamegiven", "err_eventnickchange", "err_nicktoofast", "err_unknowncommand", "err_unavailresource", - "err_umodeunknownflag" + "err_umodeunknownflag", "err_nononreg" ]; if (err && err.command) { if (failCodes.indexOf(err.command) !== -1) { diff --git a/package.json b/package.json index e984e230a..c1c916c8d 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "bluebird": "^3.1.1", "crc": "^3.2.1", "extend": "^2.0.0", - "irc": "matrix-org/node-irc#b1614bc784200c65247784d7b9e9ab867140412d", + "irc": "matrix-org/node-irc#c9abb427bec5016d94a2abf3e058cc62de09ea5a", "js-yaml": "^3.2.7", "matrix-appservice-bridge": "1.5.0a", "nedb": "^1.1.2", From 5398b0d2477645cd34e69ab22af6863641879241 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 13 Aug 2018 18:21:40 +0100 Subject: [PATCH 041/162] Format replies --- lib/bridge/MatrixHandler.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 21281ce30..2e1fcdba9 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1262,6 +1262,29 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( } let text = event.content.body; + const REPLY_SOURCE_MAX_LENGTH = 32; + const REPLY_NAME_MAX_LENGTH = 12; + if (event.content["m.relates_to"] && event.content["m.in_reply_to"]) { + // This is a reply. + const match = /> <(@.*:.*)>(.*)\n\n(.*)/.exec(text); + if (match.length === 4) { + let rplName; + let sourceLength = REPLY_SOURCE_MAX_LENGTH - "...".length; + const rplSource = match[2].substr(0, sourceLength) + (match[2].length > sourceLength ? "..." : ""); + const rplText = match[3]; + + // Fetch the sender's IRC nick. + const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, match[1]); + if (sourceClient) { + rplName = sourceClient.nick; + } + else { + // Somehow we failed, so fallback to userid. + rplName = match[1].substr(1, Math.min(REPLY_NAME_MAX_LENGTH, userid.indexOf(":") - 1)); + } + text = ` ${rplText}`; + } + } // Generate an array of individual messages that would be sent let potentialMessages = ircClient.unsafeClient.getSplitMessages(ircRoom.channel, text); From a1af8b12dbcc2e054ce3beda82852f3fce5acd02 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 13 Aug 2018 18:27:50 +0100 Subject: [PATCH 042/162] Typo --- lib/bridge/MatrixHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 2e1fcdba9..0d71cca37 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1264,7 +1264,7 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( let text = event.content.body; const REPLY_SOURCE_MAX_LENGTH = 32; const REPLY_NAME_MAX_LENGTH = 12; - if (event.content["m.relates_to"] && event.content["m.in_reply_to"]) { + if (event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]) { // This is a reply. const match = /> <(@.*:.*)>(.*)\n\n(.*)/.exec(text); if (match.length === 4) { From 05418a04becbb888e95cdd5057e4b429e2d5c00c Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 13 Aug 2018 18:34:46 +0100 Subject: [PATCH 043/162] Let's actually use the text --- lib/bridge/MatrixHandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 0d71cca37..52e8aa817 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1283,6 +1283,7 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( rplName = match[1].substr(1, Math.min(REPLY_NAME_MAX_LENGTH, userid.indexOf(":") - 1)); } text = ` ${rplText}`; + ircAction.text = text; } } From 5a8c2c5ab4693c1ae9e7274a6da6bac0384dc993 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 13 Aug 2018 18:37:41 +0100 Subject: [PATCH 044/162] Trim whitespace from source --- lib/bridge/MatrixHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 52e8aa817..b9dc18e79 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1282,7 +1282,7 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( // Somehow we failed, so fallback to userid. rplName = match[1].substr(1, Math.min(REPLY_NAME_MAX_LENGTH, userid.indexOf(":") - 1)); } - text = ` ${rplText}`; + text = `<${rplName} "${rplSource.trim()}"> ${rplText}`; ircAction.text = text; } } From 719edfed61cecee08006bfb393bc493b67930c04 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 13 Aug 2018 18:40:14 +0100 Subject: [PATCH 045/162] Linting --- lib/bridge/MatrixHandler.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index b9dc18e79..0c492c919 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1270,9 +1270,10 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( if (match.length === 4) { let rplName; let sourceLength = REPLY_SOURCE_MAX_LENGTH - "...".length; - const rplSource = match[2].substr(0, sourceLength) + (match[2].length > sourceLength ? "..." : ""); + const rplSource = match[2].substr(0, sourceLength) + + (match[2].length > sourceLength ? "..." : ""); const rplText = match[3]; - + // Fetch the sender's IRC nick. const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, match[1]); if (sourceClient) { @@ -1280,7 +1281,9 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( } else { // Somehow we failed, so fallback to userid. - rplName = match[1].substr(1, Math.min(REPLY_NAME_MAX_LENGTH, userid.indexOf(":") - 1)); + rplName = match[1].substr(1, + Math.min(REPLY_NAME_MAX_LENGTH, userid.indexOf(":") - 1) + ); } text = `<${rplName} "${rplSource.trim()}"> ${rplText}`; ircAction.text = text; From 9b6d0de15a2b2ac03fff757a0b554bbd16ecce9a Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 09:55:26 +0100 Subject: [PATCH 046/162] Fix trimming, length --- lib/bridge/MatrixHandler.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 0c492c919..b0fabf56c 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1269,9 +1269,9 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( const match = /> <(@.*:.*)>(.*)\n\n(.*)/.exec(text); if (match.length === 4) { let rplName; - let sourceLength = REPLY_SOURCE_MAX_LENGTH - "...".length; - const rplSource = match[2].substr(0, sourceLength) + - (match[2].length > sourceLength ? "..." : ""); + const rplSourceTrimmed = match[2].trim(); + const rplSource = rplSourceTrimmed.substr(0, REPLY_SOURCE_MAX_LENGTH) + + (rplSourceTrimmed.length > REPLY_SOURCE_MAX_LENGTH ? "..." : ""); const rplText = match[3]; // Fetch the sender's IRC nick. From 761a2f5da4367723b1cbe7c55d5715558abde68a Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 10:12:21 +0100 Subject: [PATCH 047/162] Add an eventCache to MatrixHandler for getting reply sources --- lib/bridge/MatrixHandler.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index b0fabf56c..66578eda7 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -19,6 +19,7 @@ const MSG_PMS_DISABLED_FEDERATION = "[Bridge] Sorry, PMs are disabled on " + const KICK_RETRY_DELAY_MS = 15000; const KICK_DELAY_JITTER = 30000; +const EVENT_CACHE_SIZE = 4096; function MatrixHandler(ircBridge) { this.ircBridge = ircBridge; @@ -30,6 +31,7 @@ function MatrixHandler(ircBridge) { }; this._memberTracker = null; + this._eventCache = new Map(); //eventId => {body, sender} } // ===== Matrix Invite Handling ===== @@ -1261,6 +1263,16 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( return; } + /** + * Cache events in here so we can refer to them for replies. + * We deliberately avoid fetching events from the homeserver due to speed concerns. + */ + this._eventCache.set(event.event_id, { body: event.content.body, sender: event.sender }); + if (this._eventCache.size > EVENT_CACHE_SIZE) { + const delKey = this._eventCache.entries().next().value[0]; + this._eventCache.delete(delKey); + } + let text = event.content.body; const REPLY_SOURCE_MAX_LENGTH = 32; const REPLY_NAME_MAX_LENGTH = 12; From e8c6a320f7f9f4384cb96d7dc1bf673d427f8b2e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 10:13:02 +0100 Subject: [PATCH 048/162] Fetch reply data from the eventCache, if found. --- lib/bridge/MatrixHandler.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 66578eda7..9e0d92ebd 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1277,17 +1277,22 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( const REPLY_SOURCE_MAX_LENGTH = 32; const REPLY_NAME_MAX_LENGTH = 12; if (event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]) { + const eventId = event.content["m.relates_to"]["m.in_reply_to"].event_id; // This is a reply. const match = /> <(@.*:.*)>(.*)\n\n(.*)/.exec(text); if (match.length === 4) { - let rplName; - const rplSourceTrimmed = match[2].trim(); - const rplSource = rplSourceTrimmed.substr(0, REPLY_SOURCE_MAX_LENGTH) + - (rplSourceTrimmed.length > REPLY_SOURCE_MAX_LENGTH ? "..." : ""); + let rplName = match[1]; + let rplSource = match[2].trim(); + if (this._eventCache.has(eventId)) { + rplName = this._eventCache.get(eventId).sender; + rplSource = this._eventCache.get(eventId).body; + } + rplSource = rplSource.substr(0, REPLY_SOURCE_MAX_LENGTH) + + (rplSource.length > REPLY_SOURCE_MAX_LENGTH ? "..." : ""); const rplText = match[3]; // Fetch the sender's IRC nick. - const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, match[1]); + const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, rplName); if (sourceClient) { rplName = sourceClient.nick; } From 6e0ac53ca578b1bf353e97a2d75f624b8612377a Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 10:13:31 +0100 Subject: [PATCH 049/162] Replace newlines with ". " so we can fit them on. --- lib/bridge/MatrixHandler.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 9e0d92ebd..5ab820fc3 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1287,6 +1287,9 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( rplName = this._eventCache.get(eventId).sender; rplSource = this._eventCache.get(eventId).body; } + // Replace newlines so we can fit this in the message body. + // It's not a perfect solution but it's legible. + rplSource = rplSource.replace(/\v/, '. '); rplSource = rplSource.substr(0, REPLY_SOURCE_MAX_LENGTH) + (rplSource.length > REPLY_SOURCE_MAX_LENGTH ? "..." : ""); const rplText = match[3]; From 900164698c2c0a3878b95eea77181f79cb253a93 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 12:08:06 +0100 Subject: [PATCH 050/162] Fix bug with _getRoomInfo --- lib/provisioning/Provisioner.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/provisioning/Provisioner.js b/lib/provisioning/Provisioner.js index 13fb356d5..081c86d15 100644 --- a/lib/provisioning/Provisioner.js +++ b/lib/provisioning/Provisioner.js @@ -935,7 +935,7 @@ Provisioner.prototype._partUnlinkedIrcClients = Promise.coroutine( // For each room, get the list of real matrix users and tally up how many times each one // appears as joined - let joinedUserCounts = Object.create(null); // user_id => Number + let joinedUserCounts = {}; // user_id => Number let unlinkedUserIds = []; let asBot = this._ircBridge.getAppServiceBridge().getBot(); for (let i = 0; i < matrixRooms.length; i++) { @@ -947,7 +947,14 @@ Provisioner.prototype._partUnlinkedIrcClients = Promise.coroutine( req.log.error("Failed to hit /state for room " + matrixRooms[i].getId()); req.log.error(err.stack); } - let roomInfo = asBot._getRoomInfo(roomId, stateEvents); + + // _getRoomInfo takes a particular format. + const joinedRoom = { + state: { + events: stateEvents + } + } + let roomInfo = asBot._getRoomInfo(roomId, joinedRoom); for (let j = 0; j < roomInfo.realJoinedUsers.length; j++) { let userId = roomInfo.realJoinedUsers[j]; if (!joinedUserCounts[userId]) { From 8124b27598cfdd185929e5f041270b96f2c1a9af Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 13:48:51 +0100 Subject: [PATCH 051/162] Function to enqueue leaving users on MLS --- lib/bridge/MemberListSyncer.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/bridge/MemberListSyncer.js b/lib/bridge/MemberListSyncer.js index 83e81edc6..0ff17dec6 100644 --- a/lib/bridge/MemberListSyncer.js +++ b/lib/bridge/MemberListSyncer.js @@ -427,6 +427,14 @@ MemberListSyncer.prototype.getUsersWaitingToLeave = function() { return this._usersToLeave; } +MemberListSyncer.prototype.addToLeavePool = function(userIds, roomId, channel) { + this._usersToLeave += 1; + this._leaveQueuePool.enqueue(roomId + " " + channel, { + roomId, + userIds + }); +} + function getRoomMemberData(server, roomId, stateEvents, appServiceUserId) { stateEvents = stateEvents || []; var data = { From 735ed8cd317018037445ae349848924252fdeb8e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 13:49:39 +0100 Subject: [PATCH 052/162] Various formatting fixes --- lib/provisioning/Provisioner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/provisioning/Provisioner.js b/lib/provisioning/Provisioner.js index 081c86d15..7beb88318 100644 --- a/lib/provisioning/Provisioner.js +++ b/lib/provisioning/Provisioner.js @@ -935,7 +935,7 @@ Provisioner.prototype._partUnlinkedIrcClients = Promise.coroutine( // For each room, get the list of real matrix users and tally up how many times each one // appears as joined - let joinedUserCounts = {}; // user_id => Number + const joinedUserCounts = {}; // user_id => Number let unlinkedUserIds = []; let asBot = this._ircBridge.getAppServiceBridge().getBot(); for (let i = 0; i < matrixRooms.length; i++) { @@ -954,7 +954,7 @@ Provisioner.prototype._partUnlinkedIrcClients = Promise.coroutine( events: stateEvents } } - let roomInfo = asBot._getRoomInfo(roomId, joinedRoom); + let roomInfo = asBot._getRoomInfo(matrixRooms[i].getId(), joinedRoom); for (let j = 0; j < roomInfo.realJoinedUsers.length; j++) { let userId = roomInfo.realJoinedUsers[j]; if (!joinedUserCounts[userId]) { From 4cdc9e776021843f9ed0cebbeb9bd5485f7e5af8 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 13:49:58 +0100 Subject: [PATCH 053/162] Function to leave matrix virtuals from a room. --- lib/provisioning/Provisioner.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/provisioning/Provisioner.js b/lib/provisioning/Provisioner.js index 7beb88318..6cc6eb3a8 100644 --- a/lib/provisioning/Provisioner.js +++ b/lib/provisioning/Provisioner.js @@ -990,6 +990,26 @@ Provisioner.prototype._partUnlinkedIrcClients = Promise.coroutine( } ); +Provisioner.prototype._leaveMatrixVirtuals = Promise.coroutine( + function*(req, roomId, server, ircChannel) { + const roomChannels = yield this._ircBridge.getStore().getIrcChannelsForRoomId( + roomId + ); + if (roomChannels > 0) { + // We can't determine who should and shouldn't be in the room. + return; + } + stateEvents = yield asBot.getClient().roomState(roomId); + let roomInfo = asBot._getRoomInfo(matrixRooms[i].getId(), { + state: { + events: stateEvents + } + }); + req.log.info(`Leaving ${roomInfo.remoteJoinedUsers.length} virtual users from ${roomId}.`); + this._ircBridge.memberListSyncers[server.domain].addToLeavePool(roomInfo.remoteJoinedUsers, roomId, ircChannel); + } +); + // Cause the bot to leave the matrix room if there are no other channels being mapped to // this room Provisioner.prototype._leaveMatrixRoomIfUnprovisioned = Promise.coroutine( From 41dbed31d2b3892936bff69362ac47bcb073441e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 13:50:15 +0100 Subject: [PATCH 054/162] Run partUnlinked and leaveMatrixVirtuals in parallel --- lib/provisioning/Provisioner.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/provisioning/Provisioner.js b/lib/provisioning/Provisioner.js index 6cc6eb3a8..60f7e04d4 100644 --- a/lib/provisioning/Provisioner.js +++ b/lib/provisioning/Provisioner.js @@ -892,7 +892,10 @@ Provisioner.prototype.unlink = Promise.coroutine(function*(req) { Provisioner.prototype._leaveIfUnprovisioned = Promise.coroutine( function*(req, roomId, server, ircChannel) { try { - yield this._partUnlinkedIrcClients(req, roomId, server, ircChannel) + yield Promise.all([ + this._partUnlinkedIrcClients(req, roomId, server, ircChannel), + this._leaveMatrixVirtuals(req, roomId, server, ircChannel) + ]); } catch (err) { // keep going, we still need to part the bot; this is just cleanup From 5570e935a75adf38af22b5744b71933203ff6637 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 13:52:15 +0100 Subject: [PATCH 055/162] Linting --- lib/provisioning/Provisioner.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/provisioning/Provisioner.js b/lib/provisioning/Provisioner.js index 60f7e04d4..3a6717cde 100644 --- a/lib/provisioning/Provisioner.js +++ b/lib/provisioning/Provisioner.js @@ -950,7 +950,7 @@ Provisioner.prototype._partUnlinkedIrcClients = Promise.coroutine( req.log.error("Failed to hit /state for room " + matrixRooms[i].getId()); req.log.error(err.stack); } - + // _getRoomInfo takes a particular format. const joinedRoom = { state: { @@ -1009,7 +1009,11 @@ Provisioner.prototype._leaveMatrixVirtuals = Promise.coroutine( } }); req.log.info(`Leaving ${roomInfo.remoteJoinedUsers.length} virtual users from ${roomId}.`); - this._ircBridge.memberListSyncers[server.domain].addToLeavePool(roomInfo.remoteJoinedUsers, roomId, ircChannel); + this._ircBridge.memberListSyncers[server.domain].addToLeavePool( + roomInfo.remoteJoinedUsers, + roomId, + ircChannel + ); } ); From 4689c81d0ff2c0fc39fbd4c9f09a0663b6fc20db Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 13:59:49 +0100 Subject: [PATCH 056/162] Define asBot --- lib/provisioning/Provisioner.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/provisioning/Provisioner.js b/lib/provisioning/Provisioner.js index 3a6717cde..92b7d9ae4 100644 --- a/lib/provisioning/Provisioner.js +++ b/lib/provisioning/Provisioner.js @@ -940,7 +940,7 @@ Provisioner.prototype._partUnlinkedIrcClients = Promise.coroutine( // appears as joined const joinedUserCounts = {}; // user_id => Number let unlinkedUserIds = []; - let asBot = this._ircBridge.getAppServiceBridge().getBot(); + const asBot = this._ircBridge.getAppServiceBridge().getBot(); for (let i = 0; i < matrixRooms.length; i++) { let stateEvents = []; try { @@ -995,15 +995,16 @@ Provisioner.prototype._partUnlinkedIrcClients = Promise.coroutine( Provisioner.prototype._leaveMatrixVirtuals = Promise.coroutine( function*(req, roomId, server, ircChannel) { + const asBot = this._ircBridge.getAppServiceBridge().getBot(); const roomChannels = yield this._ircBridge.getStore().getIrcChannelsForRoomId( roomId ); - if (roomChannels > 0) { + if (roomChannels.length > 0) { // We can't determine who should and shouldn't be in the room. return; } stateEvents = yield asBot.getClient().roomState(roomId); - let roomInfo = asBot._getRoomInfo(matrixRooms[i].getId(), { + const roomInfo = asBot._getRoomInfo(matrixRooms[i].getId(), { state: { events: stateEvents } From 1ba86511db1d0462ba42c9bce937f3c4f6cdc188 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 14:06:34 +0100 Subject: [PATCH 057/162] Define stateEvents --- lib/provisioning/Provisioner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/provisioning/Provisioner.js b/lib/provisioning/Provisioner.js index 92b7d9ae4..067507e60 100644 --- a/lib/provisioning/Provisioner.js +++ b/lib/provisioning/Provisioner.js @@ -1003,7 +1003,7 @@ Provisioner.prototype._leaveMatrixVirtuals = Promise.coroutine( // We can't determine who should and shouldn't be in the room. return; } - stateEvents = yield asBot.getClient().roomState(roomId); + const stateEvents = yield asBot.getClient().roomState(roomId); const roomInfo = asBot._getRoomInfo(matrixRooms[i].getId(), { state: { events: stateEvents From ad153fdbfdfde79c628e6e25ace32e2815682b6b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 14:16:57 +0100 Subject: [PATCH 058/162] Define roomId --- lib/provisioning/Provisioner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/provisioning/Provisioner.js b/lib/provisioning/Provisioner.js index 067507e60..1a8dfe18e 100644 --- a/lib/provisioning/Provisioner.js +++ b/lib/provisioning/Provisioner.js @@ -1004,7 +1004,7 @@ Provisioner.prototype._leaveMatrixVirtuals = Promise.coroutine( return; } const stateEvents = yield asBot.getClient().roomState(roomId); - const roomInfo = asBot._getRoomInfo(matrixRooms[i].getId(), { + const roomInfo = asBot._getRoomInfo(roomId, { state: { events: stateEvents } From cb3c7b20e522771a4b5455f6d015048bb18662d5 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 15:33:45 +0100 Subject: [PATCH 059/162] Add ClientPool method for getting nick->userids in channel. --- lib/irc/ClientPool.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/irc/ClientPool.js b/lib/irc/ClientPool.js index 71e557a4d..af7480d71 100644 --- a/lib/irc/ClientPool.js +++ b/lib/irc/ClientPool.js @@ -248,6 +248,16 @@ ClientPool.prototype.totalReconnectsWaiting = function (serverDomain) { return 0; } +ClientPool.prototype.getNickUserIdMappingForChannel = function(server, channel) { + const nickUserIdMap = {}; + Object.keys(this._virtualClients[server.domain].userIds).filter((userId) => + this._virtualClients[server.domain][userId].chanList.includes(channel) + ).forEach((userId) => { + nickUserIdMap[this._virtualClients[server.domain][userId].nick] = userId; + }); + return nickUserIdMap; +}; + ClientPool.prototype._sendConnectionMetric = function(server) { stats.ircClients(server.domain, this._getNumberOfConnections(server)); }; From 01c95c9ffd65ab8462cd02294a7f152d959a88e2 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 15:34:14 +0100 Subject: [PATCH 060/162] Add escape-string-regexp dep --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e984e230a..0679fc8f7 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "bluebird": "^3.1.1", "crc": "^3.2.1", + "escape-string-regexp": "^1.0.5", "extend": "^2.0.0", "irc": "matrix-org/node-irc#b1614bc784200c65247784d7b9e9ab867140412d", "js-yaml": "^3.2.7", From e8aec2e947acee5461795f2542b9cc20ac717fe8 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 15:34:42 +0100 Subject: [PATCH 061/162] Add mention formatting function --- lib/bridge/IrcHandler.js | 3 +++ lib/models/MatrixAction.js | 42 +++++++++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index 152191408..d93ff1fe6 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -468,6 +468,9 @@ IrcHandler.prototype.onMessage = Promise.coroutine(function*(req, server, fromUs ); let mxAction = MatrixAction.fromIrcAction(action); + action.formatMentions( + this.ircBridge.getClientPool().getNickUserIdMappingForChannel(server, channel) + ); if (!mxAction) { req.log.error("Couldn't map IRC action to matrix action"); diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 7e91ca51c..57304d464 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -1,7 +1,8 @@ "use strict"; -var ircFormatting = require("../irc/formatting"); -var log = require("../logging").get("MatrixAction"); -var ContentRepo = require("matrix-appservice-bridge").ContentRepo; +const ircFormatting = require("../irc/formatting"); +const log = require("../logging").get("MatrixAction"); +const ContentRepo = require("matrix-appservice-bridge").ContentRepo; +const escapeStringRegexp = require('escape-string-regexp'); const ACTION_TYPES = ["message", "emote", "topic", "notice", "file", "image", "video", "audio"]; const EVENT_TO_TYPE = { @@ -17,6 +18,8 @@ const MSGTYPE_TO_TYPE = { "m.file": "file" }; +const MIN_LENGTH_TO_MATCH = 4; + function MatrixAction(type, text, htmlText, timestamp) { if (ACTION_TYPES.indexOf(type) === -1) { throw new Error("Unknown MatrixAction type: " + type); @@ -26,6 +29,39 @@ function MatrixAction(type, text, htmlText, timestamp) { this.htmlText = htmlText; this.ts = timestamp || 0; } + +MatrixAction.prototype.formatMentions = function(nickUserIdMap) { + // Get people this message could be mentioning. + const regexString = "(" + + Object.keys(nickUserIdMap).map((value) => escapeStringRegexp(value)).join("|") + + ")"; + const userRegex = new RegExp(regexString, "igm"); + let match; + while ((match = userRegex.exec(this.text)) !== null) { + let matchName = match[1]; + if (matchName.length < MIN_LENGTH_TO_MATCH) { + continue; + } + log.debug(`Matched ${matchName} in ${this.text}`); + let userId = nickUserIdMap[matchName]; + if (userId === undefined) { + // Might be casing. + const nicks = Object.keys(nickUserIdMap).filter((nick) => + nick.toLowerCase() === matchName.toLowerCase() + ); + if (nicks.length === 0) { + continue; + } + userId = nickUserIdMap[nicks[0]]; + matchName = nicks[0]; + } + this.htmlText = this.htmlText.replace( + new RegExp(matchName, 'igm'), + `${matchName}` + ); + } +} + MatrixAction.fromEvent = function(client, event, mediaUrl) { event.content = event.content || {}; let type = EVENT_TO_TYPE[event.type] || "message"; // mx event type to action type From 48a60702acd7b8fcf078cd1b8c9e5aa48a037cec Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 15:53:55 +0100 Subject: [PATCH 062/162] action => mxAction --- lib/bridge/IrcHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index d93ff1fe6..a7ca27832 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -468,7 +468,7 @@ IrcHandler.prototype.onMessage = Promise.coroutine(function*(req, server, fromUs ); let mxAction = MatrixAction.fromIrcAction(action); - action.formatMentions( + mxAction.formatMentions( this.ircBridge.getClientPool().getNickUserIdMappingForChannel(server, channel) ); From 420bedcfe664e23b359f3fd3dee4859de1901f23 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 15:54:35 +0100 Subject: [PATCH 063/162] fixup getNickUserIdMappingForChannel --- lib/irc/ClientPool.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/irc/ClientPool.js b/lib/irc/ClientPool.js index af7480d71..d9f370632 100644 --- a/lib/irc/ClientPool.js +++ b/lib/irc/ClientPool.js @@ -250,10 +250,11 @@ ClientPool.prototype.totalReconnectsWaiting = function (serverDomain) { ClientPool.prototype.getNickUserIdMappingForChannel = function(server, channel) { const nickUserIdMap = {}; - Object.keys(this._virtualClients[server.domain].userIds).filter((userId) => - this._virtualClients[server.domain][userId].chanList.includes(channel) + const cliSet = this._virtualClients[server.domain].userIds; + Object.keys(cliSet).filter((userId) => + cliSet.chanList.includes(channel) ).forEach((userId) => { - nickUserIdMap[this._virtualClients[server.domain][userId].nick] = userId; + nickUserIdMap[cliSet[userId].nick] = userId; }); return nickUserIdMap; }; From a0cd371c99a5cd3ee85d4e762df9f1443a9d5c77 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 15:54:49 +0100 Subject: [PATCH 064/162] Add htmlText if not defined --- lib/models/MatrixAction.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 57304d464..c8f9406a4 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -55,8 +55,11 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { userId = nickUserIdMap[nicks[0]]; matchName = nicks[0]; } + if (this.htmlText === undefined) { + this.htmlText = this.text; + } this.htmlText = this.htmlText.replace( - new RegExp(matchName, 'igm'), + matchName, `${matchName}` ); } From a7548f4d70530dfea7003e39a33c85a037a6ded3 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 16:22:27 +0100 Subject: [PATCH 065/162] Oops --- lib/irc/ClientPool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/irc/ClientPool.js b/lib/irc/ClientPool.js index d9f370632..ee8e471d1 100644 --- a/lib/irc/ClientPool.js +++ b/lib/irc/ClientPool.js @@ -252,7 +252,7 @@ ClientPool.prototype.getNickUserIdMappingForChannel = function(server, channel) const nickUserIdMap = {}; const cliSet = this._virtualClients[server.domain].userIds; Object.keys(cliSet).filter((userId) => - cliSet.chanList.includes(channel) + cliSet[userId].chanList.includes(channel) ).forEach((userId) => { nickUserIdMap[cliSet[userId].nick] = userId; }); From 21348c740923cb52b4817ff635e1ff1e87e51157 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 16:33:20 +0100 Subject: [PATCH 066/162] Add maximum number of matches --- lib/models/MatrixAction.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index c8f9406a4..59e5ef056 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -19,6 +19,7 @@ const MSGTYPE_TO_TYPE = { }; const MIN_LENGTH_TO_MATCH = 4; +const MAX_MATCHES = 5; function MatrixAction(type, text, htmlText, timestamp) { if (ACTION_TYPES.indexOf(type) === -1) { @@ -37,7 +38,9 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { + ")"; const userRegex = new RegExp(regexString, "igm"); let match; - while ((match = userRegex.exec(this.text)) !== null) { + let i = 0; + while ((match = userRegex.exec(this.text)) !== null && i < MAX_MATCHES) { + i++; let matchName = match[1]; if (matchName.length < MIN_LENGTH_TO_MATCH) { continue; From 9c3c5bf8f4deb9ad8d01aefd8dd5be8c698263ab Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 16:33:32 +0100 Subject: [PATCH 067/162] Match all, and match it once --- lib/models/MatrixAction.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 59e5ef056..0e723663a 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -37,12 +37,13 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { Object.keys(nickUserIdMap).map((value) => escapeStringRegexp(value)).join("|") + ")"; const userRegex = new RegExp(regexString, "igm"); + const matched = new Set(); let match; let i = 0; while ((match = userRegex.exec(this.text)) !== null && i < MAX_MATCHES) { i++; let matchName = match[1]; - if (matchName.length < MIN_LENGTH_TO_MATCH) { + if (matchName.length < MIN_LENGTH_TO_MATCH || matched.has(matchName.toLowerCase())) { continue; } log.debug(`Matched ${matchName} in ${this.text}`); @@ -62,9 +63,10 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { this.htmlText = this.text; } this.htmlText = this.htmlText.replace( - matchName, + new RegExp(matchName, "igm"), `${matchName}` ); + matched.add(matchName.toLowerCase()); } } From 99daa2407a1e5bdad1f2805f27cc046dc3792806 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 16:33:54 +0100 Subject: [PATCH 068/162] Add unit tests for MatrixAction --- spec/unit/MatrixAction.spec.js | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 spec/unit/MatrixAction.spec.js diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js new file mode 100644 index 000000000..d6d9b8fc6 --- /dev/null +++ b/spec/unit/MatrixAction.spec.js @@ -0,0 +1,50 @@ +"use strict"; +const MatrixAction = require("../../lib/models/MatrixAction"); + +describe("MatrixAction", function() { + it("should not highlight mentions to text without mentions", () => { + let action = new MatrixAction("message", "Some text", "Some text", null); + action.formatMentions({ + "Some Person": "@foobar:localhost" + }); + expect(action.text).toEqual("Some text"); + }); + it("should highlight a user", () => { + let action = new MatrixAction("message", "JCDenton, it's a bomb!", "JCDenton, it's a bomb!", null); + action.formatMentions({ + "JCDenton": "@jc.denton:unatco.gov" + }); + expect(action.htmlText).toEqual("JCDenton, it's a bomb!"); + }); + it("should highlight a user, regardless of case", () => { + let action = new MatrixAction("message", "JCDenton, it's a bomb!", "JCDenton, it's a bomb!", null); + action.formatMentions({ + "jcdenton": "@jc.denton:unatco.gov" + }); + expect(action.htmlText).toEqual("jcdenton, it's a bomb!"); + }); + it("should highlight a user, with plain text", () => { + let action = new MatrixAction("message", "JCDenton, it's a bomb!"); + action.formatMentions({ + "JCDenton": "@jc.denton:unatco.gov" + }); + expect(action.htmlText).toEqual("JCDenton, it's a bomb!"); + }); + it("should highlight multiple users", () => { + let action = new MatrixAction("message", "JCDenton is sent to assassinate PaulDenton", "JCDenton is sent to assassinate PaulDenton", null); + action.formatMentions({ + "JCDenton": "@jc.denton:unatco.gov", + "PaulDenton": "@paul.denton:unatco.gov" + }); + expect(action.htmlText).toEqual( + "JCDenton is sent" + + " to assassinate PaulDenton"); + }); + it("should highlight multiple mentions of the same user", () => { + let action = new MatrixAction("message", "JCDenton, meet JCDenton", "JCDenton, meet JCDenton", null); + action.formatMentions({ + "JCDenton": "@jc.denton:unatco.gov" + }); + expect(action.htmlText).toEqual("JCDenton, meet JCDenton"); + }); +}); From 59dfe9df16e3eb5579584c5c6ba0d01ae6fa2c61 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 16:40:40 +0100 Subject: [PATCH 069/162] Escape match when replacing --- lib/models/MatrixAction.js | 2 +- spec/unit/MatrixAction.spec.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 0e723663a..86297b2cb 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -63,7 +63,7 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { this.htmlText = this.text; } this.htmlText = this.htmlText.replace( - new RegExp(matchName, "igm"), + new RegExp(escapeStringRegexp(matchName), "igm"), `${matchName}` ); matched.add(matchName.toLowerCase()); diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index d6d9b8fc6..579d808d9 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -30,6 +30,13 @@ describe("MatrixAction", function() { }); expect(action.htmlText).toEqual("JCDenton, it's a bomb!"); }); + it("should highlight a user, with weird characters", () => { + let action = new MatrixAction("message", "JCDenton[m], it's a bomb!"); + action.formatMentions({ + "JCDenton[m]": "@jc.denton:unatco.gov" + }); + expect(action.htmlText).toEqual("JCDenton[m], it's a bomb!"); + }); it("should highlight multiple users", () => { let action = new MatrixAction("message", "JCDenton is sent to assassinate PaulDenton", "JCDenton is sent to assassinate PaulDenton", null); action.formatMentions({ From 09ff08cbd1a8b4a0078908d4c5ff119ee6318fd8 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 14 Aug 2018 17:03:23 +0100 Subject: [PATCH 070/162] Fixup comments --- lib/models/MatrixAction.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 86297b2cb..f22fbf725 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -32,24 +32,23 @@ function MatrixAction(type, text, htmlText, timestamp) { } MatrixAction.prototype.formatMentions = function(nickUserIdMap) { - // Get people this message could be mentioning. const regexString = "(" + Object.keys(nickUserIdMap).map((value) => escapeStringRegexp(value)).join("|") + ")"; const userRegex = new RegExp(regexString, "igm"); - const matched = new Set(); + const matched = new Set(); // lowercased nicknames we have matched already. let match; let i = 0; while ((match = userRegex.exec(this.text)) !== null && i < MAX_MATCHES) { i++; let matchName = match[1]; + // Deliberately have a minimum length to match on, so we don't match smaller nicks accidentally. if (matchName.length < MIN_LENGTH_TO_MATCH || matched.has(matchName.toLowerCase())) { continue; } - log.debug(`Matched ${matchName} in ${this.text}`); let userId = nickUserIdMap[matchName]; if (userId === undefined) { - // Might be casing. + // We might need to search case-insensitive. const nicks = Object.keys(nickUserIdMap).filter((nick) => nick.toLowerCase() === matchName.toLowerCase() ); @@ -59,6 +58,7 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { userId = nickUserIdMap[nicks[0]]; matchName = nicks[0]; } + // If this is not HTML text already, make it so. if (this.htmlText === undefined) { this.htmlText = this.text; } @@ -66,6 +66,7 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { new RegExp(escapeStringRegexp(matchName), "igm"), `${matchName}` ); + // Don't match this name twice, we've already replaced all entries. matched.add(matchName.toLowerCase()); } } From bff259b3603417270e2dc244e4d4de66b2c5fdc8 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 11:12:10 +0100 Subject: [PATCH 071/162] Metrics for idle connections in ClientPool --- lib/irc/ClientPool.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/irc/ClientPool.js b/lib/irc/ClientPool.js index 71e557a4d..d9908e9ca 100644 --- a/lib/irc/ClientPool.js +++ b/lib/irc/ClientPool.js @@ -246,7 +246,43 @@ ClientPool.prototype.totalReconnectsWaiting = function (serverDomain) { return this._reconnectQueues[serverDomain].waitingItems; } return 0; -} +}; + +ClientPool.prototype.getIdleConnectionMetrics = function() { + return new Promise((resolve) => { + resolve(this._calculateIdleConnectionMetrics()); + }); +}; + +ClientPool.prototype._calculateIdleConnectionMetrics = function(server) { + const SEVEN_DAYS_MS = 1000 * 60 * 60 * 24 * 7; + const periodBucketTimes = { + "7d": Date.now() - SEVEN_DAYS_MS, + "14d": Date.now() - SEVEN_DAYS_MS*2, + "21d": Date.now() - SEVEN_DAYS_MS*3, + }; + + const _idleConnectionMetrics = { + "7d": 0, + "14d": 0, + "21d": 0, + }; + const clients = Object.values(this._virtualClients[server].userIds); + for (const bridgedClient of clients) { + const lastActionMS = bridgedClient.getLastActionTs(); + let bucket = null; + for (const periodBucket in periodBucketTimes) { + if (lastActionMS > periodBucketTimes[periodBucket]) { + break; + } + bucket = periodBucket; + } + if (bucket !== null) { + _idleConnectionMetrics[bucket]++; + } + } + return _idleConnectionMetrics; +}; ClientPool.prototype._sendConnectionMetric = function(server) { stats.ircClients(server.domain, this._getNumberOfConnections(server)); From 1b36fa2f6332e12b5fa5891dd14bfc77d2677fab Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 11:12:44 +0100 Subject: [PATCH 072/162] Export metrics for idle connections --- lib/bridge/IrcBridge.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/bridge/IrcBridge.js b/lib/bridge/IrcBridge.js index fba8b2b5c..86fe95e2c 100644 --- a/lib/bridge/IrcBridge.js +++ b/lib/bridge/IrcBridge.js @@ -164,6 +164,12 @@ IrcBridge.prototype._initialiseMetrics = function() { labels: ["server"] }); + const idleConnections = metrics.addGauge({ + name: "clientpool_idle_connections", + help: "Idle connections on the bridge, grouped into period buckets.", + labels: ["server", "period"] + }); + const memberListLeaveQueue = metrics.addGauge({ name: "user_leave_queue", help: "Number of leave requests queued up for virtual users on the bridge.", @@ -186,6 +192,11 @@ IrcBridge.prototype._initialiseMetrics = function() { reconnQueue.set({server: server.domain}, this._clientPool.totalReconnectsWaiting(server.domain) ); + this._clientPool.getIdleConnectionMetrics(server.domain).then((idleBuckets) => { + for (const bucket in idleBuckets) { + idleConnections.set({server: server.domain, period: bucket}, idleBuckets[bucket]); + } + }); }); Object.keys(this.memberListSyncers).forEach((server) => { From f2fdc290363924768c28845ca792dc7109adaf5b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 11:18:59 +0100 Subject: [PATCH 073/162] Increment by the user numbers --- lib/bridge/MemberListSyncer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bridge/MemberListSyncer.js b/lib/bridge/MemberListSyncer.js index 0ff17dec6..af2116958 100644 --- a/lib/bridge/MemberListSyncer.js +++ b/lib/bridge/MemberListSyncer.js @@ -428,7 +428,7 @@ MemberListSyncer.prototype.getUsersWaitingToLeave = function() { } MemberListSyncer.prototype.addToLeavePool = function(userIds, roomId, channel) { - this._usersToLeave += 1; + this._usersToLeave += userIds.length; this._leaveQueuePool.enqueue(roomId + " " + channel, { roomId, userIds From d7d424b73b14e587be41d7cca0a588f1d780f1de Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 11:31:56 +0100 Subject: [PATCH 074/162] Force some errors to the user. --- lib/irc/BridgedClient.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/irc/BridgedClient.js b/lib/irc/BridgedClient.js index 014f2c6e7..f3cd85b61 100644 --- a/lib/irc/BridgedClient.js +++ b/lib/irc/BridgedClient.js @@ -175,12 +175,13 @@ BridgedClient.prototype.connect = Promise.coroutine(function*() { } }); connInst.client.addListener("error", (err) => { + const ERRORS_TO_FORCE = ["err_nononreg"] if (!err || !err.command || connInst.dead) { return; } var msg = "Received an error on " + this.server.domain + ": " + err.command + "\n"; msg += JSON.stringify(err.args); - this._eventBroker.sendMetadata(this, msg); + this._eventBroker.sendMetadata(this, msg, ERRORS_TO_FORCE.includes(err.command)); }); return connInst; } From 204a1a0ce0b2c0a1f9bb92f343fbd4d44bb7f06e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 15:51:13 +0100 Subject: [PATCH 075/162] Add reply tests --- spec/integ/matrix-to-irc.spec.js | 104 +++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/spec/integ/matrix-to-irc.spec.js b/spec/integ/matrix-to-irc.spec.js index 3234e3953..e2c6adf23 100644 --- a/spec/integ/matrix-to-irc.spec.js +++ b/spec/integ/matrix-to-irc.spec.js @@ -240,6 +240,110 @@ describe("Matrix-to-IRC message bridging", function() { }); }); + it("should bridge matrix replies as roughly formatted text", function(done) { + // Trigger an original event + env.mockAppService._trigger("type:m.room.message", { + content: { + body: "This is the real message", + msgtype: "m.text" + }, + user_id: testUser.id, + room_id: roomMapping.roomId, + sender: "@friend:bar.com", + event_id: "$original:bar.com", + type: "m.room.message" + }).then(() => { + env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", + function(client, channel, text) { + expect(client.nick).toEqual(testUser.nick); + expect(client.addr).toEqual(roomMapping.server); + expect(channel).toEqual(roomMapping.channel); + expect(text).toEqual(' Reply Text'); + done(); + }); + + env.mockAppService._trigger("type:m.room.message", { + content: { + body: "> <@somedude:bar.com> This is the fake message\n\nReply Text", + msgtype: "m.text", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$original:bar.com" + } + }, + }, + user_id: testUser.id, + room_id: roomMapping.roomId, + type: "m.room.message" + }); + }); + }); + + it("should bridge matrix replies as roughly formatted text, newline edition", function(done) { + // Trigger an original event + env.mockAppService._trigger("type:m.room.message", { + content: { + body: "\nThis\n is the real message", + msgtype: "m.text" + }, + user_id: testUser.id, + room_id: roomMapping.roomId, + sender: "@friend:bar.com", + event_id: "$original:bar.com", + type: "m.room.message" + }).then(() => { + env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", + function(client, channel, text) { + expect(client.nick).toEqual(testUser.nick); + expect(client.addr).toEqual(roomMapping.server); + expect(channel).toEqual(roomMapping.channel); + expect(text).toEqual(' Reply Text'); + done(); + }); + + env.mockAppService._trigger("type:m.room.message", { + content: { + body: "> <@somedude:bar.com> This is the fake message\n\nReply Text", + msgtype: "m.text", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$original:bar.com" + } + }, + }, + user_id: testUser.id, + room_id: roomMapping.roomId, + type: "m.room.message" + }); + }); + }); + + it("should bridge matrix replies as roughly formatted text, no event edition", function(done) { + env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", + function(client, channel, text) { + expect(client.nick).toEqual(testUser.nick); + expect(client.addr).toEqual(roomMapping.server); + expect(channel).toEqual(roomMapping.channel); + expect(text).toEqual(' Reply Text'); + done(); + }); + + env.mockAppService._trigger("type:m.room.message", { + content: { + body: "> <@somedude:bar.com> This message is possibly fake\n\nReply Text", + msgtype: "m.text", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$original:bar.com" + } + }, + }, + user_id: testUser.id, + room_id: roomMapping.roomId, + type: "m.room.message" + }); + }); + it("should bridge matrix images as IRC action with a URL", function(done) { var tBody = "the_image.jpg"; var tMxcSegment = "/somecontentid"; From 962a9eb2ec18d961447562b966ff770f24dd8dea Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 15:52:39 +0100 Subject: [PATCH 076/162] Add configurable event cache size --- config.sample.yaml | 5 +++++ lib/bridge/IrcBridge.js | 2 +- lib/bridge/MatrixHandler.js | 10 +++++----- lib/config/schema.yml | 5 +++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/config.sample.yaml b/config.sample.yaml index ce7935990..6f846326a 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -428,3 +428,8 @@ ircService: # `!storepass server.name passw0rd. When a connection is made to IRC on behalf of # the Matrix user, this password will be sent as the server password (PASS command). passwordEncryptionKeyPath: "passkey.pem" + + # Config for Matrix -> IRC bridging + matrixHandler: + # Cache this many matrix events in memory to be used for m.relates_to messages. + eventCacheSize: 4096 \ No newline at end of file diff --git a/lib/bridge/IrcBridge.js b/lib/bridge/IrcBridge.js index fba8b2b5c..f009b26ea 100644 --- a/lib/bridge/IrcBridge.js +++ b/lib/bridge/IrcBridge.js @@ -48,7 +48,7 @@ function IrcBridge(config, registration) { this.joinedRoomList = []; // Dependency graph - this.matrixHandler = new MatrixHandler(this); + this.matrixHandler = new MatrixHandler(this, this.config.matrixHandler); this.ircHandler = new IrcHandler(this); this._clientPool = new ClientPool(this); var dirPath = this.config.ircService.databaseUri.substring("nedb://".length); diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 5ab820fc3..f5b6ee5f9 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -19,9 +19,9 @@ const MSG_PMS_DISABLED_FEDERATION = "[Bridge] Sorry, PMs are disabled on " + const KICK_RETRY_DELAY_MS = 15000; const KICK_DELAY_JITTER = 30000; -const EVENT_CACHE_SIZE = 4096; +const DEFAULT_EVENT_CACHE_SIZE = 4096; -function MatrixHandler(ircBridge) { +function MatrixHandler(ircBridge, config) { this.ircBridge = ircBridge; // maintain a list of room IDs which are being processed invite-wise. This is // required because invites are processed asyncly, so you could get invite->msg @@ -32,6 +32,8 @@ function MatrixHandler(ircBridge) { this._memberTracker = null; this._eventCache = new Map(); //eventId => {body, sender} + config = config || {} + this._eventCacheMaxSize = config.eventCacheSize || DEFAULT_EVENT_CACHE_SIZE; } // ===== Matrix Invite Handling ===== @@ -1265,10 +1267,8 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( /** * Cache events in here so we can refer to them for replies. - * We deliberately avoid fetching events from the homeserver due to speed concerns. */ - this._eventCache.set(event.event_id, { body: event.content.body, sender: event.sender }); - if (this._eventCache.size > EVENT_CACHE_SIZE) { + if (this._eventCache.size > this._eventCacheMaxSize) { const delKey = this._eventCache.entries().next().value[0]; this._eventCache.delete(delKey); } diff --git a/lib/config/schema.yml b/lib/config/schema.yml index 36eff21e9..edf30e4b9 100644 --- a/lib/config/schema.yml +++ b/lib/config/schema.yml @@ -78,6 +78,11 @@ properties: type: "number" passwordEncryptionKeyPath: type: "string" + matrixHandler: + type: "object" + properties: + eventCacheSize: + type: "integer" servers: type: "object" # all properties must follow the following From 235930b222c109ac41f0412c3acb761762ac6183 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 15:53:26 +0100 Subject: [PATCH 077/162] Move split line statement to make tests pass --- lib/bridge/MatrixHandler.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index f5b6ee5f9..7d42aa710 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1256,15 +1256,11 @@ MatrixHandler.prototype._onMessage = Promise.coroutine(function*(req, event) { MatrixHandler.prototype._sendIrcAction = Promise.coroutine( function*(req, ircRoom, ircClient, ircAction, event) { - // Send the action as is if it is not a text message - // Also, check for the existance of the getSplitMessages method. - if (event.content.msgtype !== "m.text" || - !(ircClient.unsafeClient && ircClient.unsafeClient.getSplitMessages)) { + if (event.content.msgtype !== "m.text") { yield this.ircBridge.sendIrcAction(ircRoom, ircClient, ircAction); return; } - /** * Cache events in here so we can refer to them for replies. */ @@ -1308,6 +1304,10 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( text = `<${rplName} "${rplSource.trim()}"> ${rplText}`; ircAction.text = text; } + // Check for the existance of the getSplitMessages method. + if (!(ircClient.unsafeClient && ircClient.unsafeClient.getSplitMessages)) { + yield this.ircBridge.sendIrcAction(ircRoom, ircClient, ircAction); + return; } // Generate an array of individual messages that would be sent From 35fd9b6cea177a41f9a11e208aff5e957de2c0e6 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 15:53:43 +0100 Subject: [PATCH 078/162] Move reply handling code to it's own function --- lib/bridge/MatrixHandler.js | 96 ++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 7d42aa710..0abbb0bf1 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -20,6 +20,7 @@ const MSG_PMS_DISABLED_FEDERATION = "[Bridge] Sorry, PMs are disabled on " + const KICK_RETRY_DELAY_MS = 15000; const KICK_DELAY_JITTER = 30000; const DEFAULT_EVENT_CACHE_SIZE = 4096; +const REPLY_SOURCE_MAX_LENGTH = 32; function MatrixHandler(ircBridge, config) { this.ircBridge = ircBridge; @@ -1264,46 +1265,23 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( /** * Cache events in here so we can refer to them for replies. */ + this._eventCache.set(event.event_id, { + body: event.content.body.substr(0, REPLY_SOURCE_MAX_LENGTH), + sender: event.sender } + ); + console.log("SET!", this._eventCache, this._eventCache.size); if (this._eventCache.size > this._eventCacheMaxSize) { const delKey = this._eventCache.entries().next().value[0]; this._eventCache.delete(delKey); + req.log.debug(`Dropping ${delKey} from cache`); } let text = event.content.body; - const REPLY_SOURCE_MAX_LENGTH = 32; - const REPLY_NAME_MAX_LENGTH = 12; if (event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]) { - const eventId = event.content["m.relates_to"]["m.in_reply_to"].event_id; - // This is a reply. - const match = /> <(@.*:.*)>(.*)\n\n(.*)/.exec(text); - if (match.length === 4) { - let rplName = match[1]; - let rplSource = match[2].trim(); - if (this._eventCache.has(eventId)) { - rplName = this._eventCache.get(eventId).sender; - rplSource = this._eventCache.get(eventId).body; - } - // Replace newlines so we can fit this in the message body. - // It's not a perfect solution but it's legible. - rplSource = rplSource.replace(/\v/, '. '); - rplSource = rplSource.substr(0, REPLY_SOURCE_MAX_LENGTH) + - (rplSource.length > REPLY_SOURCE_MAX_LENGTH ? "..." : ""); - const rplText = match[3]; - - // Fetch the sender's IRC nick. - const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, rplName); - if (sourceClient) { - rplName = sourceClient.nick; - } - else { - // Somehow we failed, so fallback to userid. - rplName = match[1].substr(1, - Math.min(REPLY_NAME_MAX_LENGTH, userid.indexOf(":") - 1) - ); - } - text = `<${rplName} "${rplSource.trim()}"> ${rplText}`; - ircAction.text = text; - } + text = yield this._textForReplyEvent(event, ircRoom); + ircAction.text = text; + } + // Check for the existance of the getSplitMessages method. if (!(ircClient.unsafeClient && ircClient.unsafeClient.getSplitMessages)) { yield this.ircBridge.sendIrcAction(ircRoom, ircClient, ircAction); @@ -1529,6 +1507,58 @@ MatrixHandler.prototype._onUserQuery = Promise.coroutine(function*(req, userId) yield this.ircBridge.getMatrixUser(ircUser); }); +MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, ircRoom) { + const REPLY_NAME_MAX_LENGTH = 12; + const eventId = event.content["m.relates_to"]["m.in_reply_to"].event_id; + const match = /> <(@.*:.*)>(.*)\n\n(.*)/.exec(event.content.body); + if (match.length === 4) { + let rplName; + let rplSource; + console.log(eventId, this._eventCache); + if (this._eventCache.has(eventId)) { + rplName = this._eventCache.get(eventId).sender; + rplSource = this._eventCache.get(eventId).body; + } + else { + // Fallback to what the event says if we can't find a body. + rplName = match[1]; + rplSource = match[2].trim(); + } + + // Get the first non-blank line from the source. + const lines = rplSource.split('\n').filter((line) => !/^\s*$/.test(line)) + if (lines.length > 0) { + // Cut to a maximum length. + rplSource = lines[0].substr(0, REPLY_SOURCE_MAX_LENGTH); + // Ellipsis if needed. + if (rplSource.length > REPLY_SOURCE_MAX_LENGTH) { + rplSource = rplSource + "..."; + } + // Wrap in formatting + rplSource = ` "${lines[0]}"`; + } + else { + // Don't show a source because we couldn't format one. + rplSource = ""; + } + + // Fetch the sender's IRC nick. + const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, rplName); + if (sourceClient) { + rplName = sourceClient.nick; + } + else { + // Somehow we failed, so fallback to userid. + rplName = rplName.substr(1, + Math.min(REPLY_NAME_MAX_LENGTH, rplName.indexOf(":") - 1) + ); + } + + const rplText = match[3]; + return `<${rplName}${rplSource}> ${rplText}`; + } +}); + // EXPORTS MatrixHandler.prototype.onMemberEvent = function(req, event, inviter, invitee) { From 5474d331fb157deeeee5d5cfc3f22287d0604ab1 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 15:54:51 +0100 Subject: [PATCH 079/162] Drop log lines --- lib/bridge/MatrixHandler.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 0abbb0bf1..a0fb638d4 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1269,11 +1269,9 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( body: event.content.body.substr(0, REPLY_SOURCE_MAX_LENGTH), sender: event.sender } ); - console.log("SET!", this._eventCache, this._eventCache.size); if (this._eventCache.size > this._eventCacheMaxSize) { const delKey = this._eventCache.entries().next().value[0]; this._eventCache.delete(delKey); - req.log.debug(`Dropping ${delKey} from cache`); } let text = event.content.body; @@ -1514,7 +1512,6 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, if (match.length === 4) { let rplName; let rplSource; - console.log(eventId, this._eventCache); if (this._eventCache.has(eventId)) { rplName = this._eventCache.get(eventId).sender; rplSource = this._eventCache.get(eventId).body; From f11c1983cc353403b4141e804982a791f5fa9170 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 15:57:02 +0100 Subject: [PATCH 080/162] Add comment 4 travis --- lib/irc/BridgedClient.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/irc/BridgedClient.js b/lib/irc/BridgedClient.js index f3cd85b61..7e7a96f25 100644 --- a/lib/irc/BridgedClient.js +++ b/lib/irc/BridgedClient.js @@ -175,6 +175,7 @@ BridgedClient.prototype.connect = Promise.coroutine(function*() { } }); connInst.client.addListener("error", (err) => { + // Errors we MUST notify the user about, regardless of the bridge's admin room config. const ERRORS_TO_FORCE = ["err_nononreg"] if (!err || !err.command || connInst.dead) { return; From 8a5ecc086795080f53f123e61218b304699318d5 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 17:05:27 +0100 Subject: [PATCH 081/162] Name things better --- lib/models/MatrixAction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index f22fbf725..ab26da6f0 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -18,7 +18,7 @@ const MSGTYPE_TO_TYPE = { "m.file": "file" }; -const MIN_LENGTH_TO_MATCH = 4; +const PILL_MIN_LENGTH_TO_MATCH = 4; const MAX_MATCHES = 5; function MatrixAction(type, text, htmlText, timestamp) { @@ -43,7 +43,7 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { i++; let matchName = match[1]; // Deliberately have a minimum length to match on, so we don't match smaller nicks accidentally. - if (matchName.length < MIN_LENGTH_TO_MATCH || matched.has(matchName.toLowerCase())) { + if (matchName.length < PILL_MIN_LENGTH_TO_MATCH || matched.has(matchName.toLowerCase())) { continue; } let userId = nickUserIdMap[matchName]; From fdcbc6d3ee2c72ff2ddcd0c25746ce7bc35dacad Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 17:06:01 +0100 Subject: [PATCH 082/162] Replace while with for --- lib/models/MatrixAction.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index ab26da6f0..d156a4dcc 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -38,9 +38,7 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { const userRegex = new RegExp(regexString, "igm"); const matched = new Set(); // lowercased nicknames we have matched already. let match; - let i = 0; - while ((match = userRegex.exec(this.text)) !== null && i < MAX_MATCHES) { - i++; + for (let i = 0; i < MAX_MATCHES && (match = userRegex.exec(this.text)) !== null; i++) { let matchName = match[1]; // Deliberately have a minimum length to match on, so we don't match smaller nicks accidentally. if (matchName.length < PILL_MIN_LENGTH_TO_MATCH || matched.has(matchName.toLowerCase())) { From dc522d21313f22c0b52114320ecb58b54c58a357 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 17:06:13 +0100 Subject: [PATCH 083/162] I want to escape --- lib/irc/formatting.js | 3 +++ lib/models/MatrixAction.js | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/irc/formatting.js b/lib/irc/formatting.js index dec71ce78..5319f5753 100644 --- a/lib/irc/formatting.js +++ b/lib/irc/formatting.js @@ -80,6 +80,9 @@ function escapeHtmlChars(text) { .replace(/'/g, "'"); // to work on HTML4 (' is HTML5 only) } +// It's useful! +module.exports.escapeHtmlChars = escapeHtmlChars; + /** * Given the state of the message, open or close an HTML tag with the * appropriate attributes. diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index d156a4dcc..08e8baf19 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -58,8 +58,10 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { } // If this is not HTML text already, make it so. if (this.htmlText === undefined) { - this.htmlText = this.text; + this.htmlText = ircFormatting.escapeHtmlChars(this.text); } + userId = ircFormatting.escapeHtmlChars(userId); + matchName = ircFormatting.escapeHtmlChars(matchName); this.htmlText = this.htmlText.replace( new RegExp(escapeStringRegexp(matchName), "igm"), `${matchName}` From e34aee59ffedb3e6245fc730202b4535e215575b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 17:34:35 +0100 Subject: [PATCH 084/162] Cache the responses to getNickUserIdMappingForChannel --- lib/bridge/IrcHandler.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index a7ca27832..3ae1c530d 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -21,6 +21,8 @@ const MODES_TO_WATCH = [ "s" ]; +const NICK_USERID_CACHE_MAX = 512; + function IrcHandler(ircBridge) { this.ircBridge = ircBridge; // maintain a map of which user ID is in which PM room, so we know if we @@ -50,6 +52,8 @@ function IrcHandler(ircBridge) { //'$fromUserId $toUserId' : Promise }; + this.nickUserIdMapCache = new Map(); // server:channel => mapping + // Map which contains nicks we know have been registered/has display name this._registeredNicks = Object.create(null); this.getMetrics(); @@ -468,8 +472,22 @@ IrcHandler.prototype.onMessage = Promise.coroutine(function*(req, server, fromUs ); let mxAction = MatrixAction.fromIrcAction(action); + let mapping; + if (this.nickUserIdMapCache.has(`${server.domain}:${channel}`)) { + mapping = this.nickUserIdMapCache.get(`${server.domain}:${channel}`); + } + else { + mapping = this.ircBridge.getClientPool().getNickUserIdMappingForChannel( + server, channel + ); + this.nickUserIdMapCache.set(`${server.domain}:${channel}`, mapping); + if (this.nickUserIdMapCache.size > NICK_USERID_CACHE_MAX) { + this.nickUserIdMapCache.delete(this.nickUserIdMapCache.keys()[0]); + } + } + mxAction.formatMentions( - this.ircBridge.getClientPool().getNickUserIdMappingForChannel(server, channel) + mapping ); if (!mxAction) { @@ -522,6 +540,8 @@ IrcHandler.prototype.onJoin = Promise.coroutine(function*(req, server, joiningUs this.incrementMetric("join"); } + this._invalidateNickUserIdMap(server, chan); + let nick = joiningUser.nick; let syncType = kind === "names" ? "initial" : "incremental"; if (!server.shouldSyncMembershipToMatrix(syncType, chan)) { @@ -681,6 +701,7 @@ IrcHandler.prototype.onKick = Promise.coroutine(function*(req, server, */ IrcHandler.prototype.onPart = Promise.coroutine(function*(req, server, leavingUser, chan, kind) { this.incrementMetric("part"); + this._invalidateNickUserIdMap(server, chan); // parts are always incremental (only NAMES are initial) if (!server.shouldSyncMembershipToMatrix("incremental", chan)) { req.log.info("Server doesn't mirror parts."); @@ -1006,6 +1027,10 @@ IrcHandler.prototype._setMatrixRoomAsInviteOnly = function(room, isInviteOnly) { ); }; +IrcHandler.prototype._invalidateNickUserIdMap = function(server, channel) { + this.nickUserIdMapCache.delete(`${server.domain}:${channel}`); +} + IrcHandler.prototype.incrementMetric = function(metric) { if (this._callCountMetrics[metric] === undefined) { this._callCountMetrics[metric] = 0; From 1fd67c5b7c2b6aecf3cd657b12f4f0f7cd7a7a31 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 17:52:39 +0100 Subject: [PATCH 085/162] Test text too --- spec/unit/MatrixAction.spec.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index 579d808d9..98a4aaf5f 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -8,12 +8,14 @@ describe("MatrixAction", function() { "Some Person": "@foobar:localhost" }); expect(action.text).toEqual("Some text"); + expect(action.text).toEqual("Some text"); }); it("should highlight a user", () => { let action = new MatrixAction("message", "JCDenton, it's a bomb!", "JCDenton, it's a bomb!", null); action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" }); + expect(action.text).toEqual("JCDenton, it's a bomb!"); expect(action.htmlText).toEqual("JCDenton, it's a bomb!"); }); it("should highlight a user, regardless of case", () => { @@ -21,6 +23,7 @@ describe("MatrixAction", function() { action.formatMentions({ "jcdenton": "@jc.denton:unatco.gov" }); + expect(action.text).toEqual("JCDenton, it's a bomb!"); expect(action.htmlText).toEqual("jcdenton, it's a bomb!"); }); it("should highlight a user, with plain text", () => { @@ -28,6 +31,7 @@ describe("MatrixAction", function() { action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" }); + expect(action.text).toEqual("JCDenton, it's a bomb!"); expect(action.htmlText).toEqual("JCDenton, it's a bomb!"); }); it("should highlight a user, with weird characters", () => { @@ -36,6 +40,7 @@ describe("MatrixAction", function() { "JCDenton[m]": "@jc.denton:unatco.gov" }); expect(action.htmlText).toEqual("JCDenton[m], it's a bomb!"); + expect(action.text).toEqual("`||JCDenton[m], it's a bomb!"); }); it("should highlight multiple users", () => { let action = new MatrixAction("message", "JCDenton is sent to assassinate PaulDenton", "JCDenton is sent to assassinate PaulDenton", null); @@ -43,6 +48,7 @@ describe("MatrixAction", function() { "JCDenton": "@jc.denton:unatco.gov", "PaulDenton": "@paul.denton:unatco.gov" }); + expect(action.text).toEqual("JCDenton is sent to assassinate PaulDenton"); expect(action.htmlText).toEqual( "JCDenton is sent" + " to assassinate PaulDenton"); @@ -52,6 +58,7 @@ describe("MatrixAction", function() { action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" }); + expect(action.text).toEqual("JCDenton, meet JCDenton"); expect(action.htmlText).toEqual("JCDenton, meet JCDenton"); }); }); From a9a6f816ac4f3c1cb9dff66790137d0c8125b032 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 17:52:56 +0100 Subject: [PATCH 086/162] Test even weirder characters --- spec/unit/MatrixAction.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index 98a4aaf5f..5c5320cc5 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -35,12 +35,12 @@ describe("MatrixAction", function() { expect(action.htmlText).toEqual("JCDenton, it's a bomb!"); }); it("should highlight a user, with weird characters", () => { - let action = new MatrixAction("message", "JCDenton[m], it's a bomb!"); + let action = new MatrixAction("message", "`||JCDenton[m], it's a bomb!"); action.formatMentions({ - "JCDenton[m]": "@jc.denton:unatco.gov" + "`||JCDenton[m]": "@jc.denton:unatco.gov" }); - expect(action.htmlText).toEqual("JCDenton[m], it's a bomb!"); expect(action.text).toEqual("`||JCDenton[m], it's a bomb!"); + expect(action.htmlText).toEqual("`||JCDenton[m], it's a bomb!"); }); it("should highlight multiple users", () => { let action = new MatrixAction("message", "JCDenton is sent to assassinate PaulDenton", "JCDenton is sent to assassinate PaulDenton", null); From c0b6955f7cb2bf944e6a940a5cf80918f90420a2 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 17:53:13 +0100 Subject: [PATCH 087/162] Escape html chars in name --- lib/models/MatrixAction.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 08e8baf19..41462d5ba 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -58,13 +58,12 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { } // If this is not HTML text already, make it so. if (this.htmlText === undefined) { - this.htmlText = ircFormatting.escapeHtmlChars(this.text); + this.htmlText = this.text; } userId = ircFormatting.escapeHtmlChars(userId); - matchName = ircFormatting.escapeHtmlChars(matchName); this.htmlText = this.htmlText.replace( new RegExp(escapeStringRegexp(matchName), "igm"), - `${matchName}` + `${ircFormatting.escapeHtmlChars(matchName)}` ); // Don't match this name twice, we've already replaced all entries. matched.add(matchName.toLowerCase()); From b842eedc5f91bd501696734831a2771d33bf4b80 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 17:54:43 +0100 Subject: [PATCH 088/162] Check we don't generate html on non-matches --- spec/unit/MatrixAction.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index 5c5320cc5..ecf016d0d 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -3,12 +3,12 @@ const MatrixAction = require("../../lib/models/MatrixAction"); describe("MatrixAction", function() { it("should not highlight mentions to text without mentions", () => { - let action = new MatrixAction("message", "Some text", "Some text", null); + let action = new MatrixAction("message", "Some text"); action.formatMentions({ "Some Person": "@foobar:localhost" }); expect(action.text).toEqual("Some text"); - expect(action.text).toEqual("Some text"); + expect(action.htmlText).toBeUndefined(); }); it("should highlight a user", () => { let action = new MatrixAction("message", "JCDenton, it's a bomb!", "JCDenton, it's a bomb!", null); From 2a1b0df3eacb6db701c5d0c193cf3f5ff973208f Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 15 Aug 2018 18:18:16 +0100 Subject: [PATCH 089/162] Lots of little things --- lib/bridge/IrcHandler.js | 4 +--- lib/models/MatrixAction.js | 14 ++++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index 3ae1c530d..b385a0d87 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -486,9 +486,7 @@ IrcHandler.prototype.onMessage = Promise.coroutine(function*(req, server, fromUs } } - mxAction.formatMentions( - mapping - ); + mxAction.formatMentions(mapping); if (!mxAction) { req.log.error("Couldn't map IRC action to matrix action"); diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 41462d5ba..1b32701db 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -47,17 +47,19 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { let userId = nickUserIdMap[matchName]; if (userId === undefined) { // We might need to search case-insensitive. - const nicks = Object.keys(nickUserIdMap).filter((nick) => - nick.toLowerCase() === matchName.toLowerCase() + const nick = Object.keys(nickUserIdMap).find((n) => + n.toLowerCase() === matchName.toLowerCase() ); - if (nicks.length === 0) { + if (nick === undefined) { continue; } - userId = nickUserIdMap[nicks[0]]; - matchName = nicks[0]; + userId = nickUserIdMap[nick]; + matchName = nick; } - // If this is not HTML text already, make it so. + // If this message is not HTML, we should make it so. if (this.htmlText === undefined) { + // This looks scary and unsafe, but further down we check + // if `text` contains any HTML and escape + set `htmlText` appropriately. this.htmlText = this.text; } userId = ircFormatting.escapeHtmlChars(userId); From 44475b9886cfc1665f509ecabf3ce01b5837251a Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 11:28:44 +0100 Subject: [PATCH 090/162] Add support for /room/.../event/.. for replies --- lib/bridge/MatrixHandler.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index a0fb638d4..981f7489b 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1512,14 +1512,22 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, if (match.length === 4) { let rplName; let rplSource; + const rplText = match[3]; if (this._eventCache.has(eventId)) { rplName = this._eventCache.get(eventId).sender; rplSource = this._eventCache.get(eventId).body; } else { - // Fallback to what the event says if we can't find a body. - rplName = match[1]; - rplSource = match[2].trim(); + // Fallback to fetching from the homeserver. + try { + const eventContent = yield this.ircBridge.getAppServiceBridge().getIntent().getEvent(event.room_id, eventId); + rplName = eventContent.sender; + rplSource = eventContent.body.substr(0, REPLY_SOURCE_MAX_LENGTH); + this._eventCache.set(eventId, {sender: rplName, body: rplSource}); + } catch (err) { + // If we couldn't find the event, then frankly we can't trust it and we won't treat it as a reply. + return rplText; + } } // Get the first non-blank line from the source. @@ -1551,7 +1559,6 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, ); } - const rplText = match[3]; return `<${rplName}${rplSource}> ${rplText}`; } }); From 6e76936ac5304a98abbbd94e7e3c4ba21c1ab66c Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 12:00:03 +0100 Subject: [PATCH 091/162] Check for an explicit undefined when getting cache size --- lib/bridge/MatrixHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 981f7489b..861313357 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -34,7 +34,8 @@ function MatrixHandler(ircBridge, config) { this._memberTracker = null; this._eventCache = new Map(); //eventId => {body, sender} config = config || {} - this._eventCacheMaxSize = config.eventCacheSize || DEFAULT_EVENT_CACHE_SIZE; + this._eventCacheMaxSize = config.eventCacheSize === undefined ? + DEFAULT_EVENT_CACHE_SIZE : config.eventCacheSize; } // ===== Matrix Invite Handling ===== From aa27fcf2b9a165e85b09c5c38c5acd96eafd653c Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 12:14:30 +0100 Subject: [PATCH 092/162] Store the reply text in the cache, not the whole message --- lib/bridge/MatrixHandler.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 861313357..df9643c10 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1263,24 +1263,27 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( yield this.ircBridge.sendIrcAction(ircRoom, ircClient, ircAction); return; } + + let text = event.content.body; + let cacheText = text; + if (event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]) { + const reply = yield this._textForReplyEvent(event, ircRoom); + ircAction.text = text = reply.formatted; + cacheText = reply.reply; + } + this._eventCache.set(event.event_id, { + body: cacheText.substr(0, REPLY_SOURCE_MAX_LENGTH), + sender: event.sender + }); + /** * Cache events in here so we can refer to them for replies. - */ - this._eventCache.set(event.event_id, { - body: event.content.body.substr(0, REPLY_SOURCE_MAX_LENGTH), - sender: event.sender } - ); + */ if (this._eventCache.size > this._eventCacheMaxSize) { const delKey = this._eventCache.entries().next().value[0]; this._eventCache.delete(delKey); } - let text = event.content.body; - if (event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]) { - text = yield this._textForReplyEvent(event, ircRoom); - ircAction.text = text; - } - // Check for the existance of the getSplitMessages method. if (!(ircClient.unsafeClient && ircClient.unsafeClient.getSplitMessages)) { yield this.ircBridge.sendIrcAction(ircRoom, ircClient, ircAction); @@ -1527,7 +1530,10 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, this._eventCache.set(eventId, {sender: rplName, body: rplSource}); } catch (err) { // If we couldn't find the event, then frankly we can't trust it and we won't treat it as a reply. - return rplText; + return { + formatted: rplText, + reply: rplText, + }; } } @@ -1560,7 +1566,10 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, ); } - return `<${rplName}${rplSource}> ${rplText}`; + return { + formatted: `<${rplName}${rplSource}> ${rplText}`, + reply: rplText, + }; } }); From 359ffe03a3e27bf4fc9d744bc8d39d5fdd5f70ec Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 12:32:19 +0100 Subject: [PATCH 093/162] Do not show any reply text if we cannot be sure of the source text --- spec/integ/matrix-to-irc.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/integ/matrix-to-irc.spec.js b/spec/integ/matrix-to-irc.spec.js index e2c6adf23..ed5e186f9 100644 --- a/spec/integ/matrix-to-irc.spec.js +++ b/spec/integ/matrix-to-irc.spec.js @@ -318,13 +318,13 @@ describe("Matrix-to-IRC message bridging", function() { }); }); - it("should bridge matrix replies as roughly formatted text, no event edition", function(done) { + it("should bridge matrix replies as reply only, if the original event cannot be found", function(done) { env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", function(client, channel, text) { expect(client.nick).toEqual(testUser.nick); expect(client.addr).toEqual(roomMapping.server); expect(channel).toEqual(roomMapping.channel); - expect(text).toEqual(' Reply Text'); + expect(text).toEqual('Reply Text'); done(); }); From af4feba23b24b57910a328b02432660cdf9d3af8 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 13:01:29 +0100 Subject: [PATCH 094/162] body -> content.body --- lib/bridge/MatrixHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index df9643c10..21c259c24 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1526,7 +1526,7 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, try { const eventContent = yield this.ircBridge.getAppServiceBridge().getIntent().getEvent(event.room_id, eventId); rplName = eventContent.sender; - rplSource = eventContent.body.substr(0, REPLY_SOURCE_MAX_LENGTH); + rplSource = eventContent.content.body.substr(0, EVENT_CACHE_LENGTH); this._eventCache.set(eventId, {sender: rplName, body: rplSource}); } catch (err) { // If we couldn't find the event, then frankly we can't trust it and we won't treat it as a reply. From 947768fef6b7f2b3b52877136555820764f68671 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 13:13:44 +0100 Subject: [PATCH 095/162] cacheText > cacheBody --- lib/bridge/MatrixHandler.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 21c259c24..e80b8c791 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1265,14 +1265,14 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( } let text = event.content.body; - let cacheText = text; + let cacheBody = text; if (event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]) { const reply = yield this._textForReplyEvent(event, ircRoom); ircAction.text = text = reply.formatted; - cacheText = reply.reply; + cacheBody = reply.reply; } this._eventCache.set(event.event_id, { - body: cacheText.substr(0, REPLY_SOURCE_MAX_LENGTH), + body: cacheBody.substr(0, REPLY_SOURCE_MAX_LENGTH), sender: event.sender }); From dfd73285939cb1c006dd62bf09b9ea8f23a10425 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 13:52:29 +0100 Subject: [PATCH 096/162] If else switcheroo --- lib/bridge/MatrixHandler.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index e80b8c791..62daab6a3 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1517,11 +1517,7 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, let rplName; let rplSource; const rplText = match[3]; - if (this._eventCache.has(eventId)) { - rplName = this._eventCache.get(eventId).sender; - rplSource = this._eventCache.get(eventId).body; - } - else { + if (!this._eventCache.has(eventId)) { // Fallback to fetching from the homeserver. try { const eventContent = yield this.ircBridge.getAppServiceBridge().getIntent().getEvent(event.room_id, eventId); @@ -1536,6 +1532,10 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, }; } } + else { + rplName = this._eventCache.get(eventId).sender; + rplSource = this._eventCache.get(eventId).body; + } // Get the first non-blank line from the source. const lines = rplSource.split('\n').filter((line) => !/^\s*$/.test(line)) From d957254eb85cc8fa7942045c5f714ba07de21087 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 13:52:52 +0100 Subject: [PATCH 097/162] Replies to replies should use the reply text of the original reply. --- lib/bridge/MatrixHandler.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 62daab6a3..3ef19c1af 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1510,9 +1510,10 @@ MatrixHandler.prototype._onUserQuery = Promise.coroutine(function*(req, userId) }); MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, ircRoom) { + const REPLY_REGEX = /> <(@.*:.*)>(.*)\n\n(.*)/; const REPLY_NAME_MAX_LENGTH = 12; const eventId = event.content["m.relates_to"]["m.in_reply_to"].event_id; - const match = /> <(@.*:.*)>(.*)\n\n(.*)/.exec(event.content.body); + const match = REPLY_REGEX.exec(event.content.body); if (match.length === 4) { let rplName; let rplSource; @@ -1522,7 +1523,18 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, try { const eventContent = yield this.ircBridge.getAppServiceBridge().getIntent().getEvent(event.room_id, eventId); rplName = eventContent.sender; - rplSource = eventContent.content.body.substr(0, EVENT_CACHE_LENGTH); + if (typeof(eventContent.content.body) !== "string") { + throw Error("'body' was not a string."); + } + const isReply = event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]; + if (isReply) { + let match = REPLY_REGEX.exec(event.content.body); + rplSource = match.length === 4 ? match[3] : event.content.body; + } + else { + rplSource = eventContent.content.body; + } + rplSource = eventContent.content.body.substr(0, REPLY_SOURCE_MAX_LENGTH); this._eventCache.set(eventId, {sender: rplName, body: rplSource}); } catch (err) { // If we couldn't find the event, then frankly we can't trust it and we won't treat it as a reply. From 8e0f6f76eaa6ca5f5d768ed13b83d271d649ae10 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 13:53:11 +0100 Subject: [PATCH 098/162] Comment some consts --- lib/bridge/MatrixHandler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 3ef19c1af..eaea151b9 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -19,7 +19,9 @@ const MSG_PMS_DISABLED_FEDERATION = "[Bridge] Sorry, PMs are disabled on " + const KICK_RETRY_DELAY_MS = 15000; const KICK_DELAY_JITTER = 30000; +/* Number of events to store in memory for use in replies. */ const DEFAULT_EVENT_CACHE_SIZE = 4096; +/* Length of the source text in a formatted reply message */ const REPLY_SOURCE_MAX_LENGTH = 32; function MatrixHandler(ircBridge, config) { From 32a880bc29001b3edb323a2f8bd7022c3997547e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 14:02:38 +0100 Subject: [PATCH 099/162] Testing that replies of replies don't contain the original source. --- spec/integ/matrix-to-irc.spec.js | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/spec/integ/matrix-to-irc.spec.js b/spec/integ/matrix-to-irc.spec.js index ed5e186f9..ad2d2b9ef 100644 --- a/spec/integ/matrix-to-irc.spec.js +++ b/spec/integ/matrix-to-irc.spec.js @@ -344,6 +344,61 @@ describe("Matrix-to-IRC message bridging", function() { }); }); + it("should bridge matrix replies to replies without the original source", function(done) { + env.mockAppService._trigger("type:m.room.message", { + content: { + body: "Message #1", + msgtype: "m.text" + }, + user_id: testUser.id, + room_id: roomMapping.roomId, + sender: "@friend:bar.com", + event_id: "$first:bar.com", + type: "m.room.message" + }).then(() => { + return env.mockAppService._trigger("type:m.room.message", { + content: { + body: "> <@friend:bar.com> Message#1\n\nMessage #2", + msgtype: "m.text", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$first:bar.com" + } + }, + }, + user_id: testUser.id, + room_id: roomMapping.roomId, + sender: "@friend:bar.com", + event_id: "$second:bar.com", + type: "m.room.message" + }); + }).then(() => { + env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", + function(client, channel, text) { + expect(client.nick).toEqual(testUser.nick); + expect(client.addr).toEqual(roomMapping.server); + expect(channel).toEqual(roomMapping.channel); + expect(text).toEqual(' Message #3'); + done(); + }); + + env.mockAppService._trigger("type:m.room.message", { + content: { + body: "> <@friend:bar.com> Message#2\n\nMessage #3", + msgtype: "m.text", + "m.relates_to": { + "m.in_reply_to": { + "event_id": "$second:bar.com" + } + }, + }, + user_id: testUser.id, + room_id: roomMapping.roomId, + type: "m.room.message" + }); + }); + }); + it("should bridge matrix images as IRC action with a URL", function(done) { var tBody = "the_image.jpg"; var tMxcSegment = "/somecontentid"; From 1803003fa9ea2197879922903ac121d75beb66d9 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 14:07:26 +0100 Subject: [PATCH 100/162] Don't use reply if undefined --- lib/bridge/MatrixHandler.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index eaea151b9..69b5da07a 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1270,8 +1270,10 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( let cacheBody = text; if (event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]) { const reply = yield this._textForReplyEvent(event, ircRoom); - ircAction.text = text = reply.formatted; - cacheBody = reply.reply; + if (reply !== undefined) { + ircAction.text = text = reply.formatted; + cacheBody = reply.reply; + } } this._eventCache.set(event.event_id, { body: cacheBody.substr(0, REPLY_SOURCE_MAX_LENGTH), From 1d5550699da479f0197c0162facf89064de0abc3 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 14:07:40 +0100 Subject: [PATCH 101/162] Indent smarter --- lib/bridge/MatrixHandler.js | 130 ++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 69b5da07a..c4b9deda9 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1518,75 +1518,77 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, const REPLY_NAME_MAX_LENGTH = 12; const eventId = event.content["m.relates_to"]["m.in_reply_to"].event_id; const match = REPLY_REGEX.exec(event.content.body); - if (match.length === 4) { - let rplName; - let rplSource; - const rplText = match[3]; - if (!this._eventCache.has(eventId)) { - // Fallback to fetching from the homeserver. - try { - const eventContent = yield this.ircBridge.getAppServiceBridge().getIntent().getEvent(event.room_id, eventId); - rplName = eventContent.sender; - if (typeof(eventContent.content.body) !== "string") { - throw Error("'body' was not a string."); - } - const isReply = event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]; - if (isReply) { - let match = REPLY_REGEX.exec(event.content.body); - rplSource = match.length === 4 ? match[3] : event.content.body; - } - else { - rplSource = eventContent.content.body; - } - rplSource = eventContent.content.body.substr(0, REPLY_SOURCE_MAX_LENGTH); - this._eventCache.set(eventId, {sender: rplName, body: rplSource}); - } catch (err) { - // If we couldn't find the event, then frankly we can't trust it and we won't treat it as a reply. - return { - formatted: rplText, - reply: rplText, - }; + if (match.length !== 4) { + return; + } + + let rplName; + let rplSource; + const rplText = match[3]; + if (!this._eventCache.has(eventId)) { + // Fallback to fetching from the homeserver. + try { + const eventContent = yield this.ircBridge.getAppServiceBridge().getIntent().getEvent(event.room_id, eventId); + rplName = eventContent.sender; + if (typeof(eventContent.content.body) !== "string") { + throw Error("'body' was not a string."); } - } - else { - rplName = this._eventCache.get(eventId).sender; - rplSource = this._eventCache.get(eventId).body; - } - - // Get the first non-blank line from the source. - const lines = rplSource.split('\n').filter((line) => !/^\s*$/.test(line)) - if (lines.length > 0) { - // Cut to a maximum length. - rplSource = lines[0].substr(0, REPLY_SOURCE_MAX_LENGTH); - // Ellipsis if needed. - if (rplSource.length > REPLY_SOURCE_MAX_LENGTH) { - rplSource = rplSource + "..."; + const isReply = event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]; + if (isReply) { + let match = REPLY_REGEX.exec(eventContent.content.body); + rplSource = match.length === 4 ? match[3] : event.content.body; } - // Wrap in formatting - rplSource = ` "${lines[0]}"`; - } - else { - // Don't show a source because we couldn't format one. - rplSource = ""; - } - - // Fetch the sender's IRC nick. - const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, rplName); - if (sourceClient) { - rplName = sourceClient.nick; - } - else { - // Somehow we failed, so fallback to userid. - rplName = rplName.substr(1, - Math.min(REPLY_NAME_MAX_LENGTH, rplName.indexOf(":") - 1) - ); + else { + rplSource = eventContent.content.body; + } + rplSource = rplSource.substr(0, REPLY_SOURCE_MAX_LENGTH); + this._eventCache.set(eventId, {sender: rplName, body: rplSource}); + } catch (err) { + // If we couldn't find the event, then frankly we can't trust it and we won't treat it as a reply. + return { + formatted: rplText, + reply: rplText, + }; } + } + else { + rplName = this._eventCache.get(eventId).sender; + rplSource = this._eventCache.get(eventId).body; + } - return { - formatted: `<${rplName}${rplSource}> ${rplText}`, - reply: rplText, - }; + // Get the first non-blank line from the source. + const lines = rplSource.split('\n').filter((line) => !/^\s*$/.test(line)) + if (lines.length > 0) { + // Cut to a maximum length. + rplSource = lines[0].substr(0, REPLY_SOURCE_MAX_LENGTH); + // Ellipsis if needed. + if (rplSource.length > REPLY_SOURCE_MAX_LENGTH) { + rplSource = rplSource + "..."; + } + // Wrap in formatting + rplSource = ` "${lines[0]}"`; } + else { + // Don't show a source because we couldn't format one. + rplSource = ""; + } + + // Fetch the sender's IRC nick. + const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, rplName); + if (sourceClient) { + rplName = sourceClient.nick; + } + else { + // Somehow we failed, so fallback to userid. + rplName = rplName.substr(1, + Math.min(REPLY_NAME_MAX_LENGTH, rplName.indexOf(":") - 1) + ); + } + + return { + formatted: `<${rplName}${rplSource}> ${rplText}`, + reply: rplText, + }; }); // EXPORTS From 5b61d53439f7ebf01fc305712e64534baba470a7 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 14:24:04 +0100 Subject: [PATCH 102/162] Linting --- lib/bridge/MatrixHandler.js | 23 ++++++++++++++--------- spec/integ/matrix-to-irc.spec.js | 6 +++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 450024ec3..7a464031c 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -36,7 +36,7 @@ function MatrixHandler(ircBridge, config) { this._memberTracker = null; this._eventCache = new Map(); //eventId => {body, sender} config = config || {} - this._eventCacheMaxSize = config.eventCacheSize === undefined ? + this._eventCacheMaxSize = config.eventCacheSize === undefined ? DEFAULT_EVENT_CACHE_SIZE : config.eventCacheSize; this.metrics = { //domain => {"metricname" => value} @@ -1525,30 +1525,35 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, if (match.length !== 4) { return; } - + let rplName; let rplSource; const rplText = match[3]; if (!this._eventCache.has(eventId)) { // Fallback to fetching from the homeserver. try { - const eventContent = yield this.ircBridge.getAppServiceBridge().getIntent().getEvent(event.room_id, eventId); + const eventContent = yield this.ircBridge.getAppServiceBridge().getIntent().getEvent( + event.room_id, eventId + ); rplName = eventContent.sender; if (typeof(eventContent.content.body) !== "string") { throw Error("'body' was not a string."); } - const isReply = event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]; + const isReply = event.content["m.relates_to"] && + event.content["m.relates_to"]["m.in_reply_to"]; if (isReply) { - let match = REPLY_REGEX.exec(eventContent.content.body); - rplSource = match.length === 4 ? match[3] : event.content.body; + const sourceMatch = REPLY_REGEX.exec(eventContent.content.body); + rplSource = sourceMatch.length === 4 ? sourceMatch[3] : event.content.body; } else { rplSource = eventContent.content.body; } rplSource = rplSource.substr(0, REPLY_SOURCE_MAX_LENGTH); this._eventCache.set(eventId, {sender: rplName, body: rplSource}); - } catch (err) { - // If we couldn't find the event, then frankly we can't trust it and we won't treat it as a reply. + } + catch (err) { + // If we couldn't find the event, then frankly we can't + // trust it and we won't treat it as a reply. return { formatted: rplText, reply: rplText, @@ -1576,7 +1581,7 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, // Don't show a source because we couldn't format one. rplSource = ""; } - + // Fetch the sender's IRC nick. const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, rplName); if (sourceClient) { diff --git a/spec/integ/matrix-to-irc.spec.js b/spec/integ/matrix-to-irc.spec.js index ad2d2b9ef..04ac2437b 100644 --- a/spec/integ/matrix-to-irc.spec.js +++ b/spec/integ/matrix-to-irc.spec.js @@ -261,7 +261,7 @@ describe("Matrix-to-IRC message bridging", function() { expect(text).toEqual(' Reply Text'); done(); }); - + env.mockAppService._trigger("type:m.room.message", { content: { body: "> <@somedude:bar.com> This is the fake message\n\nReply Text", @@ -318,7 +318,7 @@ describe("Matrix-to-IRC message bridging", function() { }); }); - it("should bridge matrix replies as reply only, if the original event cannot be found", function(done) { + it("should bridge matrix replies as reply only, if source not found", function(done) { env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", function(client, channel, text) { expect(client.nick).toEqual(testUser.nick); @@ -381,7 +381,7 @@ describe("Matrix-to-IRC message bridging", function() { expect(text).toEqual(' Message #3'); done(); }); - + env.mockAppService._trigger("type:m.room.message", { content: { body: "> <@friend:bar.com> Message#2\n\nMessage #3", From 323c7d81f0e823a38fe1b6370c2c59170858685d Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 14:34:53 +0100 Subject: [PATCH 103/162] Linting --- lib/bridge/IrcHandler.js | 2 +- lib/models/MatrixAction.js | 5 ++-- spec/unit/MatrixAction.spec.js | 52 +++++++++++++++++++++++++++------- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index b385a0d87..9488e1f92 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -53,7 +53,7 @@ function IrcHandler(ircBridge) { }; this.nickUserIdMapCache = new Map(); // server:channel => mapping - + // Map which contains nicks we know have been registered/has display name this._registeredNicks = Object.create(null); this.getMetrics(); diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 1b32701db..6eddc6346 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -40,7 +40,8 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { let match; for (let i = 0; i < MAX_MATCHES && (match = userRegex.exec(this.text)) !== null; i++) { let matchName = match[1]; - // Deliberately have a minimum length to match on, so we don't match smaller nicks accidentally. + // Deliberately have a minimum length to match on, + // so we don't match smaller nicks accidentally. if (matchName.length < PILL_MIN_LENGTH_TO_MATCH || matched.has(matchName.toLowerCase())) { continue; } @@ -65,7 +66,7 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { userId = ircFormatting.escapeHtmlChars(userId); this.htmlText = this.htmlText.replace( new RegExp(escapeStringRegexp(matchName), "igm"), - `${ircFormatting.escapeHtmlChars(matchName)}` +`${ircFormatting.escapeHtmlChars(matchName)}` ); // Don't match this name twice, we've already replaced all entries. matched.add(matchName.toLowerCase()); diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index ecf016d0d..b771b4a6b 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -11,20 +11,34 @@ describe("MatrixAction", function() { expect(action.htmlText).toBeUndefined(); }); it("should highlight a user", () => { - let action = new MatrixAction("message", "JCDenton, it's a bomb!", "JCDenton, it's a bomb!", null); + let action = new MatrixAction( + "message", + "JCDenton, it's a bomb!", + "JCDenton, it's a bomb!", + null + ); action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" }); expect(action.text).toEqual("JCDenton, it's a bomb!"); - expect(action.htmlText).toEqual("JCDenton, it's a bomb!"); + expect(action.htmlText).toEqual( + "JCDenton, it's a bomb!" + ); }); it("should highlight a user, regardless of case", () => { - let action = new MatrixAction("message", "JCDenton, it's a bomb!", "JCDenton, it's a bomb!", null); + let action = new MatrixAction( + "message", + "JCDenton, it's a bomb!", + "JCDenton, it's a bomb!", + null + ); action.formatMentions({ "jcdenton": "@jc.denton:unatco.gov" }); expect(action.text).toEqual("JCDenton, it's a bomb!"); - expect(action.htmlText).toEqual("jcdenton, it's a bomb!"); + expect(action.htmlText).toEqual( + "jcdenton, it's a bomb!" + ); }); it("should highlight a user, with plain text", () => { let action = new MatrixAction("message", "JCDenton, it's a bomb!"); @@ -32,7 +46,9 @@ describe("MatrixAction", function() { "JCDenton": "@jc.denton:unatco.gov" }); expect(action.text).toEqual("JCDenton, it's a bomb!"); - expect(action.htmlText).toEqual("JCDenton, it's a bomb!"); + expect(action.htmlText).toEqual( + "JCDenton, it's a bomb!" + ); }); it("should highlight a user, with weird characters", () => { let action = new MatrixAction("message", "`||JCDenton[m], it's a bomb!"); @@ -40,10 +56,17 @@ describe("MatrixAction", function() { "`||JCDenton[m]": "@jc.denton:unatco.gov" }); expect(action.text).toEqual("`||JCDenton[m], it's a bomb!"); - expect(action.htmlText).toEqual("`||JCDenton[m], it's a bomb!"); + expect(action.htmlText).toEqual( + "`||JCDenton[m], it's a bomb!" + ); }); it("should highlight multiple users", () => { - let action = new MatrixAction("message", "JCDenton is sent to assassinate PaulDenton", "JCDenton is sent to assassinate PaulDenton", null); + let action = new MatrixAction( + "message", + "JCDenton is sent to assassinate PaulDenton", + "JCDenton is sent to assassinate PaulDenton", + null + ); action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov", "PaulDenton": "@paul.denton:unatco.gov" @@ -51,14 +74,23 @@ describe("MatrixAction", function() { expect(action.text).toEqual("JCDenton is sent to assassinate PaulDenton"); expect(action.htmlText).toEqual( "JCDenton is sent" + - " to assassinate PaulDenton"); + " to assassinate PaulDenton" + ); }); it("should highlight multiple mentions of the same user", () => { - let action = new MatrixAction("message", "JCDenton, meet JCDenton", "JCDenton, meet JCDenton", null); + let action = new MatrixAction( + "message", + "JCDenton, meet JCDenton", + "JCDenton, meet JCDenton", + null + ); action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" }); expect(action.text).toEqual("JCDenton, meet JCDenton"); - expect(action.htmlText).toEqual("JCDenton, meet JCDenton"); + expect(action.htmlText).toEqual( + "JCDenton," + + " meet JCDenton" + ); }); }); From c46aa737d3425790fb219bccf715cdf6a96367c8 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 16:19:54 +0100 Subject: [PATCH 104/162] Constify requires --- lib/irc/IrcServer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/irc/IrcServer.js b/lib/irc/IrcServer.js index 42f5ced42..ed0eb9423 100644 --- a/lib/irc/IrcServer.js +++ b/lib/irc/IrcServer.js @@ -2,9 +2,9 @@ * Represents a single IRC server from config.yaml */ "use strict"; -var logging = require("../logging"); -var IrcClientConfig = require("../models/IrcClientConfig"); -var log = logging.get("IrcServer"); +const logging = require("../logging"); +const IrcClientConfig = require("../models/IrcClientConfig"); +const log = logging.get("IrcServer"); const GROUP_ID_REGEX = /^\+\S+:\S+$/; From 6d337e3b02ff81fde4b97215feca9cd7b410f622 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 16:20:13 +0100 Subject: [PATCH 105/162] Add static variables in BridgedClient --- lib/irc/BridgedClient.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/irc/BridgedClient.js b/lib/irc/BridgedClient.js index 7e7a96f25..016e10388 100644 --- a/lib/irc/BridgedClient.js +++ b/lib/irc/BridgedClient.js @@ -491,7 +491,7 @@ BridgedClient.prototype._getValidNick = function(nick, throwOnInvalid) { // strip illegal chars according to RFC 2812 Sect 2.3.1 - let n = nick.replace(/[^A-Za-z0-9\]\[\^\\\{\}\-`_\|]/g, ""); + let n = nick.replace(BridgedClient.illegalCharactersRegex, ""); if (throwOnInvalid && n !== nick) { throw new Error(`Nick '${nick}' contains illegal characters.`); } @@ -738,4 +738,6 @@ BridgedClient.prototype._joinChannel = function(channel, key, attemptCount) { return defer.promise; } +BridgedClient.illegalCharactersRegex = /[^A-Za-z0-9\]\[\^\\\{\}\-`_\|]/g; + module.exports = BridgedClient; From 77694edb57fd6677eaa678e4dc93171c588255e4 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 16:20:37 +0100 Subject: [PATCH 106/162] Strip invalid chars early in getNick() --- lib/irc/IrcServer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/irc/IrcServer.js b/lib/irc/IrcServer.js index ed0eb9423..6756a56d9 100644 --- a/lib/irc/IrcServer.js +++ b/lib/irc/IrcServer.js @@ -5,6 +5,7 @@ const logging = require("../logging"); const IrcClientConfig = require("../models/IrcClientConfig"); const log = logging.get("IrcServer"); +const BridgedClient = require("./BridgedClient"); const GROUP_ID_REGEX = /^\+\S+:\S+$/; @@ -435,6 +436,8 @@ IrcServer.prototype.getAliasFromChannel = function(channel) { IrcServer.prototype.getNick = function(userId, displayName) { var localpart = userId.substring(1).split(":")[0]; var display = displayName || localpart; + // Carefully replace any characters that will never be displayed. + display = BridgedClient.illegalCharactersRegex.replace(display, ""); var template = this.config.ircClients.nickTemplate; var nick = template.replace(/\$USERID/g, userId); nick = nick.replace(/\$LOCALPART/g, localpart); From 79067c4e13562cc3efe2af71ba6240ef35f86bbe Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 16:33:10 +0100 Subject: [PATCH 107/162] Add tests for getNick --- spec/unit/IrcServer.spec.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 spec/unit/IrcServer.spec.js diff --git a/spec/unit/IrcServer.spec.js b/spec/unit/IrcServer.spec.js new file mode 100644 index 000000000..126a506d5 --- /dev/null +++ b/spec/unit/IrcServer.spec.js @@ -0,0 +1,31 @@ +"use strict"; +const IrcServer = require("../../lib/irc/IrcServer"); +const extend = require("extend"); +describe("IrcServer", function() { + describe("getNick", function() { + it("should get a nick from a userid", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNick("@foobar:foobar")).toBe("M-foobar"); + }); + it("should get a nick from a displayname", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNick("@foobar:foobar", "wiggle")).toBe("M-wiggle"); + }); + it("should get a reduced nick if the displayname contains some invalid chars", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNick("@foobar:foobar", "💩wiggleケ")).toBe("M-wiggle"); + }); + it("should use userid if the displayname is all invalid chars", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNick("@foobar:foobar", "💩ケ")).toBe("M-foobar"); + }); + }); +}); From ee58f90a72a9334fe071a1600689eb78eea94303 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 16:33:23 +0100 Subject: [PATCH 108/162] Replace chars in both displayname and localpart --- lib/irc/IrcServer.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/irc/IrcServer.js b/lib/irc/IrcServer.js index 6756a56d9..3576d53f8 100644 --- a/lib/irc/IrcServer.js +++ b/lib/irc/IrcServer.js @@ -435,9 +435,12 @@ IrcServer.prototype.getAliasFromChannel = function(channel) { IrcServer.prototype.getNick = function(userId, displayName) { var localpart = userId.substring(1).split(":")[0]; + localpart = localpart.replace(BridgedClient.illegalCharactersRegex, ""); + if (displayName) { + // Carefully replace any characters that will never be displayed. + displayName = displayName.replace(BridgedClient.illegalCharactersRegex, ""); + } var display = displayName || localpart; - // Carefully replace any characters that will never be displayed. - display = BridgedClient.illegalCharactersRegex.replace(display, ""); var template = this.config.ircClients.nickTemplate; var nick = template.replace(/\$USERID/g, userId); nick = nick.replace(/\$LOCALPART/g, localpart); From 8751445961f2ed6f3f1d13c694d9b2416bf86850 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 16:44:22 +0100 Subject: [PATCH 109/162] More tests for invalid chars --- spec/unit/IrcServer.spec.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/spec/unit/IrcServer.spec.js b/spec/unit/IrcServer.spec.js index 126a506d5..6c96daf42 100644 --- a/spec/unit/IrcServer.spec.js +++ b/spec/unit/IrcServer.spec.js @@ -21,11 +21,30 @@ describe("IrcServer", function() { ); expect(server.getNick("@foobar:foobar", "💩wiggleケ")).toBe("M-wiggle"); }); - it("should use userid if the displayname is all invalid chars", function() { + it("should use localpart if the displayname is all invalid chars", function() { const server = new IrcServer("irc.foobar", extend(true, IrcServer.DEFAULT_CONFIG, {}) ); expect(server.getNick("@foobar:foobar", "💩ケ")).toBe("M-foobar"); }); + // These situations shouldn't happen, but we want to avoid rogue homeservers blowing us up. + it("should get a reduced nick if the localpart contains some invalid chars", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNick("@💩foobarケ:foobar")).toBe("M-foobar"); + }); + it("should use displayname if the localpart is all invalid chars", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(server.getNick("@💩ケ:foobar", "wiggle")).toBe("M-wiggle"); + }); + it("should throw if no characters could be used", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(() => {server.getNick("@💩ケ:foobar", "💩ケ")}).toThrow(); + }); }); }); From 16c8e663fba1377709a9c38a6bb1d675c1fa338b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 16:48:04 +0100 Subject: [PATCH 110/162] Cleanup code --- lib/irc/IrcServer.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/irc/IrcServer.js b/lib/irc/IrcServer.js index 3576d53f8..58cc0c700 100644 --- a/lib/irc/IrcServer.js +++ b/lib/irc/IrcServer.js @@ -434,15 +434,16 @@ IrcServer.prototype.getAliasFromChannel = function(channel) { }; IrcServer.prototype.getNick = function(userId, displayName) { - var localpart = userId.substring(1).split(":")[0]; - localpart = localpart.replace(BridgedClient.illegalCharactersRegex, ""); - if (displayName) { - // Carefully replace any characters that will never be displayed. - displayName = displayName.replace(BridgedClient.illegalCharactersRegex, ""); + const illegalChars = BridgedClient.illegalCharactersRegex; + let localpart = userId.substring(1).split(":")[0]; + localpart = localpart.replace(illegalChars, ""); + displayName = displayName ? displayName.replace(illegalChars, "") : undefined; + let display = [displayName, localpart].find((n) => Boolean(n)); + if (!display) { + throw new Error("Could not get nick for user, all characters were invalid"); } - var display = displayName || localpart; - var template = this.config.ircClients.nickTemplate; - var nick = template.replace(/\$USERID/g, userId); + const template = this.config.ircClients.nickTemplate; + let nick = template.replace(/\$USERID/g, userId); nick = nick.replace(/\$LOCALPART/g, localpart); nick = nick.replace(/\$DISPLAY/g, display); return nick; From 91eef352179dc626d60e2b52ce9427ec88d5d156 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 16:53:31 +0100 Subject: [PATCH 111/162] Log nick change failures due to an invalid nickname --- lib/bridge/MatrixHandler.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index cad20a3fd..cf7ba123a 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -904,9 +904,15 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) if (room.server.allowsNickChanges() && config.getDesiredNick() === null ) { - bridgedClient.changeNick( - room.server.getNick(bridgedClient.userId, event.content.displayname), - false); + try { + const newNick = room.server.getNick( + bridgedClient.userId, event.content.displayname + ); + bridgedClient.changeNick(newNick, false); + } + catch (e) { + req.log.warn(`Didn't change nick on the IRC side:${e}`); + } } } From acf50d90f8515241c4333b51e85c448722dd0129 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 17:01:36 +0100 Subject: [PATCH 112/162] Feedback bits and bobs --- lib/bridge/MatrixHandler.js | 2 +- lib/irc/IrcServer.js | 2 +- spec/unit/IrcServer.spec.js | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index cf7ba123a..bf58cb460 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -911,7 +911,7 @@ MatrixHandler.prototype._onJoin = Promise.coroutine(function*(req, event, user) bridgedClient.changeNick(newNick, false); } catch (e) { - req.log.warn(`Didn't change nick on the IRC side:${e}`); + req.log.warn(`Didn't change nick on the IRC side: ${e}`); } } } diff --git a/lib/irc/IrcServer.js b/lib/irc/IrcServer.js index 58cc0c700..88644b557 100644 --- a/lib/irc/IrcServer.js +++ b/lib/irc/IrcServer.js @@ -438,7 +438,7 @@ IrcServer.prototype.getNick = function(userId, displayName) { let localpart = userId.substring(1).split(":")[0]; localpart = localpart.replace(illegalChars, ""); displayName = displayName ? displayName.replace(illegalChars, "") : undefined; - let display = [displayName, localpart].find((n) => Boolean(n)); + const display = [displayName, localpart].find((n) => Boolean(n)); if (!display) { throw new Error("Could not get nick for user, all characters were invalid"); } diff --git a/spec/unit/IrcServer.spec.js b/spec/unit/IrcServer.spec.js index 6c96daf42..ab359b3ae 100644 --- a/spec/unit/IrcServer.spec.js +++ b/spec/unit/IrcServer.spec.js @@ -40,11 +40,17 @@ describe("IrcServer", function() { ); expect(server.getNick("@💩ケ:foobar", "wiggle")).toBe("M-wiggle"); }); - it("should throw if no characters could be used", function() { + it("should throw if no characters could be used, with displayname", function() { const server = new IrcServer("irc.foobar", extend(true, IrcServer.DEFAULT_CONFIG, {}) ); expect(() => {server.getNick("@💩ケ:foobar", "💩ケ")}).toThrow(); }); + it("should throw if no characters could be used, with displayname", function() { + const server = new IrcServer("irc.foobar", + extend(true, IrcServer.DEFAULT_CONFIG, {}) + ); + expect(() => {server.getNick("@💩ケ:foobar")}).toThrow(); + }); }); }); From 8fc16ea476485ccb9deae0a08f91a28f2ce77a9b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 17:31:24 +0100 Subject: [PATCH 113/162] Misc travis happy making --- config.sample.yaml | 2 +- lib/bridge/MatrixHandler.js | 10 ++++------ spec/integ/matrix-to-irc.spec.js | 8 ++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/config.sample.yaml b/config.sample.yaml index 6f846326a..499ade0b2 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -431,5 +431,5 @@ ircService: # Config for Matrix -> IRC bridging matrixHandler: - # Cache this many matrix events in memory to be used for m.relates_to messages. + # Cache this many matrix events in memory to be used for m.relates_to messages (usually replies). eventCacheSize: 4096 \ No newline at end of file diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index 7a464031c..147924a50 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1284,9 +1284,7 @@ MatrixHandler.prototype._sendIrcAction = Promise.coroutine( sender: event.sender }); - /** - * Cache events in here so we can refer to them for replies. - */ + // Cache events in here so we can refer to them for replies. if (this._eventCache.size > this._eventCacheMaxSize) { const delKey = this._eventCache.entries().next().value[0]; this._eventCache.delete(delKey); @@ -1571,11 +1569,11 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, // Cut to a maximum length. rplSource = lines[0].substr(0, REPLY_SOURCE_MAX_LENGTH); // Ellipsis if needed. - if (rplSource.length > REPLY_SOURCE_MAX_LENGTH) { + if (lines[0].length > REPLY_SOURCE_MAX_LENGTH) { rplSource = rplSource + "..."; } // Wrap in formatting - rplSource = ` "${lines[0]}"`; + rplSource = ` "${rplSource}"`; } else { // Don't show a source because we couldn't format one. @@ -1588,7 +1586,7 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, rplName = sourceClient.nick; } else { - // Somehow we failed, so fallback to userid. + // Somehow we failed, so fallback to localpart. rplName = rplName.substr(1, Math.min(REPLY_NAME_MAX_LENGTH, rplName.indexOf(":") - 1) ); diff --git a/spec/integ/matrix-to-irc.spec.js b/spec/integ/matrix-to-irc.spec.js index 04ac2437b..c28750b2e 100644 --- a/spec/integ/matrix-to-irc.spec.js +++ b/spec/integ/matrix-to-irc.spec.js @@ -307,7 +307,7 @@ describe("Matrix-to-IRC message bridging", function() { msgtype: "m.text", "m.relates_to": { "m.in_reply_to": { - "event_id": "$original:bar.com" + "event_id": "$original:bar.com" } }, }, @@ -334,7 +334,7 @@ describe("Matrix-to-IRC message bridging", function() { msgtype: "m.text", "m.relates_to": { "m.in_reply_to": { - "event_id": "$original:bar.com" + "event_id": "$original:bar.com" } }, }, @@ -362,7 +362,7 @@ describe("Matrix-to-IRC message bridging", function() { msgtype: "m.text", "m.relates_to": { "m.in_reply_to": { - "event_id": "$first:bar.com" + "event_id": "$first:bar.com" } }, }, @@ -388,7 +388,7 @@ describe("Matrix-to-IRC message bridging", function() { msgtype: "m.text", "m.relates_to": { "m.in_reply_to": { - "event_id": "$second:bar.com" + "event_id": "$second:bar.com" } }, }, From 6644ed602dc4b279e02864b25bceadb9c11ec320 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Aug 2018 17:47:44 +0100 Subject: [PATCH 114/162] Include formatted html for tests --- spec/integ/matrix-to-irc.spec.js | 46 ++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/spec/integ/matrix-to-irc.spec.js b/spec/integ/matrix-to-irc.spec.js index c28750b2e..c0531cb61 100644 --- a/spec/integ/matrix-to-irc.spec.js +++ b/spec/integ/matrix-to-irc.spec.js @@ -261,10 +261,16 @@ describe("Matrix-to-IRC message bridging", function() { expect(text).toEqual(' Reply Text'); done(); }); - + const formatted_body = constructHTMLReply( + "This is the fake message", + "@somedude:bar.com", + "Reply text" + ); env.mockAppService._trigger("type:m.room.message", { content: { body: "> <@somedude:bar.com> This is the fake message\n\nReply Text", + formatted_body, + format: "org.matrix.custom.html", msgtype: "m.text", "m.relates_to": { "m.in_reply_to": { @@ -300,10 +306,16 @@ describe("Matrix-to-IRC message bridging", function() { expect(text).toEqual(' Reply Text'); done(); }); - + const formatted_body = constructHTMLReply( + "This is the fake message", + "@somedude:bar.com", + "Reply text" + ); env.mockAppService._trigger("type:m.room.message", { content: { body: "> <@somedude:bar.com> This is the fake message\n\nReply Text", + formatted_body, + format: "org.matrix.custom.html", msgtype: "m.text", "m.relates_to": { "m.in_reply_to": { @@ -327,17 +339,25 @@ describe("Matrix-to-IRC message bridging", function() { expect(text).toEqual('Reply Text'); done(); }); + const formatted_body = constructHTMLReply( + "This message is possibly fake", + "@somedude:bar.com", + "Reply Text" + ); env.mockAppService._trigger("type:m.room.message", { content: { body: "> <@somedude:bar.com> This message is possibly fake\n\nReply Text", msgtype: "m.text", + formatted_body, + format: "org.matrix.custom.html", "m.relates_to": { "m.in_reply_to": { "event_id": "$original:bar.com" } }, }, + formatted_body, user_id: testUser.id, room_id: roomMapping.roomId, type: "m.room.message" @@ -345,6 +365,7 @@ describe("Matrix-to-IRC message bridging", function() { }); it("should bridge matrix replies to replies without the original source", function(done) { + let formatted_body; env.mockAppService._trigger("type:m.room.message", { content: { body: "Message #1", @@ -356,9 +377,16 @@ describe("Matrix-to-IRC message bridging", function() { event_id: "$first:bar.com", type: "m.room.message" }).then(() => { + formatted_body = constructHTMLReply( + "Message #1", + "@somedude:bar.com", + "Message #2" + ); return env.mockAppService._trigger("type:m.room.message", { content: { body: "> <@friend:bar.com> Message#1\n\nMessage #2", + formatted_body, + format: "org.matrix.custom.html", msgtype: "m.text", "m.relates_to": { "m.in_reply_to": { @@ -373,6 +401,11 @@ describe("Matrix-to-IRC message bridging", function() { type: "m.room.message" }); }).then(() => { + formatted_body = constructHTMLReply( + "Message #2", + "@somedude:bar.com", + "Message #3" + ); env.ircMock._whenClient(roomMapping.server, testUser.nick, "say", function(client, channel, text) { expect(client.nick).toEqual(testUser.nick); @@ -385,6 +418,8 @@ describe("Matrix-to-IRC message bridging", function() { env.mockAppService._trigger("type:m.room.message", { content: { body: "> <@friend:bar.com> Message#2\n\nMessage #3", + formatted_body, + format: "org.matrix.custom.html", msgtype: "m.text", "m.relates_to": { "m.in_reply_to": { @@ -815,3 +850,10 @@ describe("Matrix-to-IRC message bridging with media URL and drop time", function }); }); }); + +function constructHTMLReply(sourceText, sourceUser, reply) { + // This is one hella ugly format. + return "
In reply to" + + `${sourceUser}

${sourceText}

${reply}`; +} From de8d237489d3675ce81e5e6fb1f550ce97a54e0c Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 21 Aug 2018 13:39:51 +0100 Subject: [PATCH 115/162] Pin matrix-appservice-bridge dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c1c916c8d..9ef41e7d7 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "extend": "^2.0.0", "irc": "matrix-org/node-irc#c9abb427bec5016d94a2abf3e058cc62de09ea5a", "js-yaml": "^3.2.7", - "matrix-appservice-bridge": "1.5.0a", + "matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#b72744cf014fafae4a72dfffe4d290b3f1d44cdf", "nedb": "^1.1.2", "nopt": "^3.0.1", "prom-client": "^6.3.0", From c5f5278dfafa677677939eb0d90136bcf0ea2605 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 21 Aug 2018 14:23:10 +0100 Subject: [PATCH 116/162] Pin matrix-appservice-bridge for Intent.getEvent --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ef41e7d7..375b65637 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "extend": "^2.0.0", "irc": "matrix-org/node-irc#c9abb427bec5016d94a2abf3e058cc62de09ea5a", "js-yaml": "^3.2.7", - "matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#b72744cf014fafae4a72dfffe4d290b3f1d44cdf", + "matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#4cba3711ef0fad357a547e0eaca982d652c46f24", "nedb": "^1.1.2", "nopt": "^3.0.1", "prom-client": "^6.3.0", From e6facc5c34465b8d462c1ab11b4b65560d7a73eb Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 21 Aug 2018 16:29:29 +0100 Subject: [PATCH 117/162] Counters are incremental --- lib/bridge/MatrixHandler.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index ab215b657..c3bede40d 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1484,6 +1484,12 @@ MatrixHandler.prototype.onUserQuery = function(req, userId) { return reqHandler(req, this._onUserQuery(req, userId)) }; +MatrixHandler.prototype.getMetrics = function(serverDomain) { + const metrics = this.metrics[serverDomain] || {}; + this.metrics[serverDomain] = {} + return metrics || {}; +} + function reqHandler(req, promise) { return promise.then(function(res) { req.resolve(res); From 4dad73c2bb5bd0dd1d77a6611e5633421d1b281b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 09:46:05 +0100 Subject: [PATCH 118/162] We probably don't need to promise wrap this. --- lib/irc/ClientPool.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/irc/ClientPool.js b/lib/irc/ClientPool.js index d9908e9ca..e6de31640 100644 --- a/lib/irc/ClientPool.js +++ b/lib/irc/ClientPool.js @@ -248,13 +248,7 @@ ClientPool.prototype.totalReconnectsWaiting = function (serverDomain) { return 0; }; -ClientPool.prototype.getIdleConnectionMetrics = function() { - return new Promise((resolve) => { - resolve(this._calculateIdleConnectionMetrics()); - }); -}; - -ClientPool.prototype._calculateIdleConnectionMetrics = function(server) { +ClientPool.prototype.getIdleConnectionMetrics = function(server) { const SEVEN_DAYS_MS = 1000 * 60 * 60 * 24 * 7; const periodBucketTimes = { "7d": Date.now() - SEVEN_DAYS_MS, From f8134b0acea4ee9c4dead8b80f6c79fd64981d87 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 09:46:26 +0100 Subject: [PATCH 119/162] for loop -> Object.keys(...).forEach --- lib/bridge/IrcBridge.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bridge/IrcBridge.js b/lib/bridge/IrcBridge.js index 86fe95e2c..1a1835626 100644 --- a/lib/bridge/IrcBridge.js +++ b/lib/bridge/IrcBridge.js @@ -193,9 +193,9 @@ IrcBridge.prototype._initialiseMetrics = function() { this._clientPool.totalReconnectsWaiting(server.domain) ); this._clientPool.getIdleConnectionMetrics(server.domain).then((idleBuckets) => { - for (const bucket in idleBuckets) { + Object.keys(idleBuckets).forEach((bucket) => { idleConnections.set({server: server.domain, period: bucket}, idleBuckets[bucket]); - } + }); }); }); From 9d31c0a4190c7c29fabebada2a738e42d6460eb6 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 12:43:01 +0100 Subject: [PATCH 120/162] Pin matrix-appservice-bridge for AgeCounters --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e984e230a..cb1c07bce 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "extend": "^2.0.0", "irc": "matrix-org/node-irc#b1614bc784200c65247784d7b9e9ab867140412d", "js-yaml": "^3.2.7", - "matrix-appservice-bridge": "1.5.0a", + "matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#89d5e0763f1d19be55fbabbc9d021560c1332b54", "nedb": "^1.1.2", "nopt": "^3.0.1", "prom-client": "^6.3.0", From 9871d63caa049193209d53f77ab355dd600215f4 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 12:43:19 +0100 Subject: [PATCH 121/162] Removing own bucket impl in favour of ageCounter --- lib/irc/ClientPool.js | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/lib/irc/ClientPool.js b/lib/irc/ClientPool.js index e6de31640..0ceebd67c 100644 --- a/lib/irc/ClientPool.js +++ b/lib/irc/ClientPool.js @@ -248,34 +248,14 @@ ClientPool.prototype.totalReconnectsWaiting = function (serverDomain) { return 0; }; -ClientPool.prototype.getIdleConnectionMetrics = function(server) { - const SEVEN_DAYS_MS = 1000 * 60 * 60 * 24 * 7; - const periodBucketTimes = { - "7d": Date.now() - SEVEN_DAYS_MS, - "14d": Date.now() - SEVEN_DAYS_MS*2, - "21d": Date.now() - SEVEN_DAYS_MS*3, - }; - - const _idleConnectionMetrics = { - "7d": 0, - "14d": 0, - "21d": 0, - }; +ClientPool.prototype.updateActiveConnectionMetrics = function(server, ageCounter) { const clients = Object.values(this._virtualClients[server].userIds); - for (const bridgedClient of clients) { - const lastActionMS = bridgedClient.getLastActionTs(); - let bucket = null; - for (const periodBucket in periodBucketTimes) { - if (lastActionMS > periodBucketTimes[periodBucket]) { - break; - } - bucket = periodBucket; + clients.forEach((bridgedClient) => { + if (bridgedClient.isDead()) { + return; // We don't want to include dead ones. } - if (bucket !== null) { - _idleConnectionMetrics[bucket]++; - } - } - return _idleConnectionMetrics; + ageCounter.bump((Date.now() - bridgedClient.getLastActionTs()) / 1000); + }); }; ClientPool.prototype._sendConnectionMetric = function(server) { From 5cffd1173489720e4c11408b1e2c4d34d0aa394f Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 12:43:43 +0100 Subject: [PATCH 122/162] Use remoteUsersByAge rather than a custom metric --- lib/bridge/IrcBridge.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/bridge/IrcBridge.js b/lib/bridge/IrcBridge.js index 1a1835626..93a7ddfda 100644 --- a/lib/bridge/IrcBridge.js +++ b/lib/bridge/IrcBridge.js @@ -125,6 +125,14 @@ IrcBridge.prototype._initialiseMetrics = function() { const metrics = this._bridge.getPrometheusMetrics(); this._bridge.registerBridgeGauges(() => { + const remoteUsersByAge = new AgeCounters( + this.config.ircService.metrics.remoteUserAgeBuckets || ["1h", "1d", "1w"] + ); + + this.ircServers.forEach((server) => { + this._clientPool.updateActiveConnectionMetrics(server.domain, remoteUsersByAge); + }); + return { // TODO(paul): actually fill these in matrixRoomConfigs: 0, @@ -140,7 +148,7 @@ IrcBridge.prototype._initialiseMetrics = function() { remoteRoomsByAge: zeroAge, matrixUsersByAge: zeroAge, - remoteUsersByAge: zeroAge, + remoteUsersByAge, }; }); @@ -164,12 +172,6 @@ IrcBridge.prototype._initialiseMetrics = function() { labels: ["server"] }); - const idleConnections = metrics.addGauge({ - name: "clientpool_idle_connections", - help: "Idle connections on the bridge, grouped into period buckets.", - labels: ["server", "period"] - }); - const memberListLeaveQueue = metrics.addGauge({ name: "user_leave_queue", help: "Number of leave requests queued up for virtual users on the bridge.", @@ -192,11 +194,6 @@ IrcBridge.prototype._initialiseMetrics = function() { reconnQueue.set({server: server.domain}, this._clientPool.totalReconnectsWaiting(server.domain) ); - this._clientPool.getIdleConnectionMetrics(server.domain).then((idleBuckets) => { - Object.keys(idleBuckets).forEach((bucket) => { - idleConnections.set({server: server.domain, period: bucket}, idleBuckets[bucket]); - }); - }); }); Object.keys(this.memberListSyncers).forEach((server) => { From 7bb27a58e89d07b96434035df1ad4f0fbc4a2e2d Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 12:50:22 +0100 Subject: [PATCH 123/162] Add config option for age buckets --- config.sample.yaml | 11 +++++++++-- lib/config/schema.yml | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/config.sample.yaml b/config.sample.yaml index ce7935990..cfcf60571 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -377,8 +377,15 @@ ircService: # Optional. Enable Prometheus metrics. If this is enabled, you MUST install `prom-client`: # $ npm install prom-client@6.3.0 # Metrics will then be available via GET /metrics on the bridge listening port (-p). - # metrics: - # enabled: true + metrics: + # Whether to actually enable the metric endpoint. Default: false + enabled: true + # When collecting remote user active times, which "buckets" should be used. Defaults are given below. + # The bucket name is formed of a duration and a period. (h=hours,d=days,w=weeks). + remoteUserAgeBuckets: + - "1h" + - "1d" + - "1w" # The nedb database URI to connect to. This is the name of the directory to # dump .db files to. This is relative to the project directory. diff --git a/lib/config/schema.yml b/lib/config/schema.yml index 36eff21e9..be67c9f88 100644 --- a/lib/config/schema.yml +++ b/lib/config/schema.yml @@ -25,6 +25,11 @@ properties: properties: enabled: type: "boolean" + remoteUserAgeBuckets: + type: "array" + items: + type: "string" + pattern: "^[0-9]+(h|d|w)$" statsd: type: "object" properties: From 4db49e15267aecc84554beff1eac8bedf030844e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 13:56:28 +0100 Subject: [PATCH 124/162] ; please --- spec/unit/QueuePool.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/QueuePool.spec.js b/spec/unit/QueuePool.spec.js index 23cac283e..2b5537364 100644 --- a/spec/unit/QueuePool.spec.js +++ b/spec/unit/QueuePool.spec.js @@ -34,7 +34,7 @@ describe("QueuePool", function() { procFn.and.callFake((item) => { itemToDeferMap[item] = new promiseutil.defer(); return itemToDeferMap[item].promise; - }) + }); }); it("should let multiple items be processed at once", From e3e6426f263119ca62461d913cd0f5407c532ea5 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 13:56:39 +0100 Subject: [PATCH 125/162] Add test for waitingItems --- spec/unit/QueuePool.spec.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/unit/QueuePool.spec.js b/spec/unit/QueuePool.spec.js index 2b5537364..e2122d21f 100644 --- a/spec/unit/QueuePool.spec.js +++ b/spec/unit/QueuePool.spec.js @@ -135,4 +135,16 @@ describe("QueuePool", function() { yield nextTick(); expect(Object.keys(itemToDeferMap).sort()).toEqual(["b"]); })); + + it("should accurately track waiting items", test.coroutine(function*() { + for (let i = 0;i<10;i++) { + pool.enqueue(i, i); + } + expect(pool.waitingItems).toEqual(7); + for (let j = 0; j < 10; j++) { + yield nextTick(); + resolveItem(j); + } + expect(pool.waitingItems).toEqual(0); + })); }); From 3bd63f68ee14229302d440f014a56a491c181e05 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 13:56:55 +0100 Subject: [PATCH 126/162] Use the queues size rather than a overflow integer --- lib/util/QueuePool.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/util/QueuePool.js b/lib/util/QueuePool.js index 7d100370b..1c54735ad 100644 --- a/lib/util/QueuePool.js +++ b/lib/util/QueuePool.js @@ -28,7 +28,7 @@ class QueuePool { } // Get number of items waiting to be inserted into a queue. - get waitingItems() { return this._overflowCount; } + get waitingItems() { return this.overflow.size(); } // Add an item to the queue. ID and item are passed directly to the Queue. // Index is optional and should be between 0 ~ poolSize-1. It determines @@ -54,7 +54,6 @@ class QueuePool { // onto the queue pool. We want to return a promise which resolves // after the item has finished executing on the queue pool, hence // the promise chain here. - this._overflowCount++; return this.overflow.enqueue(id, { id: id, item: item, @@ -78,7 +77,6 @@ class QueuePool { return q.onceFree(); }); return Promise.any(promises).then((q) => { - this._overflowCount--; if (q.size() !== 0) { throw new Error(`QueuePool overflow: starvation. No free queues.`); } From abc5ed8c8a8cb47b43fca616183fd2e14489d868 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 13:57:15 +0100 Subject: [PATCH 127/162] Ensure that Queue size is correct --- spec/unit/Queue.spec.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/unit/Queue.spec.js b/spec/unit/Queue.spec.js index 80b35ed0f..612ae794d 100644 --- a/spec/unit/Queue.spec.js +++ b/spec/unit/Queue.spec.js @@ -115,4 +115,25 @@ describe("Queue", function() { done(); }); }); + + it("should have the correct size.", (done) => { + var thing1 = { foo: "bar"}; + var thing2 = { bar: "baz"}; + var things = [thing1, thing2]; + let expectedSize = things.length; + procFn.and.callFake((thing) => { + things.shift(); + expect(queue.size()).toEqual(expectedSize); + if (things.length === 0) { + done(); + } + expectedSize--; + return Promise.resolve(); + }); + expect(queue.size()).toEqual(0); + queue.enqueue("id1", thing1); + expect(queue.size()).toEqual(1); + queue.enqueue("id2", thing2); + expect(queue.size()).toEqual(2); + }); }); From d915d360c326b7b5ca81f8f0d8819605d019c3a2 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 14:22:06 +0100 Subject: [PATCH 128/162] Split out ci script to it's own file. And only run coverage on node 10 --- .travis.yml | 4 ++-- travis-ci.sh | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100755 travis-ci.sh diff --git a/.travis.yml b/.travis.yml index 47b502081..82f74f741 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,12 +4,12 @@ node_js: - "8" - "10" install: npm install -script: let "n = 0";npm run lint; let "n = n + $?";npm run ci-test; let "n = n + $?";(exit $n) +script: ./travis-ci.sh notifications: webhooks: urls: - "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGtlZ2FuJTNBbWF0cml4Lm9yZy8lMjFTdWR2aU9KbGltRHZyR2RGQ1klM0FtYXRyaXgub3Jn" on_success: change # always|never|change - on_failure: always + on_failure: change on_start: never diff --git a/travis-ci.sh b/travis-ci.sh new file mode 100755 index 000000000..87a58f3d5 --- /dev/null +++ b/travis-ci.sh @@ -0,0 +1,18 @@ +#!/bin/bash +export BLUEBIRD_DEBUG=1 +let "n = 0"; +npm run lint; +let "n = n + $?"; + +case "$(node --version)" in + v10*) + echo "Running istanbul because our node version is supported"; + npm run ci-test;; + *) + echo "Not running istanbul because our node version is too old"; + npm run test;; +esac + +#npm run ci-test; +let "n = n + $?"; +(exit $n) From d9bdf0cdd09101c82da1dcb670ce385a54c242b9 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 14:47:33 +0100 Subject: [PATCH 129/162] Seperate build steps for TravisCI --- .travis.yml | 12 +++++++++++- travis-ci.sh | 18 ------------------ 2 files changed, 11 insertions(+), 19 deletions(-) delete mode 100755 travis-ci.sh diff --git a/.travis.yml b/.travis.yml index 82f74f741..dece1dbab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,17 @@ node_js: - "8" - "10" install: npm install -script: ./travis-ci.sh + +jobs: + include: + - stage: linting + node_js: "10" + script: npm run lint + - stage: tests + script: npm run test + - stage: coverage + script: npm run ci-test + node_js: "10" notifications: webhooks: diff --git a/travis-ci.sh b/travis-ci.sh deleted file mode 100755 index 87a58f3d5..000000000 --- a/travis-ci.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -export BLUEBIRD_DEBUG=1 -let "n = 0"; -npm run lint; -let "n = n + $?"; - -case "$(node --version)" in - v10*) - echo "Running istanbul because our node version is supported"; - npm run ci-test;; - *) - echo "Not running istanbul because our node version is too old"; - npm run test;; -esac - -#npm run ci-test; -let "n = n + $?"; -(exit $n) From 52c6197a865c97bb8155d83276f2d96d38521ee8 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 14:49:14 +0100 Subject: [PATCH 130/162] Fix travis node version --- .travis.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index dece1dbab..c34ab8b9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,5 @@ language: node_js -node_js: - - "6" - - "8" - - "10" + install: npm install jobs: @@ -12,6 +9,10 @@ jobs: script: npm run lint - stage: tests script: npm run test + node_js: + - "6" + - "8" + - "10" - stage: coverage script: npm run ci-test node_js: "10" From 93234667dc68f1f8c04e7400fcaaccda40d03957 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 14:54:55 +0100 Subject: [PATCH 131/162] Add node module caching --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c34ab8b9f..db22c2c1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: node_js - +cache: + directories: + - "node_modules" install: npm install jobs: From 798b404e05682b21abb643843e96749b5a07e348 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 15:05:25 +0100 Subject: [PATCH 132/162] Run tests on node 6 and 8 --- .travis.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index db22c2c1a..54daf9ade 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: node_js -cache: - directories: - - "node_modules" +node_js: + - "6" + - "8" + - "10" install: npm install jobs: @@ -9,12 +10,12 @@ jobs: - stage: linting node_js: "10" script: npm run lint + - stage: tests script: npm run test - node_js: - - "6" - - "8" - - "10" + node_js: "6" + - node_js: "8" + - stage: coverage script: npm run ci-test node_js: "10" From c9c5b00d8540635d79844c7f2fd426c988544f27 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 15:07:42 +0100 Subject: [PATCH 133/162] Chuck away test stage --- .travis.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 54daf9ade..6f75fae78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,13 @@ node_js: - "8" - "10" install: npm install +script: npm run test jobs: include: - stage: linting node_js: "10" script: npm run lint - - - stage: tests - script: npm run test - node_js: "6" - - node_js: "8" - - stage: coverage script: npm run ci-test node_js: "10" From be44ad450f6bf7ebfa249f5de26f86f1526dfd65 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 15:23:39 +0100 Subject: [PATCH 134/162] Parallel tests --- .travis.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6f75fae78..b061103f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,15 +4,21 @@ node_js: - "8" - "10" install: npm install -script: npm run test jobs: include: - - stage: linting + - stage: tests + name: "Unit + Integration" + script: npm run test + node_js: "6" + - node_js: "8" + - node_js: "10" + + - script: npm run lint + name: "Linting" node_js: "10" - script: npm run lint - - stage: coverage - script: npm run ci-test + - script: npm run ci-test + name: "Coverage" node_js: "10" notifications: From 8cdc5d7ef24d2270363200d586b9c34a10d2a7d8 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 15:25:37 +0100 Subject: [PATCH 135/162] Name tests --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index b061103f7..b75b99bd3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,18 @@ language: node_js -node_js: - - "6" - - "8" - - "10" install: npm install jobs: include: - stage: tests - name: "Unit + Integration" + name: "Unit + Integration Node 6" script: npm run test node_js: "6" - - node_js: "8" - - node_js: "10" + - script: npm run test + name: "Unit + Integration Node 8" + node_js: "8" + - script: npm run test + name: "Unit + Integration Node 10" + node_js: "10" - script: npm run lint name: "Linting" From 671af7166155375663b7892e49cf9caa3d3cf11f Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 16:49:16 +0100 Subject: [PATCH 136/162] Desquish and remove the . --- spec/unit/Queue.spec.js | 8 ++++---- spec/unit/QueuePool.spec.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/unit/Queue.spec.js b/spec/unit/Queue.spec.js index 612ae794d..a55b8a371 100644 --- a/spec/unit/Queue.spec.js +++ b/spec/unit/Queue.spec.js @@ -116,10 +116,10 @@ describe("Queue", function() { }); }); - it("should have the correct size.", (done) => { - var thing1 = { foo: "bar"}; - var thing2 = { bar: "baz"}; - var things = [thing1, thing2]; + it("should have the correct size", (done) => { + const thing1 = { foo: "bar"}; + const thing2 = { bar: "baz"}; + const things = [thing1, thing2]; let expectedSize = things.length; procFn.and.callFake((thing) => { things.shift(); diff --git a/spec/unit/QueuePool.spec.js b/spec/unit/QueuePool.spec.js index e2122d21f..9a5e38c3f 100644 --- a/spec/unit/QueuePool.spec.js +++ b/spec/unit/QueuePool.spec.js @@ -137,7 +137,7 @@ describe("QueuePool", function() { })); it("should accurately track waiting items", test.coroutine(function*() { - for (let i = 0;i<10;i++) { + for (let i = 0; i < 10; i++) { pool.enqueue(i, i); } expect(pool.waitingItems).toEqual(7); From 8164cadee22a15596538eb1d2f799a149330d9aa Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 18:42:02 +0100 Subject: [PATCH 137/162] bump appservice-bridge version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8dce00dc..7c97b2172 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "extend": "^2.0.0", "irc": "matrix-org/node-irc#c9abb427bec5016d94a2abf3e058cc62de09ea5a", "js-yaml": "^3.2.7", - "matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#89d5e0763f1d19be55fbabbc9d021560c1332b54", + "matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#4175ff0bd2b0626402d8992deb3af78ba45a7a41", "nedb": "^1.1.2", "nopt": "^3.0.1", "prom-client": "^6.3.0", From 77903771f2b9490221c98c82c52f29fc4a1fc572 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 18:56:51 +0100 Subject: [PATCH 138/162] Use localpart in plaintext so we get highlighted --- lib/models/MatrixAction.js | 8 ++++++++ spec/unit/MatrixAction.spec.js | 12 ++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 6eddc6346..4b4f5aee4 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -64,10 +64,18 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { this.htmlText = this.text; } userId = ircFormatting.escapeHtmlChars(userId); + /* Due to how Riot and friends do push notifications, we need the plain text to match something. + Modern client's will pill-ify and will show a displayname, so we can use the localpart so it + matches and doesn't degrade too badly on limited clients.*/ + const localpart = userId.substr(1, userId.indexOf(":")-1); this.htmlText = this.htmlText.replace( new RegExp(escapeStringRegexp(matchName), "igm"), `${ircFormatting.escapeHtmlChars(matchName)}` ); + this.text = this.text.replace( + new RegExp(escapeStringRegexp(matchName), "igm"), + localpart + ); // Don't match this name twice, we've already replaced all entries. matched.add(matchName.toLowerCase()); } diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index b771b4a6b..2c6d91a5b 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -20,7 +20,7 @@ describe("MatrixAction", function() { action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" }); - expect(action.text).toEqual("JCDenton, it's a bomb!"); + expect(action.text).toEqual("jc.denton, it's a bomb!"); expect(action.htmlText).toEqual( "JCDenton, it's a bomb!" ); @@ -35,7 +35,7 @@ describe("MatrixAction", function() { action.formatMentions({ "jcdenton": "@jc.denton:unatco.gov" }); - expect(action.text).toEqual("JCDenton, it's a bomb!"); + expect(action.text).toEqual("jc.denton, it's a bomb!"); expect(action.htmlText).toEqual( "jcdenton, it's a bomb!" ); @@ -45,7 +45,7 @@ describe("MatrixAction", function() { action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" }); - expect(action.text).toEqual("JCDenton, it's a bomb!"); + expect(action.text).toEqual("jc.denton, it's a bomb!"); expect(action.htmlText).toEqual( "JCDenton, it's a bomb!" ); @@ -55,7 +55,7 @@ describe("MatrixAction", function() { action.formatMentions({ "`||JCDenton[m]": "@jc.denton:unatco.gov" }); - expect(action.text).toEqual("`||JCDenton[m], it's a bomb!"); + expect(action.text).toEqual("jc.denton, it's a bomb!"); expect(action.htmlText).toEqual( "`||JCDenton[m], it's a bomb!" ); @@ -71,7 +71,7 @@ describe("MatrixAction", function() { "JCDenton": "@jc.denton:unatco.gov", "PaulDenton": "@paul.denton:unatco.gov" }); - expect(action.text).toEqual("JCDenton is sent to assassinate PaulDenton"); + expect(action.text).toEqual("jc.denton is sent to assassinate paul.denton"); expect(action.htmlText).toEqual( "JCDenton is sent" + " to assassinate PaulDenton" @@ -87,7 +87,7 @@ describe("MatrixAction", function() { action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" }); - expect(action.text).toEqual("JCDenton, meet JCDenton"); + expect(action.text).toEqual("jc.denton, meet jc.denton"); expect(action.htmlText).toEqual( "JCDenton," + " meet JCDenton" From f83041517aa143eec2505a87f6236af767ad37a6 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 19:18:31 +0100 Subject: [PATCH 139/162] Use a better regex so we don't catch URLs --- lib/models/MatrixAction.js | 24 ++++++++++++++---------- spec/unit/MatrixAction.spec.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 4b4f5aee4..34c87a516 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -35,11 +35,11 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { const regexString = "(" + Object.keys(nickUserIdMap).map((value) => escapeStringRegexp(value)).join("|") + ")"; - const userRegex = new RegExp(regexString, "igm"); + const usersRegex = MentionRegex(regexString); const matched = new Set(); // lowercased nicknames we have matched already. let match; - for (let i = 0; i < MAX_MATCHES && (match = userRegex.exec(this.text)) !== null; i++) { - let matchName = match[1]; + for (let i = 0; i < MAX_MATCHES && (match = usersRegex.exec(this.text)) !== null; i++) { + let matchName = match[2]; // Deliberately have a minimum length to match on, // so we don't match smaller nicks accidentally. if (matchName.length < PILL_MIN_LENGTH_TO_MATCH || matched.has(matchName.toLowerCase())) { @@ -68,14 +68,11 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { Modern client's will pill-ify and will show a displayname, so we can use the localpart so it matches and doesn't degrade too badly on limited clients.*/ const localpart = userId.substr(1, userId.indexOf(":")-1); - this.htmlText = this.htmlText.replace( - new RegExp(escapeStringRegexp(matchName), "igm"), -`${ircFormatting.escapeHtmlChars(matchName)}` - ); - this.text = this.text.replace( - new RegExp(escapeStringRegexp(matchName), "igm"), - localpart + const regex = MentionRegex(escapeStringRegexp(matchName)); + this.htmlText = this.htmlText.replace(regex, +`$1${ircFormatting.escapeHtmlChars(matchName)}` ); + this.text = this.text.replace(regex, `$1${localpart}`); // Don't match this name twice, we've already replaced all entries. matched.add(matchName.toLowerCase()); } @@ -136,4 +133,11 @@ MatrixAction.fromIrcAction = function(ircAction) { } }; +function MentionRegex(matcher) { + const WORD_BOUNDARY = "^|\:|\#|```|\\s|$|,"; + return new RegExp( + `(${WORD_BOUNDARY})(@?(${matcher}))(?=${WORD_BOUNDARY})` + ,"igmu"); +} + module.exports = MatrixAction; diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index 2c6d91a5b..a86de8ba0 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -93,4 +93,34 @@ describe("MatrixAction", function() { " meet JCDenton" ); }); + it("should not highlight mentions in a URL with www.", () => { + let action = new MatrixAction( + "message", + "Go to http://www.JCDenton.com", + "Go to my website", + null + ); + action.formatMentions({ + "JCDenton": "@jc.denton:unatco.gov" + }); + expect(action.text).toEqual("Go to http://www.JCDenton.com"); + expect(action.htmlText).toEqual( + "Go to my website" + ); + }); + it("should not highlight mentions in a URL with http://", () => { + let action = new MatrixAction( + "message", + "Go to http://JCDenton.com", + "Go to my website", + null + ); + action.formatMentions({ + "JCDenton": "@jc.denton:unatco.gov" + }); + expect(action.text).toEqual("Go to http://JCDenton.com"); + expect(action.htmlText).toEqual( + "Go to my website" + ); + }); }); From 7dbee49c1a7b3644a8e418dc22fabaa2be97c476 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 22 Aug 2018 19:19:20 +0100 Subject: [PATCH 140/162] spacing --- lib/models/MatrixAction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 34c87a516..d537ac072 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -66,7 +66,7 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { userId = ircFormatting.escapeHtmlChars(userId); /* Due to how Riot and friends do push notifications, we need the plain text to match something. Modern client's will pill-ify and will show a displayname, so we can use the localpart so it - matches and doesn't degrade too badly on limited clients.*/ + matches and doesn't degrade too badly on limited clients.*/ const localpart = userId.substr(1, userId.indexOf(":")-1); const regex = MentionRegex(escapeStringRegexp(matchName)); this.htmlText = this.htmlText.replace(regex, From 0c088533444f7a640d85854bb8a10a264a9ec1d9 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 13:55:17 +0100 Subject: [PATCH 141/162] Promiseify formatMentions --- lib/bridge/IrcHandler.js | 2 +- lib/models/MatrixAction.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/bridge/IrcHandler.js b/lib/bridge/IrcHandler.js index 9488e1f92..f7496d900 100644 --- a/lib/bridge/IrcHandler.js +++ b/lib/bridge/IrcHandler.js @@ -486,7 +486,7 @@ IrcHandler.prototype.onMessage = Promise.coroutine(function*(req, server, fromUs } } - mxAction.formatMentions(mapping); + yield mxAction.formatMentions(mapping, this.ircBridge.getAppServiceBridge().getIntent()); if (!mxAction) { req.log.error("Couldn't map IRC action to matrix action"); diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index d537ac072..cd173ca97 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -3,6 +3,7 @@ const ircFormatting = require("../irc/formatting"); const log = require("../logging").get("MatrixAction"); const ContentRepo = require("matrix-appservice-bridge").ContentRepo; const escapeStringRegexp = require('escape-string-regexp'); +const Promise = require("bluebird"); const ACTION_TYPES = ["message", "emote", "topic", "notice", "file", "image", "video", "audio"]; const EVENT_TO_TYPE = { @@ -31,7 +32,7 @@ function MatrixAction(type, text, htmlText, timestamp) { this.ts = timestamp || 0; } -MatrixAction.prototype.formatMentions = function(nickUserIdMap) { +MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMap, intent) { const regexString = "(" + Object.keys(nickUserIdMap).map((value) => escapeStringRegexp(value)).join("|") + ")"; @@ -76,7 +77,7 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) { // Don't match this name twice, we've already replaced all entries. matched.add(matchName.toLowerCase()); } -} +}); MatrixAction.fromEvent = function(client, event, mediaUrl) { event.content = event.content || {}; From 1e120841baf8a9989f9cbacf80d505d64f868c61 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 13:55:30 +0100 Subject: [PATCH 142/162] Use getProfileInfo to fetch the displayname --- lib/models/MatrixAction.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index cd173ca97..fed547b53 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -65,15 +65,25 @@ MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMa this.htmlText = this.text; } userId = ircFormatting.escapeHtmlChars(userId); - /* Due to how Riot and friends do push notifications, we need the plain text to match something. - Modern client's will pill-ify and will show a displayname, so we can use the localpart so it - matches and doesn't degrade too badly on limited clients.*/ - const localpart = userId.substr(1, userId.indexOf(":")-1); + /* Due to how Riot and friends do push notifications, we need the plain text to match something.*/ + let identifier; + try { + identifier = yield intent.getProfileInfo(userId, 'displayname', true); + } + catch (e) { + // This shouldn't happen, but let's not fail to match if so. + + } + if (identifier === undefined) { + // Fallback to userid. + identifier = userId.substr(1, userId.indexOf(":")-1) + } + const regex = MentionRegex(escapeStringRegexp(matchName)); this.htmlText = this.htmlText.replace(regex, -`$1${ircFormatting.escapeHtmlChars(matchName)}` +`$1${ircFormatting.escapeHtmlChars(identifier)}` ); - this.text = this.text.replace(regex, `$1${localpart}`); + this.text = this.text.replace(regex, `$1${identifier}`); // Don't match this name twice, we've already replaced all entries. matched.add(matchName.toLowerCase()); } From 6d4901f363f75ba7d706a1f46ab3e558662c6771 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 13:55:42 +0100 Subject: [PATCH 143/162] Fixup tests --- spec/unit/MatrixAction.spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index a86de8ba0..f14780761 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -22,7 +22,7 @@ describe("MatrixAction", function() { }); expect(action.text).toEqual("jc.denton, it's a bomb!"); expect(action.htmlText).toEqual( - "JCDenton, it's a bomb!" + "jc.denton, it's a bomb!" ); }); it("should highlight a user, regardless of case", () => { @@ -37,7 +37,7 @@ describe("MatrixAction", function() { }); expect(action.text).toEqual("jc.denton, it's a bomb!"); expect(action.htmlText).toEqual( - "jcdenton, it's a bomb!" + "jc.denton, it's a bomb!" ); }); it("should highlight a user, with plain text", () => { @@ -47,7 +47,7 @@ describe("MatrixAction", function() { }); expect(action.text).toEqual("jc.denton, it's a bomb!"); expect(action.htmlText).toEqual( - "JCDenton, it's a bomb!" + "jc.denton, it's a bomb!" ); }); it("should highlight a user, with weird characters", () => { @@ -57,7 +57,7 @@ describe("MatrixAction", function() { }); expect(action.text).toEqual("jc.denton, it's a bomb!"); expect(action.htmlText).toEqual( - "`||JCDenton[m], it's a bomb!" + "jc.denton, it's a bomb!" ); }); it("should highlight multiple users", () => { @@ -73,8 +73,8 @@ describe("MatrixAction", function() { }); expect(action.text).toEqual("jc.denton is sent to assassinate paul.denton"); expect(action.htmlText).toEqual( - "JCDenton is sent" + - " to assassinate PaulDenton" + "jc.denton is sent" + + " to assassinate paul.denton" ); }); it("should highlight multiple mentions of the same user", () => { @@ -89,8 +89,8 @@ describe("MatrixAction", function() { }); expect(action.text).toEqual("jc.denton, meet jc.denton"); expect(action.htmlText).toEqual( - "JCDenton," + - " meet JCDenton" + "jc.denton," + + " meet jc.denton" ); }); it("should not highlight mentions in a URL with www.", () => { From a5932baeb9bdcf27d01a0d67f7762d228ffe4c49 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 14:15:07 +0100 Subject: [PATCH 144/162] Should be inc --- lib/bridge/IrcBridge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bridge/IrcBridge.js b/lib/bridge/IrcBridge.js index a432664e7..fa09f0351 100644 --- a/lib/bridge/IrcBridge.js +++ b/lib/bridge/IrcBridge.js @@ -202,7 +202,7 @@ IrcBridge.prototype._initialiseMetrics = function() { this._clientPool.totalReconnectsWaiting(server.domain) ); let mxMetrics = this.matrixHandler.getMetrics(server.domain); - matrixHandlerConnFailureKicks.set( + matrixHandlerConnFailureKicks.inc( {server: server.domain}, mxMetrics["connection_failure_kicks"] || 0 ); From 57ad38ecdcee05d4575cb86fd22d9c7045d18d45 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 14:26:36 +0100 Subject: [PATCH 145/162] *deeep sigh* --- lib/models/MatrixAction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index fed547b53..d9d9c305c 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -68,7 +68,7 @@ MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMa /* Due to how Riot and friends do push notifications, we need the plain text to match something.*/ let identifier; try { - identifier = yield intent.getProfileInfo(userId, 'displayname', true); + identifier = yield intent.getProfileInfo(userId, 'displayname', true).displayname; } catch (e) { // This shouldn't happen, but let's not fail to match if so. From f7a3419513cb95a8822792c8ce2b3714646a2f5b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 16:26:54 +0100 Subject: [PATCH 146/162] Linting --- lib/models/MatrixAction.js | 9 ++++++--- spec/unit/MatrixAction.spec.js | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index d9d9c305c..64935bf29 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -1,3 +1,4 @@ +/*eslint no-invalid-this: 0*/ // eslint doesn't understand Promise.coroutine wrapping "use strict"; const ircFormatting = require("../irc/formatting"); const log = require("../logging").get("MatrixAction"); @@ -65,7 +66,8 @@ MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMa this.htmlText = this.text; } userId = ircFormatting.escapeHtmlChars(userId); - /* Due to how Riot and friends do push notifications, we need the plain text to match something.*/ + /* Due to how Riot and friends do push notifications, + we need the plain text to match something.*/ let identifier; try { identifier = yield intent.getProfileInfo(userId, 'displayname', true).displayname; @@ -147,8 +149,9 @@ MatrixAction.fromIrcAction = function(ircAction) { function MentionRegex(matcher) { const WORD_BOUNDARY = "^|\:|\#|```|\\s|$|,"; return new RegExp( - `(${WORD_BOUNDARY})(@?(${matcher}))(?=${WORD_BOUNDARY})` - ,"igmu"); + `(${WORD_BOUNDARY})(@?(${matcher}))(?=${WORD_BOUNDARY})`, + "igmu" + ); } module.exports = MatrixAction; diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index f14780761..bb59f8fe0 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -74,7 +74,8 @@ describe("MatrixAction", function() { expect(action.text).toEqual("jc.denton is sent to assassinate paul.denton"); expect(action.htmlText).toEqual( "jc.denton is sent" + - " to assassinate paul.denton" + " to assassinate " + + "paul.denton" ); }); it("should highlight multiple mentions of the same user", () => { From f69808135148b17fc99bfef1201f687ffd6919c5 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 16:42:38 +0100 Subject: [PATCH 147/162] Less whitespace --- lib/models/MatrixAction.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index 64935bf29..d5ca0c2f1 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -66,6 +66,7 @@ MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMa this.htmlText = this.text; } userId = ircFormatting.escapeHtmlChars(userId); + /* Due to how Riot and friends do push notifications, we need the plain text to match something.*/ let identifier; @@ -74,8 +75,8 @@ MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMa } catch (e) { // This shouldn't happen, but let's not fail to match if so. - } + if (identifier === undefined) { // Fallback to userid. identifier = userId.substr(1, userId.indexOf(":")-1) From d2abf261788722b293ef6d97c56e70f48b974801 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 16:55:09 +0100 Subject: [PATCH 148/162] Trailing spaces have been neutralised --- lib/models/MatrixAction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index d5ca0c2f1..f26259bc0 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -66,7 +66,7 @@ MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMa this.htmlText = this.text; } userId = ircFormatting.escapeHtmlChars(userId); - + /* Due to how Riot and friends do push notifications, we need the plain text to match something.*/ let identifier; @@ -76,7 +76,7 @@ MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMa catch (e) { // This shouldn't happen, but let's not fail to match if so. } - + if (identifier === undefined) { // Fallback to userid. identifier = userId.substr(1, userId.indexOf(":")-1) From ab0dd9d44b9d9649803e4c32107a6fa9ee8a4c99 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 17:55:36 +0100 Subject: [PATCH 149/162] Unpin matrix-appservice-bridge --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7c97b2172..af480ac85 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "extend": "^2.0.0", "irc": "matrix-org/node-irc#c9abb427bec5016d94a2abf3e058cc62de09ea5a", "js-yaml": "^3.2.7", - "matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#4175ff0bd2b0626402d8992deb3af78ba45a7a41", + "matrix-appservice-bridge": "1.6.0", "nedb": "^1.1.2", "nopt": "^3.0.1", "prom-client": "^6.3.0", From 50430054778e8b9c9e1b5a41c5c3f7a5ce93633b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 18:12:41 +0100 Subject: [PATCH 150/162] m-a-b 1.6.0a --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af480ac85..00c6713d7 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "extend": "^2.0.0", "irc": "matrix-org/node-irc#c9abb427bec5016d94a2abf3e058cc62de09ea5a", "js-yaml": "^3.2.7", - "matrix-appservice-bridge": "1.6.0", + "matrix-appservice-bridge": "1.6.0a", "nedb": "^1.1.2", "nopt": "^3.0.1", "prom-client": "^6.3.0", From 402e6742391fd413bad79ebda61c1f4b3f3eafca Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 19:14:39 +0100 Subject: [PATCH 151/162] Test fetching displaynames --- spec/unit/MatrixAction.spec.js | 135 ++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 45 deletions(-) diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index bb59f8fe0..b6b5cd080 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -1,15 +1,34 @@ "use strict"; const MatrixAction = require("../../lib/models/MatrixAction"); +const FakeIntent = { + getProfileInfo: (userId) => { + return new Promise((resolve, reject) => { + if (userId === "@jc.denton:unatco.gov") { + resolve({displayname: "TheJCDenton"}); + } + else if (userId === "@paul.denton:unatco.gov") { + resolve({displayname: "ThePaulDenton"}); + } + else { + reject("This user was not found"); + } + }); + } +} + describe("MatrixAction", function() { + it("should not highlight mentions to text without mentions", () => { let action = new MatrixAction("message", "Some text"); - action.formatMentions({ + return action.formatMentions({ "Some Person": "@foobar:localhost" + }, FakeIntent).then(() => { + expect(action.text).toEqual("Some text"); + expect(action.htmlText).toBeUndefined(); }); - expect(action.text).toEqual("Some text"); - expect(action.htmlText).toBeUndefined(); }); + it("should highlight a user", () => { let action = new MatrixAction( "message", @@ -17,13 +36,14 @@ describe("MatrixAction", function() { "JCDenton, it's a bomb!", null ); - action.formatMentions({ + return action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" + }, FakeIntent).then(() => { + expect(action.text).toEqual("TheJCDenton, it's a bomb!"); + expect(action.htmlText).toEqual( + "TheJCDenton, it's a bomb!" + ); }); - expect(action.text).toEqual("jc.denton, it's a bomb!"); - expect(action.htmlText).toEqual( - "jc.denton, it's a bomb!" - ); }); it("should highlight a user, regardless of case", () => { let action = new MatrixAction( @@ -32,33 +52,37 @@ describe("MatrixAction", function() { "JCDenton, it's a bomb!", null ); - action.formatMentions({ + return action.formatMentions({ "jcdenton": "@jc.denton:unatco.gov" + }, FakeIntent).then(() => { + expect(action.text).toEqual("TheJCDenton, it's a bomb!"); + expect(action.htmlText).toEqual( + "TheJCDenton, it's a bomb!" + ); }); - expect(action.text).toEqual("jc.denton, it's a bomb!"); - expect(action.htmlText).toEqual( - "jc.denton, it's a bomb!" - ); + }); it("should highlight a user, with plain text", () => { let action = new MatrixAction("message", "JCDenton, it's a bomb!"); - action.formatMentions({ + return action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" + }, FakeIntent).then(() => { + expect(action.text).toEqual("TheJCDenton, it's a bomb!"); + expect(action.htmlText).toEqual( + "TheJCDenton, it's a bomb!" + ); }); - expect(action.text).toEqual("jc.denton, it's a bomb!"); - expect(action.htmlText).toEqual( - "jc.denton, it's a bomb!" - ); }); it("should highlight a user, with weird characters", () => { let action = new MatrixAction("message", "`||JCDenton[m], it's a bomb!"); - action.formatMentions({ + return action.formatMentions({ "`||JCDenton[m]": "@jc.denton:unatco.gov" + }, FakeIntent).then(() => { + expect(action.text).toEqual("TheJCDenton, it's a bomb!"); + expect(action.htmlText).toEqual( + "TheJCDenton, it's a bomb!" + ); }); - expect(action.text).toEqual("jc.denton, it's a bomb!"); - expect(action.htmlText).toEqual( - "jc.denton, it's a bomb!" - ); }); it("should highlight multiple users", () => { let action = new MatrixAction( @@ -67,16 +91,17 @@ describe("MatrixAction", function() { "JCDenton is sent to assassinate PaulDenton", null ); - action.formatMentions({ + return action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov", "PaulDenton": "@paul.denton:unatco.gov" + }, FakeIntent).then(() => { + expect(action.text).toEqual("TheJCDenton is sent to assassinate ThePaulDenton"); + expect(action.htmlText).toEqual( + "TheJCDenton is sent" + + " to assassinate " + + "ThePaulDenton" + ); }); - expect(action.text).toEqual("jc.denton is sent to assassinate paul.denton"); - expect(action.htmlText).toEqual( - "jc.denton is sent" + - " to assassinate " + - "paul.denton" - ); }); it("should highlight multiple mentions of the same user", () => { let action = new MatrixAction( @@ -85,14 +110,15 @@ describe("MatrixAction", function() { "JCDenton, meet JCDenton", null ); - action.formatMentions({ + return action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" + }, FakeIntent).then(() => { + expect(action.text).toEqual("TheJCDenton, meet TheJCDenton"); + expect(action.htmlText).toEqual( + "TheJCDenton," + + " meet TheJCDenton" + ); }); - expect(action.text).toEqual("jc.denton, meet jc.denton"); - expect(action.htmlText).toEqual( - "jc.denton," + - " meet jc.denton" - ); }); it("should not highlight mentions in a URL with www.", () => { let action = new MatrixAction( @@ -101,13 +127,14 @@ describe("MatrixAction", function() { "Go to my website", null ); - action.formatMentions({ + return action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" + }, FakeIntent).then(() => { + expect(action.text).toEqual("Go to http://www.JCDenton.com"); + expect(action.htmlText).toEqual( + "Go to my website" + ); }); - expect(action.text).toEqual("Go to http://www.JCDenton.com"); - expect(action.htmlText).toEqual( - "Go to my website" - ); }); it("should not highlight mentions in a URL with http://", () => { let action = new MatrixAction( @@ -116,12 +143,30 @@ describe("MatrixAction", function() { "Go to my website", null ); - action.formatMentions({ + return action.formatMentions({ "JCDenton": "@jc.denton:unatco.gov" + }, FakeIntent).then(() => { + expect(action.text).toEqual("Go to http://JCDenton.com"); + expect(action.htmlText).toEqual( + "Go to my website" + ); }); - expect(action.text).toEqual("Go to http://JCDenton.com"); - expect(action.htmlText).toEqual( - "Go to my website" + }); + it("should fallback to userIds", () => { + let action = new MatrixAction( + "message", + "AnnaNavarre: The machine would not make a mistake!", + "AnnaNavarre: The machine would not make a mistake!", + null ); + return action.formatMentions({ + "AnnaNavarre": "@anna.navarre:unatco.gov" + }, FakeIntent).then(() => { + expect(action.text).toEqual("anna.navarre: The machine would not make a mistake!"); + expect(action.htmlText).toEqual( + ""+ + "anna.navarre: The machine would not make a mistake!" + ); + }); }); }); From 0580ff2bf19d555d8d79c60dff7357fec0b4ce2b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 19:14:58 +0100 Subject: [PATCH 152/162] Fetch displayname properly & Fix intent --- lib/models/MatrixAction.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js index f26259bc0..f839ca0d4 100644 --- a/lib/models/MatrixAction.js +++ b/lib/models/MatrixAction.js @@ -71,7 +71,7 @@ MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMa we need the plain text to match something.*/ let identifier; try { - identifier = yield intent.getProfileInfo(userId, 'displayname', true).displayname; + identifier = (yield intent.getProfileInfo(userId, 'displayname', true)).displayname; } catch (e) { // This shouldn't happen, but let's not fail to match if so. @@ -84,7 +84,8 @@ MatrixAction.prototype.formatMentions = Promise.coroutine(function*(nickUserIdMa const regex = MentionRegex(escapeStringRegexp(matchName)); this.htmlText = this.htmlText.replace(regex, -`$1${ircFormatting.escapeHtmlChars(identifier)}` + `$1`+ + `${ircFormatting.escapeHtmlChars(identifier)}` ); this.text = this.text.replace(regex, `$1${identifier}`); // Don't match this name twice, we've already replaced all entries. From 8686f87bf78f85f74954402b52d6d2b11602d91b Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 19:17:11 +0100 Subject: [PATCH 153/162] Fix line lengths --- spec/unit/MatrixAction.spec.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/unit/MatrixAction.spec.js b/spec/unit/MatrixAction.spec.js index b6b5cd080..51b5fe453 100644 --- a/spec/unit/MatrixAction.spec.js +++ b/spec/unit/MatrixAction.spec.js @@ -41,7 +41,8 @@ describe("MatrixAction", function() { }, FakeIntent).then(() => { expect(action.text).toEqual("TheJCDenton, it's a bomb!"); expect(action.htmlText).toEqual( - "TheJCDenton, it's a bomb!" + ""+ + "TheJCDenton, it's a bomb!" ); }); }); @@ -57,7 +58,8 @@ describe("MatrixAction", function() { }, FakeIntent).then(() => { expect(action.text).toEqual("TheJCDenton, it's a bomb!"); expect(action.htmlText).toEqual( - "TheJCDenton, it's a bomb!" + ""+ + "TheJCDenton, it's a bomb!" ); }); @@ -69,7 +71,8 @@ describe("MatrixAction", function() { }, FakeIntent).then(() => { expect(action.text).toEqual("TheJCDenton, it's a bomb!"); expect(action.htmlText).toEqual( - "TheJCDenton, it's a bomb!" + ""+ + "TheJCDenton, it's a bomb!" ); }); }); @@ -80,7 +83,8 @@ describe("MatrixAction", function() { }, FakeIntent).then(() => { expect(action.text).toEqual("TheJCDenton, it's a bomb!"); expect(action.htmlText).toEqual( - "TheJCDenton, it's a bomb!" + ""+ + "TheJCDenton, it's a bomb!" ); }); }); From 3023957cf0ddf27e28bb063e79ba5a9bf6bdcb5e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 23 Aug 2018 19:50:28 +0100 Subject: [PATCH 154/162] Add changes for 0.11.0-rc1 --- CHANGELOG.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cf28d414..b4aad9d95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,32 @@ -Changes in 0.10.1 (2018-07-30) +Changes in 0.11.0-rc1 (2018-08-23) ============================== +- The bridge now depends on matrix-appservice-bridge 1.6.0a + +New features & improvements: +* Cache modes internally #630 +* Replace nicks with user pill mentions #650 #658 +* Kick users if we fail to create an IRC client for them on join (aka ILINE kicks) #639 +* SASL support #643 +* Add err_nononreg so we can announce PMs that failed #645 +* Formatting of replies #647 + +Bug Fixes: +* Fix invalidchar nick #655 +* Don't answer any msgtypes other than text in an admin room. #642 +* Fix provisoner leaving users on unlink #649 + +Metrics: +* Metrics for MatrixHandler - Iline Kicks #644 +* Idle connection metrics #651 +* QueuePool.waitingItems should use it's internal queue size #656 + +Misc: +* Section out tests, linting and coverage into seperate stages for Travis #657 + +Changes in 0.10.1 (2018-07-30) +============================== + - Missed a few changes from master Changes in 0.10.0 (2018-07-30) From d89602a6b8528792e1c772e973ccb1f3c0e22b78 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 24 Aug 2018 11:00:33 +0100 Subject: [PATCH 155/162] matrix-appservice-bridge bumped to 1.6.0b Hope you like m-a-b releases. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a00066666..a150c0e1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-appservice-irc", - "version": "0.10.1", + "version": "0.11.0-rc1", "description": "An IRC Bridge for Matrix", "main": "app.js", "bin": "./bin/matrix-appservice-irc", @@ -30,7 +30,7 @@ "extend": "^2.0.0", "irc": "matrix-org/node-irc#c9abb427bec5016d94a2abf3e058cc62de09ea5a", "js-yaml": "^3.2.7", - "matrix-appservice-bridge": "1.6.0a", + "matrix-appservice-bridge": "1.6.0b", "nedb": "^1.1.2", "nopt": "^3.0.1", "prom-client": "^6.3.0", From 99662f63e575b4786c3a794a07081d6d4950bc60 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 24 Aug 2018 11:02:20 +0100 Subject: [PATCH 156/162] Update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4aad9d95..2afcb7a70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +Changes in 0.11.0-rc2 (2018-08-23) +============================== + +- The bridge now depends on matrix-appservice-bridge 1.6.0b + +Bug Fixes: + +* There was a bug involving intents in m-a-b so it was bumped + Changes in 0.11.0-rc1 (2018-08-23) ============================== From 434424feb5d46191f169c47c65e46c95a1f5bf77 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 24 Aug 2018 11:27:47 +0100 Subject: [PATCH 157/162] Mock out authedRequest --- spec/util/client-sdk-mock.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/util/client-sdk-mock.js b/spec/util/client-sdk-mock.js index 99871649b..ca6a2cde8 100644 --- a/spec/util/client-sdk-mock.js +++ b/spec/util/client-sdk-mock.js @@ -13,6 +13,7 @@ function MockClient(config) { userId: config.userId }; this._http = { opts: {} }; + this._http.authedRequest = jasmine.createSpy("sdk.authedRequestWithPrefix()"); this._http.authedRequestWithPrefix = jasmine.createSpy("sdk.authedRequestWithPrefix()"); this.register = jasmine.createSpy("sdk.register(username, password)"); this.createRoom = jasmine.createSpy("sdk.createRoom(opts)"); @@ -39,6 +40,10 @@ function MockClient(config) { return Promise.resolve({}); }); + this._http.authedRequest.and.callFake(function() { + return Promise.resolve({}); + }); + this._http.authedRequestWithPrefix.and.callFake(function(a, method, endpoint) { if (endpoint === "/joined_rooms") { return Promise.resolve([]); From c086d4ba9a7e0d1e5835a0ceffde591ea741c927 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 24 Aug 2018 11:28:19 +0100 Subject: [PATCH 158/162] 1.6.0c --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a150c0e1a..aa2761793 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "extend": "^2.0.0", "irc": "matrix-org/node-irc#c9abb427bec5016d94a2abf3e058cc62de09ea5a", "js-yaml": "^3.2.7", - "matrix-appservice-bridge": "1.6.0b", + "matrix-appservice-bridge": "1.6.0c", "nedb": "^1.1.2", "nopt": "^3.0.1", "prom-client": "^6.3.0", From 08af8b27a9c207c1763617737018cb5c7b6eee08 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 24 Aug 2018 11:29:46 +0100 Subject: [PATCH 159/162] 0.11.0-rc3 --- CHANGELOG.md | 11 ++++++++++- package.json | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2afcb7a70..6fcebf071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ -Changes in 0.11.0-rc2 (2018-08-23) +Changes in 0.11.0-rc3 (2018-08-24) +============================== + +- The bridge now depends on matrix-appservice-bridge 1.6.0c + +Bug Fixes: + +* We were calling authedRequest but the request was not mocked out. + +Changes in 0.11.0-rc2 (2018-08-24) ============================== - The bridge now depends on matrix-appservice-bridge 1.6.0b diff --git a/package.json b/package.json index aa2761793..ab6ebf66d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-appservice-irc", - "version": "0.11.0-rc1", + "version": "0.11.0-rc3", "description": "An IRC Bridge for Matrix", "main": "app.js", "bin": "./bin/matrix-appservice-irc", From 6f3233d6d9d0089c627852df99e16577c3514bf6 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 24 Aug 2018 11:59:55 +0100 Subject: [PATCH 160/162] Reply should be checking eventContent of previous event --- lib/bridge/MatrixHandler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js index b51ae40a3..743892422 100644 --- a/lib/bridge/MatrixHandler.js +++ b/lib/bridge/MatrixHandler.js @@ -1543,8 +1543,8 @@ MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, if (typeof(eventContent.content.body) !== "string") { throw Error("'body' was not a string."); } - const isReply = event.content["m.relates_to"] && - event.content["m.relates_to"]["m.in_reply_to"]; + const isReply = eventContent.content["m.relates_to"] && + eventContent.content["m.relates_to"]["m.in_reply_to"]; if (isReply) { const sourceMatch = REPLY_REGEX.exec(eventContent.content.body); rplSource = sourceMatch.length === 4 ? sourceMatch[3] : event.content.body; From 273a6294df8c1085238b44452367c1c40f8f00e7 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 24 Aug 2018 15:57:22 +0100 Subject: [PATCH 161/162] v0.11.0-rc4 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fcebf071..6f8fa083b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +Changes in 0.11.0-rc4 (2018-08-24) +============================== + +Bug Fixes: + +* Fixed a bug where content of events the bridge hadn't cached + were not being used in replies. + Changes in 0.11.0-rc3 (2018-08-24) ============================== diff --git a/package.json b/package.json index ab6ebf66d..5ff7afd50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-appservice-irc", - "version": "0.11.0-rc3", + "version": "0.11.0-rc4", "description": "An IRC Bridge for Matrix", "main": "app.js", "bin": "./bin/matrix-appservice-irc", From 84409e96522cc43f95df57134079993578bd6bb2 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 28 Aug 2018 14:20:25 +0100 Subject: [PATCH 162/162] 0.11.0 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f8fa083b..3f3f79386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +Changes in 0.11.0 (2018-08-28) +============================== + +No changes since previous RC, see below for full list of changes + Changes in 0.11.0-rc4 (2018-08-24) ============================== diff --git a/package.json b/package.json index 5ff7afd50..a600c3714 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-appservice-irc", - "version": "0.11.0-rc4", + "version": "0.11.0", "description": "An IRC Bridge for Matrix", "main": "app.js", "bin": "./bin/matrix-appservice-irc",