You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

155 lines
6.3 KiB
JavaScript

"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");
}
})
});
}
};