Properly track user modes for context menu (#4267)
* properly track user modes for context menu The RPL_ISUPPORT response contains a PREFIX element, which not only tracks the prefix chars ("@", "+" etc) but also their corresponding mode chars (+O, +v) This commit changes the context menu to not rely on a hardcoded list but rather user the one given in the prefix response by the server. Co-authored-by: Max Leiter <maxwell.leiter@gmail.com>
This commit is contained in:
parent
03d38812e3
commit
8fcd079204
@ -252,22 +252,30 @@ export function generateUserContextMenu($root, channel, network, user) {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Names of the modes we are able to change
|
// Names of the standard modes we are able to change
|
||||||
const modes = {
|
const modeCharToName = {
|
||||||
"~": ["owner", "q"],
|
"~": "owner",
|
||||||
"&": ["admin", "a"],
|
"&": "admin",
|
||||||
"@": ["operator", "o"],
|
"@": "operator",
|
||||||
"%": ["half-op", "h"],
|
"%": "half-op",
|
||||||
"+": ["voice", "v"],
|
"+": "voice",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Labels for the mode changes. For example .rev(['admin', 'a']) => 'Revoke admin (-a)'
|
// Labels for the mode changes. For example .rev({mode: "a", symbol: "&"}) => 'Revoke admin (-a)'
|
||||||
const modeTextTemplate = {
|
const modeTextTemplate = {
|
||||||
revoke: (m) => `Revoke ${m[0]} (-${m[1]})`,
|
revoke(m) {
|
||||||
give: (m) => `Give ${m[0]} (+${m[1]})`,
|
const name = modeCharToName[m.symbol];
|
||||||
|
const res = name ? `Revoke ${name} (-${m.mode})` : `Mode -${m.mode}`;
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
give(m) {
|
||||||
|
const name = modeCharToName[m.symbol];
|
||||||
|
const res = name ? `Give ${name} (+${m.mode})` : `Mode +${m.mode}`;
|
||||||
|
return res;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const networkModes = network.serverOptions.PREFIX;
|
const networkModeSymbols = network.serverOptions.PREFIX.symbols;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether the prefix of mode p1 has access to perform actions on p2.
|
* Determine whether the prefix of mode p1 has access to perform actions on p2.
|
||||||
@ -284,38 +292,38 @@ export function generateUserContextMenu($root, channel, network, user) {
|
|||||||
function compare(p1, p2) {
|
function compare(p1, p2) {
|
||||||
// The modes ~ and @ can perform actions on their own mode. The others on modes below.
|
// The modes ~ and @ can perform actions on their own mode. The others on modes below.
|
||||||
return "~@".indexOf(p1) > -1
|
return "~@".indexOf(p1) > -1
|
||||||
? networkModes.indexOf(p1) <= networkModes.indexOf(p2)
|
? networkModeSymbols.indexOf(p1) <= networkModeSymbols.indexOf(p2)
|
||||||
: networkModes.indexOf(p1) < networkModes.indexOf(p2);
|
: networkModeSymbols.indexOf(p1) < networkModeSymbols.indexOf(p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
networkModes.forEach((prefix) => {
|
network.serverOptions.PREFIX.prefix.forEach((mode) => {
|
||||||
if (!compare(currentChannelUser.modes[0], prefix)) {
|
if (!compare(currentChannelUser.modes[0], mode.symbol)) {
|
||||||
// Our highest mode is below the current mode. Bail.
|
// Our highest mode is below the current mode. Bail.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.modes.includes(prefix)) {
|
if (!user.modes.includes(mode.symbol)) {
|
||||||
// The target doesn't already have this mode, therefore we can set it.
|
// The target doesn't already have this mode, therefore we can set it.
|
||||||
items.push({
|
items.push({
|
||||||
label: modeTextTemplate.give(modes[prefix]),
|
label: modeTextTemplate.give(mode),
|
||||||
type: "item",
|
type: "item",
|
||||||
class: "action-set-mode",
|
class: "action-set-mode",
|
||||||
action() {
|
action() {
|
||||||
socket.emit("input", {
|
socket.emit("input", {
|
||||||
target: channel.id,
|
target: channel.id,
|
||||||
text: "/mode +" + modes[prefix][1] + " " + user.nick,
|
text: "/mode +" + mode.mode + " " + user.nick,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
items.push({
|
items.push({
|
||||||
label: modeTextTemplate.revoke(modes[prefix]),
|
label: modeTextTemplate.revoke(mode),
|
||||||
type: "item",
|
type: "item",
|
||||||
class: "action-revoke-mode",
|
class: "action-revoke-mode",
|
||||||
action() {
|
action() {
|
||||||
socket.emit("input", {
|
socket.emit("input", {
|
||||||
target: channel.id,
|
target: channel.id,
|
||||||
text: "/mode -" + modes[prefix][1] + " " + user.nick,
|
text: "/mode -" + mode.mode + " " + user.nick,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -323,9 +331,9 @@ export function generateUserContextMenu($root, channel, network, user) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Determine if we are half-op or op depending on the network modes so we can kick.
|
// Determine if we are half-op or op depending on the network modes so we can kick.
|
||||||
if (!compare(networkModes.indexOf("%") > -1 ? "%" : "@", currentChannelUser.modes[0])) {
|
if (!compare(networkModeSymbols.indexOf("%") > -1 ? "%" : "@", currentChannelUser.modes[0])) {
|
||||||
|
// Check if the target user has no mode or a mode lower than ours.
|
||||||
if (user.modes.length === 0 || compare(currentChannelUser.modes[0], user.modes[0])) {
|
if (user.modes.length === 0 || compare(currentChannelUser.modes[0], user.modes[0])) {
|
||||||
// Check if the target user has no mode or a mode lower than ours.
|
|
||||||
items.push({
|
items.push({
|
||||||
label: "Kick",
|
label: "Kick",
|
||||||
type: "item",
|
type: "item",
|
||||||
|
@ -79,7 +79,7 @@ function parse(createElement, text, message = undefined, network = undefined) {
|
|||||||
// arrays of objects containing start and end markers, as well as metadata
|
// arrays of objects containing start and end markers, as well as metadata
|
||||||
// depending on what was found (channel or link).
|
// depending on what was found (channel or link).
|
||||||
const channelPrefixes = network ? network.serverOptions.CHANTYPES : ["#", "&"];
|
const channelPrefixes = network ? network.serverOptions.CHANTYPES : ["#", "&"];
|
||||||
const userModes = network ? network.serverOptions.PREFIX : ["!", "@", "%", "+"];
|
const userModes = network?.serverOptions?.PREFIX.symbols || ["!", "@", "%", "+"];
|
||||||
const channelParts = findChannels(cleanText, channelPrefixes, userModes);
|
const channelParts = findChannels(cleanText, channelPrefixes, userModes);
|
||||||
const linkParts = findLinks(cleanText);
|
const linkParts = findLinks(cleanText);
|
||||||
const emojiParts = findEmoji(cleanText);
|
const emojiParts = findEmoji(cleanText);
|
||||||
|
@ -5,6 +5,7 @@ const {v4: uuidv4} = require("uuid");
|
|||||||
const IrcFramework = require("irc-framework");
|
const IrcFramework = require("irc-framework");
|
||||||
const Chan = require("./chan");
|
const Chan = require("./chan");
|
||||||
const Msg = require("./msg");
|
const Msg = require("./msg");
|
||||||
|
const Prefix = require("./prefix");
|
||||||
const Helper = require("../helper");
|
const Helper = require("../helper");
|
||||||
const STSPolicies = require("../plugins/sts");
|
const STSPolicies = require("../plugins/sts");
|
||||||
const ClientCertificate = require("../plugins/clientCertificate");
|
const ClientCertificate = require("../plugins/clientCertificate");
|
||||||
@ -43,7 +44,12 @@ function Network(attr) {
|
|||||||
irc: null,
|
irc: null,
|
||||||
serverOptions: {
|
serverOptions: {
|
||||||
CHANTYPES: ["#", "&"],
|
CHANTYPES: ["#", "&"],
|
||||||
PREFIX: ["!", "@", "%", "+"],
|
PREFIX: new Prefix([
|
||||||
|
{symbol: "!", mode: "Y"},
|
||||||
|
{symbol: "@", mode: "o"},
|
||||||
|
{symbol: "%", mode: "h"},
|
||||||
|
{symbol: "+", mode: "v"},
|
||||||
|
]),
|
||||||
NETWORK: "",
|
NETWORK: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
33
src/models/prefix.js
Normal file
33
src/models/prefix.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
class Prefix {
|
||||||
|
constructor(prefix) {
|
||||||
|
this.prefix = prefix || []; // [{symbol: "@", mode: "o"}, ... ]
|
||||||
|
this.modeToSymbol = {};
|
||||||
|
this.symbols = [];
|
||||||
|
this._update_internals();
|
||||||
|
}
|
||||||
|
|
||||||
|
_update_internals() {
|
||||||
|
// clean out the old cruft
|
||||||
|
this.modeToSymbol = {};
|
||||||
|
this.symbols = [];
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
this.prefix.forEach(function (p) {
|
||||||
|
that.modeToSymbol[p.mode] = p.symbol;
|
||||||
|
that.symbols.push(p.symbol);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(prefix) {
|
||||||
|
this.prefix = prefix || [];
|
||||||
|
this._update_internals();
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(f) {
|
||||||
|
return this.prefix.forEach(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Prefix;
|
@ -4,7 +4,7 @@ const _ = require("lodash");
|
|||||||
|
|
||||||
module.exports = User;
|
module.exports = User;
|
||||||
|
|
||||||
function User(attr, prefixLookup) {
|
function User(attr, prefix) {
|
||||||
_.defaults(this, attr, {
|
_.defaults(this, attr, {
|
||||||
modes: [],
|
modes: [],
|
||||||
away: "",
|
away: "",
|
||||||
@ -18,12 +18,12 @@ function User(attr, prefixLookup) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setModes(this.modes, prefixLookup);
|
this.setModes(this.modes, prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
User.prototype.setModes = function (modes, prefixLookup) {
|
User.prototype.setModes = function (modes, prefix) {
|
||||||
// irc-framework sets character mode, but The Lounge works with symbols
|
// irc-framework sets character mode, but The Lounge works with symbols
|
||||||
this.modes = modes.map((mode) => prefixLookup[mode]);
|
this.modes = modes.map((mode) => prefix.modeToSymbol[mode]);
|
||||||
};
|
};
|
||||||
|
|
||||||
User.prototype.toJSON = function () {
|
User.prototype.toJSON = function () {
|
||||||
|
@ -63,10 +63,9 @@ module.exports = function (irc, network) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
irc.on("socket connected", function () {
|
irc.on("socket connected", function () {
|
||||||
network.prefixLookup = {};
|
if (irc.network.options.PREFIX) {
|
||||||
irc.network.options.PREFIX.forEach(function (mode) {
|
network.serverOptions.PREFIX.update(irc.network.options.PREFIX);
|
||||||
network.prefixLookup[mode.mode] = mode.symbol;
|
}
|
||||||
});
|
|
||||||
|
|
||||||
network.channels[0].pushMessage(
|
network.channels[0].pushMessage(
|
||||||
client,
|
client,
|
||||||
@ -197,20 +196,12 @@ module.exports = function (irc, network) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
irc.on("server options", function (data) {
|
irc.on("server options", function (data) {
|
||||||
network.prefixLookup = {};
|
network.serverOptions.PREFIX.update(data.options.PREFIX);
|
||||||
|
|
||||||
data.options.PREFIX.forEach((mode) => {
|
|
||||||
network.prefixLookup[mode.mode] = mode.symbol;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.options.CHANTYPES) {
|
if (data.options.CHANTYPES) {
|
||||||
network.serverOptions.CHANTYPES = data.options.CHANTYPES;
|
network.serverOptions.CHANTYPES = data.options.CHANTYPES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (network.serverOptions.PREFIX) {
|
|
||||||
network.serverOptions.PREFIX = data.options.PREFIX.map((p) => p.symbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
network.serverOptions.NETWORK = data.options.NETWORK;
|
network.serverOptions.NETWORK = data.options.NETWORK;
|
||||||
|
|
||||||
client.emit("network:options", {
|
client.emit("network:options", {
|
||||||
|
@ -107,7 +107,7 @@ module.exports = function (irc, network) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changedMode = network.prefixLookup[char];
|
const changedMode = network.serverOptions.PREFIX.modeToSymbol[char];
|
||||||
|
|
||||||
if (!add) {
|
if (!add) {
|
||||||
_.pull(user.modes, changedMode);
|
_.pull(user.modes, changedMode);
|
||||||
|
@ -14,7 +14,7 @@ module.exports = function (irc, network) {
|
|||||||
|
|
||||||
data.users.forEach((user) => {
|
data.users.forEach((user) => {
|
||||||
const newUser = chan.getUser(user.nick);
|
const newUser = chan.getUser(user.nick);
|
||||||
newUser.setModes(user.modes, network.prefixLookup);
|
newUser.setModes(user.modes, network.serverOptions.PREFIX);
|
||||||
|
|
||||||
newUsers.set(user.nick.toLowerCase(), newUser);
|
newUsers.set(user.nick.toLowerCase(), newUser);
|
||||||
});
|
});
|
||||||
|
@ -20,10 +20,10 @@ describe("Chan", function () {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const prefixLookup = {};
|
const prefixLookup = {modeToSymbol: {}};
|
||||||
|
|
||||||
network.network.options.PREFIX.forEach((mode) => {
|
network.network.options.PREFIX.forEach((mode) => {
|
||||||
prefixLookup[mode.mode] = mode.symbol;
|
prefixLookup.modeToSymbol[mode.mode] = mode.symbol;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#findMessage(id)", function () {
|
describe("#findMessage(id)", function () {
|
||||||
|
@ -8,7 +8,7 @@ const User = require("../../src/models/user");
|
|||||||
describe("Msg", function () {
|
describe("Msg", function () {
|
||||||
["from", "target"].forEach((prop) => {
|
["from", "target"].forEach((prop) => {
|
||||||
it(`should keep a copy of the original user in the \`${prop}\` property`, function () {
|
it(`should keep a copy of the original user in the \`${prop}\` property`, function () {
|
||||||
const prefixLookup = {a: "&", o: "@"};
|
const prefixLookup = {modeToSymbol: {a: "&", o: "@"}};
|
||||||
const user = new User(
|
const user = new User(
|
||||||
{
|
{
|
||||||
modes: ["o"],
|
modes: ["o"],
|
||||||
|
Loading…
Reference in New Issue
Block a user