diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..4106061
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "target": "es2024",
+ "module": "nodenext",
+ "strict": true,
+ "noImplicitAny": false,
+ "useUnknownInCatchVariables": false
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index aa7822f..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",
@@ -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",
@@ -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"
@@ -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..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",
@@ -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",
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/d2m/converters/message-to-event.js b/src/d2m/converters/message-to-event.js
index 1d6288a..93e120e 100644
--- a/src/d2m/converters/message-to-event.js
+++ b/src/d2m/converters/message-to-event.js
@@ -207,6 +207,7 @@ async function attachmentToEvent(mentions, attachment) {
* - alwaysReturnFormattedBody: false - formatted_body will be skipped if it is the same as body because the message is plaintext. if you want the formatted_body to be returned anyway, for example to merge it with another message, then set this to true.
* - scanTextForMentions: true - needs to be set to false when converting forwarded messages etc which may be from a different channel that can't be scanned.
* @param {{api: import("../../matrix/api"), snow?: import("snowtransfer").SnowTransfer}} di simple-as-nails dependency injection for the matrix API
+ * @returns {Promise<{$type: string, $sender?: string, [x: string]: any}[]>}
*/
async function messageToEvent(message, guild, options = {}, di) {
const events = []
@@ -408,13 +409,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)
}
@@ -611,7 +612,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/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/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"]
},
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/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)
}
/**
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"]) {
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}[]} */
diff --git a/src/matrix/api.js b/src/matrix/api.js
index 41af63f..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) {
+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
}
@@ -384,7 +391,9 @@ async function getMedia(mxc, init = {}) {
},
...init
})
- assert(res.body)
+ if (init.method !== "HEAD") {
+ assert(res.body)
+ }
// @ts-ignore
return res
}
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/src/types.d.ts b/src/types.d.ts
index 37da633..c7cb006 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
@@ -148,6 +149,14 @@ export namespace Event {
prev_content?: any
}
+ export type 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
@@ -344,7 +353,7 @@ export namespace R {
export type Hierarchy = {
avatar_url?: string
canonical_alias?: string
- children_state: {}
+ children_state: Event.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 c5f404e..0afbc49 100644
--- a/src/web/routes/link.js
+++ b/src/web/routes/link.js
@@ -75,11 +75,15 @@ 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()
+ const inviteSenderServer = inviteSender?.match(/:(.*)/)?.[1]
+ const via = [inviteSenderServer || ""]
+
// 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}`})
+ 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
@@ -134,19 +138,33 @@ 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 foundRoom = false
+ /** @type {string[]?} */
+ let foundVia = null
for await (const room of api.generateFullHierarchy(spaceID)) {
- if (room.room_id === parsedBody.matrix && !room.room_type) {
- 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
+ }
}
+
+ // 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)
+ await api.joinRoom(parsedBody.matrix, null, foundVia)
} catch (e) {
+ if (!foundVia) {
+ 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}`})
}
diff --git a/src/web/routes/link.test.js b/src/web/routes/link.test.js
index 0d8d366..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)
})
@@ -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
}
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,
diff --git a/test/test.js b/test/test.js
index 3695a84..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"
@@ -25,6 +27,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})