"use strict"; const dotty = require("dotty"); const unreachable = require("@joepie91/unreachable")("@modular-matrix/client"); // FIXME: Change name when packaging separately const addCommonFields = require("../event-add-common-fields"); const mapEncryptedFileObject = require("../map-encrypted-file-object"); const stripHTMLReplyFallback = require("../strip-html-reply-fallback"); const stripPlaintextReplyFallback = require("../strip-plaintext-reply-fallback"); const mapMaybeRedacted = require("../map-maybe-redacted"); function mapThumbnail(event) { return { hasThumbnail: (event.content.info.thumbnail_file != null || event.content.info.thumbnail_url != null), thumbnail: { filesize: dotty.get(event.content.info, [ "thumbnail_info", "size" ]), displayHeight: dotty.get(event.content.info, [ "thumbnail_info", "h" ]), displayWidth: dotty.get(event.content.info, [ "thumbnail_info", "w" ]), mimetype: dotty.get(event.content.info, [ "thumbnail_info", "mimetype" ]), ... (event.content.info.thumbnail_file != null) ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.info.thumbnail_file) } : { isEncrypted: false, url: event.content.info.thumbnail_url } } }; } module.exports = function mapMessageEvent(event, _context) { // NOTE: We have some custom redaction handling logic here, separate from the usual `mapMaybeRedacted` logic, because the `m.room.message` event gets split into several different types depending on the msgtype - but that msgtype is *also* redacted when a message is, which means we need a single dedicated "encrypted message" type. This does not mix well with mapMaybeRedacted + the requirement to return an event entirely unchanged when it is of an unrecognized msgtype, as mapMaybeRedacted would *necessarily* always return a new object. let isRedacted = dotty.exists(event, ["unsigned", "redacted_because"]); if (!isRedacted) { // This works around the weird inconsistency that stickers are not `m.room.message` events, despite exactly matching the structure of an `m.room.message` -> `m.image`. let messageType = (event.type === "m.sticker") ? "m.sticker" : event.content.msgtype; // TODO: Replace with ?. once that is available in all supported Node let replyToID = dotty.get(event, [ "content", "m.relates_to", "m.in_reply_to", "event_id" ]); let isReply = (replyToID != null); let replyFields = (isReply) ? { inReplyToID: replyToID } : {}; if (messageType === "m.text" || messageType === "m.notice" || messageType === "m.emote") { return addCommonFields(event, { type: "message", sender: event.sender, isNotice: (messageType === "m.notice"), isEmote: (messageType === "m.emote"), text: (isReply === true) ? stripPlaintextReplyFallback(event.content.body) : event.content.body, html: (event.content.format === "org.matrix.custom.html") ? (isReply === true) ? stripHTMLReplyFallback(event.content.formatted_body) : event.content.formatted_body : undefined, ... replyFields, }); } else if (messageType === "m.image" || messageType === "m.sticker") { return addCommonFields(event, { type: "image", sender: event.sender, isSticker: (messageType === "m.sticker"), description: event.content.body, image: { filesize: event.content.info.size, displayHeight: event.content.info.h, displayWidth: event.content.info.w, mimetype: event.content.info.mimetype, ... (event.content.file != null) ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.file) } : { isEncrypted: false, url: event.content.url } }, ... mapThumbnail(event), ... replyFields, }); } else if (messageType === "m.video") { return addCommonFields(event, { type: "video", sender: event.sender, description: event.content.body, video: { filesize: event.content.info.size, displayHeight: event.content.info.h, displayWidth: event.content.info.w, mimetype: event.content.info.mimetype, ... (event.content.file != null) ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.file) } : { isEncrypted: false, url: event.content.url } }, ... mapThumbnail(event), ... replyFields, }); } else if (messageType === "m.audio") { return addCommonFields(event, { type: "audio", sender: event.sender, description: event.content.body, audio: { // We nest the audio-related data inside of an object like for m.image, even though we don't have thumbnails to deal with yet, because something like cover art might be added in the future - and this way, we can keep the API consistent in that case. filesize: event.content.info.size, duration: event.content.info.duration, mimetype: event.content.info.mimetype, ... (event.content.file != null) ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.file) } : { isEncrypted: false, url: event.content.url } }, ... replyFields, }); } else if (messageType === "m.file") { return addCommonFields(event, { type: "file", sender: event.sender, description: event.content.body, filename: event.content.filename, file: { filesize: event.content.info.size, mimetype: event.content.info.mimetype, ... (event.content.file != null) ? { isEncrypted: true, encryptedFile: mapEncryptedFileObject(event.content.file) } : { isEncrypted: false, url: event.content.url } }, ... mapThumbnail(event), ... replyFields, }); } else if (messageType === "m.location") { return addCommonFields(event, { type: "location", sender: event.sender, description: event.content.body, url: event.content.geo_uri, ... replyFields, }); } else if (messageType === ":type") { // FIXME: Temporary hack to ignore an erroneous testing event, until there's unrecognized-event logging infrastructure return null; } else { // NOTE: The event should remain *completely* unaltered in this case! return event; } } else { return addCommonFields({ sender: event.sender, ... mapMaybeRedacted(event, { redacted: () => ({ type: "redactedMessage" }), notRedacted: () => { unreachable("Reached notRedacted handler for redacted message event"); } }) }); } };