From a968bacffd47f5c85359cd380539f2d7ca17139a Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 3 Sep 2025 00:00:02 +1200 Subject: [PATCH 01/19] Update discord-markdown Interpret channel URLs the same as a channel #mention --- package-lock.json | 58 +++++++++++++++++++++++------------------------ package.json | 4 ++-- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 803fe53..aa7822f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@chriscdn/promise-semaphore": "^3.0.1", - "@cloudrac3r/discord-markdown": "^2.6.5", + "@cloudrac3r/discord-markdown": "^2.6.6", "@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/in-your-element": "^1.1.1", @@ -35,7 +35,7 @@ "lru-cache": "^11.0.2", "prettier-bytes": "^1.0.4", "sharp": "^0.33.4", - "snowtransfer": "^0.14.2", + "snowtransfer": "^0.15.0", "stream-mime-type": "^1.0.2", "try-to-catch": "^3.0.1", "uqr": "^0.1.2", @@ -119,9 +119,9 @@ } }, "node_modules/@chriscdn/promise-semaphore": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@chriscdn/promise-semaphore/-/promise-semaphore-3.0.1.tgz", - "integrity": "sha512-fVlCnoYE4hDzpcYRPtmN7dmcpmd2zxyPWjyfjIKI9Y+gsI7rwZSkjtuwMi8HFtlkSmNh8L7Zr37hdqeL13sYrw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@chriscdn/promise-semaphore/-/promise-semaphore-3.1.1.tgz", + "integrity": "sha512-ALLLLYlPfd/QZLptcVi6HQRK1zaCDWZoqYYw+axLmCatFs4gVTSZ5nqlyxwFe4qwR/K84HvOMa9hxda881FqMA==", "license": "MIT" }, "node_modules/@cloudcmd/stub": { @@ -225,9 +225,9 @@ } }, "node_modules/@cloudrac3r/discord-markdown": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.5.tgz", - "integrity": "sha512-B4uQNsyva5JNW0CVYkcunMQwWfrok1Hd5FYww/cWcvb98zp/pJdJfE3hoRl9EbnxNK2l62IJQ9j8HmssMFHJ9Q==", + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.6.tgz", + "integrity": "sha512-4FNO7WmACPvcTrQjeLQLr9WRuP7JDUVUGFrRJvmAjiMs2UlUAsShfSRuU2SCqz3QqmX8vyJ06wy2hkjTTyRtbw==", "license": "MIT", "dependencies": { "simple-markdown": "^0.7.3" @@ -949,9 +949,9 @@ "license": "MIT" }, "node_modules/@stackoverflow/stacks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@stackoverflow/stacks/-/stacks-2.8.3.tgz", - "integrity": "sha512-ZGBeuXJC7moK/f+lgl2dCAW85etD/RO0DNubocdH2qzpJMuuGXX0GMeEAfrTOe+B00I8E1OqTnS1cpkqGdHBdQ==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@stackoverflow/stacks/-/stacks-2.8.4.tgz", + "integrity": "sha512-FfA7Bw7a0AQrMw3/bG6G4BUrZ698F7Cdk6HkR9T7jdaufORkiX5d16wI4j4b5Sqm1FwkaZAF+ZSKLL1w0tAsew==", "license": "MIT", "dependencies": { "@hotwired/stimulus": "^3.2.2", @@ -1107,9 +1107,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.1.tgz", - "integrity": "sha512-y3tBaz+rjspDTylNjAX37jEC3TETEFGNJL6uQDxwF9/8GLLIjW1rvVHlynyuUKMnMr1Roq8jOv3vkopBjC4/VA==", + "version": "22.18.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz", + "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1452,13 +1452,13 @@ } }, "node_modules/cloudstorm": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.14.0.tgz", - "integrity": "sha512-EgjMGxb2Z+L6Acti6DzL/bEbR495AIqPThyW4DaG6Jpvd0ZuM5eC13EiyxV8wlqAME612QO2LjqbhkdXn/327Q==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/cloudstorm/-/cloudstorm-0.14.1.tgz", + "integrity": "sha512-x95WCKg818E1rE1Ru45NPD3RoIq0pg3WxwvF0GE7Eq07pAeLcjSRqM1lUmbmfjdOqZrWdSRYA1NETVZ8QhVrIA==", "license": "MIT", "dependencies": { - "discord-api-types": "^0.38.12", - "snowtransfer": "^0.14.2" + "discord-api-types": "^0.38.21", + "snowtransfer": "^0.15.0" }, "engines": { "node": ">=22.0.0" @@ -1616,9 +1616,9 @@ } }, "node_modules/discord-api-types": { - "version": "0.38.19", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.19.tgz", - "integrity": "sha512-NUNMTgjYrgxt7wrTNEqnEez4hIAYbfyBpsjxT5gW7+82GjQCPDZvN+em6t+4/P5kGWnnwDa4ci070BV7eI6GbA==", + "version": "0.38.22", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.22.tgz", + "integrity": "sha512-2gnYrgXN3yTlv2cKBISI/A8btZwsSZLwKpIQXeI1cS8a7W7wP3sFVQOm3mPuuinTD8jJCKGPGNH399zE7Un1kA==", "license": "MIT", "workspaces": [ "scripts/actions/documentation" @@ -2719,12 +2719,12 @@ } }, "node_modules/snowtransfer": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.14.2.tgz", - "integrity": "sha512-Fi8OdRmaIgeCj58oVej+tQAoY2I+Xp/6PAYV8X93jE/2E6Anc87SbTbDV6WZXCnuzTQz3gty8JOGz02qI7Qs9A==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.15.0.tgz", + "integrity": "sha512-kEDGKtFiH5nSkHsDZonEUuDx99lUasJoZ7AGrgvE8HzVG59vjvqc//C+pjWj4DuJqTj4Q+Z1L/M/MYNim8F2VA==", "license": "MIT", "dependencies": { - "discord-api-types": "^0.38.8" + "discord-api-types": "^0.38.21" }, "engines": { "node": ">=16.15.0" @@ -3447,9 +3447,9 @@ } }, "node_modules/zod": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.17.tgz", - "integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.5.tgz", + "integrity": "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index a7d3eaa..2fb21f2 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "@chriscdn/promise-semaphore": "^3.0.1", - "@cloudrac3r/discord-markdown": "^2.6.5", + "@cloudrac3r/discord-markdown": "^2.6.6", "@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/in-your-element": "^1.1.1", @@ -44,7 +44,7 @@ "lru-cache": "^11.0.2", "prettier-bytes": "^1.0.4", "sharp": "^0.33.4", - "snowtransfer": "^0.14.2", + "snowtransfer": "^0.15.0", "stream-mime-type": "^1.0.2", "try-to-catch": "^3.0.1", "uqr": "^0.1.2", From 5a152b87b846cfc95a9987033c49a14f0564f108 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 8 Sep 2025 12:37:19 +1200 Subject: [PATCH 02/19] I guess mentions is an optional property too --- src/d2m/converters/message-to-event.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js index a8e5a6b..1d6288a 100644 --- a/src/d2m/converters/message-to-event.js +++ b/src/d2m/converters/message-to-event.js @@ -33,9 +33,10 @@ function getDiscordParseCallbacks(message, guild, useHTML) { user: node => { const mxid = select("sim", "mxid", {user_id: node.id}).pluck().get() const interaction = message.interaction_metadata || message.interaction - const username = message.mentions.find(ment => ment.id === node.id)?.username - || message.referenced_message?.mentions.find(ment => ment.id === node.id)?.username + const username = message.mentions?.find(ment => ment.id === node.id)?.username + || message.referenced_message?.mentions?.find(ment => ment.id === node.id)?.username || (interaction?.user.id === node.id ? interaction.user.username : null) + || (message.author.id === node.id ? message.author.username : null) || node.id if (mxid && useHTML) { return `@${username}` From d8e6de62e50b75e02fdfc9d57fc613d561faa033 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Mon, 8 Sep 2025 16:26:16 +1200 Subject: [PATCH 03/19] Keep sim_proxy profile data up to date --- src/d2m/actions/register-pk-user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d2m/actions/register-pk-user.js b/src/d2m/actions/register-pk-user.js index b5e44e5..27e949c 100644 --- a/src/d2m/actions/register-pk-user.js +++ b/src/d2m/actions/register-pk-user.js @@ -146,7 +146,7 @@ async function syncUser(messageID, author, roomID, shouldActuallySync) { try { // API lookup var pkMessage = await fetchMessage(messageID) - db.prepare("INSERT OR IGNORE INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES (?, ?, ?)").run(pkMessage.member.uuid, pkMessage.sender, author.username) + db.prepare("REPLACE INTO sim_proxy (user_id, proxy_owner_id, displayname) VALUES (?, ?, ?)").run(pkMessage.member.uuid, pkMessage.sender, author.username) } catch (e) { // Fall back to offline cache const senderMxid = from("sim_proxy").join("sim", "user_id").join("sim_member", "mxid").where({displayname: author.username, room_id: roomID}).pluck("mxid").get() From 389150616395f0fd98b45ad0e13ad531e2ae7ff4 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 7 Oct 2025 00:46:44 -0500 Subject: [PATCH 04/19] Roll back snowtransfer to avoid issue with pins --- package-lock.json | 28 ++++++++++++++++++++-------- package.json | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa7822f..d82b914 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "lru-cache": "^11.0.2", "prettier-bytes": "^1.0.4", "sharp": "^0.33.4", - "snowtransfer": "^0.15.0", + "snowtransfer": "^0.14.2", "stream-mime-type": "^1.0.2", "try-to-catch": "^3.0.1", "uqr": "^0.1.2", @@ -1464,6 +1464,18 @@ "node": ">=22.0.0" } }, + "node_modules/cloudstorm/node_modules/snowtransfer": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.15.0.tgz", + "integrity": "sha512-kEDGKtFiH5nSkHsDZonEUuDx99lUasJoZ7AGrgvE8HzVG59vjvqc//C+pjWj4DuJqTj4Q+Z1L/M/MYNim8F2VA==", + "license": "MIT", + "dependencies": { + "discord-api-types": "^0.38.21" + }, + "engines": { + "node": ">=16.15.0" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -2719,12 +2731,12 @@ } }, "node_modules/snowtransfer": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.15.0.tgz", - "integrity": "sha512-kEDGKtFiH5nSkHsDZonEUuDx99lUasJoZ7AGrgvE8HzVG59vjvqc//C+pjWj4DuJqTj4Q+Z1L/M/MYNim8F2VA==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/snowtransfer/-/snowtransfer-0.14.2.tgz", + "integrity": "sha512-Fi8OdRmaIgeCj58oVej+tQAoY2I+Xp/6PAYV8X93jE/2E6Anc87SbTbDV6WZXCnuzTQz3gty8JOGz02qI7Qs9A==", "license": "MIT", "dependencies": { - "discord-api-types": "^0.38.21" + "discord-api-types": "^0.38.8" }, "engines": { "node": ">=16.15.0" @@ -3076,9 +3088,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", diff --git a/package.json b/package.json index 2fb21f2..f722066 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "lru-cache": "^11.0.2", "prettier-bytes": "^1.0.4", "sharp": "^0.33.4", - "snowtransfer": "^0.15.0", + "snowtransfer": "^0.14.2", "stream-mime-type": "^1.0.2", "try-to-catch": "^3.0.1", "uqr": "^0.1.2", From 7905802825c6dd03b26770fd32f03d1b77b6bf82 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 7 Oct 2025 00:48:06 -0500 Subject: [PATCH 05/19] Allow customising port in setup --- scripts/setup.js | 23 ++++++++++++++++++----- src/m2d/actions/setup-emojis.js | 3 +-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/scripts/setup.js b/scripts/setup.js index 6bff293..ecef03d 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -120,16 +120,28 @@ function defineEchoHandler() { /** @type {string} */ // @ts-ignore const serverOrigin = await serverOriginPrompt.run() + console.log("OOYE has its own web server. It needs to be accessible on the public internet.") + console.log("What port would you like OOYE to use? You can connect your reverse proxy to this port later.") + /** @type {{socket: string | number}} */ + const portResponse = await prompt({ + type: "input", + name: "socket", + message: "Web server port", + initial: "6693" + }) + portResponse.socket = +portResponse.socket || portResponse.socket // convert to number if numeric + const app = createApp() app.use(defineEchoHandler()) const server = createServer(toNodeListener(app)) - await server.listen(6693) + await server.listen(portResponse.socket) - console.log("OOYE has its own web server. It needs to be accessible on the public internet.") - console.log("You need to enter a public URL where you will be able to host this web server.") - console.log("OOYE listens on localhost:6693, so you will probably have to set up a reverse proxy.") + console.log("Now you need to enter a public URL that OOYE's web server will live on.") + console.log("Set up your reverse proxy so that this URL accesses OOYE.") console.log("Examples: https://gitdab.com/cadence/out-of-your-element/src/branch/main/docs/get-started.md#appendix") - console.log("Now listening on port 6693. Feel free to send some test requests.") + if (typeof portResponse.socket === "number") { + console.log(`Now listening on http://localhost:${portResponse.socket}. Feel free to send some test requests.`) + } /** @type {{bridge_origin: string}} */ const bridgeOriginResponse = await prompt({ type: "input", @@ -255,6 +267,7 @@ function defineEchoHandler() { reg = { ...template, url: bridgeOriginResponse.bridge_origin, + ...portResponse, ooye: { ...template.ooye, ...bridgeOriginResponse, diff --git a/src/m2d/actions/setup-emojis.js b/src/m2d/actions/setup-emojis.js index ba2c045..1be1d2d 100644 --- a/src/m2d/actions/setup-emojis.js +++ b/src/m2d/actions/setup-emojis.js @@ -5,9 +5,8 @@ const {join} = require("path") const passthrough = require("../../passthrough") -const {id} = require("../../../addbot") - async function setupEmojis() { + const {id} = require("../../../addbot") const {discord, db} = passthrough const emojis = await discord.snow.assets.getAppEmojis(id) for (const name of ["L1", "L2"]) { From 7916f82b55fd81d95abfdea328b5decc75588492 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 7 Oct 2025 14:09:42 -0500 Subject: [PATCH 06/19] Change thread started message (closes #61) --- src/d2m/converters/thread-to-announcement.js | 3 --- src/d2m/converters/thread-to-announcement.test.js | 10 ---------- 2 files changed, 13 deletions(-) diff --git a/src/d2m/converters/thread-to-announcement.js b/src/d2m/converters/thread-to-announcement.js index 11a067f..98b8f12 100644 --- a/src/d2m/converters/thread-to-announcement.js +++ b/src/d2m/converters/thread-to-announcement.js @@ -32,13 +32,10 @@ async function threadToAnnouncement(parentRoomID, threadRoomID, creatorMxid, thr const template = creatorMxid ? "started a thread:" : "Thread started:" const via = await mxUtils.getViaServersQuery(threadRoomID, di.api) let body = `${template} ${thread.name} https://matrix.to/#/${threadRoomID}?${via.toString()}` - let html = `${template} ${thread.name}` return { msgtype, body, - format: "org.matrix.custom.html", - formatted_body: html, "m.mentions": {}, ...context } diff --git a/src/d2m/converters/thread-to-announcement.test.js b/src/d2m/converters/thread-to-announcement.test.js index 471cd94..3d5d1eb 100644 --- a/src/d2m/converters/thread-to-announcement.test.js +++ b/src/d2m/converters/thread-to-announcement.test.js @@ -55,8 +55,6 @@ test("thread2announcement: no known creator, no branched from event", async t => t.deepEqual(content, { msgtype: "m.text", body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", - format: "org.matrix.custom.html", - formatted_body: `Thread started: test thread`, "m.mentions": {} }) }) @@ -69,8 +67,6 @@ test("thread2announcement: known creator, no branched from event", async t => { t.deepEqual(content, { msgtype: "m.emote", body: "started a thread: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", - format: "org.matrix.custom.html", - formatted_body: `started a thread: test thread`, "m.mentions": {} }) }) @@ -95,8 +91,6 @@ test("thread2announcement: no known creator, branched from discord event", async t.deepEqual(content, { msgtype: "m.text", body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", - format: "org.matrix.custom.html", - formatted_body: `Thread started: test thread`, "m.mentions": {}, "m.relates_to": { "m.in_reply_to": { @@ -126,8 +120,6 @@ test("thread2announcement: known creator, branched from discord event", async t t.deepEqual(content, { msgtype: "m.emote", body: "started a thread: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", - format: "org.matrix.custom.html", - formatted_body: `started a thread: test thread`, "m.mentions": {}, "m.relates_to": { "m.in_reply_to": { @@ -157,8 +149,6 @@ test("thread2announcement: no known creator, branched from matrix event", async t.deepEqual(content, { msgtype: "m.text", body: "Thread started: test thread https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org", - format: "org.matrix.custom.html", - formatted_body: `Thread started: test thread`, "m.mentions": { user_ids: ["@cadence:cadence.moe"] }, From 5b7433de3260a0504df94f7dcda6a0f0a8ed2884 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 7 Oct 2025 14:09:50 -0500 Subject: [PATCH 07/19] Make tests time zone independent --- src/d2m/converters/message-to-event.js | 2 +- src/types.d.ts | 1 + test/test.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js index 1d6288a..f494123 100644 --- a/src/d2m/converters/message-to-event.js +++ b/src/d2m/converters/message-to-event.js @@ -611,7 +611,7 @@ async function messageToEvent(message, guild, options = {}, di) { const event = invite.guild_scheduled_event if (!event) continue // the event ID provided was not valid - const formatter = new Intl.DateTimeFormat("en-NZ", {month: "long", day: "numeric", hour: "numeric", minute: "2-digit", timeZoneName: "shortGeneric"}) // 9 June at 3:00 pm NZT + const formatter = new Intl.DateTimeFormat("en-NZ", {month: "long", day: "numeric", hour: "numeric", minute: "2-digit", timeZoneName: "shortGeneric", timeZone: reg.ooye.time_zone}) // 9 June at 3:00 pm NZT const rep = new mxUtils.MatrixStringBuilder() // Add time diff --git a/src/types.d.ts b/src/types.d.ts index 37da633..27dfddf 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -31,6 +31,7 @@ export type AppServiceRegistrationConfig = { discord_origin?: string discord_cdn_origin?: string, web_password: string + time_zone?: string } old_bridge?: { as_token: string diff --git a/test/test.js b/test/test.js index 3695a84..233fd94 100644 --- a/test/test.js +++ b/test/test.js @@ -25,6 +25,7 @@ reg.namespaces = { aliases: [{regex: "#_ooye_.*:cadence.moe", exclusive: true}] } reg.ooye.bridge_origin = "https://bridge.example.org" +reg.ooye.time_zone = "Pacific/Auckland" const sync = new HeatSync({watchFS: false}) From cffd3c9f2ea0dc1c6be04f28501f4b59ee88caa2 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 10 Oct 2025 12:26:01 -0600 Subject: [PATCH 08/19] Fix converting discord channel links --- package-lock.json | 8 +-- package.json | 2 +- src/d2m/converters/message-to-event.js | 4 +- src/d2m/converters/message-to-event.test.js | 38 ++++++++++++++ test/data.js | 57 +++++++++++++++++++++ 5 files changed, 102 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index d82b914..fda73e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@chriscdn/promise-semaphore": "^3.0.1", - "@cloudrac3r/discord-markdown": "^2.6.6", + "@cloudrac3r/discord-markdown": "^2.6.7", "@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/in-your-element": "^1.1.1", @@ -225,9 +225,9 @@ } }, "node_modules/@cloudrac3r/discord-markdown": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.6.tgz", - "integrity": "sha512-4FNO7WmACPvcTrQjeLQLr9WRuP7JDUVUGFrRJvmAjiMs2UlUAsShfSRuU2SCqz3QqmX8vyJ06wy2hkjTTyRtbw==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/@cloudrac3r/discord-markdown/-/discord-markdown-2.6.7.tgz", + "integrity": "sha512-bWLmBYWaNEDcQfZHDz4jaAxLKA9161ruEnHo3ms6kfRw8uYku/Uz7U1xTmQ2dQF/q1PiuBvM9I37pLiotlQj8A==", "license": "MIT", "dependencies": { "simple-markdown": "^0.7.3" diff --git a/package.json b/package.json index f722066..85530c1 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "@chriscdn/promise-semaphore": "^3.0.1", - "@cloudrac3r/discord-markdown": "^2.6.6", + "@cloudrac3r/discord-markdown": "^2.6.7", "@cloudrac3r/giframe": "^0.4.3", "@cloudrac3r/html-template-tag": "^5.0.1", "@cloudrac3r/in-your-element": "^1.1.1", diff --git a/src/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js index f494123..30a20fe 100644 --- a/src/d2m/converters/message-to-event.js +++ b/src/d2m/converters/message-to-event.js @@ -408,13 +408,13 @@ async function messageToEvent(message, guild, options = {}, di) { async function transformParsedVia(parsed) { for (const node of parsed) { - if (node.type === "discordChannel") { + if (node.type === "discordChannel" || node.type === "discordChannelLink") { node.row = select("channel_room", ["room_id", "name", "nick"], {channel_id: node.id}).get() if (node.row?.room_id) { node.via = await getViaServersMemo(node.row.room_id) } } - ;for (const maybeChildNodesArray of [node, node.content, node.items]) { + for (const maybeChildNodesArray of [node, node.content, node.items]) { if (Array.isArray(maybeChildNodesArray)) { await transformParsedVia(maybeChildNodesArray) } diff --git a/src/d2m/converters/message-to-event.test.js b/src/d2m/converters/message-to-event.test.js index fc933e3..ee4ec03 100644 --- a/src/d2m/converters/message-to-event.test.js +++ b/src/d2m/converters/message-to-event.test.js @@ -100,6 +100,44 @@ test("message2event: simple room mention", async t => { t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each") }) +test("message2event: simple room link", async t => { + let called = 0 + const events = await messageToEvent(data.message.simple_room_link, data.guild.general, {}, { + api: { + async getStateEvent(roomID, type, key) { + called++ + t.equal(roomID, "!BnKuBPCvyfOkhcUjEu:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + return { + users: { + "@_ooye_bot:cadence.moe": 100 + } + } + }, + async getJoinedMembers(roomID) { + called++ + t.equal(roomID, "!BnKuBPCvyfOkhcUjEu:cadence.moe") + return { + joined: { + "@_ooye_bot:cadence.moe": {display_name: null, avatar_url: null}, + "@user:matrix.org": {display_name: null, avatar_url: null} + } + } + } + } + }) + t.deepEqual(events, [{ + $type: "m.room.message", + "m.mentions": {}, + msgtype: "m.text", + body: "#worm-farm", + format: "org.matrix.custom.html", + formatted_body: '#worm-farm' + }]) + t.equal(called, 2, "should call getStateEvent and getJoinedMembers once each") +}) + test("message2event: nicked room mention", async t => { let called = 0 const events = await messageToEvent(data.message.nicked_room_mention, data.guild.general, {}, { diff --git a/test/data.js b/test/data.js index a8ff8a8..e64b9c2 100644 --- a/test/data.js +++ b/test/data.js @@ -1398,6 +1398,63 @@ module.exports = { attachments: [], guild_id: "112760669178241024" }, + simple_room_link: { + type: 0, + tts: false, + timestamp: "2023-07-10T20:04:25.939000+00:00", + referenced_message: null, + pinned: false, + nonce: "1128054139385806848", + mentions: [], + mention_roles: [], + mention_everyone: false, + member: { + roles: [ + "112767366235959296", "118924814567211009", + "204427286542417920", "199995902742626304", + "222168467627835392", "238028326281805825", + "259806643414499328", "265239342648131584", + "271173313575780353", "287733611912757249", + "225744901915148298", "305775031223320577", + "318243902521868288", "348651574924541953", + "349185088157777920", "378402925128712193", + "392141548932038658", "393912152173576203", + "482860581670486028", "495384759074160642", + "638988388740890635", "373336013109461013", + "530220455085473813", "454567553738473472", + "790724320824655873", "1123518980456452097", + "1040735082610167858", "695946570482450442", + "1123460940935991296", "849737964090556488" + ], + premium_since: null, + pending: false, + nick: null, + mute: false, + joined_at: "2015-11-11T09:55:40.321000+00:00", + flags: 0, + deaf: false, + communication_disabled_until: null, + avatar: null + }, + id: "1128054143064494233", + flags: 0, + embeds: [], + edited_timestamp: null, + content: "https://discord.com/channels/112760669178241024/1100319550446252084", + components: [], + channel_id: "266767590641238027", + author: { + username: "kumaccino", + public_flags: 128, + id: "113340068197859328", + global_name: "kumaccino", + discriminator: "0", + avatar_decoration: null, + avatar: "b48302623a12bc7c59a71328f72ccb39" + }, + attachments: [], + guild_id: "112760669178241024" + }, nicked_room_mention: { type: 0, tts: false, From 3de762d42852174c2876845dd4eacdb1698a3647 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sun, 12 Oct 2025 12:17:20 -0600 Subject: [PATCH 09/19] Fix stickers that don't provide content type --- src/matrix/api.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/matrix/api.js b/src/matrix/api.js index 41af63f..709d70c 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -384,7 +384,9 @@ async function getMedia(mxc, init = {}) { }, ...init }) - assert(res.body) + if (init.method !== "HEAD") { + assert(res.body) + } // @ts-ignore return res } From d4f4664c25454ffe50672264737be83afde824ba Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Thu, 23 Oct 2025 23:05:23 +1100 Subject: [PATCH 10/19] Fix retrying m->d message deletions --- src/m2d/actions/redact.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/m2d/actions/redact.js b/src/m2d/actions/redact.js index 1f6cef8..1d3aa67 100644 --- a/src/m2d/actions/redact.js +++ b/src/m2d/actions/redact.js @@ -13,10 +13,12 @@ const utils = sync.require("../converters/utils") */ async function deleteMessage(event) { const rows = from("event_message").join("message_channel", "message_id").select("channel_id", "message_id").where({event_id: event.redacts}).all() + if (!rows.length) return for (const row of rows) { - db.prepare("DELETE FROM message_channel WHERE message_id = ?").run(row.message_id) await discord.snow.channel.deleteMessage(row.channel_id, row.message_id, event.content.reason) + db.prepare("DELETE FROM event_message WHERE message_id = ?").run(row.message_id) } + db.prepare("DELETE FROM message_channel WHERE message_id = ?").run(rows[0].message_id) } /** From 255e166e8cd7ff95f2576ab7b55c46bc93b6c290 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 31 Oct 2025 16:22:32 +1300 Subject: [PATCH 11/19] Better message when remote emojis unavailable --- src/m2d/actions/emoji-sheet.js | 6 ++++++ src/m2d/converters/event-to-message.js | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/m2d/actions/emoji-sheet.js b/src/m2d/actions/emoji-sheet.js index a63f0b0..ed5ab88 100644 --- a/src/m2d/actions/emoji-sheet.js +++ b/src/m2d/actions/emoji-sheet.js @@ -7,6 +7,8 @@ const {sync} = require("../../passthrough") const emojiSheetConverter = sync.require("../converters/emoji-sheet") /** @type {import("../../matrix/api")} */ const api = sync.require("../../matrix/api") +/** @type {import("../../matrix/mreq")} */ +const mreq = sync.require("../../matrix/mreq") /** * Downloads the emoji from the web and converts to uncompressed PNG data. @@ -19,6 +21,10 @@ async function getAndConvertEmoji(mxc) { // If we were using connection pooling, we would be forced to download the entire GIF. // So we set no agent to ensure we are not connection pooling. const res = await api.getMedia(mxc, {signal: abortController.signal}) + if (res.status !== 200) { + const root = await res.json() + throw new mreq.MatrixServerError(root, {mxc}) + } const readable = stream.Readable.fromWeb(res.body) return emojiSheetConverter.convertImageStream(readable, () => { abortController.abort() diff --git a/src/m2d/converters/event-to-message.js b/src/m2d/converters/event-to-message.js index 3cf08cf..61525e2 100644 --- a/src/m2d/converters/event-to-message.js +++ b/src/m2d/converters/event-to-message.js @@ -11,6 +11,7 @@ const entities = require("entities") const passthrough = require("../../passthrough") const {sync, db, discord, select, from} = passthrough +const {reg} = require("../../matrix/read-registration") /** @type {import("../converters/utils")} */ const mxUtils = sync.require("../converters/utils") /** @type {import("../../discord/utils")} */ @@ -238,7 +239,8 @@ function convertEmoji(mxcUrl, nameForGuess, allowSpriteSheetIndicator, allowLink if (!found) row = null } // Or, if we don't have an emoji right now, we search for the name instead. - if (!row && nameForGuess) { + const isLocalMxc = mxcUrl?.match(/^mxc:\/\/([^/]+)/)?.[1] === reg.ooye.server_name + if (!row && nameForGuess && isLocalMxc) { const nameForGuessLower = nameForGuess.toLowerCase() for (const guild of discord.guilds.values()) { /** @type {{name: string, id: string, animated: number}[]} */ From dc7b44408684379a2c41916c2efb849fc96d93f0 Mon Sep 17 00:00:00 2001 From: Elliu Date: Thu, 21 Aug 2025 21:38:34 +0900 Subject: [PATCH 12/19] Fix matrix api joinRoom() for remote rooms When using self-service mode and trying to link with a remote matrix room (room not in the same HS as the bridge user), then we need to add the "via" HSs to join the room with, or else it fails. We get it from the "m.space.child" in the "children_state" of the space hierarchy. It seems like the "via" information can also be stored in the "m.space.parent" in the states of the room, but hopefully this shouldn't be needed in sane implementations --- src/matrix/api.js | 4 ++-- src/web/routes/link.js | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/matrix/api.js b/src/matrix/api.js index 709d70c..9daf65a 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -64,9 +64,9 @@ async function createRoom(content) { /** * @returns {Promise} room ID */ -async function joinRoom(roomIDOrAlias, mxid) { +async function joinRoom(roomIDOrAlias, mxid, via) { /** @type {Ty.R.RoomJoined} */ - const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid), {}) + const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid, via), {}) return root.room_id } diff --git a/src/web/routes/link.js b/src/web/routes/link.js index c5f404e..7654adf 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -77,7 +77,7 @@ as.router.post("/api/link-space", defineEventHandler(async event => { // Check space exists and bridge is joined try { - await api.joinRoom(parsedBody.space_id) + await api.joinRoom(parsedBody.space_id, null, via) } catch (e) { throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`}) } @@ -135,19 +135,31 @@ as.router.post("/api/link", defineEventHandler(async event => { // Check room is part of the guild's space let found = false + let via = undefined for await (const room of api.generateFullHierarchy(spaceID)) { - if (room.room_id === parsedBody.matrix && !room.room_type) { + if (via === undefined && room.room_type === "m.space") { + for (state of room.children_state) { + if (state.state_key === parsedBody.matrix){ + via = {via: state.content.via} + if (found === true) + break + } + } + } + + if (!found && room.room_id === parsedBody.matrix && !room.room_type) { found = true - break + if (via !== undefined) + break } } if (!found) throw createError({status: 400, message: "Bad Request", data: "Matrix room needs to be part of the bridged space"}) // Check room exists and bridge is joined try { - await api.joinRoom(parsedBody.matrix) + await api.joinRoom(parsedBody.matrix, null, via ?? {}) } catch (e) { - throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`}) + throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}${via === null ? " (hint: couln't find a \"via\" in the space children_state for this room in order to help joining this room)" : ""}`}) } // Check bridge has PL 100 From 1efd301e1d21764b96c0b57a680d74c962ab197f Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 22 Aug 2025 21:25:05 +1200 Subject: [PATCH 13/19] Cleanup --- src/matrix/api.js | 11 +++++++++-- src/types.d.ts | 10 +++++++++- src/web/routes/link.js | 34 ++++++++++++++++++---------------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/matrix/api.js b/src/matrix/api.js index 9daf65a..edffc45 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -22,7 +22,11 @@ function path(p, mxid, otherParams = {}) { const u = new URL(p, "http://localhost") if (mxid) u.searchParams.set("user_id", mxid) for (const entry of Object.entries(otherParams)) { - if (entry[1] != undefined) { + if (Array.isArray(entry[1])) { + for (const element of entry[1]) { + u.searchParams.append(entry[0], element) + } + } else if (entry[1] != undefined) { u.searchParams.set(entry[0], entry[1]) } } @@ -62,11 +66,14 @@ async function createRoom(content) { } /** + * @param {string} roomIDOrAlias + * @param {string?} [mxid] + * @param {string[]?} [via] * @returns {Promise} room ID */ async function joinRoom(roomIDOrAlias, mxid, via) { /** @type {Ty.R.RoomJoined} */ - const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid, via), {}) + const root = await mreq.mreq("POST", path(`/client/v3/join/${roomIDOrAlias}`, mxid, {via}), {}) return root.room_id } diff --git a/src/types.d.ts b/src/types.d.ts index 27dfddf..a34095d 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -149,6 +149,14 @@ export namespace Event { prev_content?: any } + export type Outer_StrippedChildStateEvent = { + type: string + state_key: string + sender: string + origin_server_ts: number + content: any + } + export type M_Room_Message = { msgtype: "m.text" | "m.emote" body: string @@ -345,7 +353,7 @@ export namespace R { export type Hierarchy = { avatar_url?: string canonical_alias?: string - children_state: {} + children_state: Event.Outer_StrippedChildStateEvent[] guest_can_join: boolean join_rule?: string name?: string diff --git a/src/web/routes/link.js b/src/web/routes/link.js index 7654adf..f85e66d 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -134,32 +134,34 @@ as.router.post("/api/link", defineEventHandler(async event => { if (row) throw createError({status: 400, message: "Bad Request", data: `Channel ID ${row.channel_id} or room ID ${parsedBody.matrix} are already bridged and cannot be reused`}) // Check room is part of the guild's space - let found = false - let via = undefined + let foundRoom = false + /** @type {string[]?} */ + let foundVia = null for await (const room of api.generateFullHierarchy(spaceID)) { - if (via === undefined && room.room_type === "m.space") { - for (state of room.children_state) { - if (state.state_key === parsedBody.matrix){ - via = {via: state.content.via} - if (found === true) - break - } + // When finding a space during iteration, look at space's children state, because we need a `via` to join the room (when we find it later) + for (const state of room.children_state) { + if (state.type === "m.space.child" && state.state_key === parsedBody.matrix) { + foundVia = state.content.via } } - if (!found && room.room_id === parsedBody.matrix && !room.room_type) { - found = true - if (via !== undefined) - break + // When finding a room during iteration, see if it was the requested room (to confirm that the room is in the space) + if (room.room_id === parsedBody.matrix && !room.room_type) { + foundRoom = true } + + if (foundRoom && foundVia) break } - if (!found) throw createError({status: 400, message: "Bad Request", data: "Matrix room needs to be part of the bridged space"}) + if (!foundRoom) throw createError({status: 400, message: "Bad Request", data: "Matrix room needs to be part of the bridged space"}) // Check room exists and bridge is joined try { - await api.joinRoom(parsedBody.matrix, null, via ?? {}) + await api.joinRoom(parsedBody.matrix, null, foundVia) } catch (e) { - throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}${via === null ? " (hint: couln't find a \"via\" in the space children_state for this room in order to help joining this room)" : ""}`}) + if (!foundVia) { + throw createError({status: 403, message: "Unable To Join", data: `Unable to join the requested Matrix room. Please invite the bridge to the room and try again. (Server said: ${e.errcode} - ${e.message})`}) + } + throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`}) } // Check bridge has PL 100 From ea08e16963d012943e631506be28e407f6737ebb Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 22 Aug 2025 21:42:13 +1200 Subject: [PATCH 14/19] Update tests for new types and code path --- src/web/routes/link.test.js | 58 +++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/src/web/routes/link.test.js b/src/web/routes/link.test.js index 0d8d366..068bc9b 100644 --- a/src/web/routes/link.test.js +++ b/src/web/routes/link.test.js @@ -360,7 +360,7 @@ test("web link room: check that room is part of space (not in hierarchy)", async t.equal(called, 1) }) -test("web link room: check that bridge can join room", async t => { +test("web link room: check that bridge can join room (notices lack of via and asks for invite instead)", async t => { let called = 0 const [error] = await tryToCatch(() => router.test("post", "/api/link", { sessionData: { @@ -381,7 +381,55 @@ test("web link room: check that bridge can join room", async t => { t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe") yield { room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", - children_state: {}, + children_state: [], + guest_can_join: false, + num_joined_members: 2 + } + /* c8 ignore next */ + } + } + })) + t.equal(error.data, "Unable to join the requested Matrix room. Please invite the bridge to the room and try again. (Server said: M_FORBIDDEN - not allowed to join I guess)") + t.equal(called, 2) +}) + +test("web link room: check that bridge can join room (uses via for join attempt)", async t => { + let called = 0 + const [error] = await tryToCatch(() => router.test("post", "/api/link", { + sessionData: { + managedGuilds: ["665289423482519565"] + }, + body: { + discord: "665310973967597573", + matrix: "!NDbIqNpJyPvfKRnNcr:cadence.moe", + guild_id: "665289423482519565" + }, + api: { + async joinRoom(roomID, _, via) { + called++ + t.deepEqual(via, ["cadence.moe", "hashi.re"]) + throw new MatrixServerError({errcode: "M_FORBIDDEN", error: "not allowed to join I guess"}) + }, + async *generateFullHierarchy(spaceID) { + called++ + t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe") + yield { + room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", + children_state: [], + guest_can_join: false, + num_joined_members: 2 + } + yield { + room_id: "!zTMspHVUBhFLLSdmnS:cadence.moe", + children_state: [{ + type: "m.space.child", + state_key: "!NDbIqNpJyPvfKRnNcr:cadence.moe", + sender: "@elliu:hashi.re", + content: { + via: ["cadence.moe", "hashi.re"] + }, + origin_server_ts: 0 + }], guest_can_join: false, num_joined_members: 2 } @@ -414,7 +462,7 @@ test("web link room: check that bridge has PL 100 in target room (event missing) t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe") yield { room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", - children_state: {}, + children_state: [], guest_can_join: false, num_joined_members: 2 } @@ -454,7 +502,7 @@ test("web link room: check that bridge has PL 100 in target room (users default) t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe") yield { room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", - children_state: {}, + children_state: [], guest_can_join: false, num_joined_members: 2 } @@ -494,7 +542,7 @@ test("web link room: successfully calls createRoom", async t => { t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe") yield { room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", - children_state: {}, + children_state: [], guest_can_join: false, num_joined_members: 2 } From e7b4dfea9cb48598e9102764c6077797f222c930 Mon Sep 17 00:00:00 2001 From: Elliu Date: Sat, 23 Aug 2025 00:01:30 +0900 Subject: [PATCH 15/19] Fix /api/link-space joinRoom() for remote spaces --- src/web/routes/link.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/web/routes/link.js b/src/web/routes/link.js index f85e66d..b46b78f 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -12,6 +12,20 @@ const auth = sync.require("../auth") const mreq = sync.require("../../matrix/mreq") const {reg} = require("../../matrix/read-registration") +/** + * @param {string} UserID + * @returns {string} the HS of the user, or "" if the user ID is malformed + */ +function getHSOfUser(user) { + domainStartIndex = user.indexOf(":"); + if (domainStartIndex >= 1) { + return user.slice(domainStartIndex + 1) + } + + return "" +} + + /** * @param {H3Event} event * @returns {import("../../matrix/api")} @@ -75,6 +89,9 @@ as.router.post("/api/link-space", defineEventHandler(async event => { const existing = select("guild_space", "guild_id", {}, "WHERE guild_id = ? OR space_id = ?").get(guildID, spaceID) if (existing) throw createError({status: 400, message: "Bad Request", data: `Guild ID ${guildID} or space ID ${spaceID} are already bridged and cannot be reused`}) + const inviteSender = select("invite", "mxid", {mxid: session.data.mxid, room_id: spaceID}).pluck().get() + via = [ getHSOfUser(inviteSender) ] + // Check space exists and bridge is joined try { await api.joinRoom(parsedBody.space_id, null, via) From 0776cc6ccdddef5da82caaa090121bf3809bf0c0 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 23 Aug 2025 23:46:51 +1200 Subject: [PATCH 16/19] Fill in more of reg for other people to test with --- src/matrix/api.test.js | 4 ++++ test/test.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/matrix/api.test.js b/src/matrix/api.test.js index 82565eb..da92385 100644 --- a/src/matrix/api.test.js +++ b/src/matrix/api.test.js @@ -24,3 +24,7 @@ test("api path: real world mxid", t => { test("api path: extras number works", t => { t.equal(path(`/client/v3/rooms/!example/timestamp_to_event`, null, {ts: 1687324651120}), "/client/v3/rooms/!example/timestamp_to_event?ts=1687324651120") }) + +test("api path: multiple via params", t => { + t.equal(path(`/client/v3/rooms/!example/join`, null, {via: ["cadence.moe", "matrix.org"], ts: 1687324651120}), "/client/v3/rooms/!example/join?via=cadence.moe&via=matrix.org&ts=1687324651120") +}) diff --git a/test/test.js b/test/test.js index 233fd94..b01f0ce 100644 --- a/test/test.js +++ b/test/test.js @@ -17,6 +17,8 @@ const {reg} = require("../src/matrix/read-registration") reg.ooye.discord_token = "Njg0MjgwMTkyNTUzODQ0NzQ3.Xl3zlw.baby" reg.ooye.server_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded reg.ooye.server_name = "cadence.moe" +reg.ooye.namespace_prefix = "_ooye_" +reg.sender_localpart = "_ooye_bot" reg.id = "baby" reg.as_token = "don't actually take authenticated actions on the server" reg.hs_token = "don't actually take authenticated actions on the server" From 7932f8af851148bb7a10a4e5ac8f9305f15d2be1 Mon Sep 17 00:00:00 2001 From: Elliu Date: Sun, 31 Aug 2025 20:11:07 +0900 Subject: [PATCH 17/19] Add "please try invite" message when joinRoom in /api/link-space fails --- src/web/routes/link.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/web/routes/link.js b/src/web/routes/link.js index b46b78f..d16ecea 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -96,6 +96,9 @@ as.router.post("/api/link-space", defineEventHandler(async event => { try { await api.joinRoom(parsedBody.space_id, null, via) } catch (e) { + if (via.join("") == "") { + throw createError({status: 403, message: "Unable To Join", data: `Unable to join the requested Matrix space. Please invite the bridge to the space and try again. (Server said: ${e.errcode} - ${e.message})`}) + } throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`}) } From 717dc185e5b68def578b1d31de5a158b386e0ab6 Mon Sep 17 00:00:00 2001 From: Elliu Date: Thu, 9 Oct 2025 21:54:15 +0900 Subject: [PATCH 18/19] Misc. fixes for remote join --- src/web/routes/link.js | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/web/routes/link.js b/src/web/routes/link.js index d16ecea..a7ae8ec 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -12,20 +12,6 @@ const auth = sync.require("../auth") const mreq = sync.require("../../matrix/mreq") const {reg} = require("../../matrix/read-registration") -/** - * @param {string} UserID - * @returns {string} the HS of the user, or "" if the user ID is malformed - */ -function getHSOfUser(user) { - domainStartIndex = user.indexOf(":"); - if (domainStartIndex >= 1) { - return user.slice(domainStartIndex + 1) - } - - return "" -} - - /** * @param {H3Event} event * @returns {import("../../matrix/api")} @@ -90,16 +76,13 @@ as.router.post("/api/link-space", defineEventHandler(async event => { if (existing) throw createError({status: 400, message: "Bad Request", data: `Guild ID ${guildID} or space ID ${spaceID} are already bridged and cannot be reused`}) const inviteSender = select("invite", "mxid", {mxid: session.data.mxid, room_id: spaceID}).pluck().get() - via = [ getHSOfUser(inviteSender) ] + const via = [ inviteSender?.match(/:(.*)/)?.[1] ?? "" ] // Check space exists and bridge is joined try { await api.joinRoom(parsedBody.space_id, null, via) } catch (e) { - if (via.join("") == "") { - throw createError({status: 403, message: "Unable To Join", data: `Unable to join the requested Matrix space. Please invite the bridge to the space and try again. (Server said: ${e.errcode} - ${e.message})`}) - } - throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`}) + throw createError({status: 400, message: "Unable To Join", data: `Unable to join the requested Matrix space. Please invite the bridge to the space and try again. (Server said: ${e.errcode} - ${e.message})`}) } // Check bridge has PL 100 @@ -179,7 +162,7 @@ as.router.post("/api/link", defineEventHandler(async event => { await api.joinRoom(parsedBody.matrix, null, foundVia) } catch (e) { if (!foundVia) { - throw createError({status: 403, message: "Unable To Join", data: `Unable to join the requested Matrix room. Please invite the bridge to the room and try again. (Server said: ${e.errcode} - ${e.message})`}) + throw createError({status: 400, message: "Unable To Join", data: `Unable to join the requested Matrix room. Please invite the bridge to the room and try again. (Server said: ${e.errcode} - ${e.message})`}) } throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`}) } From ab69eab8a492e99d7e30f4e3196c5a6d09debe0b Mon Sep 17 00:00:00 2001 From: Elliu Date: Sat, 1 Nov 2025 21:01:15 +0900 Subject: [PATCH 19/19] Fix tests for new link space error message --- src/web/routes/link.js | 4 ++-- src/web/routes/link.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/web/routes/link.js b/src/web/routes/link.js index a7ae8ec..d4d13da 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -76,13 +76,13 @@ as.router.post("/api/link-space", defineEventHandler(async event => { if (existing) throw createError({status: 400, message: "Bad Request", data: `Guild ID ${guildID} or space ID ${spaceID} are already bridged and cannot be reused`}) const inviteSender = select("invite", "mxid", {mxid: session.data.mxid, room_id: spaceID}).pluck().get() - const via = [ inviteSender?.match(/:(.*)/)?.[1] ?? "" ] + const via = [ inviteSender?.match(/:(.*)/)?.[1] ?? "" ] // Check space exists and bridge is joined try { await api.joinRoom(parsedBody.space_id, null, via) } catch (e) { - throw createError({status: 400, message: "Unable To Join", data: `Unable to join the requested Matrix space. Please invite the bridge to the space and try again. (Server said: ${e.errcode} - ${e.message})`}) + throw createError({status: 400, message: "Unable To Join", data: `Unable to join the requested Matrix space. Please invite the bridge to the space and try again. (Server said: ${e.errcode} - ${e.message})`}) } // Check bridge has PL 100 diff --git a/src/web/routes/link.test.js b/src/web/routes/link.test.js index 068bc9b..ffe4e5e 100644 --- a/src/web/routes/link.test.js +++ b/src/web/routes/link.test.js @@ -77,7 +77,7 @@ test("web link space: check that OOYE is joined", async t => { } } })) - t.equal(error.data, "M_FORBIDDEN - not allowed to join I guess") + t.equal(error.data, "Unable to join the requested Matrix space. Please invite the bridge to the space and try again. (Server said: M_FORBIDDEN - not allowed to join I guess)") t.equal(called, 1) })