diff --git a/d2m/actions/delete-message.js b/d2m/actions/delete-message.js new file mode 100644 index 00000000..261c8f9d --- /dev/null +++ b/d2m/actions/delete-message.js @@ -0,0 +1,29 @@ +// @ts-check + +const passthrough = require("../../passthrough") +const { sync, db } = passthrough +/** @type {import("../converters/edit-to-changes")} */ +const editToChanges = sync.require("../converters/edit-to-changes") +/** @type {import("../../matrix/api")} */ +const api = sync.require("../../matrix/api") + +/** + * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data + */ +async function deleteMessage(data) { + /** @type {string?} */ + const roomID = db.prepare("SELECT channel_id FROM channel_room WHERE channel_id = ?").pluck().get(data.channel_id) + if (!roomID) return + + /** @type {string[]} */ + const eventsToRedact = db.prepare("SELECT event_id FROM event_message WHERE message_id = ?").pluck().all(data.id) + + for (const eventID of eventsToRedact) { + // Unfortuately, we can't specify a sender to do the redaction as, unless we find out that info via the audit logs + await api.redactEvent(roomID, eventID) + db.prepare("DELETE from event_message WHERE event_id = ?").run(eventID) + // TODO: Consider whether this code could be reused between edited messages and deleted messages. + } +} + +module.exports.deleteMessage = deleteMessage diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index b8c0af6d..6ae1c22b 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -16,6 +16,7 @@ const utils = { /** @type {typeof import("./event-dispatcher")} */ const eventDispatcher = sync.require("./event-dispatcher") + // Client internals, keep track of the state we need if (message.t === "READY") { if (client.ready) return client.ready = true @@ -62,16 +63,25 @@ const utils = { } } } + } + // Event dispatcher for OOYE bridge operations + try { + if (message.t === "MESSAGE_CREATE") { + eventDispatcher.onMessageCreate(client, message.d) - } else if (message.t === "MESSAGE_CREATE") { - eventDispatcher.onMessageCreate(client, message.d) + } else if (message.t === "MESSAGE_UPDATE") { + eventDispatcher.onMessageUpdate(client, message.d) - } else if (message.t === "MESSAGE_UPDATE") { - eventDispatcher.onMessageUpdate(client, message.d) + } else if (message.t === "MESSAGE_DELETE") { + eventDispatcher.onMessageDelete(client, message.d) - } else if (message.t === "MESSAGE_REACTION_ADD") { - eventDispatcher.onReactionAdd(client, message.d) + } else if (message.t === "MESSAGE_REACTION_ADD") { + eventDispatcher.onReactionAdd(client, message.d) + } + } catch (e) { + // Let OOYE try to handle errors too + eventDispatcher.onError(client, e, message) } } } diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 1527b28b..fde228d4 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,17 +1,61 @@ const assert = require("assert").strict +const util = require("util") const {sync, db} = require("../passthrough") /** @type {import("./actions/send-message")}) */ const sendMessage = sync.require("./actions/send-message") /** @type {import("./actions/edit-message")}) */ const editMessage = sync.require("./actions/edit-message") - +/** @type {import("./actions/delete-message")}) */ +const deleteMessage = sync.require("./actions/delete-message") /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") +/** @type {import("../matrix/api")}) */ +const api = sync.require("../matrix/api") + +let lastReportedEvent = 0 // Grab Discord events we care about for the bridge, check them, and pass them on module.exports = { + /** + * @param {import("./discord-client")} client + * @param {Error} e + * @param {import("cloudstorm").IGatewayMessage} gatewayMessage + */ + onError(client, e, gatewayMessage) { + console.error("hit event-dispatcher's error handler with this exception:") + console.error(e) // TODO: also log errors into a file or into the database, maybe use a library for this? or just wing it? definitely need to be able to store the formatted event body to load back in later + console.error(`while handling this ${gatewayMessage.t} gateway event:`) + console.dir(gatewayMessage.d) + + if (Date.now() - lastReportedEvent > 5000) { + lastReportedEvent = Date.now() + const channelID = gatewayMessage.d.channel_id + if (channelID) { + const roomID = db.prepare("SELECT room_id FROM channel_room WHERE channel_id = ?").pluck().get(channelID) + let stackLines = e.stack.split("\n") + let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/")) + if (cloudstormLine !== -1) { + stackLines = stackLines.slice(0, cloudstormLine - 2) + } + api.sendEvent(roomID, "m.room.message", { + msgtype: "m.text", + body: "\u26a0 Bridged event from Discord not delivered. See formatted content for full details.", + format: "org.matrix.custom.html", + formatted_body: "\u26a0 Bridged event from Discord not delivered" + + `
Gateway event: ${gatewayMessage.t}` + + `
${stackLines.join("\n")}
` + + `
Original payload` + + `
${util.inspect(gatewayMessage.d, false, 4, false)}
`, + "m.mentions": { + user_ids: ["@cadence:cadence.moe"] + } + }) + } + } + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message @@ -38,7 +82,7 @@ module.exports = { */ onMessageUpdate(client, data) { if (data.webhook_id) { - const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(message.webhook_id) + const row = db.prepare("SELECT webhook_id FROM webhook WHERE webhook_id = ?").pluck().get(data.webhook_id) if (row) { // The update was sent by the bridge's own webhook on discord. We don't want to reflect this back, so just drop it. return @@ -67,5 +111,14 @@ module.exports = { if (data.emoji.id !== null) return // TODO: image emoji reactions console.log(data) addReaction.addReaction(data) + }, + + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data + */ + onMessageDelete(client, data) { + console.log(data) + deleteMessage.deleteMessage(data) } }