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">
<label class="opt">
<label for="highlights" class="sr-only">
Custom highlights (comma-separated keywords)
<label for="highlights" class="opt">
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>
<input
id="highlights"
@ -360,7 +367,31 @@ This may break orientation if your browser does not support that."
type="text"
name="highlights"
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>
</div>

View file

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

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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") {

View file

@ -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("<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",
});
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("<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",
});
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("<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",
});
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(`<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`,
});
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) {

View file

@ -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");

View file

@ -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",
"<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);
}
});
});