diff options
Diffstat (limited to 'comm/chat/protocols/matrix/matrixTextForEvent.sys.mjs')
-rw-r--r-- | comm/chat/protocols/matrix/matrixTextForEvent.sys.mjs | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/comm/chat/protocols/matrix/matrixTextForEvent.sys.mjs b/comm/chat/protocols/matrix/matrixTextForEvent.sys.mjs new file mode 100644 index 0000000000..2288f66ea5 --- /dev/null +++ b/comm/chat/protocols/matrix/matrixTextForEvent.sys.mjs @@ -0,0 +1,330 @@ +/* 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"; +import { MatrixSDK } from "resource:///modules/matrix-sdk.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyGetter(lazy, "_", () => + l10nHelper("chrome://chat/locale/matrix.properties") +); + +ChromeUtils.defineESModuleGetters(lazy, { + MatrixPowerLevels: "resource:///modules/matrixPowerLevels.sys.mjs", +}); + +/** + * Shared handler for verification requests. We need it twice because the + * request can be the msgtype of a normal message or its own event. + * + * @param {MatrixEvent} matrixEvent - Matrix Event this is handling. + * @param {{sender: string, content: object}} param1 - handler context. + * @returns {string} + */ +const keyVerificationRequest = (matrixEvent, { sender, content }) => { + return lazy._("message.verification.request2", sender, content.to); +}; +/** + * Shared handler for room messages, since those come in the plain text and + * encrypted form. + */ +const roomMessage = { + pivot: "msgtype", + handlers: { + [MatrixSDK.MsgType.KeyVerificationRequest]: keyVerificationRequest, + "m.bad.encrypted": () => lazy._("message.decryptionError"), + }, +}; + +/** + * Functions returning notices to display when matrix events are received. + * Top level key is the matrix event type. + * + * If the object then has a "pivot" key, the value of that key is used to get + * the value of the key with that name from the event content. This value from + * event content picks the handler from handlers. + * + * If there is no pivot, the method on the key "handler" is called. + * + * Handlers are called with the following arguments: + * - matrixEvent: MatrixEvent + * - context: { + * sender: user ID of the sender, + * content: event content object, + * } + * A handler is expected to return a string that is displayed as notice. If + * nothing is returned, no notice will be shown. The formatContext function + * optionally adds values to the context argument for the handler. + */ +const MATRIX_EVENT_HANDLERS = { + [MatrixSDK.EventType.RoomMember]: { + pivot: "membership", + formatContext(matrixEvent, { sender, content }) { + return { + sender, + content, + target: matrixEvent.target, + prevContent: matrixEvent.getPrevContent(), + reason: content.reason, + withReasonKey: content.reason ? "WithReason" : "", + }; + }, + handlers: { + ban(matrixEvent, { sender, target, reason, withReasonKey }) { + return lazy._( + "message.banned" + withReasonKey, + sender, + target.userId, + reason + ); + }, + invite(matrixEvent, { sender, content, target }) { + const thirdPartyInvite = content.third_party_invite; + if (thirdPartyInvite) { + if (thirdPartyInvite.display_name) { + return lazy._( + "message.acceptedInviteFor", + target.userId, + thirdPartyInvite.display_name + ); + } + return lazy._("message.acceptedInvite", target.userId); + } + return lazy._("message.invited", sender, target.userId); + }, + join(matrixEvent, { sender, content, prevContent, target }) { + if (prevContent && prevContent.membership == "join") { + if ( + prevContent.displayname && + content.displayname && + prevContent.displayname != content.displayname + ) { + return lazy._( + "message.displayName.changed", + sender, + prevContent.displayname, + content.displayname + ); + } else if (!prevContent.displayname && content.displayname) { + return lazy._( + "message.displayName.set", + sender, + content.displayname + ); + } else if (prevContent.displayname && !content.displayname) { + return lazy._( + "message.displayName.remove", + sender, + prevContent.displayname + ); + } + return null; + } + return lazy._("message.joined", target.userId); + }, + leave( + matrixEvent, + { sender, prevContent, target, reason, withReasonKey } + ) { + // kick and unban just change the membership to "leave". + // So we need to look at each transition to what happened to the user. + if (matrixEvent.getSender() === target.userId) { + if (prevContent.membership === "invite") { + return lazy._("message.rejectedInvite", target.userId); + } + return lazy._("message.left", target.userId); + } else if (prevContent.membership === "ban") { + return lazy._("message.unbanned", sender, target.userId); + } else if (prevContent.membership === "join") { + return lazy._( + "message.kicked" + withReasonKey, + sender, + target.userId, + reason + ); + } else if (prevContent.membership === "invite") { + return lazy._( + "message.withdrewInvite" + withReasonKey, + sender, + target.userId, + reason + ); + } + // ignore rest of the cases. + return null; + }, + }, + }, + [MatrixSDK.EventType.RoomPowerLevels]: { + handler(matrixEvent, { sender, content }) { + const prevContent = matrixEvent.getPrevContent(); + if (!prevContent?.users) { + return null; + } + const userDefault = content.users_default || lazy.MatrixPowerLevels.user; + const prevDefault = + prevContent.users_default || lazy.MatrixPowerLevels.user; + // Construct set of userIds. + let users = new Set( + Object.keys(content.users).concat(Object.keys(prevContent.users)) + ); + const changes = Array.from(users) + .map(userId => { + const prevPowerLevel = prevContent.users[userId] ?? prevDefault; + const currentPowerLevel = content.users[userId] ?? userDefault; + if (prevPowerLevel !== currentPowerLevel) { + // Handling the case where there are multiple changes. + // Example : "@Mr.B:matrix.org changed the power level of + // @Mr.B:matrix.org from Default (0) to Moderator (50)." + return lazy._( + "message.powerLevel.fromTo", + userId, + lazy.MatrixPowerLevels.toText(prevPowerLevel, prevDefault), + lazy.MatrixPowerLevels.toText(currentPowerLevel, userDefault) + ); + } + return null; + }) + .filter(change => Boolean(change)); + // Since the power levels event also contains role power levels, not + // every event update will affect user power levels. + if (!changes.length) { + return null; + } + return lazy._("message.powerLevel.changed", sender, changes.join(", ")); + }, + }, + [MatrixSDK.EventType.RoomName]: { + handler(matrixEvent, { sender, content }) { + let roomName = content.name; + if (!roomName) { + return lazy._("message.roomName.remove", sender); + } + return lazy._("message.roomName.changed", sender, roomName); + }, + }, + [MatrixSDK.EventType.RoomGuestAccess]: { + pivot: "guest_access", + handlers: { + [MatrixSDK.GuestAccess.Forbidden](matrixEvent, { sender }) { + return lazy._("message.guest.prevented", sender); + }, + [MatrixSDK.GuestAccess.CanJoin](matrixEvent, { sender }) { + return lazy._("message.guest.allowed", sender); + }, + }, + }, + [MatrixSDK.EventType.RoomHistoryVisibility]: { + pivot: "history_visibility", + handlers: { + [MatrixSDK.HistoryVisibility.WorldReadable](matrixEvent, { sender }) { + return lazy._("message.history.anyone", sender); + }, + [MatrixSDK.HistoryVisibility.Shared](matrixEvent, { sender }) { + return lazy._("message.history.shared", sender); + }, + [MatrixSDK.HistoryVisibility.Invited](matrixEvent, { sender }) { + return lazy._("message.history.invited", sender); + }, + [MatrixSDK.HistoryVisibility.Joined](matrixEvent, { sender }) { + return lazy._("message.history.joined", sender); + }, + }, + }, + [MatrixSDK.EventType.RoomCanonicalAlias]: { + handler(matrixEvent, { sender, content }) { + const prevContent = matrixEvent.getPrevContent(); + if (content.alias != prevContent.alias) { + return lazy._( + "message.alias.main", + sender, + prevContent.alias, + content.alias + ); + } + const prevAliases = prevContent.alt_aliases || []; + const aliases = content.alt_aliases || []; + const addedAliases = aliases + .filter(alias => !prevAliases.includes(alias)) + .join(", "); + const removedAliases = prevAliases + .filter(alias => !aliases.includes(alias)) + .join(", "); + if (addedAliases && removedAliases) { + return lazy._( + "message.alias.removedAndAdded", + sender, + removedAliases, + addedAliases + ); + } else if (removedAliases) { + return lazy._("message.alias.removed", sender, removedAliases); + } else if (addedAliases) { + return lazy._("message.alias.added", sender, addedAliases); + } + // No discernible changes to aliases + return null; + }, + }, + + [MatrixSDK.EventType.RoomMessage]: roomMessage, + [MatrixSDK.EventType.RoomMessageEncrypted]: roomMessage, + [MatrixSDK.EventType.KeyVerificationRequest]: { + handler: keyVerificationRequest, + }, + [MatrixSDK.EventType.KeyVerificationCancel]: { + handler(matrixEvent, { sender, content }) { + return lazy._("message.verification.cancel2", sender, content.reason); + }, + }, + [MatrixSDK.EventType.KeyVerificationDone]: { + handler(matrixEvent, { sender, content }) { + return lazy._("message.verification.done"); + }, + }, + [MatrixSDK.EventType.RoomEncryption]: { + handler(matrixEvent, { sender, content }) { + return lazy._("message.encryptionStart"); + }, + }, + + // TODO : Events to be handled: + // 'm.call.invite' + // 'm.call.answer' + // 'm.call.hangup' + // 'm.room.third_party_invite' + + // NOTE : No need to add string messages for 'm.room.topic' events, + // as setTopic is used which handles the messages too. +}; + +/** + * Generates a notice string for a matrix event. May return null if no notice + * should be shown. + * + * @param {MatrixEvent} matrixEvent - Matrix event to generate a notice for. + * @returns {string?} Text to display as notice for the given event. + */ +export function getMatrixTextForEvent(matrixEvent) { + const context = { + sender: matrixEvent.getSender(), + content: matrixEvent.getContent(), + }; + const eventHandlingInformation = MATRIX_EVENT_HANDLERS[matrixEvent.getType()]; + if (!eventHandlingInformation) { + return null; + } + const details = + eventHandlingInformation.formatContext?.(matrixEvent, context) ?? context; + if (eventHandlingInformation.pivot) { + const pivotValue = context.content[eventHandlingInformation.pivot]; + return ( + eventHandlingInformation.handlers[pivotValue]?.(matrixEvent, details) ?? + null + ); + } + return eventHandlingInformation.handler(matrixEvent, details); +} |