diff --git a/src/m2d/event-dispatcher.js b/src/m2d/event-dispatcher.js index eb1fba0..48bff11 100644 --- a/src/m2d/event-dispatcher.js +++ b/src/m2d/event-dispatcher.js @@ -357,15 +357,7 @@ async event => { await api.ackEvent(event) })) -function getFromInviteRoomState(inviteRoomState, nskey, key) { - if (!Array.isArray(inviteRoomState)) return null - for (const event of inviteRoomState) { - if (event.type === nskey && event.state_key === "") { - return event.content[key] - } - } - return null -} + sync.addTemporaryListener(as, "type:m.space.child", guard("m.space.child", /** @@ -398,24 +390,16 @@ async event => { } // We were invited to a room. We should join, and register the invite details for future reference in web. - let attemptedApiMessage = "According to unsigned invite data." - let inviteRoomState = event.unsigned?.invite_room_state - if (!Array.isArray(inviteRoomState) || inviteRoomState.length === 0) { - try { - inviteRoomState = await api.getInviteState(event.room_id) - attemptedApiMessage = "According to SSS API." - } catch (e) { - attemptedApiMessage = "According to unsigned invite data. SSS API unavailable: " + e.toString() - } + try { + var inviteRoomState = await api.getInviteState(event.room_id, event) + } catch (e) { + console.error(e) + return await api.leaveRoomWithReason(event.room_id, `I wasn't able to find out what this room is. Please report this as a bug. Check console for more details. (${e.toString()})`) } - const name = getFromInviteRoomState(inviteRoomState, "m.room.name", "name") - const topic = getFromInviteRoomState(inviteRoomState, "m.room.topic", "topic") - const avatar = getFromInviteRoomState(inviteRoomState, "m.room.avatar", "url") - const creationType = getFromInviteRoomState(inviteRoomState, "m.room.create", "type") - if (!name) return await api.leaveRoomWithReason(event.room_id, `Please only invite me to rooms that have a name/avatar set. Update the room details and reinvite! (${attemptedApiMessage})`) + if (!inviteRoomState?.name) return await api.leaveRoomWithReason(event.room_id, `Please only invite me to rooms that have a name/avatar set. Update the room details and reinvite.`) await api.joinRoom(event.room_id) - db.prepare("INSERT OR IGNORE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)").run(event.sender, event.room_id, creationType, name, topic, avatar) - if (avatar) utils.getPublicUrlForMxc(avatar) // make sure it's available in the media_proxy allowed URLs + db.prepare("INSERT OR IGNORE INTO invite (mxid, room_id, type, name, topic, avatar) VALUES (?, ?, ?, ?, ?, ?)").run(event.sender, event.room_id, inviteRoomState.type, inviteRoomState.name, inviteRoomState.topic, inviteRoomState.avatar) + if (inviteRoomState.avatar) utils.getPublicUrlForMxc(inviteRoomState.avatar) // make sure it's available in the media_proxy allowed URLs } if (utils.eventSenderIsFromDiscord(event.state_key)) return diff --git a/src/matrix/api.js b/src/matrix/api.js index b71c068..7e503c2 100644 --- a/src/matrix/api.js +++ b/src/matrix/api.js @@ -158,20 +158,82 @@ function getStateEventOuter(roomID, type, key) { /** * @param {string} roomID - * @returns {Promise} + * @param {{unsigned?: {invite_room_state?: Ty.Event.InviteStrippedState[]}}} [event] + * @returns {Promise<{name: string?, topic: string?, avatar: string?, type: string?}>} */ -async function getInviteState(roomID) { - /** @type {Ty.R.SSS} */ - const root = await mreq.mreq("POST", path("/client/unstable/org.matrix.simplified_msc3575/sync", `@${reg.sender_localpart}:${reg.ooye.server_name}`, {timeout: "0"}), { - room_subscriptions: { - [roomID]: { - timeline_limit: 0, - required_state: [] +async function getInviteState(roomID, event) { + function getFromInviteRoomState(strippedState, nskey, key) { + if (!Array.isArray(strippedState)) return null + for (const event of strippedState) { + if (event.type === nskey && event.state_key === "") { + return event.content[key] } } - }) - const roomResponse = root.rooms[roomID] - return "stripped_state" in roomResponse ? roomResponse.stripped_state : roomResponse.invite_state + return null + } + + // Try extracting from event (if passed) + if (Array.isArray(event?.unsigned?.invite_room_state) && event.unsigned.invite_room_state.length) { + return { + name: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.name", "name"), + topic: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.topic", "topic"), + avatar: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.avatar", "url"), + type: getFromInviteRoomState(event.unsigned.invite_room_state, "m.room.create", "type") + } + } + + // Try calling sliding sync API and extracting from stripped state + try { + /** @type {Ty.R.SSS} */ + var root = await mreq.mreq("POST", path("/client/unstable/org.matrix.simplified_msc3575/sync", `@${reg.sender_localpart}:${reg.ooye.server_name}`, {timeout: "0"}), { + lists: { + a: { + ranges: [[0, 999]], + timeline_limit: 0, + required_state: [], + filters: { + is_invite: true + } + } + } + }) + + // Extract from sliding sync response if valid (seems to be okay on Synapse, Tuwunel and Continuwuity at time of writing) + if ("lists" in root) { + if (!root.rooms?.[roomID]) { + const e = new Error("Room data unavailable via SSS") + e["data_sss"] = root + throw e + } + + const roomResponse = root.rooms[roomID] + const strippedState = "stripped_state" in roomResponse ? roomResponse.stripped_state : roomResponse.invite_state + + return { + name: getFromInviteRoomState(strippedState, "m.room.name", "name"), + topic: getFromInviteRoomState(strippedState, "m.room.topic", "topic"), + avatar: getFromInviteRoomState(strippedState, "m.room.avatar", "url"), + type: getFromInviteRoomState(strippedState, "m.room.create", "type") + } + } + } catch (e) {} + + // Invalid sliding sync response, try alternative (required for Conduit at time of writing) + const hierarchy = await getHierarchy(roomID, {limit: 1}) + if (hierarchy?.rooms?.[0]?.room_id === roomID) { + const room = hierarchy?.rooms?.[0] + return { + name: room.name ?? null, + topic: room.topic ?? null, + avatar: room.avatar_url ?? null, + type: room.room_type + } + } + + const e = new Error("Room data unavailable via SSS/hierarchy") + e["data_sss"] = root + e["data_hierarchy"] = hierarchy + throw e } /** diff --git a/src/matrix/mreq.js b/src/matrix/mreq.js index 9085add..bb59506 100644 --- a/src/matrix/mreq.js +++ b/src/matrix/mreq.js @@ -77,6 +77,7 @@ async function mreq(method, url, bodyIn, extra = {}) { /** @type {any} */ var root = JSON.parse(text) } catch (e) { + delete opts.headers?.["Authorization"] throw new MatrixServerError(text, {baseUrl, url, ...opts}) } diff --git a/src/types.d.ts b/src/types.d.ts index 951d93c..6ee2eb1 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -433,6 +433,7 @@ export namespace R { guest_can_join: boolean join_rule?: string name?: string + topic?: string num_joined_members: number room_id: string room_type?: string