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/bridge/MatrixHandler.js b/lib/bridge/MatrixHandler.js
index a3f0cb79b..b51ae40a3 100644
--- a/lib/bridge/MatrixHandler.js
+++ b/lib/bridge/MatrixHandler.js
@@ -1653,7 +1653,9 @@ MatrixHandler.prototype.onUserQuery = function(req, userId) {
};
MatrixHandler.prototype.getMetrics = function(serverDomain) {
- return this.metrics[serverDomain] || {};
+ const metrics = this.metrics[serverDomain] || {};
+ this.metrics[serverDomain] = {}
+ return metrics || {};
}
function reqHandler(req, promise) {
diff --git a/lib/models/MatrixAction.js b/lib/models/MatrixAction.js
index 6eddc6346..f839ca0d4 100644
--- a/lib/models/MatrixAction.js
+++ b/lib/models/MatrixAction.js
@@ -1,8 +1,10 @@
+/*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");
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,15 +33,15 @@ 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("|")
+ ")";
- 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())) {
@@ -64,14 +66,32 @@ MatrixAction.prototype.formatMentions = function(nickUserIdMap) {
this.htmlText = this.text;
}
userId = ircFormatting.escapeHtmlChars(userId);
- this.htmlText = this.htmlText.replace(
- new RegExp(escapeStringRegexp(matchName), "igm"),
-`${ircFormatting.escapeHtmlChars(matchName)}`
+
+ /* 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;
+ }
+ 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(identifier)}`
);
+ this.text = this.text.replace(regex, `$1${identifier}`);
// 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 || {};
@@ -128,4 +148,12 @@ 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 b771b4a6b..51b5fe453 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,15 @@ 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("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(
@@ -32,33 +53,40 @@ 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("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!");
- 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("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!");
- 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("`||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(
@@ -67,15 +95,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("JCDenton is sent to assassinate PaulDenton");
- expect(action.htmlText).toEqual(
- "JCDenton is sent" +
- " to assassinate PaulDenton"
- );
});
it("should highlight multiple mentions of the same user", () => {
let action = new MatrixAction(
@@ -84,13 +114,63 @@ 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("JCDenton, meet JCDenton");
- expect(action.htmlText).toEqual(
- "JCDenton," +
- " 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
);
+ 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"
+ );
+ });
+ });
+ 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
+ );
+ 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"
+ );
+ });
+ });
+ 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!"
+ );
+ });
});
});