Compare commits

...
Sign in to create a new pull request.

9 commits
main ... main

Author SHA1 Message Date
d95a114377 Fix matrix api joinRoom() for remote rooms (#60)
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.

Co-authored-by: Cadence Ember <cadence@disroot.org>
Reviewed-on: cadence/out-of-your-element#60
Co-authored-by: Elliu <elliu@hashi.re>
Co-committed-by: Elliu <elliu@hashi.re>
2025-11-02 07:50:16 +00:00
255e166e8c Better message when remote emojis unavailable 2025-10-31 16:22:32 +13:00
d4f4664c25 Fix retrying m->d message deletions 2025-10-23 23:09:14 +11:00
3de762d428 Fix stickers that don't provide content type 2025-10-12 12:17:20 -06:00
cffd3c9f2e Fix converting discord channel links 2025-10-10 12:26:01 -06:00
5b7433de32 Make tests time zone independent 2025-10-07 14:09:50 -05:00
7916f82b55 Change thread started message (closes #61) 2025-10-07 14:09:42 -05:00
7905802825 Allow customising port in setup 2025-10-07 00:48:06 -05:00
3891506163 Roll back snowtransfer to avoid issue with pins 2025-10-07 00:46:44 -05:00
19 changed files with 275 additions and 58 deletions

9
jsconfig.json Normal file
View file

@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "es2024",
"module": "nodenext",
"strict": true,
"noImplicitAny": false,
"useUnknownInCatchVariables": false
}
}

36
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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,

View file

@ -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

View file

@ -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: '<a href="https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe?via=cadence.moe&via=matrix.org">#worm-farm</a>'
}])
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, {}, {

View file

@ -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} <a href="https://matrix.to/#/${threadRoomID}?${via.toString()}">${thread.name}</a>`
return {
msgtype,
body,
format: "org.matrix.custom.html",
formatted_body: html,
"m.mentions": {},
...context
}

View file

@ -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: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"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: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"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: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"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: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"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: <a href="https://matrix.to/#/!thread?via=cadence.moe&via=matrix.org">test thread</a>`,
"m.mentions": {
user_ids: ["@cadence:cadence.moe"]
},

View file

@ -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()

View file

@ -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)
}
/**

View file

@ -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"]) {

View file

@ -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}[]} */

View file

@ -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<string>} 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
}

View file

@ -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")
})

11
src/types.d.ts vendored
View file

@ -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

View file

@ -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}`})
}

View file

@ -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
}

View file

@ -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,

View file

@ -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})