From 73922e12f10491a74ed4bbc0ba0c5b58e6aadc0f Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 30 Jun 2026 17:48:47 +0100 Subject: [PATCH 1/2] Implement MSC4497 --- changelog.d/19899.feature | 2 ++ synapse/config/experimental.py | 3 +++ synapse/rest/client/room.py | 13 ++++++++++++- tests/rest/client/test_rooms.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 changelog.d/19899.feature diff --git a/changelog.d/19899.feature b/changelog.d/19899.feature new file mode 100644 index 00000000000..39f522217da --- /dev/null +++ b/changelog.d/19899.feature @@ -0,0 +1,2 @@ +Implemement MSC4497: Filter state events by type in /state endpoint. Requests to `/_matrix/client/v3/rooms/!../state` can now be +made with a `cc.koja.types` key to filter to only specific state types. diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index 120b31cee58..514a80d69ab 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -622,3 +622,6 @@ def read_config( # MSC4455: Preview URL capability # Tracked in: https://github.com/element-hq/synapse/issues/19719 self.msc4452_enabled: bool = experimental.get("msc4452_enabled", False) + + # MSC4497: State event type filter + self.msc4497_state_event_type_filter: bool = experimental.get("msc4497_state_event_type_filter", False) diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py index 6e00197b6de..d8ffe2e834b 100644 --- a/synapse/rest/client/room.py +++ b/synapse/rest/client/room.py @@ -25,7 +25,7 @@ import re from enum import Enum from http import HTTPStatus -from typing import TYPE_CHECKING, Awaitable +from typing import TYPE_CHECKING, Awaitable, Mapping, Sequence from urllib import parse as urlparse import attr @@ -69,6 +69,7 @@ parse_json, parse_json_object_from_request, parse_string, + parse_string_from_args, parse_strings_from_args, ) from synapse.http.site import SynapseRequest @@ -990,15 +991,25 @@ def __init__(self, hs: "HomeServer"): super().__init__() self.message_handler = hs.get_message_handler() self.auth = hs.get_auth() + self.experimental_config = hs.config.experimental @cancellable async def on_GET( self, request: SynapseRequest, room_id: str ) -> tuple[int, list[JsonDict]]: requester = await self.auth.get_user_by_req(request, allow_guest=True) + args: Mapping[bytes, Sequence[bytes]] = request.args # type: ignore + type_filter = ( + parse_strings_from_args(args, "cc.koja.types", required=False) + if self.experimental_config.msc4497_state_event_type_filter + else None + ) # Get all the current state for this room events = await self.message_handler.get_state_events( room_id=room_id, + state_filter=StateFilter.from_types((x, None) for x in type_filter) + if type_filter is not None + else None, requester=requester, ) return 200, events diff --git a/tests/rest/client/test_rooms.py b/tests/rest/client/test_rooms.py index 8d4892ae91f..c295d85e3b8 100644 --- a/tests/rest/client/test_rooms.py +++ b/tests/rest/client/test_rooms.py @@ -601,6 +601,35 @@ def test_get_state_format_event(self) -> None: self.assertEqual(channel.json_body["state_key"], self.user_id) self.assertTrue(type(channel.json_body["origin_server_ts"]) is int) + def test_get_state_type_filter_single(self) -> None: + """MSC4497: filtering by a single type returns only that event type.""" + self.hs.config.experimental.msc4497_state_event_type_filter = True + room_id = self.helper.create_room_as(self.user_id) + channel = self.make_request( + "GET", + "/rooms/%s/state?cc.koja.types=m.room.create" % room_id, + ) + self.assertEqual(channel.code, HTTPStatus.OK, channel.result["body"]) + self.assertEqual( + [event["type"] for event in channel.json_list], + ["m.room.create"], + ) + + def test_get_state_type_filter_multiple(self) -> None: + """MSC4497: filtering by multiple types returns only those event types.""" + self.hs.config.experimental.msc4497_state_event_type_filter = True + room_id = self.helper.create_room_as(self.user_id) + channel = self.make_request( + "GET", + "/rooms/%s/state?cc.koja.types=m.room.create&cc.koja.types=m.room.member" + % room_id, + ) + self.assertEqual(channel.code, HTTPStatus.OK, channel.result["body"]) + self.assertCountEqual( + [event["type"] for event in channel.json_list], + ["m.room.create", "m.room.member"], + ) + class RoomsMemberListTestCase(RoomBase): """Tests /rooms/$room_id/members/list REST events.""" From f78ed7178b338c41e78261b90772dd7a1e467456 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 30 Jun 2026 17:52:36 +0100 Subject: [PATCH 2/2] lint --- synapse/config/experimental.py | 4 +++- synapse/rest/client/room.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index ac6d8a7b222..d4554e9611e 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -626,4 +626,6 @@ def read_config( self.msc4491_enabled: bool = experimental.get("msc4491_enabled", False) # MSC4497: State event type filter - self.msc4497_state_event_type_filter: bool = experimental.get("msc4497_state_event_type_filter", False) + self.msc4497_state_event_type_filter: bool = experimental.get( + "msc4497_state_event_type_filter", False + ) diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py index d8ffe2e834b..9d0782e0e79 100644 --- a/synapse/rest/client/room.py +++ b/synapse/rest/client/room.py @@ -69,7 +69,6 @@ parse_json, parse_json_object_from_request, parse_string, - parse_string_from_args, parse_strings_from_args, ) from synapse.http.site import SynapseRequest