presence_router_module.html 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <!DOCTYPE HTML>
  2. <html lang="en" class="sidebar-visible no-js light">
  3. <head>
  4. <!-- Book generated using mdBook -->
  5. <meta charset="UTF-8">
  6. <title>Presence Router - Synapse</title>
  7. <!-- Custom HTML head -->
  8. <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  9. <meta name="description" content="">
  10. <meta name="viewport" content="width=device-width, initial-scale=1">
  11. <meta name="theme-color" content="#ffffff" />
  12. <link rel="icon" href="favicon.svg">
  13. <link rel="shortcut icon" href="favicon.png">
  14. <link rel="stylesheet" href="css/variables.css">
  15. <link rel="stylesheet" href="css/general.css">
  16. <link rel="stylesheet" href="css/chrome.css">
  17. <link rel="stylesheet" href="css/print.css" media="print">
  18. <!-- Fonts -->
  19. <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
  20. <link rel="stylesheet" href="fonts/fonts.css">
  21. <!-- Highlight.js Stylesheets -->
  22. <link rel="stylesheet" href="highlight.css">
  23. <link rel="stylesheet" href="tomorrow-night.css">
  24. <link rel="stylesheet" href="ayu-highlight.css">
  25. <!-- Custom theme stylesheets -->
  26. <link rel="stylesheet" href="docs/website_files/table-of-contents.css">
  27. <link rel="stylesheet" href="docs/website_files/remove-nav-buttons.css">
  28. <link rel="stylesheet" href="docs/website_files/indent-section-headers.css">
  29. <link rel="stylesheet" href="docs/website_files/version-picker.css">
  30. </head>
  31. <body>
  32. <!-- Provide site root to javascript -->
  33. <script type="text/javascript">
  34. var path_to_root = "";
  35. var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
  36. </script>
  37. <!-- Work around some values being stored in localStorage wrapped in quotes -->
  38. <script type="text/javascript">
  39. try {
  40. var theme = localStorage.getItem('mdbook-theme');
  41. var sidebar = localStorage.getItem('mdbook-sidebar');
  42. if (theme.startsWith('"') && theme.endsWith('"')) {
  43. localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
  44. }
  45. if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
  46. localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
  47. }
  48. } catch (e) { }
  49. </script>
  50. <!-- Set the theme before any content is loaded, prevents flash -->
  51. <script type="text/javascript">
  52. var theme;
  53. try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
  54. if (theme === null || theme === undefined) { theme = default_theme; }
  55. var html = document.querySelector('html');
  56. html.classList.remove('no-js')
  57. html.classList.remove('light')
  58. html.classList.add(theme);
  59. html.classList.add('js');
  60. </script>
  61. <!-- Hide / unhide sidebar before it is displayed -->
  62. <script type="text/javascript">
  63. var html = document.querySelector('html');
  64. var sidebar = 'hidden';
  65. if (document.body.clientWidth >= 1080) {
  66. try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
  67. sidebar = sidebar || 'visible';
  68. }
  69. html.classList.remove('sidebar-visible');
  70. html.classList.add("sidebar-" + sidebar);
  71. </script>
  72. <nav id="sidebar" class="sidebar" aria-label="Table of contents">
  73. <div class="sidebar-scrollbox">
  74. <ol class="chapter"><li class="chapter-item expanded affix "><li class="part-title">Introduction</li><li class="chapter-item expanded "><a href="welcome_and_overview.html">Welcome and Overview</a></li><li class="chapter-item expanded affix "><li class="part-title">Setup</li><li class="chapter-item expanded "><a href="setup/installation.html">Installation</a></li><li class="chapter-item expanded "><a href="postgres.html">Using Postgres</a></li><li class="chapter-item expanded "><a href="reverse_proxy.html">Configuring a Reverse Proxy</a></li><li class="chapter-item expanded "><a href="turn-howto.html">Configuring a Turn Server</a></li><li class="chapter-item expanded "><a href="delegate.html">Delegation</a></li><li class="chapter-item expanded affix "><li class="part-title">Upgrading</li><li class="chapter-item expanded "><a href="upgrade.html">Upgrading between Synapse Versions</a></li><li class="chapter-item expanded "><a href="MSC1711_certificates_FAQ.html">Upgrading from pre-Synapse 1.0</a></li><li class="chapter-item expanded affix "><li class="part-title">Usage</li><li class="chapter-item expanded "><a href="federate.html">Federation</a></li><li class="chapter-item expanded "><a href="usage/configuration/index.html">Configuration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/configuration/homeserver_sample_config.html">Homeserver Sample Config File</a></li><li class="chapter-item expanded "><a href="usage/configuration/logging_sample_config.html">Logging Sample Config File</a></li><li class="chapter-item expanded "><a href="structured_logging.html">Structured Logging</a></li><li class="chapter-item expanded "><a href="usage/configuration/user_authentication/index.html">User Authentication</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Single-Sign On</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="openid.html">OpenID Connect</a></li><li class="chapter-item expanded "><div>SAML</div></li><li class="chapter-item expanded "><div>CAS</div></li><li class="chapter-item expanded "><a href="sso_mapping_providers.html">SSO Mapping Providers</a></li></ol></li><li class="chapter-item expanded "><a href="password_auth_providers.html">Password Auth Providers</a></li><li class="chapter-item expanded "><a href="jwt.html">JSON Web Tokens</a></li></ol></li><li class="chapter-item expanded "><a href="CAPTCHA_SETUP.html">Registration Captcha</a></li><li class="chapter-item expanded "><a href="application_services.html">Application Services</a></li><li class="chapter-item expanded "><a href="server_notices.html">Server Notices</a></li><li class="chapter-item expanded "><a href="consent_tracking.html">Consent Tracking</a></li><li class="chapter-item expanded "><a href="url_previews.html">URL Previews</a></li><li class="chapter-item expanded "><a href="user_directory.html">User Directory</a></li><li class="chapter-item expanded "><a href="message_retention_policies.html">Message Retention Policies</a></li><li class="chapter-item expanded "><a href="modules.html">Pluggable Modules</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Third Party Rules</div></li><li class="chapter-item expanded "><a href="spam_checker.html">Spam Checker</a></li><li class="chapter-item expanded "><a href="presence_router_module.html" class="active">Presence Router</a></li><li class="chapter-item expanded "><div>Media Storage Providers</div></li></ol></li><li class="chapter-item expanded "><a href="workers.html">Workers</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="synctl_workers.html">Using synctl with Workers</a></li><li class="chapter-item expanded "><a href="systemd-with-workers/index.html">Systemd</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="usage/administration/index.html">Administration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="usage/administration/admin_api/index.html">Admin API</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="admin_api/account_validity.html">Account Validity</a></li><li class="chapter-item expanded "><a href="admin_api/delete_group.html">Delete Group</a></li><li class="chapter-item expanded "><a href="admin_api/event_reports.html">Event Reports</a></li><li class="chapter-item expanded "><a href="admin_api/media_admin_api.html">Media</a></li><li class="chapter-item expanded "><a href="admin_api/purge_history_api.html">Purge History</a></li><li class="chapter-item expanded "><a href="admin_api/purge_room.html">Purge Rooms</a></li><li class="chapter-item expanded "><a href="admin_api/register_api.html">Register Users</a></li><li class="chapter-item expanded "><a href="admin_api/room_membership.html">Manipulate Room Membership</a></li><li class="chapter-item expanded "><a href="admin_api/rooms.html">Rooms</a></li><li class="chapter-item expanded "><a href="admin_api/server_notices.html">Server Notices</a></li><li class="chapter-item expanded "><a href="admin_api/shutdown_room.html">Shutdown Room</a></li><li class="chapter-item expanded "><a href="admin_api/statistics.html">Statistics</a></li><li class="chapter-item expanded "><a href="admin_api/user_admin_api.html">Users</a></li><li class="chapter-item expanded "><a href="admin_api/version_api.html">Server Version</a></li></ol></li><li class="chapter-item expanded "><a href="manhole.html">Manhole</a></li><li class="chapter-item expanded "><a href="metrics-howto.html">Monitoring</a></li><li class="chapter-item expanded "><a href="usage/administration/request_log.html">Request log format</a></li><li class="chapter-item expanded "><div>Scripts</div></li></ol></li><li class="chapter-item expanded "><li class="part-title">Development</li><li class="chapter-item expanded "><a href="development/contributing_guide.html">Contributing Guide</a></li><li class="chapter-item expanded "><a href="code_style.html">Code Style</a></li><li class="chapter-item expanded "><a href="dev/git.html">Git Usage</a></li><li class="chapter-item expanded "><div>Testing</div></li><li class="chapter-item expanded "><a href="opentracing.html">OpenTracing</a></li><li class="chapter-item expanded "><a href="development/database_schema.html">Database Schemas</a></li><li class="chapter-item expanded "><div>Synapse Architecture</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="log_contexts.html">Log Contexts</a></li><li class="chapter-item expanded "><a href="replication.html">Replication</a></li><li class="chapter-item expanded "><a href="tcp_replication.html">TCP Replication</a></li></ol></li><li class="chapter-item expanded "><a href="development/internal_documentation/index.html">Internal Documentation</a></li><li><ol class="section"><li class="chapter-item expanded "><div>Single Sign-On</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="dev/saml.html">SAML</a></li><li class="chapter-item expanded "><a href="dev/cas.html">CAS</a></li></ol></li><li class="chapter-item expanded "><div>State Resolution</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="auth_chain_difference_algorithm.html">The Auth Chain Difference Algorithm</a></li></ol></li><li class="chapter-item expanded "><a href="media_repository.html">Media Repository</a></li><li class="chapter-item expanded "><a href="room_and_user_statistics.html">Room and User Statistics</a></li></ol></li><li class="chapter-item expanded "><div>Scripts</div></li><li class="chapter-item expanded affix "><li class="part-title">Other</li><li class="chapter-item expanded "><a href="deprecation_policy.html">Dependency Deprecation Policy</a></li></ol>
  75. </div>
  76. <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
  77. </nav>
  78. <div id="page-wrapper" class="page-wrapper">
  79. <div class="page">
  80. <div id="menu-bar-hover-placeholder"></div>
  81. <div id="menu-bar" class="menu-bar sticky bordered">
  82. <div class="left-buttons">
  83. <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
  84. <i class="fa fa-bars"></i>
  85. </button>
  86. <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
  87. <i class="fa fa-paint-brush"></i>
  88. </button>
  89. <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
  90. <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
  91. <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
  92. <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
  93. <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
  94. <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
  95. </ul>
  96. <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
  97. <i class="fa fa-search"></i>
  98. </button>
  99. <div class="version-picker">
  100. <div class="dropdown">
  101. <div class="select">
  102. <span></span>
  103. <i class="fa fa-chevron-down"></i>
  104. </div>
  105. <input type="hidden" name="version">
  106. <ul class="dropdown-menu">
  107. <!-- Versions will be added dynamically in version-picker.js -->
  108. </ul>
  109. </div>
  110. </div>
  111. </div>
  112. <h1 class="menu-title">Synapse</h1>
  113. <div class="right-buttons">
  114. <a href="print.html" title="Print this book" aria-label="Print this book">
  115. <i id="print-button" class="fa fa-print"></i>
  116. </a>
  117. <a href="https://github.com/matrix-org/synapse" title="Git repository" aria-label="Git repository">
  118. <i id="git-repository-button" class="fa fa-github"></i>
  119. </a>
  120. <a href="https://github.com/matrix-org/synapse/edit/develop/docs/presence_router_module.md" title="Suggest an edit" aria-label="Suggest an edit">
  121. <i id="git-edit-button" class="fa fa-edit"></i>
  122. </a>
  123. </div>
  124. </div>
  125. <div id="search-wrapper" class="hidden">
  126. <form id="searchbar-outer" class="searchbar-outer">
  127. <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
  128. </form>
  129. <div id="searchresults-outer" class="searchresults-outer hidden">
  130. <div id="searchresults-header" class="searchresults-header"></div>
  131. <ul id="searchresults">
  132. </ul>
  133. </div>
  134. </div>
  135. <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
  136. <script type="text/javascript">
  137. document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
  138. document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
  139. Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
  140. link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
  141. });
  142. </script>
  143. <div id="content" class="content">
  144. <main>
  145. <!-- Page table of contents -->
  146. <div class="sidetoc">
  147. <nav class="pagetoc"></nav>
  148. </div>
  149. <h1 id="presence-router-module"><a class="header" href="#presence-router-module">Presence Router Module</a></h1>
  150. <p>Synapse supports configuring a module that can specify additional users
  151. (local or remote) to should receive certain presence updates from local
  152. users.</p>
  153. <p>Note that routing presence via Application Service transactions is not
  154. currently supported.</p>
  155. <p>The presence routing module is implemented as a Python class, which will
  156. be imported by the running Synapse.</p>
  157. <h2 id="python-presence-router-class"><a class="header" href="#python-presence-router-class">Python Presence Router Class</a></h2>
  158. <p>The Python class is instantiated with two objects:</p>
  159. <ul>
  160. <li>A configuration object of some type (see below).</li>
  161. <li>An instance of <code>synapse.module_api.ModuleApi</code>.</li>
  162. </ul>
  163. <p>It then implements methods related to presence routing.</p>
  164. <p>Note that one method of <code>ModuleApi</code> that may be useful is:</p>
  165. <pre><code class="language-python">async def ModuleApi.send_local_online_presence_to(users: Iterable[str]) -&gt; None
  166. </code></pre>
  167. <p>which can be given a list of local or remote MXIDs to broadcast known, online user
  168. presence to (for those users that the receiving user is considered interested in).
  169. It does not include state for users who are currently offline, and it can only be
  170. called on workers that support sending federation. Additionally, this method must
  171. only be called from the process that has been configured to write to the
  172. the <a href="workers.html#stream-writers">presence stream</a>.
  173. By default, this is the main process, but another worker can be configured to do
  174. so.</p>
  175. <h3 id="module-structure"><a class="header" href="#module-structure">Module structure</a></h3>
  176. <p>Below is a list of possible methods that can be implemented, and whether they are
  177. required.</p>
  178. <h4 id="parse_config"><a class="header" href="#parse_config"><code>parse_config</code></a></h4>
  179. <pre><code class="language-python">def parse_config(config_dict: dict) -&gt; Any
  180. </code></pre>
  181. <p><strong>Required.</strong> A static method that is passed a dictionary of config options, and
  182. should return a validated config object. This method is described further in
  183. <a href="#configuration">Configuration</a>.</p>
  184. <h4 id="get_users_for_states"><a class="header" href="#get_users_for_states"><code>get_users_for_states</code></a></h4>
  185. <pre><code class="language-python">async def get_users_for_states(
  186. self,
  187. state_updates: Iterable[UserPresenceState],
  188. ) -&gt; Dict[str, Set[UserPresenceState]]:
  189. </code></pre>
  190. <p><strong>Required.</strong> An asynchronous method that is passed an iterable of user presence
  191. state. This method can determine whether a given presence update should be sent to certain
  192. users. It does this by returning a dictionary with keys representing local or remote
  193. Matrix User IDs, and values being a python set
  194. of <code>synapse.handlers.presence.UserPresenceState</code> instances.</p>
  195. <p>Synapse will then attempt to send the specified presence updates to each user when
  196. possible.</p>
  197. <h4 id="get_interested_users"><a class="header" href="#get_interested_users"><code>get_interested_users</code></a></h4>
  198. <pre><code class="language-python">async def get_interested_users(self, user_id: str) -&gt; Union[Set[str], str]
  199. </code></pre>
  200. <p><strong>Required.</strong> An asynchronous method that is passed a single Matrix User ID. This
  201. method is expected to return the users that the passed in user may be interested in the
  202. presence of. Returned users may be local or remote. The presence routed as a result of
  203. what this method returns is sent in addition to the updates already sent between users
  204. that share a room together. Presence updates are deduplicated.</p>
  205. <p>This method should return a python set of Matrix User IDs, or the object
  206. <code>synapse.events.presence_router.PresenceRouter.ALL_USERS</code> to indicate that the passed
  207. user should receive presence information for <em>all</em> known users.</p>
  208. <p>For clarity, if the user <code>@alice:example.org</code> is passed to this method, and the Set
  209. <code>{&quot;@bob:example.com&quot;, &quot;@charlie:somewhere.org&quot;}</code> is returned, this signifies that Alice
  210. should receive presence updates sent by Bob and Charlie, regardless of whether these
  211. users share a room.</p>
  212. <h3 id="example"><a class="header" href="#example">Example</a></h3>
  213. <p>Below is an example implementation of a presence router class.</p>
  214. <pre><code class="language-python">from typing import Dict, Iterable, Set, Union
  215. from synapse.events.presence_router import PresenceRouter
  216. from synapse.handlers.presence import UserPresenceState
  217. from synapse.module_api import ModuleApi
  218. class PresenceRouterConfig:
  219. def __init__(self):
  220. # Config options with their defaults
  221. # A list of users to always send all user presence updates to
  222. self.always_send_to_users = [] # type: List[str]
  223. # A list of users to ignore presence updates for. Does not affect
  224. # shared-room presence relationships
  225. self.blacklisted_users = [] # type: List[str]
  226. class ExamplePresenceRouter:
  227. &quot;&quot;&quot;An example implementation of synapse.presence_router.PresenceRouter.
  228. Supports routing all presence to a configured set of users, or a subset
  229. of presence from certain users to members of certain rooms.
  230. Args:
  231. config: A configuration object.
  232. module_api: An instance of Synapse's ModuleApi.
  233. &quot;&quot;&quot;
  234. def __init__(self, config: PresenceRouterConfig, module_api: ModuleApi):
  235. self._config = config
  236. self._module_api = module_api
  237. @staticmethod
  238. def parse_config(config_dict: dict) -&gt; PresenceRouterConfig:
  239. &quot;&quot;&quot;Parse a configuration dictionary from the homeserver config, do
  240. some validation and return a typed PresenceRouterConfig.
  241. Args:
  242. config_dict: The configuration dictionary.
  243. Returns:
  244. A validated config object.
  245. &quot;&quot;&quot;
  246. # Initialise a typed config object
  247. config = PresenceRouterConfig()
  248. always_send_to_users = config_dict.get(&quot;always_send_to_users&quot;)
  249. blacklisted_users = config_dict.get(&quot;blacklisted_users&quot;)
  250. # Do some validation of config options... otherwise raise a
  251. # synapse.config.ConfigError.
  252. config.always_send_to_users = always_send_to_users
  253. config.blacklisted_users = blacklisted_users
  254. return config
  255. async def get_users_for_states(
  256. self,
  257. state_updates: Iterable[UserPresenceState],
  258. ) -&gt; Dict[str, Set[UserPresenceState]]:
  259. &quot;&quot;&quot;Given an iterable of user presence updates, determine where each one
  260. needs to go. Returned results will not affect presence updates that are
  261. sent between users who share a room.
  262. Args:
  263. state_updates: An iterable of user presence state updates.
  264. Returns:
  265. A dictionary of user_id -&gt; set of UserPresenceState that the user should
  266. receive.
  267. &quot;&quot;&quot;
  268. destination_users = {} # type: Dict[str, Set[UserPresenceState]
  269. # Ignore any updates for blacklisted users
  270. desired_updates = set()
  271. for update in state_updates:
  272. if update.state_key not in self._config.blacklisted_users:
  273. desired_updates.add(update)
  274. # Send all presence updates to specific users
  275. for user_id in self._config.always_send_to_users:
  276. destination_users[user_id] = desired_updates
  277. return destination_users
  278. async def get_interested_users(
  279. self,
  280. user_id: str,
  281. ) -&gt; Union[Set[str], PresenceRouter.ALL_USERS]:
  282. &quot;&quot;&quot;
  283. Retrieve a list of users that `user_id` is interested in receiving the
  284. presence of. This will be in addition to those they share a room with.
  285. Optionally, the object PresenceRouter.ALL_USERS can be returned to indicate
  286. that this user should receive all incoming local and remote presence updates.
  287. Note that this method will only be called for local users.
  288. Args:
  289. user_id: A user requesting presence updates.
  290. Returns:
  291. A set of user IDs to return additional presence updates for, or
  292. PresenceRouter.ALL_USERS to return presence updates for all other users.
  293. &quot;&quot;&quot;
  294. if user_id in self._config.always_send_to_users:
  295. return PresenceRouter.ALL_USERS
  296. return set()
  297. </code></pre>
  298. <h4 id="a-note-on-get_users_for_states-and-get_interested_users"><a class="header" href="#a-note-on-get_users_for_states-and-get_interested_users">A note on <code>get_users_for_states</code> and <code>get_interested_users</code></a></h4>
  299. <p>Both of these methods are effectively two different sides of the same coin. The logic
  300. regarding which users should receive updates for other users should be the same
  301. between them.</p>
  302. <p><code>get_users_for_states</code> is called when presence updates come in from either federation
  303. or local users, and is used to either direct local presence to remote users, or to
  304. wake up the sync streams of local users to collect remote presence.</p>
  305. <p>In contrast, <code>get_interested_users</code> is used to determine the users that presence should
  306. be fetched for when a local user is syncing. This presence is then retrieved, before
  307. being fed through <code>get_users_for_states</code> once again, with only the syncing user's
  308. routing information pulled from the resulting dictionary.</p>
  309. <p>Their routing logic should thus line up, else you may run into unintended behaviour.</p>
  310. <h2 id="configuration"><a class="header" href="#configuration">Configuration</a></h2>
  311. <p>Once you've crafted your module and installed it into the same Python environment as
  312. Synapse, amend your homeserver config file with the following.</p>
  313. <pre><code class="language-yaml">presence:
  314. enabled: true
  315. presence_router:
  316. module: my_module.ExamplePresenceRouter
  317. config:
  318. # Any configuration options for your module. The below is an example.
  319. # of setting options for ExamplePresenceRouter.
  320. always_send_to_users: [&quot;@presence_gobbler:example.org&quot;]
  321. blacklisted_users:
  322. - &quot;@alice:example.com&quot;
  323. - &quot;@bob:example.com&quot;
  324. ...
  325. </code></pre>
  326. <p>The contents of <code>config</code> will be passed as a Python dictionary to the static
  327. <code>parse_config</code> method of your class. The object returned by this method will
  328. then be passed to the <code>__init__</code> method of your module as <code>config</code>.</p>
  329. </main>
  330. <nav class="nav-wrapper" aria-label="Page navigation">
  331. <!-- Mobile navigation buttons -->
  332. <a rel="prev" href="spam_checker.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
  333. <i class="fa fa-angle-left"></i>
  334. </a>
  335. <a rel="next" href="workers.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
  336. <i class="fa fa-angle-right"></i>
  337. </a>
  338. <div style="clear: both"></div>
  339. </nav>
  340. </div>
  341. </div>
  342. <nav class="nav-wide-wrapper" aria-label="Page navigation">
  343. <a rel="prev" href="spam_checker.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
  344. <i class="fa fa-angle-left"></i>
  345. </a>
  346. <a rel="next" href="workers.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
  347. <i class="fa fa-angle-right"></i>
  348. </a>
  349. </nav>
  350. </div>
  351. <script type="text/javascript">
  352. window.playground_copyable = true;
  353. </script>
  354. <script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
  355. <script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
  356. <script src="searcher.js" type="text/javascript" charset="utf-8"></script>
  357. <script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
  358. <script src="highlight.js" type="text/javascript" charset="utf-8"></script>
  359. <script src="book.js" type="text/javascript" charset="utf-8"></script>
  360. <!-- Custom JS scripts -->
  361. <script type="text/javascript" src="docs/website_files/table-of-contents.js"></script>
  362. <script type="text/javascript" src="docs/website_files/version-picker.js"></script>
  363. <script type="text/javascript" src="docs/website_files/version.js"></script>
  364. </body>
  365. </html>