mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-24 02:25:45 +03:00
Port the PresenceRouter module interface to the new generic interface (#10524)
Port the PresenceRouter module interface to the new generic interface introduced in v1.37.0
This commit is contained in:
parent
272b89d547
commit
1a9f531c79
9 changed files with 326 additions and 69 deletions
1
changelog.d/10524.feature
Normal file
1
changelog.d/10524.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Port the PresenceRouter module interface to the new generic interface.
|
|
@ -282,6 +282,52 @@ the request is a server admin.
|
||||||
Modules can modify the `request_content` (by e.g. adding events to its `initial_state`),
|
Modules can modify the `request_content` (by e.g. adding events to its `initial_state`),
|
||||||
or deny the room's creation by raising a `module_api.errors.SynapseError`.
|
or deny the room's creation by raising a `module_api.errors.SynapseError`.
|
||||||
|
|
||||||
|
#### Presence router callbacks
|
||||||
|
|
||||||
|
Presence router callbacks allow module developers to specify additional users (local or remote)
|
||||||
|
to receive certain presence updates from local users. Presence router callbacks can be
|
||||||
|
registered using the module API's `register_presence_router_callbacks` method.
|
||||||
|
|
||||||
|
The available presence router callbacks are:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def get_users_for_states(
|
||||||
|
self,
|
||||||
|
state_updates: Iterable["synapse.api.UserPresenceState"],
|
||||||
|
) -> Dict[str, Set["synapse.api.UserPresenceState"]]:
|
||||||
|
```
|
||||||
|
**Requires** `get_interested_users` to also be registered
|
||||||
|
|
||||||
|
Called when processing updates to the presence state of one or more users. This callback can
|
||||||
|
be used to instruct the server to forward that presence state to specific users. The module
|
||||||
|
must return a dictionary that maps from Matrix user IDs (which can be local or remote) to the
|
||||||
|
`UserPresenceState` changes that they should be forwarded.
|
||||||
|
|
||||||
|
Synapse will then attempt to send the specified presence updates to each user when possible.
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def get_interested_users(
|
||||||
|
self,
|
||||||
|
user_id: str
|
||||||
|
) -> Union[Set[str], "synapse.module_api.PRESENCE_ALL_USERS"]
|
||||||
|
```
|
||||||
|
**Requires** `get_users_for_states` to also be registered
|
||||||
|
|
||||||
|
Called when determining which users someone should be able to see the presence state of. This
|
||||||
|
callback should return complementary results to `get_users_for_state` or the presence information
|
||||||
|
may not be properly forwarded.
|
||||||
|
|
||||||
|
The callback is given the Matrix user ID for a local user that is requesting presence data and
|
||||||
|
should return the Matrix user IDs of the users whose presence state they are allowed to
|
||||||
|
query. The returned users can be local or remote.
|
||||||
|
|
||||||
|
Alternatively the callback can return `synapse.module_api.PRESENCE_ALL_USERS`
|
||||||
|
to indicate that the user should receive updates from all known users.
|
||||||
|
|
||||||
|
For example, if the user `@alice:example.org` is passed to this method, and the Set
|
||||||
|
`{"@bob:example.com", "@charlie:somewhere.org"}` is returned, this signifies that Alice
|
||||||
|
should receive presence updates sent by Bob and Charlie, regardless of whether these users
|
||||||
|
share a room.
|
||||||
|
|
||||||
### Porting an existing module that uses the old interface
|
### Porting an existing module that uses the old interface
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
<h2 style="color:red">
|
||||||
|
This page of the Synapse documentation is now deprecated. For up to date
|
||||||
|
documentation on setting up or writing a presence router module, please see
|
||||||
|
<a href="modules.md">this page</a>.
|
||||||
|
</h2>
|
||||||
|
|
||||||
# Presence Router Module
|
# Presence Router Module
|
||||||
|
|
||||||
Synapse supports configuring a module that can specify additional users
|
Synapse supports configuring a module that can specify additional users
|
||||||
|
|
|
@ -108,20 +108,6 @@ presence:
|
||||||
#
|
#
|
||||||
#enabled: false
|
#enabled: false
|
||||||
|
|
||||||
# Presence routers are third-party modules that can specify additional logic
|
|
||||||
# to where presence updates from users are routed.
|
|
||||||
#
|
|
||||||
presence_router:
|
|
||||||
# The custom module's class. Uncomment to use a custom presence router module.
|
|
||||||
#
|
|
||||||
#module: "my_custom_router.PresenceRouter"
|
|
||||||
|
|
||||||
# Configuration options of the custom module. Refer to your module's
|
|
||||||
# documentation for available options.
|
|
||||||
#
|
|
||||||
#config:
|
|
||||||
# example_option: 'something'
|
|
||||||
|
|
||||||
# Whether to require authentication to retrieve profile data (avatars,
|
# Whether to require authentication to retrieve profile data (avatars,
|
||||||
# display names) of other users through the client API. Defaults to
|
# display names) of other users through the client API. Defaults to
|
||||||
# 'false'. Note that profile data is also available via the federation
|
# 'false'. Note that profile data is also available via the federation
|
||||||
|
|
|
@ -37,6 +37,7 @@ from synapse.app import check_bind_error
|
||||||
from synapse.app.phone_stats_home import start_phone_stats_home
|
from synapse.app.phone_stats_home import start_phone_stats_home
|
||||||
from synapse.config.homeserver import HomeServerConfig
|
from synapse.config.homeserver import HomeServerConfig
|
||||||
from synapse.crypto import context_factory
|
from synapse.crypto import context_factory
|
||||||
|
from synapse.events.presence_router import load_legacy_presence_router
|
||||||
from synapse.events.spamcheck import load_legacy_spam_checkers
|
from synapse.events.spamcheck import load_legacy_spam_checkers
|
||||||
from synapse.events.third_party_rules import load_legacy_third_party_event_rules
|
from synapse.events.third_party_rules import load_legacy_third_party_event_rules
|
||||||
from synapse.logging.context import PreserveLoggingContext
|
from synapse.logging.context import PreserveLoggingContext
|
||||||
|
@ -370,6 +371,7 @@ async def start(hs: "HomeServer"):
|
||||||
|
|
||||||
load_legacy_spam_checkers(hs)
|
load_legacy_spam_checkers(hs)
|
||||||
load_legacy_third_party_event_rules(hs)
|
load_legacy_third_party_event_rules(hs)
|
||||||
|
load_legacy_presence_router(hs)
|
||||||
|
|
||||||
# If we've configured an expiry time for caches, start the background job now.
|
# If we've configured an expiry time for caches, start the background job now.
|
||||||
setup_expire_lru_cache_entries(hs)
|
setup_expire_lru_cache_entries(hs)
|
||||||
|
|
|
@ -248,6 +248,7 @@ class ServerConfig(Config):
|
||||||
self.use_presence = config.get("use_presence", True)
|
self.use_presence = config.get("use_presence", True)
|
||||||
|
|
||||||
# Custom presence router module
|
# Custom presence router module
|
||||||
|
# This is the legacy way of configuring it (the config should now be put in the modules section)
|
||||||
self.presence_router_module_class = None
|
self.presence_router_module_class = None
|
||||||
self.presence_router_config = None
|
self.presence_router_config = None
|
||||||
presence_router_config = presence_config.get("presence_router")
|
presence_router_config = presence_config.get("presence_router")
|
||||||
|
@ -858,20 +859,6 @@ class ServerConfig(Config):
|
||||||
#
|
#
|
||||||
#enabled: false
|
#enabled: false
|
||||||
|
|
||||||
# Presence routers are third-party modules that can specify additional logic
|
|
||||||
# to where presence updates from users are routed.
|
|
||||||
#
|
|
||||||
presence_router:
|
|
||||||
# The custom module's class. Uncomment to use a custom presence router module.
|
|
||||||
#
|
|
||||||
#module: "my_custom_router.PresenceRouter"
|
|
||||||
|
|
||||||
# Configuration options of the custom module. Refer to your module's
|
|
||||||
# documentation for available options.
|
|
||||||
#
|
|
||||||
#config:
|
|
||||||
# example_option: 'something'
|
|
||||||
|
|
||||||
# Whether to require authentication to retrieve profile data (avatars,
|
# Whether to require authentication to retrieve profile data (avatars,
|
||||||
# display names) of other users through the client API. Defaults to
|
# display names) of other users through the client API. Defaults to
|
||||||
# 'false'. Note that profile data is also available via the federation
|
# 'false'. Note that profile data is also available via the federation
|
||||||
|
|
|
@ -11,45 +11,115 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import logging
|
||||||
from typing import TYPE_CHECKING, Dict, Iterable, Set, Union
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Awaitable,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
Iterable,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
from synapse.api.presence import UserPresenceState
|
from synapse.api.presence import UserPresenceState
|
||||||
|
from synapse.util.async_helpers import maybe_awaitable
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
|
|
||||||
|
GET_USERS_FOR_STATES_CALLBACK = Callable[
|
||||||
|
[Iterable[UserPresenceState]], Awaitable[Dict[str, Set[UserPresenceState]]]
|
||||||
|
]
|
||||||
|
GET_INTERESTED_USERS_CALLBACK = Callable[
|
||||||
|
[str], Awaitable[Union[Set[str], "PresenceRouter.ALL_USERS"]]
|
||||||
|
]
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def load_legacy_presence_router(hs: "HomeServer"):
|
||||||
|
"""Wrapper that loads a presence router module configured using the old
|
||||||
|
configuration, and registers the hooks they implement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if hs.config.presence_router_module_class is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
module = hs.config.presence_router_module_class
|
||||||
|
config = hs.config.presence_router_config
|
||||||
|
api = hs.get_module_api()
|
||||||
|
|
||||||
|
presence_router = module(config=config, module_api=api)
|
||||||
|
|
||||||
|
# The known hooks. If a module implements a method which name appears in this set,
|
||||||
|
# we'll want to register it.
|
||||||
|
presence_router_methods = {
|
||||||
|
"get_users_for_states",
|
||||||
|
"get_interested_users",
|
||||||
|
}
|
||||||
|
|
||||||
|
# All methods that the module provides should be async, but this wasn't enforced
|
||||||
|
# in the old module system, so we wrap them if needed
|
||||||
|
def async_wrapper(f: Optional[Callable]) -> Optional[Callable[..., Awaitable]]:
|
||||||
|
# f might be None if the callback isn't implemented by the module. In this
|
||||||
|
# case we don't want to register a callback at all so we return None.
|
||||||
|
if f is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def run(*args, **kwargs):
|
||||||
|
# mypy doesn't do well across function boundaries so we need to tell it
|
||||||
|
# f is definitely not None.
|
||||||
|
assert f is not None
|
||||||
|
|
||||||
|
return maybe_awaitable(f(*args, **kwargs))
|
||||||
|
|
||||||
|
return run
|
||||||
|
|
||||||
|
# Register the hooks through the module API.
|
||||||
|
hooks = {
|
||||||
|
hook: async_wrapper(getattr(presence_router, hook, None))
|
||||||
|
for hook in presence_router_methods
|
||||||
|
}
|
||||||
|
|
||||||
|
api.register_presence_router_callbacks(**hooks)
|
||||||
|
|
||||||
|
|
||||||
class PresenceRouter:
|
class PresenceRouter:
|
||||||
"""
|
"""
|
||||||
A module that the homeserver will call upon to help route user presence updates to
|
A module that the homeserver will call upon to help route user presence updates to
|
||||||
additional destinations. If a custom presence router is configured, calls will be
|
additional destinations.
|
||||||
passed to that instead.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ALL_USERS = "ALL"
|
ALL_USERS = "ALL"
|
||||||
|
|
||||||
def __init__(self, hs: "HomeServer"):
|
def __init__(self, hs: "HomeServer"):
|
||||||
self.custom_presence_router = None
|
# Initially there are no callbacks
|
||||||
|
self._get_users_for_states_callbacks: List[GET_USERS_FOR_STATES_CALLBACK] = []
|
||||||
|
self._get_interested_users_callbacks: List[GET_INTERESTED_USERS_CALLBACK] = []
|
||||||
|
|
||||||
# Check whether a custom presence router module has been configured
|
def register_presence_router_callbacks(
|
||||||
if hs.config.presence_router_module_class:
|
self,
|
||||||
# Initialise the module
|
get_users_for_states: Optional[GET_USERS_FOR_STATES_CALLBACK] = None,
|
||||||
self.custom_presence_router = hs.config.presence_router_module_class(
|
get_interested_users: Optional[GET_INTERESTED_USERS_CALLBACK] = None,
|
||||||
config=hs.config.presence_router_config, module_api=hs.get_module_api()
|
):
|
||||||
|
# PresenceRouter modules are required to implement both of these methods
|
||||||
|
# or neither of them as they are assumed to act in a complementary manner
|
||||||
|
paired_methods = [get_users_for_states, get_interested_users]
|
||||||
|
if paired_methods.count(None) == 1:
|
||||||
|
raise RuntimeError(
|
||||||
|
"PresenceRouter modules must register neither or both of the paired callbacks: "
|
||||||
|
"[get_users_for_states, get_interested_users]"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure the module has implemented the required methods
|
# Append the methods provided to the lists of callbacks
|
||||||
required_methods = ["get_users_for_states", "get_interested_users"]
|
if get_users_for_states is not None:
|
||||||
for method_name in required_methods:
|
self._get_users_for_states_callbacks.append(get_users_for_states)
|
||||||
if not hasattr(self.custom_presence_router, method_name):
|
|
||||||
raise Exception(
|
if get_interested_users is not None:
|
||||||
"PresenceRouter module '%s' must implement all required methods: %s"
|
self._get_interested_users_callbacks.append(get_interested_users)
|
||||||
% (
|
|
||||||
hs.config.presence_router_module_class.__name__,
|
|
||||||
", ".join(required_methods),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_users_for_states(
|
async def get_users_for_states(
|
||||||
self,
|
self,
|
||||||
|
@ -66,15 +136,41 @@ class PresenceRouter:
|
||||||
A dictionary of user_id -> set of UserPresenceState, indicating which
|
A dictionary of user_id -> set of UserPresenceState, indicating which
|
||||||
presence updates each user should receive.
|
presence updates each user should receive.
|
||||||
"""
|
"""
|
||||||
if self.custom_presence_router is not None:
|
|
||||||
# Ask the custom module
|
|
||||||
return await self.custom_presence_router.get_users_for_states(
|
|
||||||
state_updates=state_updates
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# Bail out early if we don't have any callbacks to run.
|
||||||
|
if len(self._get_users_for_states_callbacks) == 0:
|
||||||
# Don't include any extra destinations for presence updates
|
# Don't include any extra destinations for presence updates
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
users_for_states = {}
|
||||||
|
# run all the callbacks for get_users_for_states and combine the results
|
||||||
|
for callback in self._get_users_for_states_callbacks:
|
||||||
|
try:
|
||||||
|
result = await callback(state_updates)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Failed to run module API callback %s: %s", callback, e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not isinstance(result, Dict):
|
||||||
|
logger.warning(
|
||||||
|
"Wrong type returned by module API callback %s: %s, expected Dict",
|
||||||
|
callback,
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for key, new_entries in result.items():
|
||||||
|
if not isinstance(new_entries, Set):
|
||||||
|
logger.warning(
|
||||||
|
"Wrong type returned by module API callback %s: %s, expected Set",
|
||||||
|
callback,
|
||||||
|
new_entries,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
users_for_states.setdefault(key, set()).update(new_entries)
|
||||||
|
|
||||||
|
return users_for_states
|
||||||
|
|
||||||
async def get_interested_users(self, user_id: str) -> Union[Set[str], ALL_USERS]:
|
async def get_interested_users(self, user_id: str) -> Union[Set[str], ALL_USERS]:
|
||||||
"""
|
"""
|
||||||
Retrieve a list of users that `user_id` is interested in receiving the
|
Retrieve a list of users that `user_id` is interested in receiving the
|
||||||
|
@ -92,12 +188,36 @@ class PresenceRouter:
|
||||||
A set of user IDs to return presence updates for, or ALL_USERS to return all
|
A set of user IDs to return presence updates for, or ALL_USERS to return all
|
||||||
known updates.
|
known updates.
|
||||||
"""
|
"""
|
||||||
if self.custom_presence_router is not None:
|
|
||||||
# Ask the custom module for interested users
|
|
||||||
return await self.custom_presence_router.get_interested_users(
|
|
||||||
user_id=user_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# A custom presence router is not defined.
|
# Bail out early if we don't have any callbacks to run.
|
||||||
|
if len(self._get_interested_users_callbacks) == 0:
|
||||||
# Don't report any additional interested users
|
# Don't report any additional interested users
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
interested_users = set()
|
||||||
|
# run all the callbacks for get_interested_users and combine the results
|
||||||
|
for callback in self._get_interested_users_callbacks:
|
||||||
|
try:
|
||||||
|
result = await callback(user_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Failed to run module API callback %s: %s", callback, e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If one of the callbacks returns ALL_USERS then we can stop calling all
|
||||||
|
# of the other callbacks, since the set of interested_users is already as
|
||||||
|
# large as it can possibly be
|
||||||
|
if result == PresenceRouter.ALL_USERS:
|
||||||
|
return PresenceRouter.ALL_USERS
|
||||||
|
|
||||||
|
if not isinstance(result, Set):
|
||||||
|
logger.warning(
|
||||||
|
"Wrong type returned by module API callback %s: %s, expected set",
|
||||||
|
callback,
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add the new interested users to the set
|
||||||
|
interested_users.update(result)
|
||||||
|
|
||||||
|
return interested_users
|
||||||
|
|
|
@ -32,6 +32,7 @@ from twisted.internet import defer
|
||||||
from twisted.web.resource import IResource
|
from twisted.web.resource import IResource
|
||||||
|
|
||||||
from synapse.events import EventBase
|
from synapse.events import EventBase
|
||||||
|
from synapse.events.presence_router import PresenceRouter
|
||||||
from synapse.http.client import SimpleHttpClient
|
from synapse.http.client import SimpleHttpClient
|
||||||
from synapse.http.server import (
|
from synapse.http.server import (
|
||||||
DirectServeHtmlResource,
|
DirectServeHtmlResource,
|
||||||
|
@ -57,6 +58,8 @@ This package defines the 'stable' API which can be used by extension modules whi
|
||||||
are loaded into Synapse.
|
are loaded into Synapse.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
PRESENCE_ALL_USERS = PresenceRouter.ALL_USERS
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"errors",
|
"errors",
|
||||||
"make_deferred_yieldable",
|
"make_deferred_yieldable",
|
||||||
|
@ -70,6 +73,7 @@ __all__ = [
|
||||||
"DirectServeHtmlResource",
|
"DirectServeHtmlResource",
|
||||||
"DirectServeJsonResource",
|
"DirectServeJsonResource",
|
||||||
"ModuleApi",
|
"ModuleApi",
|
||||||
|
"PRESENCE_ALL_USERS",
|
||||||
]
|
]
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -111,6 +115,7 @@ class ModuleApi:
|
||||||
self._spam_checker = hs.get_spam_checker()
|
self._spam_checker = hs.get_spam_checker()
|
||||||
self._account_validity_handler = hs.get_account_validity_handler()
|
self._account_validity_handler = hs.get_account_validity_handler()
|
||||||
self._third_party_event_rules = hs.get_third_party_event_rules()
|
self._third_party_event_rules = hs.get_third_party_event_rules()
|
||||||
|
self._presence_router = hs.get_presence_router()
|
||||||
|
|
||||||
#################################################################################
|
#################################################################################
|
||||||
# The following methods should only be called during the module's initialisation.
|
# The following methods should only be called during the module's initialisation.
|
||||||
|
@ -130,6 +135,11 @@ class ModuleApi:
|
||||||
"""Registers callbacks for third party event rules capabilities."""
|
"""Registers callbacks for third party event rules capabilities."""
|
||||||
return self._third_party_event_rules.register_third_party_rules_callbacks
|
return self._third_party_event_rules.register_third_party_rules_callbacks
|
||||||
|
|
||||||
|
@property
|
||||||
|
def register_presence_router_callbacks(self):
|
||||||
|
"""Registers callbacks for presence router capabilities."""
|
||||||
|
return self._presence_router.register_presence_router_callbacks
|
||||||
|
|
||||||
def register_web_resource(self, path: str, resource: IResource):
|
def register_web_resource(self, path: str, resource: IResource):
|
||||||
"""Registers a web resource to be served at the given path.
|
"""Registers a web resource to be served at the given path.
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ from unittest.mock import Mock
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
from synapse.api.constants import EduTypes
|
from synapse.api.constants import EduTypes
|
||||||
from synapse.events.presence_router import PresenceRouter
|
from synapse.events.presence_router import PresenceRouter, load_legacy_presence_router
|
||||||
from synapse.federation.units import Transaction
|
from synapse.federation.units import Transaction
|
||||||
from synapse.handlers.presence import UserPresenceState
|
from synapse.handlers.presence import UserPresenceState
|
||||||
from synapse.module_api import ModuleApi
|
from synapse.module_api import ModuleApi
|
||||||
|
@ -34,7 +34,7 @@ class PresenceRouterTestConfig:
|
||||||
users_who_should_receive_all_presence = attr.ib(type=List[str], default=[])
|
users_who_should_receive_all_presence = attr.ib(type=List[str], default=[])
|
||||||
|
|
||||||
|
|
||||||
class PresenceRouterTestModule:
|
class LegacyPresenceRouterTestModule:
|
||||||
def __init__(self, config: PresenceRouterTestConfig, module_api: ModuleApi):
|
def __init__(self, config: PresenceRouterTestConfig, module_api: ModuleApi):
|
||||||
self._config = config
|
self._config = config
|
||||||
self._module_api = module_api
|
self._module_api = module_api
|
||||||
|
@ -77,6 +77,53 @@ class PresenceRouterTestModule:
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
class PresenceRouterTestModule:
|
||||||
|
def __init__(self, config: PresenceRouterTestConfig, api: ModuleApi):
|
||||||
|
self._config = config
|
||||||
|
self._module_api = api
|
||||||
|
api.register_presence_router_callbacks(
|
||||||
|
get_users_for_states=self.get_users_for_states,
|
||||||
|
get_interested_users=self.get_interested_users,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_users_for_states(
|
||||||
|
self, state_updates: Iterable[UserPresenceState]
|
||||||
|
) -> Dict[str, Set[UserPresenceState]]:
|
||||||
|
users_to_state = {
|
||||||
|
user_id: set(state_updates)
|
||||||
|
for user_id in self._config.users_who_should_receive_all_presence
|
||||||
|
}
|
||||||
|
return users_to_state
|
||||||
|
|
||||||
|
async def get_interested_users(
|
||||||
|
self, user_id: str
|
||||||
|
) -> Union[Set[str], PresenceRouter.ALL_USERS]:
|
||||||
|
if user_id in self._config.users_who_should_receive_all_presence:
|
||||||
|
return PresenceRouter.ALL_USERS
|
||||||
|
|
||||||
|
return set()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_config(config_dict: dict) -> PresenceRouterTestConfig:
|
||||||
|
"""Parse a configuration dictionary from the homeserver config, do
|
||||||
|
some validation and return a typed PresenceRouterConfig.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_dict: The configuration dictionary.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A validated config object.
|
||||||
|
"""
|
||||||
|
# Initialise a typed config object
|
||||||
|
config = PresenceRouterTestConfig()
|
||||||
|
|
||||||
|
config.users_who_should_receive_all_presence = config_dict.get(
|
||||||
|
"users_who_should_receive_all_presence"
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
class PresenceRouterTestCase(FederatingHomeserverTestCase):
|
class PresenceRouterTestCase(FederatingHomeserverTestCase):
|
||||||
servlets = [
|
servlets = [
|
||||||
admin.register_servlets,
|
admin.register_servlets,
|
||||||
|
@ -86,9 +133,17 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase):
|
||||||
]
|
]
|
||||||
|
|
||||||
def make_homeserver(self, reactor, clock):
|
def make_homeserver(self, reactor, clock):
|
||||||
return self.setup_test_homeserver(
|
hs = self.setup_test_homeserver(
|
||||||
federation_transport_client=Mock(spec=["send_transaction"]),
|
federation_transport_client=Mock(spec=["send_transaction"]),
|
||||||
)
|
)
|
||||||
|
# Load the modules into the homeserver
|
||||||
|
module_api = hs.get_module_api()
|
||||||
|
for module, config in hs.config.modules.loaded_modules:
|
||||||
|
module(config=config, api=module_api)
|
||||||
|
|
||||||
|
load_legacy_presence_router(hs)
|
||||||
|
|
||||||
|
return hs
|
||||||
|
|
||||||
def prepare(self, reactor, clock, homeserver):
|
def prepare(self, reactor, clock, homeserver):
|
||||||
self.sync_handler = self.hs.get_sync_handler()
|
self.sync_handler = self.hs.get_sync_handler()
|
||||||
|
@ -98,7 +153,7 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase):
|
||||||
{
|
{
|
||||||
"presence": {
|
"presence": {
|
||||||
"presence_router": {
|
"presence_router": {
|
||||||
"module": __name__ + ".PresenceRouterTestModule",
|
"module": __name__ + ".LegacyPresenceRouterTestModule",
|
||||||
"config": {
|
"config": {
|
||||||
"users_who_should_receive_all_presence": [
|
"users_who_should_receive_all_presence": [
|
||||||
"@presence_gobbler:test",
|
"@presence_gobbler:test",
|
||||||
|
@ -109,7 +164,28 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase):
|
||||||
"send_federation": True,
|
"send_federation": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
def test_receiving_all_presence_legacy(self):
|
||||||
|
self.receiving_all_presence_test_body()
|
||||||
|
|
||||||
|
@override_config(
|
||||||
|
{
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"module": __name__ + ".PresenceRouterTestModule",
|
||||||
|
"config": {
|
||||||
|
"users_who_should_receive_all_presence": [
|
||||||
|
"@presence_gobbler:test",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"send_federation": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
def test_receiving_all_presence(self):
|
def test_receiving_all_presence(self):
|
||||||
|
self.receiving_all_presence_test_body()
|
||||||
|
|
||||||
|
def receiving_all_presence_test_body(self):
|
||||||
"""Test that a user that does not share a room with another other can receive
|
"""Test that a user that does not share a room with another other can receive
|
||||||
presence for them, due to presence routing.
|
presence for them, due to presence routing.
|
||||||
"""
|
"""
|
||||||
|
@ -203,7 +279,7 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase):
|
||||||
{
|
{
|
||||||
"presence": {
|
"presence": {
|
||||||
"presence_router": {
|
"presence_router": {
|
||||||
"module": __name__ + ".PresenceRouterTestModule",
|
"module": __name__ + ".LegacyPresenceRouterTestModule",
|
||||||
"config": {
|
"config": {
|
||||||
"users_who_should_receive_all_presence": [
|
"users_who_should_receive_all_presence": [
|
||||||
"@presence_gobbler1:test",
|
"@presence_gobbler1:test",
|
||||||
|
@ -216,7 +292,30 @@ class PresenceRouterTestCase(FederatingHomeserverTestCase):
|
||||||
"send_federation": True,
|
"send_federation": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
def test_send_local_online_presence_to_with_module_legacy(self):
|
||||||
|
self.send_local_online_presence_to_with_module_test_body()
|
||||||
|
|
||||||
|
@override_config(
|
||||||
|
{
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"module": __name__ + ".PresenceRouterTestModule",
|
||||||
|
"config": {
|
||||||
|
"users_who_should_receive_all_presence": [
|
||||||
|
"@presence_gobbler1:test",
|
||||||
|
"@presence_gobbler2:test",
|
||||||
|
"@far_away_person:island",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"send_federation": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
def test_send_local_online_presence_to_with_module(self):
|
def test_send_local_online_presence_to_with_module(self):
|
||||||
|
self.send_local_online_presence_to_with_module_test_body()
|
||||||
|
|
||||||
|
def send_local_online_presence_to_with_module_test_body(self):
|
||||||
"""Tests that send_local_presence_to_users sends local online presence to a set
|
"""Tests that send_local_presence_to_users sends local online presence to a set
|
||||||
of specified local and remote users, with a custom PresenceRouter module enabled.
|
of specified local and remote users, with a custom PresenceRouter module enabled.
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue