forked from cadence/out-of-your-element
support rich replies, support basic m.mentions
This commit is contained in:
parent
5326b7d6be
commit
328ae74b61
7 changed files with 689 additions and 16 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue