// @ts-check const assert = require("assert") const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough /** @type {import("./message-to-event")} */ const messageToEvent = sync.require("../converters/message-to-event") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") /** @type {import("../actions/register-user")} */ const registerUser = sync.require("./register-user") /** @type {import("../actions/create-room")} */ const createRoom = sync.require("../actions/create-room") /** * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message * IMPORTANT: This may not have all the normal fields! The API documentation doesn't provide possible types, just says it's all optional! * Since I don't have a spec, I will have to capture some real traffic and add it as test cases... I hope they don't change anything later... * @param {import("discord-api-types/v10").APIGuild} guild */ async function editToChanges(message, guild) { // Figure out what events we will be replacing const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").get(message.channel_id) const senderMxid = await registerUser.ensureSimJoined(message.author, roomID) /** @type {{event_id: string, event_type: string, event_subtype: string?, part: number}[]} */ const oldEventRows = db.prepare("SELECT event_id, event_type, event_subtype, part FROM event_message WHERE message_id = ?").all(message.id) // Figure out what we will be replacing them with const newEvents = await messageToEvent.messageToEvent(message, guild, api) // Match the new events to the old events /* Rules: + The events must have the same type. + The events must have the same subtype. Events will therefore be divided into three categories: */ /** 1. Events that are matched, and should be edited by sending another m.replace event */ let eventsToReplace = [] /** 2. Events that are present in the old version only, and should be blanked or redacted */ let eventsToRedact = [] /** 3. Events that are present in the new version only, and should be sent as new, with references back to the context */ let eventsToSend = [] // For each old event... outer: while (newEvents.length) { const newe = newEvents[0] // Find a new event to pair it with... let handled = false for (let i = 0; i < oldEventRows.length; i++) { const olde = oldEventRows[i] if (olde.event_type === newe.$type && olde.event_subtype === (newe.msgtype || null)) { // Found one! // Set up the pairing eventsToReplace.push({ old: olde, new: newe }) // These events have been handled now, so remove them from the source arrays newEvents.shift() oldEventRows.splice(i, 1) // Go all the way back to the start of the next iteration of the outer loop continue outer } } // 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() } // Anything remaining in oldEventRows is present in the old version only and should be redacted. eventsToRedact = oldEventRows // Now, everything in eventsToSend and eventsToRedact is a real change, but everything in eventsToReplace might not have actually changed! // (Consider a MESSAGE_UPDATE for a text+image message - Discord does not allow the image to be changed, but the text might have been.) // So we'll remove entries from eventsToReplace that *definitely* cannot have changed. Everything remaining *may* have changed. eventsToReplace = eventsToReplace.filter(ev => { // Discord does not allow files, images, attachments, or videos to be edited. if (ev.old.event_type === "m.room.message" && ev.old.event_subtype !== "m.text" && ev.old.event_subtype !== "m.emote") { return false } // Discord does not allow stickers to be edited. if (ev.old.event_type === "m.sticker") { return false } // Anything else is fair game. return true }) return {eventsToReplace, eventsToRedact, eventsToSend} } module.exports.editMessage = editMessage