From 61120d92c6b1cadc66e5b1433a9804b55689fc98 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 4 Jul 2023 17:19:17 +1200 Subject: [PATCH] m2d reactions (untested) --- d2m/actions/add-reaction.js | 4 ++-- m2d/actions/add-reaction.js | 25 +++++++++++++++++++++++++ m2d/converters/event-to-message.js | 3 ++- m2d/event-dispatcher.js | 15 +++++++++++++-- matrix/api.js | 13 +++++++------ types.d.ts | 8 ++++++++ types.js | 1 + 7 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 m2d/actions/add-reaction.js create mode 100644 types.js diff --git a/d2m/actions/add-reaction.js b/d2m/actions/add-reaction.js index cd3d296..49afca9 100644 --- a/d2m/actions/add-reaction.js +++ b/d2m/actions/add-reaction.js @@ -1,6 +1,6 @@ // @ts-check -const assert = require("assert") +const assert = require("assert").strict const passthrough = require("../../passthrough") const { discord, sync, db } = passthrough @@ -18,7 +18,7 @@ async function addReaction(data) { const user = data.member?.user assert.ok(user && user.username) const parentID = db.prepare("SELECT event_id FROM event_message WHERE message_id = ? AND part = 0").pluck().get(data.message_id) // 0 = primary - if (!parentID) return // TODO: how to handle reactions for unbridged messages? is there anything I can do? + if (!parentID) return // Nothing can be done if the parent message was never bridged. assert.equal(typeof parentID, "string") const roomID = await createRoom.ensureRoom(data.channel_id) const senderMxid = await registerUser.ensureSimJoined(user, roomID) diff --git a/m2d/actions/add-reaction.js b/m2d/actions/add-reaction.js new file mode 100644 index 0000000..342550d --- /dev/null +++ b/m2d/actions/add-reaction.js @@ -0,0 +1,25 @@ +// @ts-check + +const assert = require("assert").strict +const Ty = require("../../types") + +const passthrough = require("../../passthrough") +const { discord, sync, db } = passthrough + +/** + * @param {Ty.Event.Outer} event + */ +async function addReaction(event) { + const channelID = db.prepare("SELECT channel_id FROM channel_room WHERE room_id = ?").pluck().get(event.room_id) + if (!channelID) return // We just assume the bridge has already been created + const messageID = db.prepare("SELECT message_id FROM event_message WHERE event_id = ? AND part = 0").pluck().get(event.content["m.relates_to"].event_id) // 0 = primary + if (!messageID) return // Nothing can be done if the parent message was never bridged. + + // no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it + + const emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions + + return discord.snow.channel.createReaction(channelID, messageID, emoji) +} + +module.exports.addReaction = addReaction diff --git a/m2d/converters/event-to-message.js b/m2d/converters/event-to-message.js index 8b41903..817ffff 100644 --- a/m2d/converters/event-to-message.js +++ b/m2d/converters/event-to-message.js @@ -1,5 +1,6 @@ // @ts-check +const Ty = require("../../types") const DiscordTypes = require("discord-api-types/v10") const markdown = require("discord-markdown") @@ -9,7 +10,7 @@ const { sync, db, discord } = passthrough const file = sync.require("../../matrix/file") /** - * @param {import("../../types").Event.Outer} event + * @param {Ty.Event.Outer} event */ function eventToMessage(event) { /** @type {(DiscordTypes.RESTPostAPIWebhookWithTokenJSONBody & {files?: {name: string, file: Buffer}[]})[]} */ diff --git a/m2d/event-dispatcher.js b/m2d/event-dispatcher.js index 01a3dcc..82ebd75 100644 --- a/m2d/event-dispatcher.js +++ b/m2d/event-dispatcher.js @@ -4,19 +4,30 @@ * Grab Matrix events we care about, check them, and bridge them. */ +const Ty = require("../types") const {sync, as} = require("../passthrough") /** @type {import("./actions/send-event")} */ const sendEvent = sync.require("./actions/send-event") +/** @type {import("./actions/add-reaction")} */ +const addReaction = sync.require("./actions/add-reaction") /** @type {import("./converters/utils")} */ const utils = sync.require("./converters/utils") - sync.addTemporaryListener(as, "type:m.room.message", /** - * @param {import("../types").Event.Outer} event it is a m.room.message because that's what this listener is filtering for + * @param {Ty.Event.Outer} event it is a m.room.message because that's what this listener is filtering for */ async event => { if (utils.eventSenderIsFromDiscord(event.sender)) return const messageResponses = await sendEvent.sendEvent(event) }) + +sync.addTemporaryListener(as, "type:m.reaction", +/** + * @param {Ty.Event.Outer} event it is a m.reaction because that's what this listener is filtering for + */ +async event => { + if (utils.eventSenderIsFromDiscord(event.sender)) return + await addReaction.addReaction(event) +}) diff --git a/matrix/api.js b/matrix/api.js index ec85795..cf22933 100644 --- a/matrix/api.js +++ b/matrix/api.js @@ -1,5 +1,6 @@ // @ts-check +const Ty = require("../types") const assert = require("assert") const passthrough = require("../passthrough") @@ -25,7 +26,7 @@ function path(p, mxid) { /** * @param {string} username - * @returns {Promise} + * @returns {Promise} */ function register(username) { console.log(`[api] register: ${username}`) @@ -40,7 +41,7 @@ function register(username) { */ async function createRoom(content) { console.log(`[api] create room:`, content) - /** @type {import("../types").R.RoomCreated} */ + /** @type {Ty.R.RoomCreated} */ const root = await mreq.mreq("POST", "/client/v3/createRoom", content) return root.room_id } @@ -49,7 +50,7 @@ async function createRoom(content) { * @returns {Promise} room ID */ async function joinRoom(roomIDOrAlias, mxid) { - /** @type {import("../types").R.RoomJoined} */ + /** @type {Ty.R.RoomJoined} */ const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid)) return root.room_id } @@ -66,7 +67,7 @@ async function leaveRoom(roomID, mxid) { /** * @param {string} roomID - * @returns {Promise} + * @returns {Promise} */ function getAllState(roomID) { return mreq.mreq("GET", `/client/v3/rooms/${roomID}/state`) @@ -83,14 +84,14 @@ async function sendState(roomID, type, stateKey, content, mxid) { console.log(`[api] state: ${roomID}: ${type}/${stateKey}`) assert.ok(type) assert.ok(typeof stateKey === "string") - /** @type {import("../types").R.EventSent} */ + /** @type {Ty.R.EventSent} */ const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/state/${type}/${stateKey}`, mxid), content) return root.event_id } async function sendEvent(roomID, type, content, mxid) { console.log(`[api] event to ${roomID} as ${mxid || "default sim"}`) - /** @type {import("../types").R.EventSent} */ + /** @type {Ty.R.EventSent} */ const root = await mreq.mreq("PUT", path(`/client/v3/rooms/${roomID}/send/${type}/${makeTxnId.makeTxnId()}`, mxid), content) return root.event_id } diff --git a/types.d.ts b/types.d.ts index 19ef1f2..01ff6a1 100644 --- a/types.d.ts +++ b/types.d.ts @@ -62,6 +62,14 @@ namespace Event { display_name?: string avatar_url?: string } + + export type M_Reaction = { + "m.relates_to": { + rel_type: "m.annotation" + event_id: string // the event that was reacted to + key: string // the unicode emoji, mxc uri, or reaction text + } + } } namespace R { diff --git a/types.js b/types.js new file mode 100644 index 0000000..4ba52ba --- /dev/null +++ b/types.js @@ -0,0 +1 @@ +module.exports = {}