From 50a047249ba45f0d68caaec8ed1b28ba65078615 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sun, 22 Jun 2025 18:38:20 +1200 Subject: [PATCH] Check hierarchy instead of m.space.child --- src/d2m/actions/create-space.js | 14 ++-- src/matrix/api.js | 18 +++++ src/web/routes/link.js | 14 ++-- src/web/routes/link.test.js | 120 ++++++++++++++------------------ 4 files changed, 84 insertions(+), 82 deletions(-) diff --git a/src/d2m/actions/create-space.js b/src/d2m/actions/create-space.js index 4ab831a..8bce3ad 100644 --- a/src/d2m/actions/create-space.js +++ b/src/d2m/actions/create-space.js @@ -129,16 +129,10 @@ async function _syncSpace(guild, shouldActuallySync) { // don't try to update rooms with custom avatars though const roomsWithCustomAvatars = select("channel_room", "room_id", {}, "WHERE custom_avatar IS NOT NULL").pluck().all() - const state = await ks.kstateToState(spaceKState) - const childRooms = state.filter(({type, state_key, content}) => { - return type === "m.space.child" && "via" in content && !roomsWithCustomAvatars.includes(state_key) - }).map(({state_key}) => state_key) - - for (const roomID of childRooms) { - const avatarEventContent = await api.getStateEvent(roomID, "m.room.avatar", "") - if (avatarEventContent.url !== newAvatarState.url) { - await api.sendState(roomID, "m.room.avatar", "", newAvatarState) - } + for await (const room of api.generateFullHierarchy(spaceID)) { + if (room.avatar_url === newAvatarState.url) continue + if (roomsWithCustomAvatars.includes(room.room_id)) continue + await api.sendState(room.room_id, "m.room.avatar", "", newAvatarState) } } diff --git a/src/matrix/api.js b/src/matrix/api.js index eb534c0..1e3f607 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -181,6 +181,23 @@ async function getFullHierarchy(roomID) { return rooms } +/** + * Like `getFullHierarchy` but reveals a page at a time through an async iterator. + * @param {string} roomID + */ +async function* generateFullHierarchy(roomID) { + /** @type {string | undefined} */ + let nextBatch = undefined + do { + /** @type {Ty.HierarchyPagination} */ + const res = await getHierarchy(roomID, {from: nextBatch}) + for (const room of res.rooms) { + yield room + } + nextBatch = res.next_batch + } while (nextBatch) +} + /** * @param {string} roomID * @param {string} eventID @@ -442,6 +459,7 @@ module.exports.getJoinedMembers = getJoinedMembers module.exports.getMembers = getMembers module.exports.getHierarchy = getHierarchy module.exports.getFullHierarchy = getFullHierarchy +module.exports.generateFullHierarchy = generateFullHierarchy module.exports.getRelations = getRelations module.exports.getFullRelations = getFullRelations module.exports.sendState = sendState diff --git a/src/web/routes/link.js b/src/web/routes/link.js index 080ffc5..2d0277c 100644 --- a/src/web/routes/link.js +++ b/src/web/routes/link.js @@ -134,12 +134,14 @@ 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 - /** @type {Ty.Event.M_Space_Child?} */ - let spaceChildEvent = null - try { - spaceChildEvent = await api.getStateEvent(spaceID, "m.space.child", parsedBody.matrix) - } catch (e) {} - if (!Array.isArray(spaceChildEvent?.via)) throw createError({status: 400, message: "Bad Request", data: "Matrix room needs to be part of the bridged space"}) + let found = false + for await (const room of api.generateFullHierarchy(spaceID)) { + if (room.room_id === parsedBody.matrix && !room.room_type) { + found = true + 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 { diff --git a/src/web/routes/link.test.js b/src/web/routes/link.test.js index 3c503cf..0d8d366 100644 --- a/src/web/routes/link.test.js +++ b/src/web/routes/link.test.js @@ -233,13 +233,7 @@ test("web link space: successfully adds entry to database and loads page", async mxid: "@cadence:cadence.moe" }, api: { - async getStateEvent(roomID, type, key) { - return {} - }, - async getMembers(roomID, membership) { - return {chunk: []} - }, - async getFullHierarchy(roomID) { + async getFullHierarchy(spaceID) { return [] } } @@ -344,7 +338,7 @@ test("web link room: checks the autocreate setting if the space doesn't exist ye t.equal(called, 1) }) -test("web link room: check that room is part of space (event missing)", async t => { +test("web link room: check that room is part of space (not in hierarchy)", async t => { let called = 0 const [error] = await tryToCatch(() => router.test("post", "/api/link", { sessionData: { @@ -356,37 +350,9 @@ test("web link room: check that room is part of space (event missing)", async t guild_id: "665289423482519565" }, api: { - async getStateEvent(roomID, type, key) { + async *generateFullHierarchy(spaceID) { called++ - t.equal(roomID, "!zTMspHVUBhFLLSdmnS:cadence.moe") - t.equal(type, "m.space.child") - t.equal(key, "!NDbIqNpJyPvfKRnNcr:cadence.moe") - throw new MatrixServerError({errcode: "M_NOT_FOUND", error: "what if I told you there was no such thing as a space"}) - } - } - })) - t.equal(error.data, "Matrix room needs to be part of the bridged space") - t.equal(called, 1) -}) - -test("web link room: check that room is part of space (event empty)", 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 getStateEvent(roomID, type, key) { - called++ - t.equal(roomID, "!zTMspHVUBhFLLSdmnS:cadence.moe") - t.equal(type, "m.space.child") - t.equal(key, "!NDbIqNpJyPvfKRnNcr:cadence.moe") - return {} + t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe") } } })) @@ -410,12 +376,16 @@ test("web link room: check that bridge can join room", async t => { called++ throw new MatrixServerError({errcode: "M_FORBIDDEN", error: "not allowed to join I guess"}) }, - async getStateEvent(roomID, type, key) { + async *generateFullHierarchy(spaceID) { called++ - t.equal(type, "m.space.child") - t.equal(roomID, "!zTMspHVUBhFLLSdmnS:cadence.moe") - t.equal(key, "!NDbIqNpJyPvfKRnNcr:cadence.moe") - return {via: ["cadence.moe"]} + t.equal(spaceID, "!zTMspHVUBhFLLSdmnS:cadence.moe") + yield { + room_id: "!NDbIqNpJyPvfKRnNcr:cadence.moe", + children_state: {}, + guest_can_join: false, + num_joined_members: 2 + } + /* c8 ignore next */ } } })) @@ -439,17 +409,23 @@ test("web link room: check that bridge has PL 100 in target room (event missing) called++ return roomID }, + 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 + } + /* c8 ignore next */ + }, async getStateEvent(roomID, type, key) { called++ - if (type === "m.space.child") { - t.equal(roomID, "!zTMspHVUBhFLLSdmnS:cadence.moe") - t.equal(key, "!NDbIqNpJyPvfKRnNcr:cadence.moe") - return {via: ["cadence.moe"]} - } else if (type === "m.room.power_levels") { - t.equal(roomID, "!NDbIqNpJyPvfKRnNcr:cadence.moe") - t.equal(key, "") - throw new MatrixServerError({errcode: "M_NOT_FOUND", error: "what if I told you there's no such thing as power levels"}) - } + t.equal(roomID, "!NDbIqNpJyPvfKRnNcr:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + throw new MatrixServerError({errcode: "M_NOT_FOUND", error: "what if I told you there's no such thing as power levels"}) } } })) @@ -473,17 +449,23 @@ test("web link room: check that bridge has PL 100 in target room (users default) called++ return roomID }, + 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 + } + /* c8 ignore next */ + }, async getStateEvent(roomID, type, key) { called++ - if (type === "m.space.child") { - t.equal(roomID, "!zTMspHVUBhFLLSdmnS:cadence.moe") - t.equal(key, "!NDbIqNpJyPvfKRnNcr:cadence.moe") - return {via: ["cadence.moe"]} - } else if (type === "m.room.power_levels") { - t.equal(roomID, "!NDbIqNpJyPvfKRnNcr:cadence.moe") - t.equal(key, "") - return {users_default: 50} - } + t.equal(roomID, "!NDbIqNpJyPvfKRnNcr:cadence.moe") + t.equal(type, "m.room.power_levels") + t.equal(key, "") + return {users_default: 50} } } })) @@ -507,17 +489,23 @@ test("web link room: successfully calls createRoom", async t => { called++ return roomID }, + 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 + } + /* c8 ignore next */ + }, async getStateEvent(roomID, type, key) { if (type === "m.room.power_levels") { called++ t.equal(roomID, "!NDbIqNpJyPvfKRnNcr:cadence.moe") t.equal(key, "") return {users: {"@_ooye_bot:cadence.moe": 100}} - } else if (type === "m.space.child") { - called++ - t.equal(roomID, "!zTMspHVUBhFLLSdmnS:cadence.moe") - t.equal(key, "!NDbIqNpJyPvfKRnNcr:cadence.moe") - return {via: ["cadence.moe"]} } else if (type === "m.room.name") { called++ t.equal(roomID, "!NDbIqNpJyPvfKRnNcr:cadence.moe")