Add support for /ignore, /unignore and /ignorelist commands

This commit is contained in:
Alexandre Oliveira 2018-03-11 15:17:57 -03:00
parent 0de90daa64
commit 468427bfdb
14 changed files with 317 additions and 4 deletions

View file

@ -1248,15 +1248,18 @@ kbd {
}
#chat table.channel-list,
#chat table.ban-list {
#chat table.ban-list,
#chat table.ignore-list {
margin: 5px 10px;
width: calc(100% - 30px);
}
#chat table.channel-list th,
#chat table.ban-list th,
#chat table.ignore-list th,
#chat table.channel-list td,
#chat table.ban-list td {
#chat table.ban-list td,
#chat.table.ignore-list td {
padding: 5px;
vertical-align: top;
border-bottom: #eee 1px solid;
@ -1270,7 +1273,9 @@ kbd {
#chat table.channel-list .topic,
#chat table.ban-list .hostmask,
#chat table.ban-list .banned_by,
#chat table.ban-list .banned_at {
#chat table.ban-list .banned_at,
#chat table.ignore-list .hostmask,
#chat table.ignore-list .when {
text-align: left;
}

View file

@ -38,6 +38,8 @@ const commands = [
"/expand",
"/ho",
"/hs",
"/ignore",
"/ignorelist",
"/invite",
"/join",
"/kick",
@ -64,6 +66,7 @@ const commands = [
"/slap",
"/topic",
"/unban",
"/unignore",
"/voice",
"/whois",
];

View file

@ -293,6 +293,23 @@ function addJoinItem() {
});
}
function addIgnoreListItem() {
function ignorelist(itemData) {
socket.emit("input", {
target: parseInt(itemData, 10),
text: "/ignorelist",
});
}
addContextMenuItem({
check: (target) => target.hasClass("lobby"),
className: "list",
displayName: "List ignored users",
data: (target) => target.data("id"),
callback: ignorelist,
});
}
function addDefaultItems() {
addWhoisItem();
addQueryItem();
@ -306,4 +323,5 @@ function addDefaultItems() {
addChannelListItem();
addBanListItem();
addJoinItem();
addIgnoreListItem();
}

View file

@ -52,7 +52,7 @@ function processReceivedMessage(data) {
const container = channel.find(".messages");
const activeChannelId = chat.find(".chan.active").data("id");
if (data.msg.type === "channel_list" || data.msg.type === "ban_list") {
if (data.msg.type === "channel_list" || data.msg.type === "ban_list" || data.msg.type === "ignore_list") {
$(container).empty();
}

View file

@ -0,0 +1,16 @@
<table class="ignore-list">
<thead>
<tr>
<th class="hostmask">Hostmask</th>
<th class="when">Ignored At</th>
</tr>
</thead>
<tbody>
{{#each ignored}}
<tr>
<td class="hostmask">{{hostmask}}</td>
<td class="when">{{{localetime when}}}</td>
</tr>
{{/each}}
</tbody>
</table>

View file

@ -360,6 +360,26 @@
</div>
</div>
<div class="help-item">
<div class="subject">
<code>/ignore nick</code>
</div>
<div class="description">
<p>
Block any messages from the specified user on the current network.
This can be a nickname or a hostmask.</p>
</div>
</div>
<div class="help-item">
<div class="subject">
<code>/ignorelist</code>
</div>
<div class="description">
<p>Load the list of ignored users for the current network.</p>
</div>
</div>
<div class="help-item">
<div class="subject">
<code>/join channel</code>
@ -537,6 +557,17 @@
</div>
</div>
<div class="help-item">
<div class="subject">
<code>/unignore nick</code>
</div>
<div class="description">
<p>
Unblock messages from the specified user on the current network.
This can be a nickname or a hostmask.</p>
</div>
</div>
<div class="help-item">
<div class="subject">
<code>/voice nick [...nick]</code>

View file

@ -48,6 +48,7 @@ const inputs = [
"away",
"connect",
"disconnect",
"ignore",
"invite",
"kick",
"mode",

View file

@ -33,6 +33,8 @@ const Helper = {
ip2hex,
mergeConfig,
getDefaultNick,
parseHostmask,
compareHostmask,
password: {
hash: passwordHash,
@ -226,3 +228,43 @@ function mergeConfig(oldConfig, newConfig) {
}
});
}
function parseHostmask(hostmask) {
let nick = "";
let ident = "*";
let hostname = "*";
let parts = [];
// Parse hostname first, then parse the rest
parts = hostmask.split("@");
if (parts.length >= 2) {
hostname = parts[1] || "*";
hostmask = parts[0];
}
hostname = hostname.toLowerCase();
parts = hostmask.split("!");
if (parts.length >= 2) {
ident = parts[1] || "*";
hostmask = parts[0];
}
ident = ident.toLowerCase();
nick = hostmask.toLowerCase() || "*";
const result = {
nick: nick,
ident: ident,
hostname: hostname,
};
return result;
}
function compareHostmask(a, b) {
return (a.nick.toLowerCase() === b.nick.toLowerCase() || a.nick === "*") && (a.ident.toLowerCase() === b.ident.toLowerCase() || a.ident === "*") && (a.hostname.toLowerCase() === b.hostname.toLowerCase() || a.hostname === "*");
}

View file

@ -67,6 +67,7 @@ Msg.Type = {
TOPIC_SET_BY: "topic_set_by",
WHOIS: "whois",
BANLIST: "ban_list",
IGNORELIST: "ignore_list",
};
module.exports = Msg;

View file

@ -18,6 +18,7 @@ const filteredFromClient = {
highlightRegex: true,
irc: true,
password: true,
ignoreList: true,
};
function Network(attr) {
@ -41,6 +42,7 @@ function Network(attr) {
NETWORK: "",
},
chanCache: [],
ignoreList: [],
});
if (!this.uuid) {
@ -325,6 +327,7 @@ Network.prototype.export = function() {
"commands",
"ip",
"hostname",
"ignoreList",
]);
network.channels = this.channels

View file

@ -0,0 +1,120 @@
"use strict";
const Chan = require("../../models/chan");
const Msg = require("../../models/msg");
const Helper = require("../../helper");
exports.commands = [
"ignore",
"unignore",
"ignorelist",
];
exports.input = function(network, chan, cmd, args) {
const client = this;
let target;
let hostmask;
if (cmd !== "ignorelist" && (args.length === 0 || args[0].trim().length === 0)) {
chan.pushMessage(client, new Msg({
type: Msg.Type.ERROR,
text: `Usage: /${cmd} <nick>[!ident][@host]`,
}));
return;
}
if (cmd !== "ignorelist") {
// Trim to remove any spaces from the hostmask
target = args[0].trim();
hostmask = Helper.parseHostmask(target);
}
switch (cmd) {
case "ignore": {
// IRC nicks are case insensitive
if (hostmask.nick.toLowerCase() === network.nick.toLowerCase()) {
chan.pushMessage(client, new Msg({
type: Msg.Type.ERROR,
text: "You can't ignore yourself",
}));
} else if (!network.ignoreList.some(function(entry) {
return Helper.compareHostmask(entry, hostmask);
})) {
hostmask.when = Date.now();
network.ignoreList.push(hostmask);
client.save();
chan.pushMessage(client, new Msg({
type: Msg.Type.ERROR,
text: `\u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f added to ignorelist`,
}));
} else {
chan.pushMessage(client, new Msg({
type: Msg.Type.ERROR,
text: "The specified user/hostmask is already ignored",
}));
}
break;
}
case "unignore": {
const idx = network.ignoreList.findIndex(function(entry) {
return Helper.compareHostmask(entry, hostmask);
});
// Check if the entry exists before removing it, otherwise
// let the user know.
if (idx !== -1) {
network.ignoreList.splice(idx, 1);
client.save();
chan.pushMessage(client, new Msg({
type: Msg.Type.ERROR,
text: `Successfully removed \u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f from ignorelist`,
}));
} else {
chan.pushMessage(client, new Msg({
type: Msg.Type.ERROR,
text: "The specified user/hostmask is not ignored",
}));
}
break;
}
case "ignorelist":
if (network.ignoreList.length === 0) {
chan.pushMessage(client, new Msg({
type: Msg.Type.ERROR,
text: "Ignorelist is empty",
}));
} else {
const chanName = "Ignored users";
let newChan = network.getChannel(chanName);
if (typeof newChan === "undefined") {
newChan = client.createChannel({
type: Chan.Type.SPECIAL,
name: chanName,
});
client.emit("join", {
network: network.uuid,
chan: newChan.getFilteredClone(true),
index: network.addChannel(newChan),
});
}
newChan.pushMessage(client, new Msg({
type: Msg.Type.IGNORELIST,
ignored: network.ignoreList.map((data) => ({
hostmask: `${data.nick}!${data.ident}@${data.hostname}`,
when: data.when,
})),
}), true);
}
break;
}
};

View file

@ -4,6 +4,7 @@ const Chan = require("../../models/chan");
const Msg = require("../../models/msg");
const LinkPrefetch = require("./link");
const cleanIrcMessage = require("../../../client/js/libs/handlebars/ircmessageparser/cleanIrcMessage");
const Helper = require("../../helper");
const nickRegExp = /(?:\x03[0-9]{1,2}(?:,[0-9]{1,2})?)?([\w[\]\\`^{|}-]+)/g;
module.exports = function(irc, network) {
@ -43,11 +44,20 @@ module.exports = function(irc, network) {
let showInActive = false;
const self = data.nick === irc.user.nick;
// Check if the sender is in our ignore list
const shouldIgnore = network.ignoreList.some(function(entry) {
return Helper.compareHostmask(entry, data);
});
// Server messages go to server window, no questions asked
if (data.from_server) {
chan = network.channels[0];
from = chan.getUser(data.nick);
} else {
if (shouldIgnore) {
return;
}
let target = data.target;
// If the message is targeted at us, use sender as target instead

View file

@ -47,6 +47,7 @@ describe("Network", function() {
{name: "&secure", key: "bar"},
{name: "PrivateChat", type: "query"},
],
ignoreList: [],
});
});

62
test/tests/hostmask.js Normal file
View file

@ -0,0 +1,62 @@
"use strict";
const expect = require("chai").expect;
const Helper = require("../../src/helper");
describe("Hostmask", function() {
it(".parseHostmask", function() {
expect(Helper.parseHostmask("nick").nick).to.equal("nick");
expect(Helper.parseHostmask("nick").ident).to.equal("*");
expect(Helper.parseHostmask("nick").hostname).to.equal("*");
expect(Helper.parseHostmask("!user").nick).to.equal("*");
expect(Helper.parseHostmask("!user").ident).to.equal("user");
expect(Helper.parseHostmask("!user").hostname).to.equal("*");
expect(Helper.parseHostmask("@host").nick).to.equal("*");
expect(Helper.parseHostmask("@host").ident).to.equal("*");
expect(Helper.parseHostmask("@host").hostname).to.equal("host");
expect(Helper.parseHostmask("!").nick).to.equal("*");
expect(Helper.parseHostmask("!").ident).to.equal("*");
expect(Helper.parseHostmask("!").hostname).to.equal("*");
expect(Helper.parseHostmask("@").nick).to.equal("*");
expect(Helper.parseHostmask("@").ident).to.equal("*");
expect(Helper.parseHostmask("@").hostname).to.equal("*");
expect(Helper.parseHostmask("!@").nick).to.equal("*");
expect(Helper.parseHostmask("!@").ident).to.equal("*");
expect(Helper.parseHostmask("!@").hostname).to.equal("*");
expect(Helper.parseHostmask("nick!user@host").nick).to.equal("nick");
expect(Helper.parseHostmask("nick!user@host").ident).to.equal("user");
expect(Helper.parseHostmask("nick!user@host").hostname).to.equal("host");
expect(Helper.parseHostmask("nick!!!!@thing@@host").nick).to.equal("nick");
expect(Helper.parseHostmask("nick!!!!@thing@@host").ident).to.equal("*");
expect(Helper.parseHostmask("nick!!!!@thing@@host").hostname).to.equal("thing");
expect(Helper.parseHostmask("!!!!@thing@@host").nick).to.equal("*");
expect(Helper.parseHostmask("!!!!@thing@@host").ident).to.equal("*");
expect(Helper.parseHostmask("!!!!@thing@@host").hostname).to.equal("thing");
expect(Helper.parseHostmask("NiCK!uSEr@HOST").nick).to.equal("nick");
expect(Helper.parseHostmask("NiCK!uSEr@HOST").ident).to.equal("user");
expect(Helper.parseHostmask("NiCK!uSEr@HOST").hostname).to.equal("host");
});
it(".compareHostmask (wildcard)", function() {
const a = Helper.parseHostmask("nick!user@host");
const b = Helper.parseHostmask("nick!*@*");
expect(Helper.compareHostmask(b, a)).to.be.true;
expect(Helper.compareHostmask(a, b)).to.be.false;
});
it(".compareHostmask", function() {
const a = Helper.parseHostmask("nick!user@host");
const b = Helper.parseHostmask("NiCK!useR@HOST");
expect(Helper.compareHostmask(b, a)).to.be.true;
expect(Helper.compareHostmask(a, b)).to.be.true;
});
});