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."
+ +
+
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); + } + }); });