diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py
index ab7ef8f950..6e7f5238fe 100644
--- a/synapse/module_api/__init__.py
+++ b/synapse/module_api/__init__.py
@@ -33,6 +33,7 @@ import jinja2
from twisted.internet import defer
from twisted.web.resource import IResource
+from synapse.api.errors import SynapseError
from synapse.events import EventBase
from synapse.events.presence_router import PresenceRouter
from synapse.http.client import SimpleHttpClient
@@ -46,6 +47,7 @@ from synapse.http.site import SynapseRequest
from synapse.logging.context import make_deferred_yieldable, run_in_background
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.rest.client.login import LoginResponse
+from synapse.storage import DataStore
from synapse.storage.database import DatabasePool, LoggingTransaction
from synapse.storage.databases.main.roommember import ProfileInfo
from synapse.storage.state import StateFilter
@@ -53,6 +55,7 @@ from synapse.types import (
DomainSpecificString,
JsonDict,
Requester,
+ StateMap,
UserID,
UserInfo,
create_requester,
@@ -61,6 +64,7 @@ from synapse.util import Clock
from synapse.util.caches.descriptors import cached
if TYPE_CHECKING:
+ from synapse.app.generic_worker import GenericWorkerSlavedStore
from synapse.server import HomeServer
"""
@@ -86,6 +90,8 @@ __all__ = [
"PRESENCE_ALL_USERS",
"LoginResponse",
"JsonDict",
+ "EventBase",
+ "StateMap",
]
logger = logging.getLogger(__name__)
@@ -111,7 +117,9 @@ class ModuleApi:
def __init__(self, hs: "HomeServer", auth_handler):
self._hs = hs
- self._store = hs.get_datastore()
+ # TODO: Fix this type hint once the types for the data stores have been ironed
+ # out.
+ self._store: Union[DataStore, "GenericWorkerSlavedStore"] = hs.get_datastore()
self._auth = hs.get_auth()
self._auth_handler = auth_handler
self._server_name = hs.hostname
@@ -150,27 +158,42 @@ class ModuleApi:
@property
def register_spam_checker_callbacks(self):
- """Registers callbacks for spam checking capabilities."""
+ """Registers callbacks for spam checking capabilities.
+
+ Added in Synapse v1.37.0.
+ """
return self._spam_checker.register_callbacks
@property
def register_account_validity_callbacks(self):
- """Registers callbacks for account validity capabilities."""
+ """Registers callbacks for account validity capabilities.
+
+ Added in Synapse v1.39.0.
+ """
return self._account_validity_handler.register_account_validity_callbacks
@property
def register_third_party_rules_callbacks(self):
- """Registers callbacks for third party event rules capabilities."""
+ """Registers callbacks for third party event rules capabilities.
+
+ Added in Synapse v1.39.0.
+ """
return self._third_party_event_rules.register_third_party_rules_callbacks
@property
def register_presence_router_callbacks(self):
- """Registers callbacks for presence router capabilities."""
+ """Registers callbacks for presence router capabilities.
+
+ Added in Synapse v1.42.0.
+ """
return self._presence_router.register_presence_router_callbacks
@property
def register_password_auth_provider_callbacks(self):
- """Registers callbacks for password auth provider capabilities."""
+ """Registers callbacks for password auth provider capabilities.
+
+ Added in Synapse v1.46.0.
+ """
return self._password_auth_provider.register_password_auth_provider_callbacks
def register_web_resource(self, path: str, resource: IResource):
@@ -181,6 +204,8 @@ class ModuleApi:
If multiple modules register a resource for the same path, the module that
appears the highest in the configuration file takes priority.
+ Added in Synapse v1.37.0.
+
Args:
path: The path to register the resource for.
resource: The resource to attach to this path.
@@ -195,6 +220,8 @@ class ModuleApi:
"""Allows making outbound HTTP requests to remote resources.
An instance of synapse.http.client.SimpleHttpClient
+
+ Added in Synapse v1.22.0.
"""
return self._http_client
@@ -204,22 +231,32 @@ class ModuleApi:
public room list.
An instance of synapse.module_api.PublicRoomListManager
+
+ Added in Synapse v1.22.0.
"""
return self._public_room_list_manager
@property
def public_baseurl(self) -> str:
- """The configured public base URL for this homeserver."""
+ """The configured public base URL for this homeserver.
+
+ Added in Synapse v1.39.0.
+ """
return self._hs.config.server.public_baseurl
@property
def email_app_name(self) -> str:
- """The application name configured in the homeserver's configuration."""
+ """The application name configured in the homeserver's configuration.
+
+ Added in Synapse v1.39.0.
+ """
return self._hs.config.email.email_app_name
async def get_userinfo_by_id(self, user_id: str) -> Optional[UserInfo]:
"""Get user info by user_id
+ Added in Synapse v1.41.0.
+
Args:
user_id: Fully qualified user id.
Returns:
@@ -235,6 +272,8 @@ class ModuleApi:
) -> Requester:
"""Check the access_token provided for a request
+ Added in Synapse v1.39.0.
+
Args:
req: Incoming HTTP request
allow_guest: True if guest users should be allowed. If this
@@ -260,6 +299,8 @@ class ModuleApi:
async def is_user_admin(self, user_id: str) -> bool:
"""Checks if a user is a server admin.
+ Added in Synapse v1.39.0.
+
Args:
user_id: The Matrix ID of the user to check.
@@ -274,6 +315,8 @@ class ModuleApi:
Takes a user id provided by the user and adds the @ and :domain to
qualify it, if necessary
+ Added in Synapse v0.25.0.
+
Args:
username (str): provided user id
@@ -287,6 +330,8 @@ class ModuleApi:
async def get_profile_for_user(self, localpart: str) -> ProfileInfo:
"""Look up the profile info for the user with the given localpart.
+ Added in Synapse v1.39.0.
+
Args:
localpart: The localpart to look up profile information for.
@@ -299,6 +344,8 @@ class ModuleApi:
"""Look up the threepids (email addresses and phone numbers) associated with the
given Matrix user ID.
+ Added in Synapse v1.39.0.
+
Args:
user_id: The Matrix user ID to look up threepids for.
@@ -313,6 +360,8 @@ class ModuleApi:
def check_user_exists(self, user_id):
"""Check if user exists.
+ Added in Synapse v0.25.0.
+
Args:
user_id (str): Complete @user:id
@@ -332,6 +381,8 @@ class ModuleApi:
return that device to the user. Prefer separate calls to register_user and
register_device.
+ Added in Synapse v0.25.0.
+
Args:
localpart (str): The localpart of the new user.
displayname (str|None): The displayname of the new user.
@@ -352,6 +403,8 @@ class ModuleApi:
):
"""Registers a new user with given localpart and optional displayname, emails.
+ Added in Synapse v1.2.0.
+
Args:
localpart (str): The localpart of the new user.
displayname (str|None): The displayname of the new user.
@@ -375,6 +428,8 @@ class ModuleApi:
def register_device(self, user_id, device_id=None, initial_display_name=None):
"""Register a device for a user and generate an access token.
+ Added in Synapse v1.2.0.
+
Args:
user_id (str): full canonical @user:id
device_id (str|None): The device ID to check, or None to generate
@@ -398,6 +453,8 @@ class ModuleApi:
) -> defer.Deferred:
"""Record a mapping from an external user id to a mxid
+ Added in Synapse v1.9.0.
+
Args:
auth_provider: identifier for the remote auth provider
external_id: id on that system
@@ -417,6 +474,8 @@ class ModuleApi:
) -> str:
"""Generate a login token suitable for m.login.token authentication
+ Added in Synapse v1.9.0.
+
Args:
user_id: gives the ID of the user that the token is for
@@ -436,6 +495,8 @@ class ModuleApi:
def invalidate_access_token(self, access_token):
"""Invalidate an access token for a user
+ Added in Synapse v0.25.0.
+
Args:
access_token(str): access token
@@ -466,6 +527,8 @@ class ModuleApi:
def run_db_interaction(self, desc, func, *args, **kwargs):
"""Run a function with a database connection
+ Added in Synapse v0.25.0.
+
Args:
desc (str): description for the transaction, for metrics etc
func (func): function to be run. Passed a database cursor object
@@ -489,6 +552,8 @@ class ModuleApi:
This is deprecated in favor of complete_sso_login_async.
+ Added in Synapse v1.11.1.
+
Args:
registered_user_id: The MXID that has been registered as a previous step of
of this SSO login.
@@ -515,6 +580,8 @@ class ModuleApi:
want their access token sent to `client_redirect_url`, or redirect them to that
URL with a token directly if the URL matches with one of the whitelisted clients.
+ Added in Synapse v1.13.0.
+
Args:
registered_user_id: The MXID that has been registered as a previous step of
of this SSO login.
@@ -543,6 +610,8 @@ class ModuleApi:
(This is exposed for compatibility with the old SpamCheckerApi. We should
probably deprecate it and replace it with an async method in a subclass.)
+ Added in Synapse v1.22.0.
+
Args:
room_id: The room ID to get state events in.
types: The event type and state key (using None
@@ -560,8 +629,107 @@ class ModuleApi:
state = yield defer.ensureDeferred(self._store.get_events(state_ids.values()))
return state.values()
+ async def update_room_membership(
+ self,
+ sender: str,
+ target: str,
+ room_id: str,
+ new_membership: str,
+ content: Optional[JsonDict] = None,
+ ) -> EventBase:
+ """Updates the membership of a user to the given value.
+
+ Added in Synapse v1.46.0.
+
+ Args:
+ sender: The user performing the membership change. Must be a user local to
+ this homeserver.
+ target: The user whose membership is changing. This is often the same value
+ as `sender`, but it might differ in some cases (e.g. when kicking a user,
+ the `sender` is the user performing the kick and the `target` is the user
+ being kicked).
+ room_id: The room in which to change the membership.
+ new_membership: The new membership state of `target` after this operation. See
+ https://spec.matrix.org/unstable/client-server-api/#mroommember for the
+ list of allowed values.
+ content: Additional values to include in the resulting event's content.
+
+ Returns:
+ The newly created membership event.
+
+ Raises:
+ RuntimeError if the `sender` isn't a local user.
+ ShadowBanError if a shadow-banned requester attempts to send an invite.
+ SynapseError if the module attempts to send a membership event that isn't
+ allowed, either by the server's configuration (e.g. trying to set a
+ per-room display name that's too long) or by the validation rules around
+ membership updates (e.g. the `membership` value is invalid).
+ """
+ if not self.is_mine(sender):
+ raise RuntimeError(
+ "Tried to send an event as a user that isn't local to this homeserver",
+ )
+
+ requester = create_requester(sender)
+ target_user_id = UserID.from_string(target)
+
+ if content is None:
+ content = {}
+
+ # Set the profile if not already done by the module.
+ if "avatar_url" not in content or "displayname" not in content:
+ try:
+ # Try to fetch the user's profile.
+ profile = await self._hs.get_profile_handler().get_profile(
+ target_user_id.to_string(),
+ )
+ except SynapseError as e:
+ # If the profile couldn't be found, use default values.
+ profile = {
+ "displayname": target_user_id.localpart,
+ "avatar_url": None,
+ }
+
+ if e.code != 404:
+ # If the error isn't 404, it means we tried to fetch the profile over
+ # federation but the remote server responded with a non-standard
+ # status code.
+ logger.error(
+ "Got non-404 error status when fetching profile for %s",
+ target_user_id.to_string(),
+ )
+
+ # Set the profile where it needs to be set.
+ if "avatar_url" not in content:
+ content["avatar_url"] = profile["avatar_url"]
+
+ if "displayname" not in content:
+ content["displayname"] = profile["displayname"]
+
+ event_id, _ = await self._hs.get_room_member_handler().update_membership(
+ requester=requester,
+ target=target_user_id,
+ room_id=room_id,
+ action=new_membership,
+ content=content,
+ )
+
+ # Try to retrieve the resulting event.
+ event = await self._hs.get_datastore().get_event(event_id)
+
+ # update_membership is supposed to always return after the event has been
+ # successfully persisted.
+ assert event is not None
+
+ return event
+
async def create_and_send_event_into_room(self, event_dict: JsonDict) -> EventBase:
- """Create and send an event into a room. Membership events are currently not supported.
+ """Create and send an event into a room.
+
+ Membership events are not supported by this method. To update a user's membership
+ in a room, please use the `update_room_membership` method instead.
+
+ Added in Synapse v1.22.0.
Args:
event_dict: A dictionary representing the event to send.
@@ -603,6 +771,8 @@ class ModuleApi:
Note that this method can only be run on the process that is configured to write to the
presence stream. By default this is the main process.
+
+ Added in Synapse v1.32.0.
"""
if self._hs._instance_name not in self._hs.config.worker.writers.presence:
raise Exception(
@@ -657,6 +827,8 @@ class ModuleApi:
Waits `msec` initially before calling `f` for the first time.
+ Added in Synapse v1.39.0.
+
Args:
f: The function to call repeatedly. f can be either synchronous or
asynchronous, and must follow Synapse's logcontext rules.
@@ -696,6 +868,8 @@ class ModuleApi:
):
"""Send an email on behalf of the homeserver.
+ Added in Synapse v1.39.0.
+
Args:
recipient: The email address for the recipient.
subject: The email's subject.
@@ -719,6 +893,8 @@ class ModuleApi:
By default, Synapse will look for these templates in its configured template
directory, but another directory to search in can be provided.
+ Added in Synapse v1.39.0.
+
Args:
filenames: The name of the template files to look for.
custom_template_directory: An additional directory to look for the files in.
@@ -736,13 +912,13 @@ class ModuleApi:
"""
Checks whether an ID (user id, room, ...) comes from this homeserver.
+ Added in Synapse v1.44.0.
+
Args:
id: any Matrix id (e.g. user id, room id, ...), either as a raw id,
e.g. string "@user:example.com" or as a parsed UserID, RoomID, ...
Returns:
True if id comes from this homeserver, False otherwise.
-
- Added in Synapse v1.44.0.
"""
if isinstance(id, DomainSpecificString):
return self._hs.is_mine(id)
@@ -755,6 +931,8 @@ class ModuleApi:
"""
Return the list of user IPs and agents for a user.
+ Added in Synapse v1.44.0.
+
Args:
user_id: the id of a user, local or remote
since_ts: a timestamp in seconds since the epoch,
@@ -763,8 +941,6 @@ class ModuleApi:
The list of all UserIpAndAgent that the user has
used to connect to this homeserver since `since_ts`.
If the user is remote, this list is empty.
-
- Added in Synapse v1.44.0.
"""
# Don't hit the db if this is not a local user.
is_mine = False
@@ -791,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
@@ -803,6 +1025,8 @@ class PublicRoomListManager:
async def room_is_in_public_room_list(self, room_id: str) -> bool:
"""Checks whether a room is in the public room list.
+ Added in Synapse v1.22.0.
+
Args:
room_id: The ID of the room.
@@ -819,6 +1043,8 @@ class PublicRoomListManager:
async def add_room_to_public_room_list(self, room_id: str) -> None:
"""Publishes a room to the public room list.
+ Added in Synapse v1.22.0.
+
Args:
room_id: The ID of the room.
"""
@@ -827,6 +1053,8 @@ class PublicRoomListManager:
async def remove_room_from_public_room_list(self, room_id: str) -> None:
"""Removes a room from the public room list.
+ Added in Synapse v1.22.0.
+
Args:
room_id: The ID of the room.
"""
|