diff --git a/client/components/Windows/Settings.vue b/client/components/Windows/Settings.vue
index ce9d3865..394cb160 100644
--- a/client/components/Windows/Settings.vue
+++ b/client/components/Windows/Settings.vue
@@ -351,8 +351,15 @@ This may break orientation if your browser does not support that."
+
+
+
+
+ Highlight exceptions
+
+
+
+
+
diff --git a/client/js/settings.js b/client/js/settings.js
index b1d5308a..43bf2cfe 100644
--- a/client/js/settings.js
+++ b/client/js/settings.js
@@ -46,6 +46,10 @@ export const config = normalizeConfig({
default: "",
sync: "always",
},
+ highlightExceptions: {
+ default: "",
+ sync: "always",
+ },
awayMessage: {
default: "",
sync: "always",
diff --git a/src/client.js b/src/client.js
index 0793330d..6f9fd023 100644
--- a/src/client.js
+++ b/src/client.js
@@ -62,6 +62,7 @@ function Client(manager, name, config = {}) {
manager: manager,
messageStorage: [],
highlightRegex: null,
+ highlightExceptionRegex: null,
});
const client = this;
@@ -424,30 +425,32 @@ Client.prototype.inputLine = function (data) {
};
Client.prototype.compileCustomHighlights = function () {
- const client = this;
+ this.highlightRegex = compileHighlightRegex(this.config.clientSettings.highlights);
+ this.highlightExceptionRegex = compileHighlightRegex(
+ this.config.clientSettings.highlightExceptions
+ );
+};
- if (typeof client.config.clientSettings.highlights !== "string") {
- client.highlightRegex = null;
- return;
+function compileHighlightRegex(customHighlightString) {
+ if (typeof customHighlightString !== "string") {
+ return null;
}
- // Ensure we don't have empty string in the list of highlights
- // otherwise, users get notifications for everything
- const highlightsTokens = client.config.clientSettings.highlights
+ // Ensure we don't have empty strings in the list of highlights
+ const highlightsTokens = customHighlightString
.split(",")
.map((highlight) => escapeRegExp(highlight.trim()))
.filter((highlight) => highlight.length > 0);
if (highlightsTokens.length === 0) {
- client.highlightRegex = null;
- return;
+ return null;
}
- client.highlightRegex = new RegExp(
+ return new RegExp(
`(?:^|[ .,+!?|/:<>(){}'"@&~-])(?:${highlightsTokens.join("|")})(?:$|[ .,+!?|/:<>(){}'"-])`,
"i"
);
-};
+}
Client.prototype.more = function (data) {
const client = this;
diff --git a/src/plugins/irc-events/link.js b/src/plugins/irc-events/link.js
index 4f68f9df..841f0500 100644
--- a/src/plugins/irc-events/link.js
+++ b/src/plugins/irc-events/link.js
@@ -5,21 +5,17 @@ const got = require("got");
const URL = require("url").URL;
const mime = require("mime-types");
const Helper = require("../../helper");
-const cleanIrcMessage = require("../../../client/js/helpers/ircmessageparser/cleanIrcMessage");
const {findLinksWithSchema} = require("../../../client/js/helpers/ircmessageparser/findLinks");
const storage = require("../storage");
const currentFetchPromises = new Map();
const imageTypeRegex = /^image\/.+/;
const mediaTypeRegex = /^(audio|video)\/.+/;
-module.exports = function (client, chan, msg) {
+module.exports = function (client, chan, msg, cleanText) {
if (!Helper.config.prefetch) {
return;
}
- // Remove all IRC formatting characters before searching for links
- const cleanText = cleanIrcMessage(msg.text);
-
msg.previews = findLinksWithSchema(cleanText).reduce((cleanLinks, link) => {
const url = normalizeURL(link.link);
diff --git a/src/plugins/irc-events/message.js b/src/plugins/irc-events/message.js
index a2ff0081..664227c2 100644
--- a/src/plugins/irc-events/message.js
+++ b/src/plugins/irc-events/message.js
@@ -115,6 +115,9 @@ module.exports = function (irc, network) {
msg.showInActive = true;
}
+ // remove IRC formatting for custom highlight testing
+ const cleanMessage = cleanIrcMessage(data.message);
+
// Self messages in channels are never highlighted
// Non-self messages are highlighted as soon as the nick is detected
if (!msg.highlight && !msg.self) {
@@ -122,10 +125,15 @@ module.exports = function (irc, network) {
// If we still don't have a highlight, test against custom highlights if there's any
if (!msg.highlight && client.highlightRegex) {
- msg.highlight = client.highlightRegex.test(data.message);
+ msg.highlight = client.highlightRegex.test(cleanMessage);
}
}
+ // if highlight exceptions match, do not highlight at all
+ if (msg.highlight && client.highlightExceptionRegex) {
+ msg.highlight = !client.highlightExceptionRegex.test(cleanMessage);
+ }
+
if (data.group) {
msg.statusmsgGroup = data.group;
}
@@ -140,7 +148,7 @@ module.exports = function (irc, network) {
// No prefetch URLs unless are simple MESSAGE or ACTION types
if ([Msg.Type.MESSAGE, Msg.Type.ACTION].includes(data.type)) {
- LinkPrefetch(client, chan, msg);
+ LinkPrefetch(client, chan, msg, cleanMessage);
}
chan.pushMessage(client, msg, !msg.self);
@@ -148,7 +156,7 @@ module.exports = function (irc, network) {
// Do not send notifications for messages older than 15 minutes (znc buffer for example)
if (msg.highlight && (!data.time || data.time > Date.now() - 900000)) {
let title = chan.name;
- let body = cleanIrcMessage(data.message);
+ let body = cleanMessage;
if (msg.type === Msg.Type.ACTION) {
// For actions, do not include colon in the message
diff --git a/src/server.js b/src/server.js
index f9bb0dc4..5c941db1 100644
--- a/src/server.js
+++ b/src/server.js
@@ -621,7 +621,7 @@ function initializeClient(socket, client, token, lastMessage, openChannel) {
client.save();
- if (newSetting.name === "highlights") {
+ if (newSetting.name === "highlights" || newSetting.name === "highlightExceptions") {
client.compileCustomHighlights();
} else if (newSetting.name === "awayMessage") {
if (typeof newSetting.value !== "string") {
diff --git a/test/plugins/link.js b/test/plugins/link.js
index b22937a8..a7df98b5 100644
--- a/test/plugins/link.js
+++ b/test/plugins/link.js
@@ -49,7 +49,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: url,
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
expect(message.previews).to.deep.equal([
{
@@ -86,7 +86,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: url,
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
expect(message.previews).to.deep.equal([
{
@@ -122,7 +122,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: url,
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/truncate", function (req, res) {
res.send(
@@ -146,7 +146,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/basic-og",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/basic-og", function (req, res) {
res.send("test");
@@ -163,7 +163,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/duplicate-tags",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/duplicate-tags", function (req, res) {
res.send(
@@ -183,7 +183,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/description-og",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/description-og", function (req, res) {
res.send(
@@ -203,7 +203,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/thumb",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/thumb", function (req, res) {
res.send(
@@ -249,7 +249,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + port + "/thumb",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
this.irc.once("msg:preview", function (data) {
expect(data.preview.head).to.equal("Google");
@@ -276,7 +276,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + port + "/thumb",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
this.irc.once("msg:preview", function (data) {
expect(data.preview.head).to.equal("Google");
@@ -292,7 +292,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/thumb-image-src",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/thumb-image-src", function (req, res) {
res.send(
@@ -314,7 +314,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/thumb-image-src",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/thumb-image-src", function (req, res) {
res.send("");
@@ -334,7 +334,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/relative-thumb",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/relative-thumb", function (req, res) {
res.send(
@@ -358,7 +358,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/thumb-no-title",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/thumb-no-title", function (req, res) {
res.send(
@@ -384,7 +384,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/body-no-title",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/body-no-title", function (req, res) {
res.send("");
@@ -405,7 +405,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/thumb-404",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/thumb-404", function (req, res) {
res.send(
@@ -429,7 +429,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + port + "/real-test-image.png",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
this.irc.once("msg:preview", function (data) {
expect(data.preview.type).to.equal("image");
@@ -448,7 +448,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + port + "/one http://localhost:" + this.port + "/two",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
expect(message.previews).to.eql([
{
@@ -511,7 +511,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/language-check",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
});
it("should send accept text/html for initial request", function (done) {
@@ -527,7 +527,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/accept-header-html",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
});
it("should send accept */* for meta image", function (done) {
@@ -551,7 +551,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + port + "/accept-header-thumb",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
});
it("should not add slash to url", function (done) {
@@ -560,7 +560,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + port + "",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
this.irc.once("msg:preview", function (data) {
expect(data.preview.link).to.equal("http://localhost:" + port + "");
@@ -591,7 +591,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
"/unicodeq/?q=🙈-emoji-test",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
app.get("/unicode/:q", function (req, res) {
res.send(`${req.params.q}`);
@@ -629,7 +629,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: `//localhost:${port} localhost:${port} //localhost:${port}/test localhost:${port}/test`,
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
expect(message.previews).to.be.empty;
});
@@ -647,7 +647,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
"",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
expect(message.previews).to.deep.equal([
{
@@ -695,9 +695,9 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
this.irc.config.browser.language = "very nice language";
- link(this.irc, this.network.channels[0], message);
- link(this.irc, this.network.channels[0], message);
- process.nextTick(() => link(this.irc, this.network.channels[0], message));
+ link(this.irc, this.network.channels[0], message, message.text);
+ link(this.irc, this.network.channels[0], message, message.text);
+ process.nextTick(() => link(this.irc, this.network.channels[0], message, message.text));
app.get("/basic-og-once", function (req, res) {
requests++;
@@ -734,11 +734,11 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
let responses = 0;
this.irc.config.browser.language = "first language";
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
setTimeout(() => {
this.irc.config.browser.language = "second language";
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
}, 100);
app.get("/basic-og-once-lang", function (req, res) {
diff --git a/test/plugins/storage.js b/test/plugins/storage.js
index 9d3bbc8f..c8c4a14d 100644
--- a/test/plugins/storage.js
+++ b/test/plugins/storage.js
@@ -76,7 +76,7 @@ describe("Image storage", function () {
text: "http://localhost:" + port + "/thumb",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
this.app.get("/thumb", function (req, res) {
res.send(
@@ -100,7 +100,7 @@ describe("Image storage", function () {
text: "http://localhost:" + port + "/real-test-image.png",
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
this.irc.once("msg:preview", function (data) {
expect(data.preview.type).to.equal("image");
@@ -124,7 +124,7 @@ describe("Image storage", function () {
);
});
- link(this.irc, this.network.channels[0], message);
+ link(this.irc, this.network.channels[0], message, message.text);
this.irc.once("msg:preview", function (data) {
expect(data.preview.type).to.equal("link");
diff --git a/test/tests/customhighlights.js b/test/tests/customhighlights.js
index 86aa3883..64147d67 100644
--- a/test/tests/customhighlights.js
+++ b/test/tests/customhighlights.js
@@ -22,7 +22,10 @@ describe("Custom highlights", function () {
},
"test",
{
- clientSettings: {highlights: "foo, @all, sp ace , ê³ "},
+ clientSettings: {
+ highlights: "foo, @all, sp ace , ê³ ",
+ highlightExceptions: "foo bar, bar @all, test sp ace test",
+ },
}
);
@@ -96,4 +99,53 @@ describe("Custom highlights", function () {
client.compileCustomHighlights();
expect(client.highlightRegex).to.be.null;
});
+
+ // tests for highlight exceptions
+ it("should NOT highlight due to highlight exceptions", function () {
+ const teststrings = [
+ "foo bar baz",
+ "test foo bar",
+ "foo bar @all test",
+ "with a test sp ace test",
+ ];
+
+ for (const teststring of teststrings) {
+ expect(teststring).to.match(client.highlightExceptionRegex);
+ }
+ });
+
+ it("should highlight regardless of highlight exceptions", function () {
+ const teststrings = [
+ "Hey foo hello",
+ "hey Foo: hi",
+ "hey Foo, hi",
+ " testing",
+ "foo",
+ "hey @all test",
+ "testing foo's stuff",
+ '"foo"',
+ '"@all"',
+ "foo!",
+ "www.foo.bar",
+ "www.bar.foo/page",
+ "ê³ ",
+ "test ê³ ",
+ "ê³ !",
+ "www.ê³ .com",
+ "hey @Foo",
+ "hey ~Foo",
+ "hey +Foo",
+ "hello &foo",
+ "@all",
+ "@all wtf",
+ "wtfbar @all",
+ "@@all",
+ "@ê³ ",
+ "f00 sp ace: bar",
+ ];
+
+ for (const teststring of teststrings) {
+ expect(teststring).to.not.match(client.highlightExceptionRegex);
+ }
+ });
});