From 8f6bb86b927792b70f3cb49f2faf48ef647082e1 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 16 Aug 2023 20:44:38 +1200 Subject: [PATCH] record more update events --- d2m/actions/send-message.js | 2 +- d2m/converters/edit-to-changes.js | 47 +++++----- d2m/converters/edit-to-changes.test.js | 9 +- d2m/converters/message-to-event.js | 20 +++- d2m/converters/message-to-event.test.js | 116 +++++++++++++++--------- scripts/events.db | Bin 8192 -> 98304 bytes 6 files changed, 122 insertions(+), 72 deletions(-) diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index 258efcf..cf87d35 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -27,7 +27,7 @@ async function sendMessage(message, guild) { await registerUser.syncUser(message.author, message.member, message.guild_id, roomID) } - const events = await messageToEvent.messageToEvent(message, guild, api) + const events = await messageToEvent.messageToEvent(message, guild, {}, {api}) const eventIDs = [] let eventPart = 0 // 0 is primary, 1 is supporting for (const event of events) { diff --git a/d2m/converters/edit-to-changes.js b/d2m/converters/edit-to-changes.js index 87e769b..4afa3ce 100644 --- a/d2m/converters/edit-to-changes.js +++ b/d2m/converters/edit-to-changes.js @@ -29,7 +29,9 @@ async function editToChanges(message, guild) { // Figure out what we will be replacing them with - const newEvents = await messageToEvent.messageToEvent(message, guild, api) + const newFallbackContent = await messageToEvent.messageToEvent(message, guild, {includeEditFallbackStar: true}, {api}) + const newInnerContent = await messageToEvent.messageToEvent(message, guild, {includeReplyFallback: false}, {api}) + assert.ok(newFallbackContent.length === newInnerContent.length) // Match the new events to the old events @@ -47,21 +49,27 @@ async function editToChanges(message, guild) { let eventsToSend = [] // 4. Events that are matched and have definitely not changed, so they don't need to be edited or replaced at all. This is represented as nothing. + function shift() { + newFallbackContent.shift() + newInnerContent.shift() + } + // For each old event... - outer: while (newEvents.length) { - const newe = newEvents[0] + outer: while (newFallbackContent.length) { + const newe = newFallbackContent[0] // Find a new event to pair it with... for (let i = 0; i < oldEventRows.length; i++) { const olde = oldEventRows[i] - if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { + if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype ?? null)) { // The spec does allow subtypes to change, so I can change this condition later if I want to // Found one! // Set up the pairing eventsToReplace.push({ old: olde, - new: newe + newFallbackContent: newFallbackContent[0], + newInnerContent: newInnerContent[0] }) // These events have been handled now, so remove them from the source arrays - newEvents.shift() + shift() oldEventRows.splice(i, 1) // Go all the way back to the start of the next iteration of the outer loop continue outer @@ -69,7 +77,7 @@ async function editToChanges(message, guild) { } // If we got this far, we could not pair it to an existing event, so it'll have to be a new one eventsToSend.push(newe) - newEvents.shift() + shift() } // Anything remaining in oldEventRows is present in the old version only and should be redacted. eventsToRedact = oldEventRows @@ -92,7 +100,7 @@ async function editToChanges(message, guild) { // Removing unnecessary properties before returning eventsToRedact = eventsToRedact.map(e => e.event_id) - eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.new)})) + eventsToReplace = eventsToReplace.map(e => ({oldID: e.old.event_id, new: eventToReplacementEvent(e.old.event_id, e.newFallbackContent, e.newInnerContent)})) return {eventsToReplace, eventsToRedact, eventsToSend} } @@ -100,31 +108,26 @@ async function editToChanges(message, guild) { /** * @template T * @param {string} oldID - * @param {T} content + * @param {T} newFallbackContent + * @param {T} newInnerContent * @returns {import("../../types").Event.ReplacementContent} content */ -function eventToReplacementEvent(oldID, content) { - const newContent = { - ...content, +function eventToReplacementEvent(oldID, newFallbackContent, newInnerContent) { + const content = { + ...newFallbackContent, "m.mentions": {}, "m.new_content": { - ...content + ...newInnerContent }, "m.relates_to": { rel_type: "m.replace", event_id: oldID } } - if (typeof newContent.body === "string") { - newContent.body = "* " + newContent.body - } - if (typeof newContent.formatted_body === "string") { - newContent.formatted_body = "* " + newContent.formatted_body - } - delete newContent["m.new_content"]["$type"] + delete content["m.new_content"]["$type"] // Client-Server API spec 11.37.3: Any m.relates_to property within m.new_content is ignored. - delete newContent["m.new_content"]["m.relates_to"] - return newContent + delete content["m.new_content"]["m.relates_to"] + return content } module.exports.editToChanges = editToChanges diff --git a/d2m/converters/edit-to-changes.test.js b/d2m/converters/edit-to-changes.test.js index b3e6e0c..f6ecc8d 100644 --- a/d2m/converters/edit-to-changes.test.js +++ b/d2m/converters/edit-to-changes.test.js @@ -47,9 +47,13 @@ test("edit2changes: edit of reply to skull webp attachment with content", async oldID: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M", new: { $type: "m.room.message", - // TODO: read "edits of replies" in the spec!!! msgtype: "m.text", - body: "* Edit", + body: "> Extremity: Image\n\n* Edit", + format: "org.matrix.custom.html", + formatted_body: + '
In reply to Extremity' + + '
Image
' + + '* Edit', "m.mentions": {}, "m.new_content": { msgtype: "m.text", @@ -60,7 +64,6 @@ test("edit2changes: edit of reply to skull webp attachment with content", async rel_type: "m.replace", event_id: "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M" } - // TODO: read "edits of replies" in the spec!!! } }]) }) diff --git a/d2m/converters/message-to-event.js b/d2m/converters/message-to-event.js index a2c4915..c128595 100644 --- a/d2m/converters/message-to-event.js +++ b/d2m/converters/message-to-event.js @@ -55,9 +55,12 @@ 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 + * @param {{includeReplyFallback?: boolean, includeEditFallbackStar?: boolean}} options default values: + * - includeReplyFallback: true + * - includeEditFallbackStar: false + * @param {{api: import("../../matrix/api")}} di simple-as-nails dependency injection for the matrix API */ -async function messageToEvent(message, guild, api) { +async function messageToEvent(message, guild, options = {}, di) { const events = [] /** @@ -99,7 +102,7 @@ async function messageToEvent(message, guild, api) { } if (repliedToEventOriginallyFromMatrix) { // Need to figure out who sent that event... - const event = await api.getEvent(repliedToEventRoomId, repliedToEventId) + const event = await di.api.getEvent(repliedToEventRoomId, repliedToEventId) repliedToEventSenderMxid = event.sender // Need to add the sender to m.mentions addMention(repliedToEventSenderMxid) @@ -133,7 +136,7 @@ async function messageToEvent(message, guild, api) { if (matches.length && matches.some(m => m[1].match(/[a-z]/i))) { const writtenMentionsText = matches.map(m => m[1].toLowerCase()) const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(message.channel_id) - const {joined} = await api.getJoinedMembers(roomID) + const {joined} = await di.api.getJoinedMembers(roomID) for (const [mxid, member] of Object.entries(joined)) { if (!userRegex.some(rx => mxid.match(rx))) { const localpart = mxid.match(/@([^:]*)/) @@ -143,8 +146,15 @@ async function messageToEvent(message, guild, api) { } } + // Star * prefix for fallback edits + if (options.includeEditFallbackStar) { + body = "* " + body + html = "* " + html + } + // Fallback body/formatted_body for replies - if (repliedToEventId) { + // This branch is optional - do NOT change anything apart from the reply fallback, since it may not be run + if (repliedToEventId && options.includeReplyFallback !== false) { let repliedToDisplayName let repliedToUserHtml if (repliedToEventOriginallyFromMatrix && repliedToEventSenderMxid) { diff --git a/d2m/converters/message-to-event.test.js b/d2m/converters/message-to-event.test.js index 17079e5..4200afe 100644 --- a/d2m/converters/message-to-event.test.js +++ b/d2m/converters/message-to-event.test.js @@ -30,7 +30,7 @@ function mockGetEvent(t, roomID_in, eventID_in, outer) { } test("message2event: simple plaintext", async t => { - const events = await messageToEvent(data.message.simple_plaintext, data.guild.general) + const events = await messageToEvent(data.message.simple_plaintext, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -40,7 +40,7 @@ test("message2event: simple plaintext", async t => { }) test("message2event: simple user mention", async t => { - const events = await messageToEvent(data.message.simple_user_mention, data.guild.general) + const events = await messageToEvent(data.message.simple_user_mention, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -52,7 +52,7 @@ test("message2event: simple user mention", async t => { }) test("message2event: simple room mention", async t => { - const events = await messageToEvent(data.message.simple_room_mention, data.guild.general) + const events = await messageToEvent(data.message.simple_room_mention, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -64,7 +64,7 @@ test("message2event: simple room mention", async t => { }) test("message2event: simple message link", async t => { - const events = await messageToEvent(data.message.simple_message_link, data.guild.general) + const events = await messageToEvent(data.message.simple_message_link, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -76,7 +76,7 @@ test("message2event: simple message link", async t => { }) test("message2event: attachment with no content", async t => { - const events = await messageToEvent(data.message.attachment_no_content, data.guild.general) + const events = await messageToEvent(data.message.attachment_no_content, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -94,7 +94,7 @@ test("message2event: attachment with no content", async t => { }) test("message2event: stickers", async t => { - const events = await messageToEvent(data.message.sticker, data.guild.general) + const events = await messageToEvent(data.message.sticker, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -127,7 +127,7 @@ test("message2event: stickers", async t => { }) test("message2event: skull webp attachment with content", async t => { - const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general) + const events = await messageToEvent(data.message.skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.mentions": {}, @@ -150,7 +150,7 @@ test("message2event: skull webp attachment with content", async t => { }) test("message2event: reply to skull webp attachment with content", async t => { - const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general) + const events = await messageToEvent(data.message.reply_to_skull_webp_attachment_with_content, data.guild.general, {}) t.deepEqual(events, [{ $type: "m.room.message", "m.relates_to": { @@ -183,15 +183,17 @@ test("message2event: reply to skull webp attachment with content", async t => { }) test("message2event: simple reply to matrix user", async t => { - const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, { - getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { - type: "m.room.message", - content: { - msgtype: "m.text", - body: "so can you reply to my webhook uwu" - }, - sender: "@cadence:cadence.moe" - }) + const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, {}, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + } }) t.deepEqual(events, [{ $type: "m.room.message", @@ -215,34 +217,66 @@ test("message2event: simple reply to matrix user", async t => { }]) }) +test("message2event: simple reply to matrix user, reply fallbacks disabled", async t => { + const events = await messageToEvent(data.message.simple_reply_to_matrix_user, data.guild.general, {includeReplyFallback: false}, { + api: { + getEvent: mockGetEvent(t, "!kLRqKKUQXcibIMtOpl:cadence.moe", "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4", { + type: "m.room.message", + content: { + msgtype: "m.text", + body: "so can you reply to my webhook uwu" + }, + sender: "@cadence:cadence.moe" + }) + } + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$Ij3qo7NxMA4VPexlAiIx2CB9JbsiGhJeyt-2OvkAUe4" + } + }, + "m.mentions": { + user_ids: [ + "@cadence:cadence.moe" + ] + }, + msgtype: "m.text", + body: "Reply" + }]) +}) + test("message2event: simple written @mention for matrix user", async t => { - const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, { - async getJoinedMembers(roomID) { - t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") - return new Promise(resolve => { - setTimeout(() => { - resolve({ - joined: { - "@cadence:cadence.moe": { - display_name: "cadence [they]", - avatar_url: "whatever" - }, - "@huckleton:cadence.moe": { - display_name: "huck", - avatar_url: "whatever" - }, - "@_ooye_botrac4r:cadence.moe": { - display_name: "botrac4r", - avatar_url: "whatever" - }, - "@_ooye_bot:cadence.moe": { - display_name: "Out Of Your Element", - avatar_url: "whatever" + const events = await messageToEvent(data.message.simple_written_at_mention_for_matrix, data.guild.general, {}, { + api: { + async getJoinedMembers(roomID) { + t.equal(roomID, "!kLRqKKUQXcibIMtOpl:cadence.moe") + return new Promise(resolve => { + setTimeout(() => { + resolve({ + joined: { + "@cadence:cadence.moe": { + display_name: "cadence [they]", + avatar_url: "whatever" + }, + "@huckleton:cadence.moe": { + display_name: "huck", + avatar_url: "whatever" + }, + "@_ooye_botrac4r:cadence.moe": { + display_name: "botrac4r", + avatar_url: "whatever" + }, + "@_ooye_bot:cadence.moe": { + display_name: "Out Of Your Element", + avatar_url: "whatever" + } } - } + }) }) }) - }) + } } }) t.deepEqual(events, [{ diff --git a/scripts/events.db b/scripts/events.db index c8f5bad88858b0ddc7a3b54ec36577c94791c001..3356cbe2ba67631f1efdf34292de8256ab6ee024 100644 GIT binary patch literal 98304 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