Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use localpart in plaintext so we get highlighted for pills #658

Merged
merged 23 commits into from
Aug 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0d52d37
Merge branch 'hs/joined-rooms-no-join' into develop
Half-Shot Jul 10, 2018
e15ff60
Merge branch 'hs/reject-invites' into develop
Half-Shot Jul 10, 2018
bb8649f
Bump matrix-appservice-bridge to 1.5.0a
Half-Shot Jul 25, 2018
7bde9d7
Merge branch 'develop' of github.com:Half-Shot/matrix-appservice-irc …
Half-Shot Aug 10, 2018
e6facc5
Counters are incremental
Half-Shot Aug 21, 2018
7790377
Use localpart in plaintext so we get highlighted
Half-Shot Aug 22, 2018
f830415
Use a better regex so we don't catch URLs
Half-Shot Aug 22, 2018
7dbee49
spacing
Half-Shot Aug 22, 2018
0c08853
Promiseify formatMentions
Half-Shot Aug 23, 2018
1e12084
Use getProfileInfo to fetch the displayname
Half-Shot Aug 23, 2018
6d4901f
Fixup tests
Half-Shot Aug 23, 2018
6fce830
Merge remote-tracking branch 'origin/develop' into hs/mentions-use-di…
Half-Shot Aug 23, 2018
adeb5fe
Merge remote-tracking branch 'upstream/develop' into hs/mentions-use-…
Half-Shot Aug 23, 2018
0e7930e
Merge remote-tracking branch 'upstream/develop' into hs/mentions-use-…
Half-Shot Aug 23, 2018
57ad38e
*deeep sigh*
Half-Shot Aug 23, 2018
f7a3419
Linting
Half-Shot Aug 23, 2018
f698081
Less whitespace
Half-Shot Aug 23, 2018
d2abf26
Trailing spaces have been neutralised
Half-Shot Aug 23, 2018
7faeb2b
Merge remote-tracking branch 'upstream/develop' into hs/mentions-use-…
Half-Shot Aug 23, 2018
2509712
Merge remote-tracking branch 'upstream/develop' into hs/mentions-use-…
Half-Shot Aug 23, 2018
402e674
Test fetching displaynames
Half-Shot Aug 23, 2018
0580ff2
Fetch displayname properly & Fix intent
Half-Shot Aug 23, 2018
8686f87
Fix line lengths
Half-Shot Aug 23, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/bridge/IrcHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
4 changes: 3 additions & 1 deletion lib/bridge/MatrixHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
44 changes: 36 additions & 8 deletions lib/models/MatrixAction.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -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())) {
Expand All @@ -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"),
`<a href="https://matrix.to/#/${userId}">${ircFormatting.escapeHtmlChars(matchName)}</a>`

/* 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<a href="https://matrix.to/#/${userId}">`+
`${ircFormatting.escapeHtmlChars(identifier)}</a>`
);
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 || {};
Expand Down Expand Up @@ -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;
148 changes: 114 additions & 34 deletions spec/unit/MatrixAction.spec.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
"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",
"JCDenton, it's a bomb!",
"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(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">"+
"TheJCDenton</a>, it's a bomb!"
);
});
expect(action.text).toEqual("JCDenton, it's a bomb!");
expect(action.htmlText).toEqual(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">JCDenton</a>, it's a bomb!"
);
});
it("should highlight a user, regardless of case", () => {
let action = new MatrixAction(
Expand All @@ -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(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">"+
"TheJCDenton</a>, it's a bomb!"
);
});
expect(action.text).toEqual("JCDenton, it's a bomb!");
expect(action.htmlText).toEqual(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">jcdenton</a>, 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(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">"+
"TheJCDenton</a>, it's a bomb!"
);
});
expect(action.text).toEqual("JCDenton, it's a bomb!");
expect(action.htmlText).toEqual(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">JCDenton</a>, 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(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">"+
"TheJCDenton</a>, it's a bomb!"
);
});
expect(action.text).toEqual("`||JCDenton[m], it's a bomb!");
expect(action.htmlText).toEqual(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">`||JCDenton[m]</a>, it's a bomb!"
);
});
it("should highlight multiple users", () => {
let action = new MatrixAction(
Expand All @@ -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(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">TheJCDenton</a> is sent" +
" to assassinate <a href=\"https://matrix.to/#/@paul.denton:unatco.gov\">" +
"ThePaulDenton</a>"
);
});
expect(action.text).toEqual("JCDenton is sent to assassinate PaulDenton");
expect(action.htmlText).toEqual(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">JCDenton</a> is sent" +
" to assassinate <a href=\"https://matrix.to/#/@paul.denton:unatco.gov\">PaulDenton</a>"
);
});
it("should highlight multiple mentions of the same user", () => {
let action = new MatrixAction(
Expand All @@ -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(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">TheJCDenton</a>," +
" meet <a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">TheJCDenton</a>"
);
});
expect(action.text).toEqual("JCDenton, meet JCDenton");
expect(action.htmlText).toEqual(
"<a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">JCDenton</a>," +
" meet <a href=\"https://matrix.to/#/@jc.denton:unatco.gov\">JCDenton</a>"
});
it("should not highlight mentions in a URL with www.", () => {
let action = new MatrixAction(
"message",
"Go to http://www.JCDenton.com",
"Go to <a href='http://www.JCDenton.com'>my website</a>",
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 <a href='http://www.JCDenton.com'>my website</a>"
);
});
});
it("should not highlight mentions in a URL with http://", () => {
let action = new MatrixAction(
"message",
"Go to http://JCDenton.com",
"Go to <a href='http://JCDenton.com'>my website</a>",
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 <a href='http://JCDenton.com'>my website</a>"
);
});
});
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(
"<a href=\"https://matrix.to/#/@anna.navarre:unatco.gov\">"+
"anna.navarre</a>: The machine would not make a mistake!"
);
});
});
});