diff --git a/d2m/actions/delete-message.js b/d2m/actions/delete-message.js index f893e6d..cca5d25 100644 --- a/d2m/actions/delete-message.js +++ b/d2m/actions/delete-message.js @@ -1,7 +1,7 @@ // @ts-check const passthrough = require("../../passthrough") -const {sync, db, select} = passthrough +const {sync, db, select, from} = passthrough /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") @@ -21,4 +21,22 @@ async function deleteMessage(data) { } } +/** + * @param {import("discord-api-types/v10").GatewayMessageDeleteBulkDispatchData} data + */ +async function deleteMessageBulk(data) { + const roomID = select("channel_room", "room_id", {channel_id: data.channel_id}).pluck().get() + if (!roomID) return + + const sids = JSON.stringify(data.ids) + const eventsToRedact = from("event_message").pluck("event_id").and("WHERE message_id IN (SELECT value FROM json_each(?)").all(sids) + db.prepare("DELETE FROM message_channel WHERE message_id IN (SELECT value FROM json_each(?)").run(sids) + db.prepare("DELETE FROM event_message WHERE message_id IN (SELECT value FROM json_each(?)").run(sids) + for (const eventID of eventsToRedact) { + // Awaiting will make it go slower, but since this could be a long-running operation either way, we want to leave rate limit capacity for other operations + await api.redactEvent(roomID, eventID) + } +} + module.exports.deleteMessage = deleteMessage +module.exports.deleteMessageBulk = deleteMessageBulk diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index b7093a8..1129b71 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -153,6 +153,9 @@ const utils = { } else if (message.t === "MESSAGE_DELETE") { await eventDispatcher.onMessageDelete(client, message.d) + } else if (message.t === "MESSAGE_DELETE_BULK") { + await eventDispatcher.onMessageDeleteBulk(client, message.d) + } else if (message.t === "TYPING_START") { await eventDispatcher.onTypingStart(client, message.d) diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index b586f61..7974be6 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -1,3 +1,5 @@ +// @ts-check + const assert = require("assert").strict const DiscordTypes = require("discord-api-types/v10") const util = require("util") @@ -47,27 +49,33 @@ module.exports = { if (Date.now() - lastReportedEvent < 5000) return lastReportedEvent = Date.now() - const channelID = gatewayMessage.d.channel_id + const channelID = gatewayMessage.d["channel_id"] if (!channelID) return const roomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get() if (!roomID) return - 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) + let stackLines = null + if (e.stack) { + stackLines = e.stack.split("\n") + let cloudstormLine = stackLines.findIndex(l => l.includes("/node_modules/cloudstorm/")) + if (cloudstormLine !== -1) { + stackLines = stackLines.slice(0, cloudstormLine - 2) + } } + let formattedBody = "\u26a0 Bridged event from Discord not delivered" + + `
Gateway event: ${gatewayMessage.t}` + + `
${e.toString()}` + if (stackLines) { + formattedBody += `
Error trace` + + `
${stackLines.join("\n")}
` + } + formattedBody += `
Original payload` + + `
${util.inspect(gatewayMessage.d, false, 4, false)}
`, 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}` - + `
${e.toString()}` - + `
Error trace` - + `
${stackLines.join("\n")}
` - + `
Original payload` - + `
${util.inspect(gatewayMessage.d, false, 4, false)}
`, + formatted_body: formattedBody, "moe.cadence.ooye.error": { source: "discord", payload: gatewayMessage @@ -91,7 +99,7 @@ module.exports = { const prepared = select("event_message", "event_id", {}, "WHERE message_id = ?").pluck() for (const channel of guild.channels.concat(guild.threads)) { if (!bridgedChannels.includes(channel.id)) continue - if (!channel.last_message_id) continue + if (!("last_message_id" in channel) || !channel.last_message_id) continue const latestWasBridged = prepared.get(channel.last_message_id) if (latestWasBridged) continue @@ -116,7 +124,6 @@ module.exports = { for (let i = Math.min(messages.length, latestBridgedMessageIndex)-1; i >= 0; i--) { const simulatedGatewayDispatchData = { guild_id: guild.id, - mentions: [], backfill: true, ...messages[i] } @@ -127,7 +134,6 @@ module.exports = { /** * When logging back in, check if we missed any changes to emojis or stickers. Apply the changes if so. - * @param {import("./discord-client")} client * @param {DiscordTypes.GatewayGuildCreateDispatchData} guild */ async checkMissedExpressions(guild) { @@ -142,7 +148,8 @@ module.exports = { * @param {DiscordTypes.APIThreadChannel} thread */ async onThreadCreate(client, thread) { - const parentRoomID = select("channel_room", "room_id", {channel_id: thread.parent_id}).pluck().get() + const channelID = thread.parent_id || undefined + const parentRoomID = select("channel_room", "room_id", {channel_id: channelID}).pluck().get() if (!parentRoomID) return // Not interested in a thread if we aren't interested in its wider channel const threadRoomID = await createRoom.syncRoom(thread.id) // Create room (will share the same inflight as the initial message to the thread) await announceThread.announceThread(parentRoomID, threadRoomID, thread) @@ -192,10 +199,10 @@ module.exports = { return } } - /** @type {DiscordTypes.APIGuildChannel} */ const channel = client.channels.get(message.channel_id) - if (!channel.guild_id) return // Nothing we can do in direct messages. + if (!channel || !("guild_id" in channel) || !channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) + assert(guild) await sendMessage.sendMessage(message, guild), await discordCommandHandler.execute(message, channel, guild) @@ -217,11 +224,12 @@ module.exports = { // If the message content is a string then it includes all interesting fields and is meaningful. if (typeof data.content === "string") { /** @type {DiscordTypes.GatewayMessageCreateDispatchData} */ + // @ts-ignore const message = data - /** @type {DiscordTypes.APIGuildChannel} */ const channel = client.channels.get(message.channel_id) - if (!channel.guild_id) return // Nothing we can do in direct messages. + if (!channel || !("guild_id" in channel) || !channel.guild_id) return // Nothing we can do in direct messages. const guild = client.guilds.get(channel.guild_id) + assert(guild) await editMessage.editMessage(message, guild) } }, @@ -252,6 +260,14 @@ module.exports = { await deleteMessage.deleteMessage(data) }, + /** + * @param {import("./discord-client")} client + * @param {DiscordTypes.GatewayMessageDeleteBulkDispatchData} data + */ + async onMessageDeleteBulk(client, data) { + await deleteMessage.deleteMessageBulk(data) + }, + /** * @param {import("./discord-client")} client * @param {DiscordTypes.GatewayTypingStartDispatchData} data