From 6a03c9698450d99c555fa593e73011dfc8b10c6b Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sun, 27 Aug 2023 23:25:08 +1200 Subject: [PATCH 1/2] m->d move reply message preview to own line --- d2m/event-dispatcher.js | 8 ++++---- m2d/converters/event-to-message.js | 4 ++-- m2d/event-dispatcher.js | 8 ++++---- notes.md | 22 ++++++++++++++++++++++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 6939c59..049df27 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -61,10 +61,10 @@ module.exports = { formatted_body: "\u26a0 Bridged event from Discord not delivered" + `
Gateway event: ${gatewayMessage.t}` + `
${e.toString()}` - + `
Error trace` - + `
${stackLines.join("\n")}
` - + `
Original payload` - + `
${util.inspect(gatewayMessage.d, false, 4, false)}
`, + + `
Error trace` + + `
${stackLines.join("\n")}
` + + `
Original payload` + + `
${util.inspect(gatewayMessage.d, false, 4, false)}
`, "m.mentions": { user_ids: ["@cadence:cadence.moe"] } diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 22ed377..534becb 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -173,9 +173,9 @@ async function eventToMessage(event, guild, di) { replyLine += `Ⓜ️**${senderName}**: ` } const repliedToContent = repliedToEvent.content.formatted_body || repliedToEvent.content.body - const contentPreviewChunks = chunk(repliedToContent.replace(/.*<\/mx-reply>/, "").replace(/(?:\n|
)+/g, " ").replace(/<[^>]+>/g, ""), 24) + const contentPreviewChunks = chunk(repliedToContent.replace(/.*<\/mx-reply>/, "").replace(/(?:\n|
)+/g, " ").replace(/<[^>]+>/g, ""), 50) const contentPreview = contentPreviewChunks.length > 1 ? contentPreviewChunks[0] + "..." : contentPreviewChunks[0] - replyLine += contentPreview + "\n" + replyLine = `> ${replyLine}\n> ${contentPreview}\n` })() // Handling mentions of Discord users diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 9a575fc..6adacf7 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -40,10 +40,10 @@ function guard(type, fn) { formatted_body: "\u26a0 Matrix event not delivered to Discord" + `
Event type: ${type}` + `
${e.toString()}` - + `
Error trace` - + `
${stackLines.join("\n")}
` - + `
Original payload` - + `
${util.inspect(event, false, 4, false)}
`, + + `
Error trace` + + `
${stackLines.join("\n")}
` + + `
Original payload` + + `
${util.inspect(event, false, 4, false)}
`, "m.mentions": { user_ids: ["@cadence:cadence.moe"] } diff --git a/notes.md b/notes.md index 7383d3e..aa9066d 100644 --- a/notes.md +++ b/notes.md @@ -1,5 +1,27 @@ # d2m +## Known issues + +- m->d attachments do not work +- m->d edits do not work +- m->d spoilers do not work +- d->m support the rest of the attachments by reading the matrix spec instead of the current approach of whitelisting mime types +- d->m emojis do not work at all (inline chat, single emoji size, reactions, bridged state) +- m->d code blocks have slightly too much spacing +- m->d some reactions don't work because of the variation selector +- <--> check whether I implemented deletions +- rooms will be set up even if the bridge does not have permission for them, then break when it restarts and tries to reach messages + - test private threads as part of this + - solution part 1: calculate the permissions to see if the bot should be able to do stuff + - solution part 2: attempt a get messages request anyway before bridging a new room, just to make sure! + - solution part 3: revisit the permissions to add newly available rooms and to close newly inaccessible rooms +- consider a way to jump to a timestamp by making up a discord snowflake. practical? helpful? +- clean up and write documentation to selfhost +- pluralkit considerations for artemis +- consider whether to use nested spaces for channel categories and threads + +## Mapping + Remember that a discord message may be transformed to multiple matrix messages. A database will be used to store the discord id to matrix event id mapping. Table columns: From 55da70f8fce8de8e801b7bae940f7de34ab47fdd Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 28 Aug 2023 01:30:07 +1200 Subject: [PATCH 2/2] m->d start coding message edits --- m2d/actions/send-event.js | 8 +++-- m2d/converters/event-to-message.js | 50 ++++++++++++++++++++++++++++-- types.d.ts | 2 ++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index 016768e..3aa6346 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -29,13 +29,15 @@ async function sendEvent(event) { // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it - const messages = await eventToMessage.eventToMessage(event, guild, {api}) - assert(Array.isArray(messages)) // sanity + const {messagesToEdit, messagesToSend, messagesToDelete} = await eventToMessage.eventToMessage(event, guild, {api}) /** @type {DiscordTypes.APIMessage[]} */ const messageResponses = [] let eventPart = 0 // 0 is primary, 1 is supporting - for (const message of messages) { + // for (const message of messagesToEdit) { + // eventPart = 1 + // TODO ... + for (const message of messagesToSend) { const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message, threadID) db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 0)").run(event.event_id, event.type, event.content.msgtype || null, messageResponse.id, channelID, eventPart) // source 0 = matrix diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 534becb..70a0a69 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -130,6 +130,8 @@ async function eventToMessage(event, guild, di) { let displayName = event.sender let avatarURL = undefined + /** @type {string[]} */ + let messageIDsToEdit = [] let replyLine = "" // Extract a basic display name from the sender const match = event.sender.match(/^@(.*?):/) @@ -152,9 +154,37 @@ async function eventToMessage(event, guild, di) { // input = input.replace(/ /g, " ") // There is also a corresponding test to uncomment, named "event2message: whitespace is retained" - // Handling replies. We'll look up the data of the replied-to event from the Matrix homeserver. + // Handling edits. If the edit was an edit of a reply, edits do not include the reply reference, so we need to fetch up to 2 more events. + // this event ---is an edit of--> original event ---is a reply to--> past event await (async () => { - const repliedToEventId = event.content["m.relates_to"]?.["m.in_reply_to"].event_id + if (!event.content["m.new_content"]) return + const relatesTo = event.content["m.relates_to"] + if (!relatesTo) return + // Check if we have a pointer to what was edited + const relType = relatesTo.rel_type + if (relType !== "m.replace") return + const originalEventId = relatesTo.event_id + if (!originalEventId) return + console.log("a", originalEventId) + messageIDsToEdit = db.prepare("SELECT message_id FROM event_message WHERE event_id = ? ORDER BY part").pluck().all(originalEventId) + if (!messageIDsToEdit.length) return + // Get the original event, then check if it was a reply + const originalEvent = await di.api.getEvent(event.room_id, originalEventId) + if (!originalEvent) return + const repliedToEventId = originalEvent.content["m.relates_to"]?.["m.in_reply_to"]?.event_id + if (!repliedToEventId) return + console.log("c") + // After all that, it's an edit of a reply. + // We'll be sneaky and prepare the message data so that everything else can handle it just like original messages. + Object.assign(event.content, event.content["m.new_content"]) + input = event.content.formatted_body || event.content.body + relatesTo["m.in_reply_to"] = {event_id: repliedToEventId} + })() + + // Handling replies. We'll look up the data of the replied-to event from the Matrix homeserver. + // Note that an element is not guaranteed because this might be m.new_content. + await (async () => { + const repliedToEventId = event.content["m.relates_to"]?.["m.in_reply_to"]?.event_id if (!repliedToEventId) return const repliedToEvent = await di.api.getEvent(event.room_id, repliedToEventId) if (!repliedToEvent) return @@ -235,7 +265,21 @@ async function eventToMessage(event, guild, di) { avatar_url: avatarURL }))) - return messages + const messagesToEdit = [] + const messagesToSend = [] + for (let i = 0; i < messages.length; i++) { + if (messageIDsToEdit.length) { + messagesToEdit.push({id: messageIDsToEdit.shift(), message: messages[i]}) + } else { + messagesToSend.push(messages[i]) + } + } + + return { + messagesToEdit, + messagesToSend, + messagesToDelete: messageIDsToEdit + } } module.exports.eventToMessage = eventToMessage diff --git a/types.d.ts b/types.d.ts index 5475904..dcde3ad 100644 --- a/types.d.ts +++ b/types.d.ts @@ -75,6 +75,8 @@ export namespace Event { "m.in_reply_to": { event_id: string } + rel_type?: "m.replace" + event_id?: string } }