diff --git a/client/js/helpers/contextMenu.js b/client/js/helpers/contextMenu.js index feadd15c..b17c3794 100644 --- a/client/js/helpers/contextMenu.js +++ b/client/js/helpers/contextMenu.js @@ -252,22 +252,30 @@ export function generateUserContextMenu($root, channel, network, user) { return items; } - // Names of the modes we are able to change - const modes = { - "~": ["owner", "q"], - "&": ["admin", "a"], - "@": ["operator", "o"], - "%": ["half-op", "h"], - "+": ["voice", "v"], + // Names of the standard modes we are able to change + const modeCharToName = { + "~": "owner", + "&": "admin", + "@": "operator", + "%": "half-op", + "+": "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 = { - revoke: (m) => `Revoke ${m[0]} (-${m[1]})`, - give: (m) => `Give ${m[0]} (+${m[1]})`, + revoke(m) { + 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. @@ -284,38 +292,38 @@ export function generateUserContextMenu($root, channel, network, user) { function compare(p1, p2) { // The modes ~ and @ can perform actions on their own mode. The others on modes below. return "~@".indexOf(p1) > -1 - ? networkModes.indexOf(p1) <= networkModes.indexOf(p2) - : networkModes.indexOf(p1) < networkModes.indexOf(p2); + ? networkModeSymbols.indexOf(p1) <= networkModeSymbols.indexOf(p2) + : networkModeSymbols.indexOf(p1) < networkModeSymbols.indexOf(p2); } - networkModes.forEach((prefix) => { - if (!compare(currentChannelUser.modes[0], prefix)) { + network.serverOptions.PREFIX.prefix.forEach((mode) => { + if (!compare(currentChannelUser.modes[0], mode.symbol)) { // Our highest mode is below the current mode. Bail. 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. items.push({ - label: modeTextTemplate.give(modes[prefix]), + label: modeTextTemplate.give(mode), type: "item", class: "action-set-mode", action() { socket.emit("input", { target: channel.id, - text: "/mode +" + modes[prefix][1] + " " + user.nick, + text: "/mode +" + mode.mode + " " + user.nick, }); }, }); } else { items.push({ - label: modeTextTemplate.revoke(modes[prefix]), + label: modeTextTemplate.revoke(mode), type: "item", class: "action-revoke-mode", action() { socket.emit("input", { 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. - 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])) { - // Check if the target user has no mode or a mode lower than ours. items.push({ label: "Kick", type: "item", diff --git a/client/js/helpers/parse.js b/client/js/helpers/parse.js index 9097d96f..675dd0b0 100644 --- a/client/js/helpers/parse.js +++ b/client/js/helpers/parse.js @@ -79,7 +79,7 @@ function parse(createElement, text, message = undefined, network = undefined) { // arrays of objects containing start and end markers, as well as metadata // depending on what was found (channel or link). 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 linkParts = findLinks(cleanText); const emojiParts = findEmoji(cleanText); diff --git a/src/models/network.js b/src/models/network.js index 13879fb2..20e58752 100644 --- a/src/models/network.js +++ b/src/models/network.js @@ -5,6 +5,7 @@ const {v4: uuidv4} = require("uuid"); const IrcFramework = require("irc-framework"); const Chan = require("./chan"); const Msg = require("./msg"); +const Prefix = require("./prefix"); const Helper = require("../helper"); const STSPolicies = require("../plugins/sts"); const ClientCertificate = require("../plugins/clientCertificate"); @@ -43,7 +44,12 @@ function Network(attr) { irc: null, serverOptions: { CHANTYPES: ["#", "&"], - PREFIX: ["!", "@", "%", "+"], + PREFIX: new Prefix([ + {symbol: "!", mode: "Y"}, + {symbol: "@", mode: "o"}, + {symbol: "%", mode: "h"}, + {symbol: "+", mode: "v"}, + ]), NETWORK: "", }, diff --git a/src/models/prefix.js b/src/models/prefix.js new file mode 100644 index 00000000..331efdff --- /dev/null +++ b/src/models/prefix.js @@ -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; diff --git a/src/models/user.js b/src/models/user.js index 591ebddd..dee1e9d0 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -4,7 +4,7 @@ const _ = require("lodash"); module.exports = User; -function User(attr, prefixLookup) { +function User(attr, prefix) { _.defaults(this, attr, { modes: [], 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 - this.modes = modes.map((mode) => prefixLookup[mode]); + this.modes = modes.map((mode) => prefix.modeToSymbol[mode]); }; User.prototype.toJSON = function () { diff --git a/src/plugins/irc-events/connection.js b/src/plugins/irc-events/connection.js index 029eefc2..3ef3000a 100644 --- a/src/plugins/irc-events/connection.js +++ b/src/plugins/irc-events/connection.js @@ -63,10 +63,9 @@ module.exports = function (irc, network) { }); irc.on("socket connected", function () { - network.prefixLookup = {}; - irc.network.options.PREFIX.forEach(function (mode) { - network.prefixLookup[mode.mode] = mode.symbol; - }); + if (irc.network.options.PREFIX) { + network.serverOptions.PREFIX.update(irc.network.options.PREFIX); + } network.channels[0].pushMessage( client, @@ -197,20 +196,12 @@ module.exports = function (irc, network) { }); irc.on("server options", function (data) { - network.prefixLookup = {}; - - data.options.PREFIX.forEach((mode) => { - network.prefixLookup[mode.mode] = mode.symbol; - }); + network.serverOptions.PREFIX.update(data.options.PREFIX); if (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; client.emit("network:options", { diff --git a/src/plugins/irc-events/mode.js b/src/plugins/irc-events/mode.js index b07f0954..9c9bf6b2 100644 --- a/src/plugins/irc-events/mode.js +++ b/src/plugins/irc-events/mode.js @@ -107,7 +107,7 @@ module.exports = function (irc, network) { return; } - const changedMode = network.prefixLookup[char]; + const changedMode = network.serverOptions.PREFIX.modeToSymbol[char]; if (!add) { _.pull(user.modes, changedMode); diff --git a/src/plugins/irc-events/names.js b/src/plugins/irc-events/names.js index 2e0a7db8..8368b281 100644 --- a/src/plugins/irc-events/names.js +++ b/src/plugins/irc-events/names.js @@ -14,7 +14,7 @@ module.exports = function (irc, network) { data.users.forEach((user) => { 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); }); diff --git a/test/models/chan.js b/test/models/chan.js index 33ee41e5..613bece3 100644 --- a/test/models/chan.js +++ b/test/models/chan.js @@ -20,10 +20,10 @@ describe("Chan", function () { }, }; - const prefixLookup = {}; + const prefixLookup = {modeToSymbol: {}}; network.network.options.PREFIX.forEach((mode) => { - prefixLookup[mode.mode] = mode.symbol; + prefixLookup.modeToSymbol[mode.mode] = mode.symbol; }); describe("#findMessage(id)", function () { diff --git a/test/models/msg.js b/test/models/msg.js index 3c7d59b7..7690754a 100644 --- a/test/models/msg.js +++ b/test/models/msg.js @@ -8,7 +8,7 @@ const User = require("../../src/models/user"); describe("Msg", function () { ["from", "target"].forEach((prop) => { 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( { modes: ["o"],