diff --git a/changelog.d/11204.feature b/changelog.d/11204.feature
new file mode 100644
index 0000000000..f58ed4b3dc
--- /dev/null
+++ b/changelog.d/11204.feature
@@ -0,0 +1 @@
+Add a module API method to retrieve the current state of a room.
diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py
index 36042ed2e0..6e7f5238fe 100644
--- a/synapse/module_api/__init__.py
+++ b/synapse/module_api/__init__.py
@@ -55,6 +55,7 @@ from synapse.types import (
DomainSpecificString,
JsonDict,
Requester,
+ StateMap,
UserID,
UserInfo,
create_requester,
@@ -89,6 +90,8 @@ __all__ = [
"PRESENCE_ALL_USERS",
"LoginResponse",
"JsonDict",
+ "EventBase",
+ "StateMap",
]
logger = logging.getLogger(__name__)
@@ -964,6 +967,52 @@ class ModuleApi:
else:
return []
+ 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
+
+ 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()}
+
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 37852852a8..525b83141b 100644
--- a/tests/module_api/test_api.py
+++ b/tests/module_api/test_api.py
@@ -15,7 +15,7 @@ from unittest.mock import Mock
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
@@ -509,6 +509,29 @@ class ModuleApiTestCase(HomeserverTestCase):
self.assertEqual(res["displayname"], "simone")
self.assertIsNone(res["avatar_url"])
+ 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.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):
"""For testing ModuleApi functionality in a multi-worker setup"""
|