add highlight exceptions

This commit is contained in:
Jay2k1 2020-07-22 17:28:12 +02:00
parent c29ae50392
commit b97b145df1
9 changed files with 150 additions and 56 deletions

View file

@ -351,8 +351,15 @@ This may break orientation if your browser does not support that."
<div v-if="!$store.state.serverConfiguration.public && $store.state.settings.advanced"> <div v-if="!$store.state.serverConfiguration.public && $store.state.settings.advanced">
<label class="opt"> <label class="opt">
<label for="highlights" class="sr-only"> <label for="highlights" class="opt">
Custom highlights (comma-separated keywords) Custom highlights
<span
class="tooltipped tooltipped-n tooltipped-no-delay"
aria-label="If a message contains any of these comma-separated
expressions, it will trigger a highlight."
>
<button class="extra-help" />
</span>
</label> </label>
<input <input
id="highlights" id="highlights"
@ -360,7 +367,31 @@ This may break orientation if your browser does not support that."
type="text" type="text"
name="highlights" name="highlights"
class="input" class="input"
placeholder="Custom highlights (comma-separated keywords)" placeholder="Comma-separated, e.g.: word, some more words, anotherword"
/>
</label>
</div>
<div v-if="!$store.state.serverConfiguration.public && $store.state.settings.advanced">
<label class="opt">
<label for="highlightExceptions" class="opt">
Highlight exceptions
<span
class="tooltipped tooltipped-n tooltipped-no-delay"
aria-label="If a message contains any of these comma-separated
expressions, it will not trigger a highlight even if it contains
your nickname or expressions defined in custom highlights."
>
<button class="extra-help" />
</span>
</label>
<input
id="highlightExceptions"
:value="$store.state.settings.highlightExceptions"
type="text"
name="highlightExceptions"
class="input"
placeholder="Comma-separated, e.g.: word, some more words, anotherword"
/> />
</label> </label>
</div> </div>

View file

@ -46,6 +46,10 @@ export const config = normalizeConfig({
default: "", default: "",
sync: "always", sync: "always",
}, },
highlightExceptions: {
default: "",
sync: "always",
},
awayMessage: { awayMessage: {
default: "", default: "",
sync: "always", sync: "always",

View file

@ -62,6 +62,7 @@ function Client(manager, name, config = {}) {
manager: manager, manager: manager,
messageStorage: [], messageStorage: [],
highlightRegex: null, highlightRegex: null,
highlightExceptionRegex: null,
}); });
const client = this; const client = this;
@ -424,30 +425,32 @@ Client.prototype.inputLine = function (data) {
}; };
Client.prototype.compileCustomHighlights = function () { 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") { function compileHighlightRegex(customHighlightString) {
client.highlightRegex = null; if (typeof customHighlightString !== "string") {
return; return null;
} }
// Ensure we don't have empty string in the list of highlights // Ensure we don't have empty strings in the list of highlights
// otherwise, users get notifications for everything const highlightsTokens = customHighlightString
const highlightsTokens = client.config.clientSettings.highlights
.split(",") .split(",")
.map((highlight) => escapeRegExp(highlight.trim())) .map((highlight) => escapeRegExp(highlight.trim()))
.filter((highlight) => highlight.length > 0); .filter((highlight) => highlight.length > 0);
if (highlightsTokens.length === 0) { if (highlightsTokens.length === 0) {
client.highlightRegex = null; return null;
return;
} }
client.highlightRegex = new RegExp( return new RegExp(
`(?:^|[ .,+!?|/:<>(){}'"@&~-])(?:${highlightsTokens.join("|")})(?:$|[ .,+!?|/:<>(){}'"-])`, `(?:^|[ .,+!?|/:<>(){}'"@&~-])(?:${highlightsTokens.join("|")})(?:$|[ .,+!?|/:<>(){}'"-])`,
"i" "i"
); );
}; }
Client.prototype.more = function (data) { Client.prototype.more = function (data) {
const client = this; const client = this;

View file

@ -5,21 +5,17 @@ const got = require("got");
const URL = require("url").URL; const URL = require("url").URL;
const mime = require("mime-types"); const mime = require("mime-types");
const Helper = require("../../helper"); const Helper = require("../../helper");
const cleanIrcMessage = require("../../../client/js/helpers/ircmessageparser/cleanIrcMessage");
const {findLinksWithSchema} = require("../../../client/js/helpers/ircmessageparser/findLinks"); const {findLinksWithSchema} = require("../../../client/js/helpers/ircmessageparser/findLinks");
const storage = require("../storage"); const storage = require("../storage");
const currentFetchPromises = new Map(); const currentFetchPromises = new Map();
const imageTypeRegex = /^image\/.+/; const imageTypeRegex = /^image\/.+/;
const mediaTypeRegex = /^(audio|video)\/.+/; const mediaTypeRegex = /^(audio|video)\/.+/;
module.exports = function (client, chan, msg) { module.exports = function (client, chan, msg, cleanText) {
if (!Helper.config.prefetch) { if (!Helper.config.prefetch) {
return; return;
} }
// Remove all IRC formatting characters before searching for links
const cleanText = cleanIrcMessage(msg.text);
msg.previews = findLinksWithSchema(cleanText).reduce((cleanLinks, link) => { msg.previews = findLinksWithSchema(cleanText).reduce((cleanLinks, link) => {
const url = normalizeURL(link.link); const url = normalizeURL(link.link);

View file

@ -115,6 +115,9 @@ module.exports = function (irc, network) {
msg.showInActive = true; msg.showInActive = true;
} }
// remove IRC formatting for custom highlight testing
const cleanMessage = cleanIrcMessage(data.message);
// Self messages in channels are never highlighted // Self messages in channels are never highlighted
// Non-self messages are highlighted as soon as the nick is detected // Non-self messages are highlighted as soon as the nick is detected
if (!msg.highlight && !msg.self) { 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 we still don't have a highlight, test against custom highlights if there's any
if (!msg.highlight && client.highlightRegex) { 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) { if (data.group) {
msg.statusmsgGroup = 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 // No prefetch URLs unless are simple MESSAGE or ACTION types
if ([Msg.Type.MESSAGE, Msg.Type.ACTION].includes(data.type)) { 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); 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) // Do not send notifications for messages older than 15 minutes (znc buffer for example)
if (msg.highlight && (!data.time || data.time > Date.now() - 900000)) { if (msg.highlight && (!data.time || data.time > Date.now() - 900000)) {
let title = chan.name; let title = chan.name;
let body = cleanIrcMessage(data.message); let body = cleanMessage;
if (msg.type === Msg.Type.ACTION) { if (msg.type === Msg.Type.ACTION) {
// For actions, do not include colon in the message // For actions, do not include colon in the message

View file

@ -621,7 +621,7 @@ function initializeClient(socket, client, token, lastMessage, openChannel) {
client.save(); client.save();
if (newSetting.name === "highlights") { if (newSetting.name === "highlights" || newSetting.name === "highlightExceptions") {
client.compileCustomHighlights(); client.compileCustomHighlights();
} else if (newSetting.name === "awayMessage") { } else if (newSetting.name === "awayMessage") {
if (typeof newSetting.value !== "string") { if (typeof newSetting.value !== "string") {

View file

@ -49,7 +49,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: url, 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([ expect(message.previews).to.deep.equal([
{ {
@ -86,7 +86,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: url, 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([ expect(message.previews).to.deep.equal([
{ {
@ -122,7 +122,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: url, 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) { app.get("/truncate", function (req, res) {
res.send( res.send(
@ -146,7 +146,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/basic-og", 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) { app.get("/basic-og", function (req, res) {
res.send("<title>test</title><meta property='og:title' content='opengraph test'>"); res.send("<title>test</title><meta property='og:title' content='opengraph test'>");
@ -163,7 +163,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/duplicate-tags", 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) { app.get("/duplicate-tags", function (req, res) {
res.send( res.send(
@ -183,7 +183,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/description-og", 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) { app.get("/description-og", function (req, res) {
res.send( res.send(
@ -203,7 +203,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/thumb", 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) { app.get("/thumb", function (req, res) {
res.send( res.send(
@ -249,7 +249,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + port + "/thumb", 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) { this.irc.once("msg:preview", function (data) {
expect(data.preview.head).to.equal("Google"); expect(data.preview.head).to.equal("Google");
@ -276,7 +276,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + port + "/thumb", 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) { this.irc.once("msg:preview", function (data) {
expect(data.preview.head).to.equal("Google"); 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", 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) { app.get("/thumb-image-src", function (req, res) {
res.send( res.send(
@ -314,7 +314,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/thumb-image-src", 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) { app.get("/thumb-image-src", function (req, res) {
res.send("<link rel='image_src' href='//localhost:" + port + "/real-test-image.png'>"); res.send("<link rel='image_src' href='//localhost:" + port + "/real-test-image.png'>");
@ -334,7 +334,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/relative-thumb", 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) { app.get("/relative-thumb", function (req, res) {
res.send( res.send(
@ -358,7 +358,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/thumb-no-title", 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) { app.get("/thumb-no-title", function (req, res) {
res.send( res.send(
@ -384,7 +384,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/body-no-title", 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) { app.get("/body-no-title", function (req, res) {
res.send("<meta name='description' content='hello world'>"); res.send("<meta name='description' content='hello world'>");
@ -405,7 +405,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/thumb-404", 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) { app.get("/thumb-404", function (req, res) {
res.send( res.send(
@ -429,7 +429,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + port + "/real-test-image.png", 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) { this.irc.once("msg:preview", function (data) {
expect(data.preview.type).to.equal("image"); 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", 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([ expect(message.previews).to.eql([
{ {
@ -511,7 +511,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: "http://localhost:" + this.port + "/language-check", 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) { 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", 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) { 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", 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) { 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 + "", 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) { this.irc.once("msg:preview", function (data) {
expect(data.preview.link).to.equal("http://localhost:" + port + ""); 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", "/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) { app.get("/unicode/:q", function (req, res) {
res.send(`<title>${req.params.q}</title>`); res.send(`<title>${req.params.q}</title>`);
@ -629,7 +629,7 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
text: `//localhost:${port} localhost:${port} //localhost:${port}/test localhost:${port}/test`, 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; 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([ 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"; this.irc.config.browser.language = "very nice language";
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); link(this.irc, this.network.channels[0], message, message.text);
process.nextTick(() => link(this.irc, this.network.channels[0], message)); process.nextTick(() => link(this.irc, this.network.channels[0], message, message.text));
app.get("/basic-og-once", function (req, res) { app.get("/basic-og-once", function (req, res) {
requests++; requests++;
@ -734,11 +734,11 @@ Vivamus bibendum vulputate tincidunt. Sed vitae ligula felis.`;
let responses = 0; let responses = 0;
this.irc.config.browser.language = "first language"; 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(() => { setTimeout(() => {
this.irc.config.browser.language = "second language"; 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); }, 100);
app.get("/basic-og-once-lang", function (req, res) { app.get("/basic-og-once-lang", function (req, res) {

View file

@ -76,7 +76,7 @@ describe("Image storage", function () {
text: "http://localhost:" + port + "/thumb", 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) { this.app.get("/thumb", function (req, res) {
res.send( res.send(
@ -100,7 +100,7 @@ describe("Image storage", function () {
text: "http://localhost:" + port + "/real-test-image.png", 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) { this.irc.once("msg:preview", function (data) {
expect(data.preview.type).to.equal("image"); 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) { this.irc.once("msg:preview", function (data) {
expect(data.preview.type).to.equal("link"); expect(data.preview.type).to.equal("link");

View file

@ -22,7 +22,10 @@ describe("Custom highlights", function () {
}, },
"test", "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(); client.compileCustomHighlights();
expect(client.highlightRegex).to.be.null; 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",
"<foo> 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);
}
});
}); });