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
155 lines
6.3 KiB
JavaScript
3 years ago
|
"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");
|
||
|
}
|
||
|
})
|
||
|
});
|
||
|
}
|
||
|
};
|