diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index 068962b..dc2d52d 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -10,8 +10,9 @@ const api = sync.require("../../matrix/api") const registerUser = sync.require("./register-user") /** @type {import("../actions/create-room")} */ const createRoom = sync.require("../actions/create-room") -/** @type {import("../../matrix/file")} */ -const file = sync.require("../../matrix/file") +/** @type {import("../converters/emoji-to-key")} */ +const emojiToKey = sync.require("../converters/emoji-to-key") + /** * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data @@ -24,24 +25,7 @@ async function addReaction(data) { if (!parentID) return // Nothing can be done if the parent message was never bridged. assert.equal(typeof parentID, "string") - let key - if (data.emoji.id) { - // Custom emoji - const mxc = select("emoji", "mxc_url", "WHERE id = ?").pluck().get(data.emoji.id) - if (mxc) { - // The custom emoji is registered and we should send it - key = mxc - } else { - // The custom emoji is not registered. We will register it and then add it. - const mxc = await file.uploadDiscordFileToMxc(file.emoji(data.emoji.id, data.emoji.animated)) - db.prepare("INSERT OR IGNORE INTO emoji (id, name, animated, mxc_url) VALUES (?, ?, ?, ?)").run(data.emoji.id, data.emoji.name, +!!data.emoji.animated, mxc) - key = mxc - // TODO: what happens if the matrix user also tries adding this reaction? the bridge bot isn't able to use that emoji... - } - } else { - // Default emoji - key = data.emoji.name - } + const key = await emojiToKey.emojiToKey(data.emoji) const roomID = await createRoom.ensureRoom(data.channel_id) const senderMxid = await registerUser.ensureSimJoined(user, roomID) diff --git a/d2m/actions/remove-reaction.js b/d2m/actions/remove-reaction.js index 821b9d0..70ae5c3 100644 --- a/d2m/actions/remove-reaction.js +++ b/d2m/actions/remove-reaction.js @@ -7,12 +7,8 @@ const passthrough = require("../../passthrough") const {discord, sync, db, select} = passthrough /** @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") -/** @type {import("../../matrix/file")} */ -const file = sync.require("../../matrix/file") +/** @type {import("../converters/emoji-to-key")} */ +const emojiToKey = sync.require("../converters/emoji-to-key") /** * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveDispatchData} data @@ -27,10 +23,11 @@ async function removeReaction(data) { /** @type {Ty.Pagination>} */ const relations = await api.getRelations(roomID, eventIDForMessage, "m.annotation") - const eventIDForReaction = relations.chunk.find(e => e.sender === mxid && e.content["m.relates_to"].key === data.emoji) // TODO: get the key from the emoji + const key = await emojiToKey.emojiToKey(data.emoji) + const eventIDForReaction = relations.chunk.find(e => e.sender === mxid && e.content["m.relates_to"].key === key) if (!eventIDForReaction) return - await api.redactEvent(roomID, eventIDForReaction, mxid) + await api.redactEvent(roomID, eventIDForReaction.event_id, mxid) } module.exports.removeReaction = removeReaction diff --git a/d2m/converters/emoji-to-key.js b/d2m/converters/emoji-to-key.js new file mode 100644 index 0000000..7d04f97 --- /dev/null +++ b/d2m/converters/emoji-to-key.js @@ -0,0 +1,38 @@ +// @ts-check + +const assert = require("assert").strict +const passthrough = require("../../passthrough") +const {discord, sync, db, select} = passthrough +/** @type {import("../../matrix/file")} */ +const file = sync.require("../../matrix/file") + +/** + * @param {import("discord-api-types/v10").APIEmoji} emoji + * @returns {Promise} + */ +async function emojiToKey(emoji) { + let key + if (emoji.id) { + // Custom emoji + const mxc = select("emoji", "mxc_url", "WHERE id = ?").pluck().get(emoji.id) + if (mxc) { + // The custom emoji is registered and we should send it + key = mxc + } else { + // The custom emoji is not registered. We will register it and then add it. + assert(emoji.name) // The docs say: "name may be null when custom emoji data is not available, for example, if it was deleted from the guild" + const mxc = await file.uploadDiscordFileToMxc(file.emoji(emoji.id, emoji.animated)) + db.prepare("INSERT OR IGNORE INTO emoji (id, name, animated, mxc_url) VALUES (?, ?, ?, ?)").run(emoji.id, emoji.name, +!!emoji.animated, mxc) + key = mxc + // TODO: what happens if the matrix user also tries adding this reaction? the bridge bot isn't able to use that emoji... + } + } else { + // Default emoji + const name = emoji.name + assert(name) + key = name + } + return key +} + +module.exports.emojiToKey = emojiToKey diff --git a/d2m/discord-packets.js b/d2m/discord-packets.js index b8f8eec..8c5a24a 100644 --- a/d2m/discord-packets.js +++ b/d2m/discord-packets.js @@ -155,6 +155,9 @@ const utils = { } else if (message.t === "MESSAGE_REACTION_ADD") { await eventDispatcher.onReactionAdd(client, message.d) + + } else if (message.t === "MESSAGE_REACTION_REMOVE") { + await eventDispatcher.onReactionRemove(client, message.d) } } catch (e) { // Let OOYE try to handle errors too diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index 32b8f49..a415b86 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -10,6 +10,8 @@ const editMessage = sync.require("./actions/edit-message") const deleteMessage = sync.require("./actions/delete-message") /** @type {import("./actions/add-reaction")}) */ const addReaction = sync.require("./actions/add-reaction") +/** @type {import("./actions/remove-reaction")}) */ +const removeReaction = sync.require("./actions/remove-reaction") /** @type {import("./actions/announce-thread")}) */ const announceThread = sync.require("./actions/announce-thread") /** @type {import("./actions/create-room")}) */ @@ -210,6 +212,15 @@ module.exports = { await addReaction.addReaction(data) }, + /** + * @param {import("./discord-client")} client + * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data + */ + async onReactionRemove(client, data) { + if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix. + await removeReaction.removeReaction(data) + }, + /** * @param {import("./discord-client")} client * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data diff --git a/m2d/actions/redact.js b/m2d/actions/redact.js index 9b03abb..e2d9ece 100644 --- a/m2d/actions/redact.js +++ b/m2d/actions/redact.js @@ -24,7 +24,8 @@ async function removeReaction(event) { const hash = utils.getEventIDHash(event.redacts) const row = from("reaction").join("message_channel", "message_id").select("channel_id", "message_id", "encoded_emoji").and("WHERE hashed_event_id = ?").get(hash) if (!row) return - return discord.snow.channel.deleteReactionSelf(row.channel_id, row.message_id, row.encoded_emoji) + await discord.snow.channel.deleteReactionSelf(row.channel_id, row.message_id, row.encoded_emoji) + db.prepare("DELETE FROM reaction WHERE hashed_event_id = ?").run(hash) } /** diff --git a/matrix/api.js b/matrix/api.js index c429216..032e702 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -155,7 +155,10 @@ async function sendEvent(roomID, type, content, mxid, timestamp) { } /** - * @returns {Promise} room ID + * @param {string} roomID + * @param {string} eventID + * @param {string?} [mxid] + * @returns {Promise} event ID */ async function redactEvent(roomID, eventID, mxid) { /** @type {Ty.R.EventRedacted} */