From e64ef108029e6ebed3c4400ddedb3dda07c92658 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 28 Oct 2021 17:22:16 +0100 Subject: [PATCH 1/7] Add a module API method to retrieve state from a room --- synapse/module_api/__init__.py | 15 ++++++++++++++- tests/module_api/test_api.py | 24 +++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index d707a9325d58..633df65f9adc 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -56,7 +56,7 @@ Requester, UserID, UserInfo, - create_requester, + create_requester, StateMap, ) from synapse.util import Clock from synapse.util.caches.descriptors import cached @@ -866,6 +866,19 @@ async def get_user_ip_and_agents( else: return [] + async def get_room_state(self, room_id: str) -> StateMap[EventBase]: + """Returns the current state of the given room. + + The events are returned as a mapping, in which the key for each event is a tuple + which first element is the event's type and the second one is its state key. + + Added in Synapse v1.47.0 + """ + state_ids = await self._store.get_current_state_ids(room_id) + state_events = await self._store.get_events(state_ids.values()) + + return {key: state_events[event_id] for key, event_id in state_ids.items()} + class PublicRoomListManager: """Contains methods for adding to, removing from and querying whether a room diff --git a/tests/module_api/test_api.py b/tests/module_api/test_api.py index e915dd5c7cd2..68e20bd12807 100644 --- a/tests/module_api/test_api.py +++ b/tests/module_api/test_api.py @@ -15,7 +15,7 @@ from twisted.internet import defer -from synapse.api.constants import EduTypes +from synapse.api.constants import EduTypes, EventTypes from synapse.events import EventBase from synapse.federation.units import Transaction from synapse.handlers.presence import UserPresenceState @@ -385,6 +385,28 @@ def test_send_local_online_presence_to_federation(self): self.assertTrue(found_update) + def test_get_room_state(self): + """Tests that a module can retrieve the state of a room through the module API. + """ + user_id = self.register_user("peter", "hackme") + tok = self.login("peter", "hackme") + + # Create a room and send some custom state in it. + room_id = self.helper.create_room_as(tok=tok) + self.helper.send_state(room_id, "org.matrix.test", {}, tok=tok) + + # Check that the module API can successfully fetch state for the room. + state = self.get_success( + defer.ensureDeferred(self.module_api.get_room_state(room_id)) + ) + + # Check that a few standard events are in the returned state. + self.assertIn((EventTypes.Create, ""), state) + self.assertIn((EventTypes.Member, user_id), state) + + # Check that our custom state event is in the returned state. + self.assertIn(("org.matrix.test", ""), state) + class ModuleApiWorkerTestCase(BaseMultiWorkerStreamTestCase): """For testing ModuleApi functionality in a multi-worker setup""" From 872f23b95fa980a61b0866c1475e84491991fa20 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 28 Oct 2021 17:23:04 +0100 Subject: [PATCH 2/7] Changelog --- changelog.d/11204.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/11204.feature diff --git a/changelog.d/11204.feature b/changelog.d/11204.feature new file mode 100644 index 000000000000..f58ed4b3dc8c --- /dev/null +++ b/changelog.d/11204.feature @@ -0,0 +1 @@ +Add a module API method to retrieve the current state of a room. From 34ae2cb53610cf2b3ad97f6c6dd0946c6d00e98d Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 28 Oct 2021 17:24:20 +0100 Subject: [PATCH 3/7] Lint --- synapse/module_api/__init__.py | 3 ++- tests/module_api/test_api.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index 633df65f9adc..06bcc516e213 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -54,9 +54,10 @@ DomainSpecificString, JsonDict, Requester, + StateMap, UserID, UserInfo, - create_requester, StateMap, + create_requester, ) from synapse.util import Clock from synapse.util.caches.descriptors import cached diff --git a/tests/module_api/test_api.py b/tests/module_api/test_api.py index 68e20bd12807..fac25f11c5e6 100644 --- a/tests/module_api/test_api.py +++ b/tests/module_api/test_api.py @@ -386,8 +386,7 @@ def test_send_local_online_presence_to_federation(self): self.assertTrue(found_update) def test_get_room_state(self): - """Tests that a module can retrieve the state of a room through the module API. - """ + """Tests that a module can retrieve the state of a room through the module API.""" user_id = self.register_user("peter", "hackme") tok = self.login("peter", "hackme") From 5c1f39012cdbaa919a23ece67e6f815efb0b9d68 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 29 Oct 2021 15:17:41 +0100 Subject: [PATCH 4/7] Allow providing a filter so we don't retrieve the whole state of the room --- synapse/module_api/__init__.py | 38 +++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index 06bcc516e213..47f8bced4e66 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -867,15 +867,47 @@ async def get_user_ip_and_agents( else: return [] - async def get_room_state(self, room_id: str) -> StateMap[EventBase]: + async def get_room_state( + self, + room_id: str, + event_filter: Optional[Iterable[Tuple[str, Optional[str]]]] = None, + ) -> StateMap[EventBase]: """Returns the current state of the given room. The events are returned as a mapping, in which the key for each event is a tuple which first element is the event's type and the second one is its state key. Added in Synapse v1.47.0 - """ - state_ids = await self._store.get_current_state_ids(room_id) + + Args: + room_id: The ID of the room to get state from. + event_filter: A filter to apply when retrieving events. None if no filter + should be applied. If provided, must be an iterable of tuples. A tuple's + first element is the event type and the second is the state key, or is + None if the state key should not be filtered on. + An example of a filter is: + [ + ("m.room.member", "@alice:example.com"), # Member event for @alice:example.com + ("org.matrix.some_event", ""), # State event of type "org.matrix.some_event" + # with an empty string as its state key + ("org.matrix.some_other_event", None), # State events of type "org.matrix.some_other_event" + # regardless of their state key + ] + """ + if event_filter: + # If a filter was provided, turn it into a StateFilter and retrieve a filtered + # view of the state. + state_filter = StateFilter.from_types(event_filter) + state_ids = await self._store.get_filtered_current_state_ids( + room_id, state_filter, + ) + else: + # If no filter was provided, get the whole state. We could also reuse the call + # to get_filtered_current_state_ids above, with `state_filter = StateFilter.all()`, + # but get_filtered_current_state_ids isn't cached and `get_current_state_ids` + # is, so using the latter when we can is better for perf. + state_ids = await self._store.get_current_state_ids(room_id) + state_events = await self._store.get_events(state_ids.values()) return {key: state_events[event_id] for key, event_id in state_ids.items()} From eec3a23b32d7d64d0b8474704d19f7af58114092 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 29 Oct 2021 15:18:47 +0100 Subject: [PATCH 5/7] Also expose EventBase and StateMap --- synapse/module_api/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index 47f8bced4e66..395df2a29dee 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -89,6 +89,8 @@ "PRESENCE_ALL_USERS", "LoginResponse", "JsonDict", + "EventBase", + "StateMap", ] logger = logging.getLogger(__name__) From b56bdfd5c0987d6d7083e6ffda92c01d332872f2 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 29 Oct 2021 15:21:36 +0100 Subject: [PATCH 6/7] Test the content of the returned custom state event --- tests/module_api/test_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/module_api/test_api.py b/tests/module_api/test_api.py index fac25f11c5e6..5256633ef3ab 100644 --- a/tests/module_api/test_api.py +++ b/tests/module_api/test_api.py @@ -404,7 +404,9 @@ def test_get_room_state(self): self.assertIn((EventTypes.Member, user_id), state) # Check that our custom state event is in the returned state. - self.assertIn(("org.matrix.test", ""), state) + self.assertEqual(state[("org.matrix.test", "")].sender, user_id) + self.assertEqual(state[("org.matrix.test", "")].state_key, "") + self.assertEqual(state[("org.matrix.test", "")].content, {}) class ModuleApiWorkerTestCase(BaseMultiWorkerStreamTestCase): From 08a0f70820587bbad3c74697db13167583479b41 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 29 Oct 2021 16:18:07 +0100 Subject: [PATCH 7/7] Lint --- synapse/module_api/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py index d1a4e500291e..6e7f5238fed2 100644 --- a/synapse/module_api/__init__.py +++ b/synapse/module_api/__init__.py @@ -999,7 +999,8 @@ async def get_room_state( # view of the state. state_filter = StateFilter.from_types(event_filter) state_ids = await self._store.get_filtered_current_state_ids( - room_id, state_filter, + room_id, + state_filter, ) else: # If no filter was provided, get the whole state. We could also reuse the call