You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

presence_router_module.md 9.1 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. <h2 style="color:red">
  2. This page of the Synapse documentation is now deprecated. For up to date
  3. documentation on setting up or writing a presence router module, please see
  4. <a href="modules.md">this page</a>.
  5. </h2>
  6. # Presence Router Module
  7. Synapse supports configuring a module that can specify additional users
  8. (local or remote) to should receive certain presence updates from local
  9. users.
  10. Note that routing presence via Application Service transactions is not
  11. currently supported.
  12. The presence routing module is implemented as a Python class, which will
  13. be imported by the running Synapse.
  14. ## Python Presence Router Class
  15. The Python class is instantiated with two objects:
  16. * A configuration object of some type (see below).
  17. * An instance of `synapse.module_api.ModuleApi`.
  18. It then implements methods related to presence routing.
  19. Note that one method of `ModuleApi` that may be useful is:
  20. ```python
  21. async def ModuleApi.send_local_online_presence_to(users: Iterable[str]) -> None
  22. ```
  23. which can be given a list of local or remote MXIDs to broadcast known, online user
  24. presence to (for those users that the receiving user is considered interested in).
  25. It does not include state for users who are currently offline, and it can only be
  26. called on workers that support sending federation. Additionally, this method must
  27. only be called from the process that has been configured to write to the
  28. the [presence stream](workers.md#stream-writers).
  29. By default, this is the main process, but another worker can be configured to do
  30. so.
  31. ### Module structure
  32. Below is a list of possible methods that can be implemented, and whether they are
  33. required.
  34. #### `parse_config`
  35. ```python
  36. def parse_config(config_dict: dict) -> Any
  37. ```
  38. **Required.** A static method that is passed a dictionary of config options, and
  39. should return a validated config object. This method is described further in
  40. [Configuration](#configuration).
  41. #### `get_users_for_states`
  42. ```python
  43. async def get_users_for_states(
  44. self,
  45. state_updates: Iterable[UserPresenceState],
  46. ) -> Dict[str, Set[UserPresenceState]]:
  47. ```
  48. **Required.** An asynchronous method that is passed an iterable of user presence
  49. state. This method can determine whether a given presence update should be sent to certain
  50. users. It does this by returning a dictionary with keys representing local or remote
  51. Matrix User IDs, and values being a python set
  52. of `synapse.handlers.presence.UserPresenceState` instances.
  53. Synapse will then attempt to send the specified presence updates to each user when
  54. possible.
  55. #### `get_interested_users`
  56. ```python
  57. async def get_interested_users(self, user_id: str) -> Union[Set[str], str]
  58. ```
  59. **Required.** An asynchronous method that is passed a single Matrix User ID. This
  60. method is expected to return the users that the passed in user may be interested in the
  61. presence of. Returned users may be local or remote. The presence routed as a result of
  62. what this method returns is sent in addition to the updates already sent between users
  63. that share a room together. Presence updates are deduplicated.
  64. This method should return a python set of Matrix User IDs, or the object
  65. `synapse.events.presence_router.PresenceRouter.ALL_USERS` to indicate that the passed
  66. user should receive presence information for *all* known users.
  67. For clarity, if the user `@alice:example.org` is passed to this method, and the Set
  68. `{"@bob:example.com", "@charlie:somewhere.org"}` is returned, this signifies that Alice
  69. should receive presence updates sent by Bob and Charlie, regardless of whether these
  70. users share a room.
  71. ### Example
  72. Below is an example implementation of a presence router class.
  73. ```python
  74. from typing import Dict, Iterable, Set, Union
  75. from synapse.events.presence_router import PresenceRouter
  76. from synapse.handlers.presence import UserPresenceState
  77. from synapse.module_api import ModuleApi
  78. class PresenceRouterConfig:
  79. def __init__(self):
  80. # Config options with their defaults
  81. # A list of users to always send all user presence updates to
  82. self.always_send_to_users = [] # type: List[str]
  83. # A list of users to ignore presence updates for. Does not affect
  84. # shared-room presence relationships
  85. self.blacklisted_users = [] # type: List[str]
  86. class ExamplePresenceRouter:
  87. """An example implementation of synapse.presence_router.PresenceRouter.
  88. Supports routing all presence to a configured set of users, or a subset
  89. of presence from certain users to members of certain rooms.
  90. Args:
  91. config: A configuration object.
  92. module_api: An instance of Synapse's ModuleApi.
  93. """
  94. def __init__(self, config: PresenceRouterConfig, module_api: ModuleApi):
  95. self._config = config
  96. self._module_api = module_api
  97. @staticmethod
  98. def parse_config(config_dict: dict) -> PresenceRouterConfig:
  99. """Parse a configuration dictionary from the homeserver config, do
  100. some validation and return a typed PresenceRouterConfig.
  101. Args:
  102. config_dict: The configuration dictionary.
  103. Returns:
  104. A validated config object.
  105. """
  106. # Initialise a typed config object
  107. config = PresenceRouterConfig()
  108. always_send_to_users = config_dict.get("always_send_to_users")
  109. blacklisted_users = config_dict.get("blacklisted_users")
  110. # Do some validation of config options... otherwise raise a
  111. # synapse.config.ConfigError.
  112. config.always_send_to_users = always_send_to_users
  113. config.blacklisted_users = blacklisted_users
  114. return config
  115. async def get_users_for_states(
  116. self,
  117. state_updates: Iterable[UserPresenceState],
  118. ) -> Dict[str, Set[UserPresenceState]]:
  119. """Given an iterable of user presence updates, determine where each one
  120. needs to go. Returned results will not affect presence updates that are
  121. sent between users who share a room.
  122. Args:
  123. state_updates: An iterable of user presence state updates.
  124. Returns:
  125. A dictionary of user_id -> set of UserPresenceState that the user should
  126. receive.
  127. """
  128. destination_users = {} # type: Dict[str, Set[UserPresenceState]
  129. # Ignore any updates for blacklisted users
  130. desired_updates = set()
  131. for update in state_updates:
  132. if update.state_key not in self._config.blacklisted_users:
  133. desired_updates.add(update)
  134. # Send all presence updates to specific users
  135. for user_id in self._config.always_send_to_users:
  136. destination_users[user_id] = desired_updates
  137. return destination_users
  138. async def get_interested_users(
  139. self,
  140. user_id: str,
  141. ) -> Union[Set[str], PresenceRouter.ALL_USERS]:
  142. """
  143. Retrieve a list of users that `user_id` is interested in receiving the
  144. presence of. This will be in addition to those they share a room with.
  145. Optionally, the object PresenceRouter.ALL_USERS can be returned to indicate
  146. that this user should receive all incoming local and remote presence updates.
  147. Note that this method will only be called for local users.
  148. Args:
  149. user_id: A user requesting presence updates.
  150. Returns:
  151. A set of user IDs to return additional presence updates for, or
  152. PresenceRouter.ALL_USERS to return presence updates for all other users.
  153. """
  154. if user_id in self._config.always_send_to_users:
  155. return PresenceRouter.ALL_USERS
  156. return set()
  157. ```
  158. #### A note on `get_users_for_states` and `get_interested_users`
  159. Both of these methods are effectively two different sides of the same coin. The logic
  160. regarding which users should receive updates for other users should be the same
  161. between them.
  162. `get_users_for_states` is called when presence updates come in from either federation
  163. or local users, and is used to either direct local presence to remote users, or to
  164. wake up the sync streams of local users to collect remote presence.
  165. In contrast, `get_interested_users` is used to determine the users that presence should
  166. be fetched for when a local user is syncing. This presence is then retrieved, before
  167. being fed through `get_users_for_states` once again, with only the syncing user's
  168. routing information pulled from the resulting dictionary.
  169. Their routing logic should thus line up, else you may run into unintended behaviour.
  170. ## Configuration
  171. Once you've crafted your module and installed it into the same Python environment as
  172. Synapse, amend your homeserver config file with the following.
  173. ```yaml
  174. presence:
  175. enabled: true
  176. presence_router:
  177. module: my_module.ExamplePresenceRouter
  178. config:
  179. # Any configuration options for your module. The below is an example.
  180. # of setting options for ExamplePresenceRouter.
  181. always_send_to_users: ["@presence_gobbler:example.org"]
  182. blacklisted_users:
  183. - "@alice:example.com"
  184. - "@bob:example.com"
  185. ...
  186. ```
  187. The contents of `config` will be passed as a Python dictionary to the static
  188. `parse_config` method of your class. The object returned by this method will
  189. then be passed to the `__init__` method of your module as `config`.