From aa09db11694d2332d59559157e6117daa293789b Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 28 Jul 2023 17:05:13 +1200 Subject: [PATCH] progress on edits --- d2m/actions/edit-message.js | 118 ++++++++++++++++++++++++++++++ d2m/actions/send-message.js | 2 +- db/ooye.db | Bin 303104 -> 360448 bytes m2d/actions/send-event.js | 2 +- matrix/api.js | 19 ++++- scripts/save-event-types-to-db.js | 30 ++++++++ 6 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 d2m/actions/edit-message.js create mode 100644 scripts/save-event-types-to-db.js diff --git a/d2m/actions/edit-message.js b/d2m/actions/edit-message.js new file mode 100644 index 0000000..662e124 --- /dev/null +++ b/d2m/actions/edit-message.js @@ -0,0 +1,118 @@ +// @ts-check + +const assert = require("assert") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough +/** @type {import("../converters/message-to-event")} */ +const messageToEvent = sync.require("../converters/message-to-event") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") +/** @type {import("./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 + * @param {import("discord-api-types/v10").APIGuild} guild + */ +async function editMessage(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 + }) + + // Action time! + + // 1. Replace all the things. + + + // 2. Redact all the things. + + // 3. Send all the things. + + // old code lies here + let eventPart = 0 // TODO: what to do about eventPart when editing? probably just need to make sure that exactly 1 value of '1' remains in the database? + for (const event of events) { + const eventType = event.$type + /** @type {Pick> & { $type?: string }} */ + const eventWithoutType = {...event} + delete eventWithoutType.$type + + const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) + db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord + + eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting + eventIDs.push(eventID) + } + + return eventIDs +} + +module.exports.editMessage = editMessage diff --git a/d2m/actions/send-message.js b/d2m/actions/send-message.js index ff3c1de..258efcf 100644 --- a/d2m/actions/send-message.js +++ b/d2m/actions/send-message.js @@ -37,7 +37,7 @@ async function sendMessage(message, guild) { delete eventWithoutType.$type const eventID = await api.sendEvent(roomID, eventType, event, senderMxid) - db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 1)").run(eventID, message.id, message.channel_id, eventPart) // source 1 = discord + db.prepare("INSERT INTO event_message (event_id, event_type, event_subtype, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, ?, ?, 1)").run(eventID, eventType, event.msgtype || null, message.id, message.channel_id, eventPart) // source 1 = discord eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting eventIDs.push(eventID) diff --git a/db/ooye.db b/db/ooye.db index a22816bad5426477af017e68fc08af75a4b99f42..33c5ef8abaf5ad514d187a005124f5526b054c64 100644 GIT binary patch delta 28262 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 delta 16202 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 F{{RLcI+Xwb diff --git a/m2d/actions/send-event.js b/m2d/actions/send-event.js index c05f08c..3f49fa4 100644 --- a/m2d/actions/send-event.js +++ b/m2d/actions/send-event.js @@ -25,7 +25,7 @@ async function sendEvent(event) { let eventPart = 0 // 0 is primary, 1 is supporting for (const message of messages) { const messageResponse = await channelWebhook.sendMessageWithWebhook(channelID, message) - db.prepare("INSERT INTO event_message (event_id, message_id, channel_id, part, source) VALUES (?, ?, ?, ?, 0)").run(event.event_id, messageResponse.id, channelID, eventPart) // source 0 = matrix + 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 eventPart = 1 // TODO: use more intelligent algorithm to determine whether primary or supporting? messageResponses.push(messageResponse) diff --git a/matrix/api.js b/matrix/api.js index 7f9d74e..9111909 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -15,12 +15,18 @@ const makeTxnId = sync.require("./txnid") /** * @param {string} p endpoint to access * @param {string} [mxid] optional: user to act as, for the ?user_id parameter + * @param {{[x: string]: any}} [otherParams] optional: any other query parameters to add * @returns {string} the new endpoint */ -function path(p, mxid) { +function path(p, mxid, otherParams = {}) { if (!mxid) return p const u = new URL(p, "http://localhost") u.searchParams.set("user_id", mxid) + for (const entry of Object.entries(otherParams)) { + if (entry[1] != undefined) { + u.searchParams.set(entry[0], entry[1]) + } + } return u.pathname + "?" + u.searchParams.toString() } @@ -109,10 +115,17 @@ async function sendState(roomID, type, stateKey, content, mxid) { return root.event_id } -async function sendEvent(roomID, type, content, mxid) { +/** + * @param {string} roomID + * @param {string} type + * @param {any} content + * @param {string} [mxid] + * @param {number} [timestamp] timestamp of the newly created event, in unix milliseconds + */ +async function sendEvent(roomID, type, content, mxid, timestamp) { console.log(`[api] event ${type} to ${roomID} as ${mxid || "default sim"}`) /** @type {Ty.R.EventSent} */ - const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid), content) + const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid, {ts: timestamp}), content) return root.event_id } diff --git a/scripts/save-event-types-to-db.js b/scripts/save-event-types-to-db.js new file mode 100644 index 0000000..83f5d2b --- /dev/null +++ b/scripts/save-event-types-to-db.js @@ -0,0 +1,30 @@ +// @ts-check + +const sqlite = require("better-sqlite3") +const HeatSync = require("heatsync") + +const passthrough = require("../passthrough") +const db = new sqlite("db/ooye.db") + +const sync = new HeatSync({watchFS: false}) + +Object.assign(passthrough, {sync, db}) + +const api = require("../matrix/api") + +/** @type {{event_id: string, room_id: string, event_type: string}[]} */ // @ts-ignore +const rows = db.prepare("SELECT event_id, room_id, event_type FROM event_message INNER JOIN channel_room USING (channel_id)").all() + +const preparedUpdate = db.prepare("UPDATE event_message SET event_type = ?, event_subtype = ? WHERE event_id = ?") + +;(async () => { + for (const row of rows) { + if (row.event_type == null) { + const event = await api.getEvent(row.room_id, row.event_id) + const type = event.type + const subtype = event.content.msgtype || null + preparedUpdate.run(type, subtype, row.event_id) + console.log(`Updated ${row.event_id} -> ${type} + ${subtype}`) + } + } +})()