Compare commits

...

7 commits

Author SHA1 Message Date
f6c749acca Add "please try invite" message when joinRoom in /api/link-space fails 2025-08-31 20:11:38 +09:00
ecb4ae0315 Fill in more of reg for other people to test with 2025-08-31 19:44:36 +09:00
37da02d13a Fix /api/link-space joinRoom() for remote spaces 2025-08-31 19:44:36 +09:00
74172f3d3d Update tests for new types and code path 2025-08-31 19:44:36 +09:00
af79c96aeb Cleanup 2025-08-31 19:44:36 +09:00
91d62e7b28 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
2025-08-31 19:44:36 +09:00
c71044fdec Only edit events if the text has changed 2025-08-29 00:09:18 +12:00
8 changed files with 202 additions and 47 deletions

View file

@ -22,6 +22,10 @@ function eventCanBeEdited(ev) {
return true return true
} }
function eventIsText(ev) {
return ev.old.event_type === "m.room.message" && (ev.old.event_subtype === "m.text" || ev.old.event_subtype === "m.notice")
}
/** /**
* @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message * @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message
* @param {import("discord-api-types/v10").APIGuild} guild * @param {import("discord-api-types/v10").APIGuild} guild
@ -121,6 +125,20 @@ async function editToChanges(message, guild, api) {
unchangedEvents.push(...eventsToReplace.filter(ev => !eventCanBeEdited(ev))) // Move them from eventsToRedact to unchangedEvents. unchangedEvents.push(...eventsToReplace.filter(ev => !eventCanBeEdited(ev))) // Move them from eventsToRedact to unchangedEvents.
eventsToReplace = eventsToReplace.filter(eventCanBeEdited) eventsToReplace = eventsToReplace.filter(eventCanBeEdited)
// Now, everything in eventsToReplace has the potential to have changed, but did it actually?
// (Example: if a URL preview was generated or updated, the message text won't have changed.)
// Only way to detect this is by text content. So we'll remove text events from eventsToReplace that have the same new text as text currently in the event.
for (let i = eventsToReplace.length; i--;) { // move backwards through array
const event = eventsToReplace[i]
if (!eventIsText(event)) continue // not text, can't analyse
const oldEvent = await api.getEvent(roomID, eventsToReplace[i].old.event_id)
const oldEventBodyWithoutQuotedReply = oldEvent.content.body?.replace(/^(>.*\n)*\n*/sm, "")
if (oldEventBodyWithoutQuotedReply !== event.newInnerContent.body) continue // event changed, must replace it
// Move it from eventsToRedact to unchangedEvents.
unchangedEvents.push(...eventsToReplace.filter(ev => ev.old.event_id === event.old.event_id))
eventsToReplace = eventsToReplace.filter(ev => ev.old.event_id !== event.old.event_id)
}
// We want to maintain exactly one part = 0 and one reaction_part = 0 database row at all times. // We want to maintain exactly one part = 0 and one reaction_part = 0 database row at all times.
// This would be disrupted if existing events that are (reaction_)part = 0 will be redacted. // This would be disrupted if existing events that are (reaction_)part = 0 will be redacted.
// If that is the case, pick a different existing or newly sent event to be (reaction_)part = 0. // If that is the case, pick a different existing or newly sent event to be (reaction_)part = 0.
@ -193,4 +211,3 @@ function makeReplacementEventContent(oldID, newFallbackContent, newInnerContent)
} }
module.exports.editToChanges = editToChanges module.exports.editToChanges = editToChanges
module.exports.makeReplacementEventContent = makeReplacementEventContent

View file

@ -4,7 +4,14 @@ const data = require("../../../test/data")
const Ty = require("../../types") const Ty = require("../../types")
test("edit2changes: edit by webhook", async t => { test("edit2changes: edit by webhook", async t => {
const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.edit_by_webhook, data.guild.general, {}) let called = 0
const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.edit_by_webhook, data.guild.general, {
getEvent(roomID, eventID) {
called++
t.equal(eventID, "$zXSlyI78DQqQwwfPUSzZ1b-nXzbUrCDljJgnGDdoI10")
return {content: {body: "dummy"}}
}
})
t.deepEqual(eventsToRedact, []) t.deepEqual(eventsToRedact, [])
t.deepEqual(eventsToSend, []) t.deepEqual(eventsToSend, [])
t.deepEqual(eventsToReplace, [{ t.deepEqual(eventsToReplace, [{
@ -28,10 +35,15 @@ test("edit2changes: edit by webhook", async t => {
}]) }])
t.equal(senderMxid, null) t.equal(senderMxid, null)
t.deepEqual(promotions, []) t.deepEqual(promotions, [])
t.equal(called, 1)
}) })
test("edit2changes: bot response", async t => { test("edit2changes: bot response", async t => {
const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.bot_response, data.guild.general, { const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.bot_response, data.guild.general, {
getEvent(roomID, eventID) {
t.equal(eventID, "$fdD9OZ55xg3EAsfvLZza5tMhtjUO91Wg3Otuo96TplY")
return {content: {body: "dummy"}}
},
async getJoinedMembers(roomID) { async getJoinedMembers(roomID) {
t.equal(roomID, "!hYnGGlPHlbujVVfktC:cadence.moe") t.equal(roomID, "!hYnGGlPHlbujVVfktC:cadence.moe")
return new Promise(resolve => { return new Promise(resolve => {
@ -123,7 +135,14 @@ test("edit2changes: add caption back to that image (due to it having a reaction,
}) })
test("edit2changes: stickers and attachments are not changed, only the content can be edited", async t => { test("edit2changes: stickers and attachments are not changed, only the content can be edited", async t => {
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edited_content_with_sticker_and_attachments, data.guild.general, {}) let called = 0
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edited_content_with_sticker_and_attachments, data.guild.general, {
getEvent(roomID, eventID) {
called++
t.equal(eventID, "$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qdFv4")
return {content: {body: "dummy"}}
}
})
t.deepEqual(eventsToRedact, []) t.deepEqual(eventsToRedact, [])
t.deepEqual(eventsToSend, []) t.deepEqual(eventsToSend, [])
t.deepEqual(eventsToReplace, [{ t.deepEqual(eventsToReplace, [{
@ -145,10 +164,16 @@ test("edit2changes: stickers and attachments are not changed, only the content c
} }
} }
}]) }])
t.equal(called, 1)
}) })
test("edit2changes: edit of reply to skull webp attachment with content", async t => { test("edit2changes: edit of reply to skull webp attachment with content", async t => {
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {}) const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edit_of_reply_to_skull_webp_attachment_with_content, data.guild.general, {
getEvent(roomID, eventID) {
t.equal(eventID, "$vgTKOR5ZTYNMKaS7XvgEIDaOWZtVCEyzLLi5Pc5Gz4M")
return {content: {body: "dummy"}}
}
})
t.deepEqual(eventsToRedact, []) t.deepEqual(eventsToRedact, [])
t.deepEqual(eventsToSend, []) t.deepEqual(eventsToSend, [])
t.deepEqual(eventsToReplace, [{ t.deepEqual(eventsToReplace, [{
@ -177,7 +202,12 @@ test("edit2changes: edit of reply to skull webp attachment with content", async
}) })
test("edit2changes: edits the text event when multiple rows have part = 0 (should never happen in real life, but make sure the safety net works)", async t => { test("edit2changes: edits the text event when multiple rows have part = 0 (should never happen in real life, but make sure the safety net works)", async t => {
const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edited_content_with_sticker_and_attachments_but_all_parts_equal_0, data.guild.general, {}) const {eventsToRedact, eventsToReplace, eventsToSend} = await editToChanges(data.message_update.edited_content_with_sticker_and_attachments_but_all_parts_equal_0, data.guild.general, {
getEvent(roomID, eventID) {
t.equal(eventID, "$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qd999")
return {content: {body: "dummy"}}
}
})
t.deepEqual(eventsToRedact, []) t.deepEqual(eventsToRedact, [])
t.deepEqual(eventsToSend, []) t.deepEqual(eventsToSend, [])
t.deepEqual(eventsToReplace, [{ t.deepEqual(eventsToReplace, [{
@ -202,7 +232,12 @@ test("edit2changes: edits the text event when multiple rows have part = 0 (shoul
}) })
test("edit2changes: promotes the text event when multiple rows have part = 1 (should never happen in real life, but make sure the safety net works)", async t => { test("edit2changes: promotes the text event when multiple rows have part = 1 (should never happen in real life, but make sure the safety net works)", async t => {
const {eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.edited_content_with_sticker_and_attachments_but_all_parts_equal_1, data.guild.general, {}) const {eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.edited_content_with_sticker_and_attachments_but_all_parts_equal_1, data.guild.general, {
getEvent(roomID, eventID) {
t.equal(eventID, "$lnAF9IosAECTnlv9p2e18FG8rHn-JgYKHEHIh5qd111")
return {content: {body: "dummy"}}
}
})
t.deepEqual(eventsToRedact, []) t.deepEqual(eventsToRedact, [])
t.deepEqual(eventsToSend, []) t.deepEqual(eventsToSend, [])
t.deepEqual(eventsToReplace, [{ t.deepEqual(eventsToReplace, [{
@ -279,32 +314,31 @@ test("edit2changes: generated embed", async t => {
}) })
test("edit2changes: generated embed on a reply", async t => { test("edit2changes: generated embed on a reply", async t => {
const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.embed_generated_on_reply, data.guild.general, {}) let called = 0
t.deepEqual(eventsToRedact, []) const {senderMxid, eventsToRedact, eventsToReplace, eventsToSend, promotions} = await editToChanges(data.message_update.embed_generated_on_reply, data.guild.general, {
t.deepEqual(eventsToReplace, [{ getEvent(roomID, eventID) {
oldID: "$UTqiL3Zj3FC4qldxRLggN1fhygpKl8sZ7XGY5f9MNbF", called++
newContent: { t.equal(eventID, "$UTqiL3Zj3FC4qldxRLggN1fhygpKl8sZ7XGY5f9MNbF")
$type: "m.room.message", return {
type: "m.room.message",
content: {
// Unfortunately the edited message doesn't include the message_reference field. Fine. Whatever. It looks normal if you're using a good client. // Unfortunately the edited message doesn't include the message_reference field. Fine. Whatever. It looks normal if you're using a good client.
body: "> a Discord user: [Replied-to message content wasn't provided by Discord]" body: "> a Discord user: [Replied-to message content wasn't provided by Discord]"
+ "\n\n* https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM", + "\n\nhttps://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM",
format: "org.matrix.custom.html", format: "org.matrix.custom.html",
formatted_body: "<mx-reply><blockquote><a href=\"https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM\">In reply to</a> a Discord user<br>[Replied-to message content wasn't provided by Discord]</blockquote></mx-reply>* <a href=\"https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM\">https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM</a>", formatted_body: "<mx-reply><blockquote><a href=\"https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM\">In reply to</a> a Discord user<br>[Replied-to message content wasn't provided by Discord]</blockquote></mx-reply><a href=\"https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM\">https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM</a>",
"m.mentions": {}, "m.mentions": {},
"m.new_content": {
body: "https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM",
format: "org.matrix.custom.html",
formatted_body: "<a href=\"https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM\">https://matrix.to/#/!BnKuBPCvyfOkhcUjEu:cadence.moe/$aLVZyiC3HlOu-prCSIaXlQl68I8leUdnPFiCwkgn6qM</a>",
"m.mentions": {},
msgtype: "m.text",
},
"m.relates_to": { "m.relates_to": {
event_id: "$UTqiL3Zj3FC4qldxRLggN1fhygpKl8sZ7XGY5f9MNbF", event_id: "$UTqiL3Zj3FC4qldxRLggN1fhygpKl8sZ7XGY5f9MNbF",
rel_type: "m.replace", rel_type: "m.replace",
}, },
msgtype: "m.text", msgtype: "m.text",
}, }
}]) }
}
})
t.deepEqual(eventsToRedact, [])
t.deepEqual(eventsToReplace, [])
t.deepEqual(eventsToSend, [{ t.deepEqual(eventsToSend, [{
$type: "m.room.message", $type: "m.room.message",
msgtype: "m.notice", msgtype: "m.notice",
@ -324,4 +358,5 @@ test("edit2changes: generated embed on a reply", async t => {
"nextEvent": true, "nextEvent": true,
}]) }])
t.equal(senderMxid, "@_ooye_cadence:cadence.moe") t.equal(senderMxid, "@_ooye_cadence:cadence.moe")
t.equal(called, 1)
}) })

View file

@ -22,7 +22,11 @@ function path(p, mxid, otherParams = {}) {
const u = new URL(p, "http://localhost") const u = new URL(p, "http://localhost")
if (mxid) u.searchParams.set("user_id", mxid) if (mxid) u.searchParams.set("user_id", mxid)
for (const entry of Object.entries(otherParams)) { 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]) 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 * @returns {Promise<string>} room ID
*/ */
async function joinRoom(roomIDOrAlias, mxid) { async function joinRoom(roomIDOrAlias, mxid, via) {
/** @type {Ty.R.RoomJoined} */ /** @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 return root.room_id
} }

View file

@ -24,3 +24,7 @@ test("api path: real world mxid", t => {
test("api path: extras number works", 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") 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")
})

10
src/types.d.ts vendored
View file

@ -148,6 +148,14 @@ export namespace Event {
prev_content?: any 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 = { export type M_Room_Message = {
msgtype: "m.text" | "m.emote" msgtype: "m.text" | "m.emote"
body: string body: string
@ -344,7 +352,7 @@ export namespace R {
export type Hierarchy = { export type Hierarchy = {
avatar_url?: string avatar_url?: string
canonical_alias?: string canonical_alias?: string
children_state: {} children_state: Event.Outer_StrippedChildStateEvent[]
guest_can_join: boolean guest_can_join: boolean
join_rule?: string join_rule?: string
name?: string name?: string

View file

@ -12,6 +12,20 @@ const auth = sync.require("../auth")
const mreq = sync.require("../../matrix/mreq") const mreq = sync.require("../../matrix/mreq")
const {reg} = require("../../matrix/read-registration") 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 * @param {H3Event} event
* @returns {import("../../matrix/api")} * @returns {import("../../matrix/api")}
@ -75,10 +89,16 @@ 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) 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`}) 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 // Check space exists and bridge is joined
try { try {
await api.joinRoom(parsedBody.space_id) await api.joinRoom(parsedBody.space_id, null, via)
} catch (e) { } 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: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`})
} }
@ -134,19 +154,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`}) 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 // 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)) { for await (const room of api.generateFullHierarchy(spaceID)) {
// 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) { if (room.room_id === parsedBody.matrix && !room.room_type) {
found = true foundRoom = true
break
} }
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 // Check room exists and bridge is joined
try { try {
await api.joinRoom(parsedBody.matrix) await api.joinRoom(parsedBody.matrix, null, foundVia)
} catch (e) { } 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: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`}) throw createError({status: 403, message: e.errcode, data: `${e.errcode} - ${e.message}`})
} }

View file

@ -360,7 +360,7 @@ test("web link room: check that room is part of space (not in hierarchy)", async
t.equal(called, 1) 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 let called = 0
const [error] = await tryToCatch(() => router.test("post", "/api/link", { const [error] = await tryToCatch(() => router.test("post", "/api/link", {
sessionData: { sessionData: {
@ -381,7 +381,55 @@ test("web link room: check that bridge can join room", async t => {
t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe") t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
yield { yield {
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", 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, guest_can_join: false,
num_joined_members: 2 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") t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
yield { yield {
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
children_state: {}, children_state: [],
guest_can_join: false, guest_can_join: false,
num_joined_members: 2 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") t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
yield { yield {
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
children_state: {}, children_state: [],
guest_can_join: false, guest_can_join: false,
num_joined_members: 2 num_joined_members: 2
} }
@ -494,7 +542,7 @@ test("web link room: successfully calls createRoom", async t => {
t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe") t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe")
yield { yield {
room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe",
children_state: {}, children_state: [],
guest_can_join: false, guest_can_join: false,
num_joined_members: 2 num_joined_members: 2
} }

View file

@ -17,6 +17,8 @@ const {reg} = require("../src/matrix/read-registration")
reg.ooye.discord_token = "Njg0MjgwMTkyNTUzODQ0NzQ3.Xl3zlw.baby" 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_origin = "https://matrix.cadence.moe" // so that tests will pass even when hard-coded
reg.ooye.server_name = "cadence.moe" reg.ooye.server_name = "cadence.moe"
reg.ooye.namespace_prefix = "_ooye_"
reg.sender_localpart = "_ooye_bot"
reg.id = "baby" reg.id = "baby"
reg.as_token = "don't actually take authenticated actions on the server" reg.as_token = "don't actually take authenticated actions on the server"
reg.hs_token = "don't actually take authenticated actions on the server" reg.hs_token = "don't actually take authenticated actions on the server"