support rich replies, support basic m.mentions
This commit is contained in:
		| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| const assert = require("assert").strict | ||||
| const markdown = require("discord-markdown") | ||||
| const DiscordTypes = require("discord-api-types/v10") | ||||
|  | ||||
| const passthrough = require("../../passthrough") | ||||
| const { sync, db, discord } = passthrough | ||||
| @@ -39,10 +40,56 @@ function getDiscordParseCallbacks(message, useHTML) { | ||||
| /** | ||||
|  * @param {import("discord-api-types/v10").APIMessage} message | ||||
|  * @param {import("discord-api-types/v10").APIGuild} guild | ||||
|  * @param {import("../../matrix/api")} api simple-as-nails dependency injection for the matrix API | ||||
|  */ | ||||
| async function messageToEvent(message, guild) { | ||||
| async function messageToEvent(message, guild, api) { | ||||
| 	const events = [] | ||||
|  | ||||
| 	/** | ||||
| 	   @type {{room?: boolean, user_ids?: string[]}} | ||||
| 		We should consider the following scenarios for mentions: | ||||
| 		1. TODO A discord user rich-replies to a matrix user with a text post | ||||
| 			+ The matrix user needs to be m.mentioned in the text event | ||||
| 			+ The matrix user needs to have their name/mxid/link in the text event (notification fallback) | ||||
| 				- So prepend their `@name:` to the start of the plaintext body | ||||
| 		2. TODO A discord user rich-replies to a matrix user with an image event only | ||||
| 			+ The matrix user needs to be m.mentioned in the image event | ||||
| 			+ The matrix user needs to have their name/mxid in the image event's body field, alongside the filename (notification fallback) | ||||
| 				- So append their name to the filename body, I guess!!! | ||||
| 		3. TODO A discord user `@`s a matrix user in the text body of their text box | ||||
| 			+ The matrix user needs to be m.mentioned in the text event | ||||
| 			+ No change needed to the text event content: it already has their name | ||||
| 				- So make sure we don't do anything in this case. | ||||
| 	*/ | ||||
| 	const mentions = {} | ||||
| 	let repliedToEventId = null | ||||
| 	let repliedToEventRoomId = null | ||||
| 	let repliedToEventSenderMxid = null | ||||
| 	let repliedToEventOriginallyFromMatrix = false | ||||
|  | ||||
| 	function addMention(mxid) { | ||||
| 		if (!mentions.user_ids) mentions.user_ids = [] | ||||
| 		mentions.user_ids.push(mxid) | ||||
| 	} | ||||
|  | ||||
| 	// Mentions scenarios 1 and 2, part A. i.e. translate relevant message.mentions to m.mentions | ||||
| 	// (Still need to do scenarios 1 and 2 part B, and scenario 3.) | ||||
| 	if (message.type === DiscordTypes.MessageType.Reply && message.message_reference?.message_id) { | ||||
| 		const row = db.prepare("SELECT event_id, room_id, source FROM event_message INNER JOIN channel_room USING (channel_id) WHERE message_id = ? AND part = 0").get(message.message_reference.message_id) | ||||
| 		if (row) { | ||||
| 			repliedToEventId = row.event_id | ||||
| 			repliedToEventRoomId = row.room_id | ||||
| 			repliedToEventOriginallyFromMatrix = row.source === 0 // source 0 = matrix | ||||
| 		} | ||||
| 	} | ||||
| 	if (repliedToEventOriginallyFromMatrix) { | ||||
| 		// Need to figure out who sent that event... | ||||
| 		const event = await api.getEvent(repliedToEventRoomId, repliedToEventId) | ||||
| 		repliedToEventSenderMxid = event.sender | ||||
| 		// Need to add the sender to m.mentions | ||||
| 		addMention(repliedToEventSenderMxid) | ||||
| 	} | ||||
|  | ||||
| 	// Text content appears first | ||||
| 	if (message.content) { | ||||
| 		let content = message.content | ||||
| @@ -55,33 +102,63 @@ async function messageToEvent(message, guild) { | ||||
| 			} | ||||
| 		}) | ||||
|  | ||||
| 		const html = markdown.toHTML(content, { | ||||
| 		let html = markdown.toHTML(content, { | ||||
| 			discordCallback: getDiscordParseCallbacks(message, true) | ||||
| 		}, null, null) | ||||
|  | ||||
| 		const body = markdown.toHTML(content, { | ||||
| 		let body = markdown.toHTML(content, { | ||||
| 			discordCallback: getDiscordParseCallbacks(message, false), | ||||
| 			discordOnly: true, | ||||
| 			escapeHTML: false, | ||||
| 		}, null, null) | ||||
|  | ||||
| 		// Fallback body/formatted_body for replies | ||||
| 		if (repliedToEventId) { | ||||
| 			let repliedToDisplayName | ||||
| 			let repliedToUserHtml | ||||
| 			if (repliedToEventOriginallyFromMatrix && repliedToEventSenderMxid) { | ||||
| 				const match = repliedToEventSenderMxid.match(/^@([^:]*)/) | ||||
| 				assert(match) | ||||
| 				repliedToDisplayName = match[1] || "a Matrix user" // grab the localpart as the display name, whatever | ||||
| 				repliedToUserHtml = `<a href="https://matrix.to/#/${repliedToEventSenderMxid}">${repliedToDisplayName}</a>` | ||||
| 			} else { | ||||
| 				repliedToDisplayName = message.referenced_message?.author.global_name || message.referenced_message?.author.username || "a Discord user" | ||||
| 				repliedToUserHtml = repliedToDisplayName | ||||
| 			} | ||||
| 			const repliedToContent = message.referenced_message?.content || "[Replied-to message content wasn't provided by Discord]" | ||||
| 			const repliedToHtml = markdown.toHTML(repliedToContent, { | ||||
| 				discordCallback: getDiscordParseCallbacks(message, true) | ||||
| 			}, null, null) | ||||
| 			const repliedToBody = markdown.toHTML(repliedToContent, { | ||||
| 				discordCallback: getDiscordParseCallbacks(message, false), | ||||
| 				discordOnly: true, | ||||
| 				escapeHTML: false, | ||||
| 			}, null, null) | ||||
| 			html = `<mx-reply><blockquote><a href="https://matrix.to/#/${repliedToEventRoomId}/${repliedToEventId}">In reply to</a> ${repliedToUserHtml}` | ||||
| 				+ `<br>${repliedToHtml}</blockquote></mx-reply>` | ||||
| 				+ html | ||||
| 			body = (`${repliedToDisplayName}: ` // scenario 1 part B for mentions | ||||
| 				+ repliedToBody).split("\n").map(line => "> " + line).join("\n") | ||||
| 				+ "\n\n" + body | ||||
| 		} | ||||
|  | ||||
| 		const newTextMessageEvent = { | ||||
| 			$type: "m.room.message", | ||||
| 			"m.mentions": mentions, | ||||
| 			msgtype: "m.text", | ||||
| 			body: body | ||||
| 		} | ||||
|  | ||||
| 		const isPlaintext = body === html | ||||
|  | ||||
| 		if (isPlaintext) { | ||||
| 			events.push({ | ||||
| 				$type: "m.room.message", | ||||
| 				msgtype: "m.text", | ||||
| 				body: body | ||||
| 			}) | ||||
| 		} else { | ||||
| 			events.push({ | ||||
| 				$type: "m.room.message", | ||||
| 				msgtype: "m.text", | ||||
| 				body: body, | ||||
| 		if (!isPlaintext) { | ||||
| 			Object.assign(newTextMessageEvent, { | ||||
| 				format: "org.matrix.custom.html", | ||||
| 				formatted_body: html | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		events.push(newTextMessageEvent) | ||||
| 	} | ||||
|  | ||||
| 	// Then attachments | ||||
| @@ -90,6 +167,7 @@ async function messageToEvent(message, guild) { | ||||
| 		if (attachment.content_type?.startsWith("image/") && attachment.width && attachment.height) { | ||||
| 			return { | ||||
| 				$type: "m.room.message", | ||||
| 				"m.mentions": mentions, | ||||
| 				msgtype: "m.image", | ||||
| 				url: await file.uploadDiscordFileToMxc(attachment.url), | ||||
| 				external_url: attachment.url, | ||||
| @@ -105,6 +183,7 @@ async function messageToEvent(message, guild) { | ||||
| 		} else { | ||||
| 			return { | ||||
| 				$type: "m.room.message", | ||||
| 				"m.mentions": mentions, | ||||
| 				msgtype: "m.text", | ||||
| 				body: "Unsupported attachment:\n" + JSON.stringify(attachment, null, 2) | ||||
| 			} | ||||
| @@ -122,6 +201,7 @@ async function messageToEvent(message, guild) { | ||||
| 				if (sticker && sticker.description) body += ` - ${sticker.description}` | ||||
| 				return { | ||||
| 					$type: "m.sticker", | ||||
| 					"m.mentions": mentions, | ||||
| 					body, | ||||
| 					info: { | ||||
| 						mimetype: format.mime | ||||
| @@ -131,6 +211,7 @@ async function messageToEvent(message, guild) { | ||||
| 			} else { | ||||
| 				return { | ||||
| 					$type: "m.room.message", | ||||
| 					"m.mentions": mentions, | ||||
| 					msgtype: "m.text", | ||||
| 					body: "Unsupported sticker format. Name: " + stickerItem.name | ||||
| 				} | ||||
| @@ -139,6 +220,17 @@ async function messageToEvent(message, guild) { | ||||
| 		events.push(...stickerEvents) | ||||
| 	} | ||||
|  | ||||
| 	// Rich replies | ||||
| 	if (repliedToEventId) { | ||||
| 		Object.assign(events[0], { | ||||
| 			"m.relates_to": { | ||||
| 				"m.in_reply_to": { | ||||
| 					event_id: repliedToEventId | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return events | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Cadence Ember
					Cadence Ember