From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/chat/protocols/matrix/matrixCommands.sys.mjs | 490 ++++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 comm/chat/protocols/matrix/matrixCommands.sys.mjs (limited to 'comm/chat/protocols/matrix/matrixCommands.sys.mjs') diff --git a/comm/chat/protocols/matrix/matrixCommands.sys.mjs b/comm/chat/protocols/matrix/matrixCommands.sys.mjs new file mode 100644 index 0000000000..068ef684bb --- /dev/null +++ b/comm/chat/protocols/matrix/matrixCommands.sys.mjs @@ -0,0 +1,490 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { l10nHelper } from "resource:///modules/imXPCOMUtils.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyGetter(lazy, "_", () => + l10nHelper("chrome://chat/locale/matrix.properties") +); + +ChromeUtils.defineESModuleGetters(lazy, { + MatrixPowerLevels: "resource:///modules/matrixPowerLevels.sys.mjs", + MatrixSDK: "resource:///modules/matrix-sdk.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "EVENT_TO_STRING", () => ({ + ban: "powerLevel.ban", + [lazy.MatrixSDK.EventType.RoomAvatar]: "powerLevel.roomAvatar", + [lazy.MatrixSDK.EventType.RoomCanonicalAlias]: "powerLevel.mainAddress", + [lazy.MatrixSDK.EventType.RoomHistoryVisibility]: "powerLevel.history", + [lazy.MatrixSDK.EventType.RoomName]: "powerLevel.roomName", + [lazy.MatrixSDK.EventType.RoomPowerLevels]: "powerLevel.changePermissions", + [lazy.MatrixSDK.EventType.RoomServerAcl]: "powerLevel.server_acl", + [lazy.MatrixSDK.EventType.RoomTombstone]: "powerLevel.upgradeRoom", + invite: "powerLevel.inviteUser", + kick: "powerLevel.kickUsers", + redact: "powerLevel.remove", + state_default: "powerLevel.state_default", + users_default: "powerLevel.defaultRole", + events_default: "powerLevel.events_default", + [lazy.MatrixSDK.EventType.RoomEncryption]: "powerLevel.encryption", + [lazy.MatrixSDK.EventType.RoomTopic]: "powerLevel.topic", +})); + +// Commands from element that we're not yet supporting (including equivalents): +// - /nick (no proper display name change support in matrix.jsm yet) +// - /myroomnick +// - /roomavatar [] +// - /myroomavatar [] +// - /myavatar [] +// - /ignore (kind of available, but not matrix level ignores) +// - /unignore +// - /whois +// - /converttodm +// - /converttoroom +// - /upgraderoom + +function getConv(conv) { + return conv.wrappedJSObject; +} + +function getAccount(conv) { + return getConv(conv)._account; +} + +/** + * Generates a string representing the required power level to send an event + * in a room. + * + * @param {string} eventType - Matrix event type. + * @param {number} userPower - Power level required to send the events. + * @returns {string} Human readable representation of the event type and its + * required power level. + */ +function getEventString(eventType, userPower) { + if (lazy.EVENT_TO_STRING.hasOwnProperty(eventType)) { + return lazy._(lazy.EVENT_TO_STRING[eventType], userPower); + } + return null; +} + +/** + * Lists out many room details, like aliases and permissions, as notices in + * their room. + * + * @param {imIAccount} account - Account of the room. + * @param {prplIConversation} conv - Conversation to list details for. + */ +function publishRoomDetails(account, conv) { + let roomState = conv.roomState; + let powerLevelEvent = roomState.getStateEvents( + lazy.MatrixSDK.EventType.RoomPowerLevels, + "" + ); + let room = conv.room; + + let name = room.name; + let nameString = lazy._("detail.name", name); + conv.writeMessage(account.userId, nameString, { + system: true, + }); + + let roomId = room.roomId; + let roomIdString = lazy._("detail.roomId", roomId); + conv.writeMessage(account.userId, roomIdString, { + system: true, + }); + + let roomVersion = room.getVersion(); + let versionString = lazy._("detail.version", roomVersion); + conv.writeMessage(account.userId, versionString, { + system: true, + }); + + let topic = null; + if (roomState.getStateEvents(lazy.MatrixSDK.EventType.RoomTopic)?.length) { + topic = roomState + .getStateEvents(lazy.MatrixSDK.EventType.RoomTopic)[0] + .getContent().topic; + } + let topicString = lazy._("detail.topic", topic); + conv.writeMessage(account.userId, topicString, { + system: true, + }); + + let guestAccess = roomState + .getStateEvents(lazy.MatrixSDK.EventType.RoomGuestAccess, "") + ?.getContent()?.guest_access; + let guestAccessString = lazy._("detail.guest", guestAccess); + conv.writeMessage(account.userId, guestAccessString, { + system: true, + }); + + let admins = []; + let moderators = []; + + let powerLevel = powerLevelEvent.getContent(); + for (let [key, value] of Object.entries(powerLevel.users)) { + if (value >= lazy.MatrixPowerLevels.admin) { + admins.push(key); + } else if (value >= lazy.MatrixPowerLevels.moderator) { + moderators.push(key); + } + } + + if (admins.length) { + let adminString = lazy._("detail.admin", admins.join(", ")); + conv.writeMessage(account.userId, adminString, { + system: true, + }); + } + + if (moderators.length) { + let moderatorString = lazy._("detail.moderator", moderators.join(", ")); + conv.writeMessage(account.userId, moderatorString, { + system: true, + }); + } + + if ( + roomState.getStateEvents(lazy.MatrixSDK.EventType.RoomCanonicalAlias) + ?.length + ) { + let canonicalAlias = room.getCanonicalAlias(); + let aliases = room.getAltAliases(); + if (canonicalAlias && !aliases.includes(canonicalAlias)) { + aliases.unshift(canonicalAlias); + } + if (aliases.length) { + let aliasString = lazy._("detail.alias", aliases.join(",")); + conv.writeMessage(account.userId, aliasString, { + system: true, + }); + } + } + + conv.writeMessage(account.userId, lazy._("detail.power"), { + system: true, + }); + + const defaultLevel = lazy.MatrixPowerLevels.getUserDefaultLevel(powerLevel); + for (let [key, value] of Object.entries(powerLevel)) { + if (key == "users") { + continue; + } + if (key == "events") { + for (let [userKey, userValue] of Object.entries(powerLevel.events)) { + let userPower = lazy.MatrixPowerLevels.toText(userValue, defaultLevel); + let powerString = getEventString(userKey, userPower); + if (!powerString) { + continue; + } + conv.writeMessage(account.userId, powerString, { + system: true, + }); + } + continue; + } + let userPower = lazy.MatrixPowerLevels.toText(value, defaultLevel); + let powerString = getEventString(key, userPower); + if (!powerString) { + continue; + } + conv.writeMessage(account.userId, powerString, { + system: true, + }); + } +} + +/** + * Generic command handler for commands with up to 2 params. + * + * @param {(imIAccount, prplIConversation, string[]) => boolean} commandCallback - Command handler implementation. Returns true when successful. + * @param {number} parameterCount - Number of parameters. Maximum 2. + * @param {object} [options] - Extra options. + * @param {number} [options.requiredCount] - How many of the parameters are required (from the start). + * @param {(string[]) => boolean} [options.validateParams] - Validator function for params. + * @param {(prplIConversation, string[]) => any[]} [options.formatParams] - Formatting function for params. + * @returns {(string, imIConversation) => boolean} Command handler function that returns true when the command was handled. + */ +function runCommand( + commandCallback, + parameterCount, + { + requiredCount = parameterCount, + validateParams = params => true, + formatParams = (conv, params) => [conv._roomId, ...params], + } = {} +) { + if (parameterCount > 2) { + throw new Error("Can not handle more than two parameters"); + } + return (msg, convObj) => { + // Parse msg into the given parameter count + let params = []; + const trimmedMsg = msg.trim(); + if (parameterCount === 0) { + if (trimmedMsg) { + return false; + } + } else if (parameterCount === 1) { + if (!trimmedMsg.length && requiredCount > 0) { + return false; + } + params.push(trimmedMsg); + } else if (parameterCount === 2) { + if ( + (!trimmedMsg.length && requiredCount > 0) || + (!trimmedMsg.includes(" ") && requiredCount > 1) + ) { + return false; + } + const separatorIndex = trimmedMsg.indexOf(" "); + if (separatorIndex > 0) { + params.push( + trimmedMsg.slice(0, separatorIndex), + trimmedMsg.slice(separatorIndex + 1) + ); + } else { + params.push(trimmedMsg); + } + } + + if (!validateParams(params)) { + return false; + } + + const account = getAccount(convObj); + const conv = getConv(convObj); + params = formatParams(conv, params); + return commandCallback(account, conv, params); + }; +} + +/** + * Generic command handler that calls a matrix JS client method. First param + * is always the roomId. + * + * @param {string} clientMethod - Name of the method on the matrix client. + * @param {number} parameterCount - Number of parameters. Maximum 2. + * @param {object} [options] - Extra options. + * @param {number} [options.requiredCount] - How many of the parameters are required (from the start). + * @param {(string[]) => boolean} [options.validateParams] - Validator function for params. + * @param {(prplIConversation, string[]) => any[]} [options.formatParams] - Formatting function for params. + * @returns {(string, imIConversation) => boolean} Command handler function that returns true when the command was handled. + */ +function clientCommand(clientMethod, parameterCount, options) { + return runCommand( + (account, conv, params) => { + account._client[clientMethod](...params).catch(error => { + conv.writeMessage(account.userId, error.message, { + system: true, + error: true, + }); + }); + return true; + }, + parameterCount, + options + ); +} + +export var commands = [ + { + name: "ban", + get helpString() { + return lazy._("command.ban", "ban"); + }, + run: clientCommand("ban", 2, { requiredCount: 1 }), + }, + { + name: "unban", + get helpString() { + return lazy._("command.unban", "unban"); + }, + run: clientCommand("unban", 1), + }, + { + name: "invite", + get helpString() { + return lazy._("command.invite", "invite"); + }, + usageContext: Ci.imICommand.CMD_CONTEXT_CHAT, + run: clientCommand("invite", 1), + }, + { + name: "kick", + get helpString() { + return lazy._("command.kick", "kick"); + }, + run: clientCommand("kick", 2, { requiredCount: 1 }), + }, + { + name: "op", + get helpString() { + return lazy._("command.op", "op"); + }, + usageContext: Ci.imICommand.CMD_CONTEXT_CHAT, + run: clientCommand("setPowerLevel", 2, { + validateParams([userId, powerLevelString]) { + const powerLevel = Number.parseInt(powerLevelString); + return ( + Number.isInteger(powerLevel) && + powerLevel >= lazy.MatrixPowerLevels.user + ); + }, + formatParams(conv, [userId, powerLevelString]) { + const powerLevel = Number.parseInt(powerLevelString); + let powerLevelEvent = conv.roomState.getStateEvents( + lazy.MatrixSDK.EventType.RoomPowerLevels, + "" + ); + return [conv._roomId, userId, powerLevel, powerLevelEvent]; + }, + }), + }, + { + name: "deop", + get helpString() { + return lazy._("command.deop", "deop"); + }, + usageContext: Ci.imICommand.CMD_CONTEXT_CHAT, + run: clientCommand("setPowerLevel", 1, { + formatParams(conv, [userId]) { + const powerLevelEvent = conv.roomState.getStateEvents( + lazy.MatrixSDK.EventType.RoomPowerLevels, + "" + ); + return [ + conv._roomId, + userId, + lazy.MatrixPowerLevels.user, + powerLevelEvent, + ]; + }, + }), + }, + { + name: "part", + get helpString() { + return lazy._("command.leave", "part"); + }, + run: clientCommand("leave", 0), + }, + { + name: "topic", + get helpString() { + return lazy._("command.topic", "topic"); + }, + run: runCommand((account, conv, [roomId, topic]) => { + conv.topic = topic; + return true; + }, 1), + }, + { + name: "visibility", + get helpString() { + return lazy._("command.visibility", "visibility"); + }, + run: clientCommand("setRoomDirectoryVisibility", 1, { + formatParams(conv, [visibilityString]) { + const visibility = + Number.parseInt(visibilityString) === 1 + ? lazy.MatrixSDK.Visibility.Public + : lazy.MatrixSDK.Visibility.Private; + return [conv._roomId, visibility]; + }, + }), + }, + { + name: "roomname", + get helpString() { + return lazy._("command.roomname", "roomname"); + }, + run: clientCommand("setRoomName", 1), + }, + { + name: "detail", + get helpString() { + return lazy._("command.detail", "detail"); + }, + run(msg, convObj, returnedConv) { + let account = getAccount(convObj); + let conv = getConv(convObj); + publishRoomDetails(account, conv); + return true; + }, + }, + { + name: "addalias", + get helpString() { + return lazy._("command.addalias", "addalias"); + }, + run: clientCommand("createAlias", 1, { + formatParams(conv, [alias]) { + return [alias, conv._roomId]; + }, + }), + }, + { + name: "removealias", + get helpString() { + return lazy._("command.removealias", "removealias"); + }, + run: clientCommand("deleteAlias", 1, { + formatParams(conv, [alias]) { + return [alias]; + }, + }), + }, + { + name: "me", + get helpString() { + return lazy._("command.me", "me"); + }, + run: runCommand((account, conv, [roomId, message]) => { + conv.sendMsg(message, true); + return true; + }, 1), + }, + { + name: "msg", + get helpString() { + return lazy._("command.msg", "msg"); + }, + run: runCommand((account, conv, [roomId, userId, message]) => { + const room = account.getDirectConversation(userId); + if (room) { + room.waitForRoom().then(readyRoom => { + readyRoom.sendMsg(message); + }); + } else { + account.ERROR("Could not create room for direct message to " + userId); + } + return true; + }, 2), + }, + { + name: "join", + get helpString() { + return lazy._("command.join", "join"); + }, + run: runCommand( + (account, conv, [currentRoomId, joinRoomId]) => { + account.getGroupConversation(joinRoomId); + return true; + }, + 1, + { + validateParams([roomId]) { + // TODO support joining rooms without a human readable ID. + return roomId.startsWith("#"); + }, + } + ), + }, +]; -- cgit v1.2.3