From 954d41269c5034316014974484c0327a8558dd5f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 21 Aug 2025 11:30:23 +1200 Subject: [PATCH] Store directs in database rather than account data --- src/db/migrations/0024-add-direct.sql | 9 ++++++ src/db/orm-defs.d.ts | 38 +++++++++++++---------- src/m2d/event-dispatcher.js | 8 ++++- src/web/routes/log-in-with-matrix.js | 20 +++++------- src/web/routes/log-in-with-matrix.test.js | 32 ++++--------------- test/ooye-test-data.sql | 4 +++ 6 files changed, 54 insertions(+), 57 deletions(-) create mode 100644 src/db/migrations/0024-add-direct.sql diff --git a/src/db/migrations/0024-add-direct.sql b/src/db/migrations/0024-add-direct.sql new file mode 100644 index 0000000..94dc4ae --- /dev/null +++ b/src/db/migrations/0024-add-direct.sql @@ -0,0 +1,9 @@ +BEGIN TRANSACTION; + +CREATE TABLE direct ( + mxid TEXT NOT NULL, + room_id TEXT NOT NULL, + PRIMARY KEY (mxid) +) WITHOUT ROWID; + +COMMIT; diff --git a/src/db/orm-defs.d.ts b/src/db/orm-defs.d.ts index d96ccf2..79fd501 100644 --- a/src/db/orm-defs.d.ts +++ b/src/db/orm-defs.d.ts @@ -1,4 +1,9 @@ export type Models = { + auto_emoji: { + name: string + emoji_id: string + } + channel_room: { channel_id: string room_id: string @@ -14,6 +19,18 @@ export type Models = { custom_topic: number } + direct: { + mxid: string + room_id: string + } + + emoji: { + emoji_id: string + name: string + animated: number + mxc_url: string + } + event_message: { event_id: string message_id: string @@ -55,6 +72,10 @@ export type Models = { mxc_url: string } + media_proxy: { + permitted_hash: number + } + member_cache: { room_id: string mxid: string @@ -99,29 +120,12 @@ export type Models = { webhook_token: string } - emoji: { - emoji_id: string - name: string - animated: number - mxc_url: string - } - reaction: { hashed_event_id: number message_id: string encoded_emoji: string original_encoding: string | null } - - auto_emoji: { - name: string - emoji_id: string - guild_id: string - } - - media_proxy: { - permitted_hash: number - } } export type Prepared = { diff --git a/src/m2d/event-dispatcher.js b/src/m2d/event-dispatcher.js index ce5f79c..ce3638c 100644 --- a/src/m2d/event-dispatcher.js +++ b/src/m2d/event-dispatcher.js @@ -339,7 +339,13 @@ async event => { if (event.content.membership === "leave" || event.content.membership === "ban") { // Member is gone - return db.prepare("DELETE FROM member_cache WHERE room_id = ? and mxid = ?").run(event.room_id, event.state_key) + db.prepare("DELETE FROM member_cache WHERE room_id = ? and mxid = ?").run(event.room_id, event.state_key) + + // Unregister room's use as a direct chat if the bot itself left + const bot = `@${reg.sender_localpart}:${reg.ooye.server_name}` + if (event.state_key === bot) { + db.prepare("DELETE FROM direct WHERE room_id = ?").run(event.room_id) + } } const exists = select("channel_room", "room_id", {room_id: event.room_id}) ?? select("guild_space", "space_id", {space_id: event.room_id}) diff --git a/src/web/routes/log-in-with-matrix.js b/src/web/routes/log-in-with-matrix.js index 9943b82..574c312 100644 --- a/src/web/routes/log-in-with-matrix.js +++ b/src/web/routes/log-in-with-matrix.js @@ -5,7 +5,7 @@ const {randomUUID} = require("crypto") const {defineEventHandler, getValidatedQuery, sendRedirect, readValidatedBody, createError, getRequestHeader, H3Event} = require("h3") const {LRUCache} = require("lru-cache") -const {as} = require("../../passthrough") +const {as, db, select} = require("../../passthrough") const {reg} = require("../../matrix/read-registration") const {sync} = require("../../passthrough") @@ -79,15 +79,9 @@ as.router.post("/api/log-in-with-matrix", defineEventHandler(async event => { } } - // Get list of existing DMs from account data - let directData = {} - try { - directData = await api.getAccountData("m.direct") - } catch (e) {} - let roomID = directData[mxid]?.at(-1) - - // Reuse an existing DM, if able - if (typeof roomID === "string") { + // Check if we have an existing DM + let roomID = select("direct", "room_id", {mxid}).pluck().get() + if (roomID) { // Check that the person is/still in the room try { var member = await api.getStateEvent(roomID, "m.room.member", mxid) @@ -98,7 +92,8 @@ as.router.post("/api/log-in-with-matrix", defineEventHandler(async event => { await api.inviteToRoom(roomID, mxid) } } - // No existing DMs, create a new room and invite + + // No existing DM, create a new room and invite else { roomID = await api.createRoom({ invite: [mxid], @@ -106,8 +101,7 @@ as.router.post("/api/log-in-with-matrix", defineEventHandler(async event => { preset: "trusted_private_chat" }) // Store the newly created room in account data (Matrix doesn't do this for us automatically, sigh...) - ;(directData[mxid] ??= []).push(roomID) - await api.setAccountData("m.direct", directData) + db.prepare("REPLACE INTO direct (mxid, room_id) VALUES (?, ?)").run(mxid, roomID) } const token = randomUUID() diff --git a/src/web/routes/log-in-with-matrix.test.js b/src/web/routes/log-in-with-matrix.test.js index c86b92f..bc9c7e0 100644 --- a/src/web/routes/log-in-with-matrix.test.js +++ b/src/web/routes/log-in-with-matrix.test.js @@ -34,7 +34,7 @@ test("log in with matrix: checks if mxid domain format looks valid", async t => t.match(error.data.fieldErrors.mxid, /must match pattern/) }) -test("log in with matrix: sends message when there is no m.direct data", async t => { +test("log in with matrix: sends message when there is no existing dm room", async t => { const event = {} let called = 0 await router.test("post", "/api/log-in-with-matrix", { @@ -42,20 +42,10 @@ test("log in with matrix: sends message when there is no m.direct data", async t mxid: "@cadence:cadence.moe" }, api: { - async getAccountData(type) { - called++ - t.equal(type, "m.direct") - throw new MatrixServerError({errcode: "M_NOT_FOUND"}) - }, async createRoom() { called++ return "!created:cadence.moe" }, - async setAccountData(type, content) { - called++ - t.equal(type, "m.direct") - t.deepEqual(content, {"@cadence:cadence.moe": ["!created:cadence.moe"]}) - }, async sendEvent(roomID, type, content) { called++ t.equal(roomID, "!created:cadence.moe") @@ -68,7 +58,7 @@ test("log in with matrix: sends message when there is no m.direct data", async t event }) t.match(event.node.res.getHeader("location"), /Please check your inbox on Matrix/) - t.equal(called, 4) + t.equal(called, 2) }) test("log in with matrix: does not send another message when a log in is in progress", async t => { @@ -82,7 +72,7 @@ test("log in with matrix: does not send another message when a log in is in prog t.match(event.node.res.getHeader("location"), /We already sent you a link on Matrix/) }) -test("log in with matrix: reuses room from m.direct", async t => { +test("log in with matrix: reuses room from direct", async t => { const event = {} let called = 0 await router.test("post", "/api/log-in-with-matrix", { @@ -90,11 +80,6 @@ test("log in with matrix: reuses room from m.direct", async t => { mxid: "@user1:example.org" }, api: { - async getAccountData(type) { - called++ - t.equal(type, "m.direct") - return {"@user1:example.org": ["!existing:cadence.moe"]} - }, async getStateEvent(roomID, type, key) { called++ t.equal(roomID, "!existing:cadence.moe") @@ -111,10 +96,10 @@ test("log in with matrix: reuses room from m.direct", async t => { event }) t.match(event.node.res.getHeader("location"), /Please check your inbox on Matrix/) - t.equal(called, 3) + t.equal(called, 2) }) -test("log in with matrix: reuses room from m.direct, reinviting if user has left", async t => { +test("log in with matrix: reuses room from direct, reinviting if user has left", async t => { const event = {} let called = 0 await router.test("post", "/api/log-in-with-matrix", { @@ -122,11 +107,6 @@ test("log in with matrix: reuses room from m.direct, reinviting if user has left mxid: "@user2:example.org" }, api: { - async getAccountData(type) { - called++ - t.equal(type, "m.direct") - return {"@user2:example.org": ["!existing:cadence.moe"]} - }, async getStateEvent(roomID, type, key) { called++ t.equal(roomID, "!existing:cadence.moe") @@ -148,7 +128,7 @@ test("log in with matrix: reuses room from m.direct, reinviting if user has left event }) t.match(event.node.res.getHeader("location"), /Please check your inbox on Matrix/) - t.equal(called, 4) + t.equal(called, 3) }) // ***** third request ***** diff --git a/test/ooye-test-data.sql b/test/ooye-test-data.sql index faca448..b31f2c3 100644 --- a/test/ooye-test-data.sql +++ b/test/ooye-test-data.sql @@ -188,4 +188,8 @@ INSERT INTO invite (mxid, room_id, type, name, avatar, topic) VALUES ('@cadence:cadence.moe', '!room:cadence.moe', NULL, 'some room', NULL, NULL), ('@rnl:cadence.moe', '!space:cadence.moe', NULL, 'somebody else''s space', NULL, NULL); +INSERT INTO direct (mxid, room_id) VALUES +('@user1:example.org', '!existing:cadence.moe'), +('@user2:example.org', '!existing:cadence.moe'); + COMMIT;