mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-14 11:57:44 +00:00
Add avatar and topic settings for server notice room (#16679)
This commit is contained in:
parent
9f6c644825
commit
e108c31fc0
6 changed files with 235 additions and 14 deletions
1
changelog.d/16679.feature
Normal file
1
changelog.d/16679.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add config options to set the avatar and the topic of the server notices room.
|
|
@ -44,14 +44,16 @@ section, which should look like this:
|
||||||
server_notices:
|
server_notices:
|
||||||
system_mxid_localpart: server
|
system_mxid_localpart: server
|
||||||
system_mxid_display_name: "Server Notices"
|
system_mxid_display_name: "Server Notices"
|
||||||
system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
|
system_mxid_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
room_name: "Server Notices"
|
room_name: "Server Notices"
|
||||||
|
room_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
|
room_topic: "Room used by your server admin to notice you of important information"
|
||||||
auto_join: true
|
auto_join: true
|
||||||
```
|
```
|
||||||
|
|
||||||
The only compulsory setting is `system_mxid_localpart`, which defines the user
|
The only compulsory setting is `system_mxid_localpart`, which defines the user
|
||||||
id of the Server Notices user, as above. `room_name` defines the name of the
|
id of the Server Notices user, as above. `room_name` defines the name of the
|
||||||
room which will be created.
|
room which will be created, `room_avatar_url` its avatar and `room_topic` its topic.
|
||||||
|
|
||||||
`system_mxid_display_name` and `system_mxid_avatar_url` can be used to set the
|
`system_mxid_display_name` and `system_mxid_avatar_url` can be used to set the
|
||||||
displayname and avatar of the Server Notices user.
|
displayname and avatar of the Server Notices user.
|
||||||
|
|
|
@ -3837,16 +3837,22 @@ Sub-options for this setting include:
|
||||||
* `system_mxid_display_name`: set the display name of the "notices" user
|
* `system_mxid_display_name`: set the display name of the "notices" user
|
||||||
* `system_mxid_avatar_url`: set the avatar for the "notices" user
|
* `system_mxid_avatar_url`: set the avatar for the "notices" user
|
||||||
* `room_name`: set the room name of the server notices room
|
* `room_name`: set the room name of the server notices room
|
||||||
|
* `room_avatar_url`: optional string. The room avatar to use for server notice rooms. If set to the empty string `""`, notice rooms will not be given an avatar. Defaults to the empty string. _Added in Synapse 1.99.0._
|
||||||
|
* `room_topic`: optional string. The topic to use for server notice rooms. If set to the empty string `""`, notice rooms will not be given a topic. Defaults to the empty string. _Added in Synapse 1.99.0._
|
||||||
* `auto_join`: boolean. If true, the user will be automatically joined to the room instead of being invited.
|
* `auto_join`: boolean. If true, the user will be automatically joined to the room instead of being invited.
|
||||||
Defaults to false. _Added in Synapse 1.98.0._
|
Defaults to false. _Added in Synapse 1.98.0._
|
||||||
|
|
||||||
|
Note that the name, topic and avatar of existing server notice rooms will only be updated when a new notice event is sent.
|
||||||
|
|
||||||
Example configuration:
|
Example configuration:
|
||||||
```yaml
|
```yaml
|
||||||
server_notices:
|
server_notices:
|
||||||
system_mxid_localpart: notices
|
system_mxid_localpart: notices
|
||||||
system_mxid_display_name: "Server Notices"
|
system_mxid_display_name: "Server Notices"
|
||||||
system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
|
system_mxid_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
room_name: "Server Notices"
|
room_name: "Server Notices"
|
||||||
|
room_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
|
||||||
|
room_topic: "Room used by your server admin to notice you of important information"
|
||||||
auto_join: true
|
auto_join: true
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
|
|
|
@ -38,6 +38,14 @@ class ServerNoticesConfig(Config):
|
||||||
server_notices_room_name (str|None):
|
server_notices_room_name (str|None):
|
||||||
The name to use for the server notices room.
|
The name to use for the server notices room.
|
||||||
None if server notices are not enabled.
|
None if server notices are not enabled.
|
||||||
|
|
||||||
|
server_notices_room_avatar_url (str|None):
|
||||||
|
The avatar URL to use for the server notices room.
|
||||||
|
None if server notices are not enabled.
|
||||||
|
|
||||||
|
server_notices_room_topic (str|None):
|
||||||
|
The topic to use for the server notices room.
|
||||||
|
None if server notices are not enabled.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
section = "servernotices"
|
section = "servernotices"
|
||||||
|
@ -48,6 +56,8 @@ class ServerNoticesConfig(Config):
|
||||||
self.server_notices_mxid_display_name: Optional[str] = None
|
self.server_notices_mxid_display_name: Optional[str] = None
|
||||||
self.server_notices_mxid_avatar_url: Optional[str] = None
|
self.server_notices_mxid_avatar_url: Optional[str] = None
|
||||||
self.server_notices_room_name: Optional[str] = None
|
self.server_notices_room_name: Optional[str] = None
|
||||||
|
self.server_notices_room_avatar_url: Optional[str] = None
|
||||||
|
self.server_notices_room_topic: Optional[str] = None
|
||||||
self.server_notices_auto_join: bool = False
|
self.server_notices_auto_join: bool = False
|
||||||
|
|
||||||
def read_config(self, config: JsonDict, **kwargs: Any) -> None:
|
def read_config(self, config: JsonDict, **kwargs: Any) -> None:
|
||||||
|
@ -63,4 +73,6 @@ class ServerNoticesConfig(Config):
|
||||||
self.server_notices_mxid_avatar_url = c.get("system_mxid_avatar_url", None)
|
self.server_notices_mxid_avatar_url = c.get("system_mxid_avatar_url", None)
|
||||||
# todo: i18n
|
# todo: i18n
|
||||||
self.server_notices_room_name = c.get("room_name", "Server Notices")
|
self.server_notices_room_name = c.get("room_name", "Server Notices")
|
||||||
|
self.server_notices_room_avatar_url = c.get("room_avatar_url", None)
|
||||||
|
self.server_notices_room_topic = c.get("room_topic", None)
|
||||||
self.server_notices_auto_join = c.get("auto_join", False)
|
self.server_notices_auto_join = c.get("auto_join", False)
|
||||||
|
|
|
@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from synapse.api.constants import EventTypes, Membership, RoomCreationPreset
|
from synapse.api.constants import EventTypes, Membership, RoomCreationPreset
|
||||||
from synapse.events import EventBase
|
from synapse.events import EventBase
|
||||||
from synapse.types import Requester, StreamKeyType, UserID, create_requester
|
from synapse.types import JsonDict, Requester, StreamKeyType, UserID, create_requester
|
||||||
from synapse.util.caches.descriptors import cached
|
from synapse.util.caches.descriptors import cached
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -36,6 +36,7 @@ class ServerNoticesManager:
|
||||||
self._room_member_handler = hs.get_room_member_handler()
|
self._room_member_handler = hs.get_room_member_handler()
|
||||||
self._event_creation_handler = hs.get_event_creation_handler()
|
self._event_creation_handler = hs.get_event_creation_handler()
|
||||||
self._message_handler = hs.get_message_handler()
|
self._message_handler = hs.get_message_handler()
|
||||||
|
self._storage_controllers = hs.get_storage_controllers()
|
||||||
self._is_mine_id = hs.is_mine_id
|
self._is_mine_id = hs.is_mine_id
|
||||||
self._server_name = hs.hostname
|
self._server_name = hs.hostname
|
||||||
|
|
||||||
|
@ -160,6 +161,27 @@ class ServerNoticesManager:
|
||||||
self._config.servernotices.server_notices_mxid_display_name,
|
self._config.servernotices.server_notices_mxid_display_name,
|
||||||
self._config.servernotices.server_notices_mxid_avatar_url,
|
self._config.servernotices.server_notices_mxid_avatar_url,
|
||||||
)
|
)
|
||||||
|
await self._update_room_info(
|
||||||
|
requester,
|
||||||
|
room_id,
|
||||||
|
EventTypes.Name,
|
||||||
|
"name",
|
||||||
|
self._config.servernotices.server_notices_room_name,
|
||||||
|
)
|
||||||
|
await self._update_room_info(
|
||||||
|
requester,
|
||||||
|
room_id,
|
||||||
|
EventTypes.RoomAvatar,
|
||||||
|
"url",
|
||||||
|
self._config.servernotices.server_notices_room_avatar_url,
|
||||||
|
)
|
||||||
|
await self._update_room_info(
|
||||||
|
requester,
|
||||||
|
room_id,
|
||||||
|
EventTypes.Topic,
|
||||||
|
"topic",
|
||||||
|
self._config.servernotices.server_notices_room_topic,
|
||||||
|
)
|
||||||
return room_id
|
return room_id
|
||||||
|
|
||||||
# apparently no existing notice room: create a new one
|
# apparently no existing notice room: create a new one
|
||||||
|
@ -178,15 +200,31 @@ class ServerNoticesManager:
|
||||||
"avatar_url": self._config.servernotices.server_notices_mxid_avatar_url,
|
"avatar_url": self._config.servernotices.server_notices_mxid_avatar_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
room_config: JsonDict = {
|
||||||
|
"preset": RoomCreationPreset.PRIVATE_CHAT,
|
||||||
|
"power_level_content_override": {"users_default": -10},
|
||||||
|
}
|
||||||
|
|
||||||
|
if self._config.servernotices.server_notices_room_name:
|
||||||
|
room_config["name"] = self._config.servernotices.server_notices_room_name
|
||||||
|
if self._config.servernotices.server_notices_room_topic:
|
||||||
|
room_config["topic"] = self._config.servernotices.server_notices_room_topic
|
||||||
|
if self._config.servernotices.server_notices_room_avatar_url:
|
||||||
|
room_config["initial_state"] = [
|
||||||
|
{
|
||||||
|
"type": EventTypes.RoomAvatar,
|
||||||
|
"state_key": "",
|
||||||
|
"content": {
|
||||||
|
"url": self._config.servernotices.server_notices_room_avatar_url,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
# `ignore_forced_encryption` is used to bypass `encryption_enabled_by_default_for_room_type`
|
# `ignore_forced_encryption` is used to bypass `encryption_enabled_by_default_for_room_type`
|
||||||
# setting if it set, since the server notices will not be encrypted anyway.
|
# setting if it set, since the server notices will not be encrypted anyway.
|
||||||
room_id, _, _ = await self._room_creation_handler.create_room(
|
room_id, _, _ = await self._room_creation_handler.create_room(
|
||||||
requester,
|
requester,
|
||||||
config={
|
config=room_config,
|
||||||
"preset": RoomCreationPreset.PRIVATE_CHAT,
|
|
||||||
"name": self._config.servernotices.server_notices_room_name,
|
|
||||||
"power_level_content_override": {"users_default": -10},
|
|
||||||
},
|
|
||||||
ratelimit=False,
|
ratelimit=False,
|
||||||
creator_join_profile=join_profile,
|
creator_join_profile=join_profile,
|
||||||
ignore_forced_encryption=True,
|
ignore_forced_encryption=True,
|
||||||
|
@ -265,11 +303,12 @@ class ServerNoticesManager:
|
||||||
|
|
||||||
assert self.server_notices_mxid is not None
|
assert self.server_notices_mxid is not None
|
||||||
|
|
||||||
notice_user_data_in_room = await self._message_handler.get_room_data(
|
notice_user_data_in_room = (
|
||||||
create_requester(self.server_notices_mxid),
|
await self._storage_controllers.state.get_current_state_event(
|
||||||
room_id,
|
room_id,
|
||||||
EventTypes.Member,
|
EventTypes.Member,
|
||||||
self.server_notices_mxid,
|
self.server_notices_mxid,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
assert notice_user_data_in_room is not None
|
assert notice_user_data_in_room is not None
|
||||||
|
@ -288,3 +327,55 @@ class ServerNoticesManager:
|
||||||
ratelimit=False,
|
ratelimit=False,
|
||||||
content={"displayname": display_name, "avatar_url": avatar_url},
|
content={"displayname": display_name, "avatar_url": avatar_url},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _update_room_info(
|
||||||
|
self,
|
||||||
|
requester: Requester,
|
||||||
|
room_id: str,
|
||||||
|
info_event_type: str,
|
||||||
|
info_content_key: str,
|
||||||
|
info_value: Optional[str],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Updates a specific notice room's info if it's different from what is set.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requester: The user who is performing the update.
|
||||||
|
room_id: The ID of the server notice room
|
||||||
|
info_event_type: The event type holding the specific info
|
||||||
|
info_content_key: The key containing the specific info in the event's content
|
||||||
|
info_value: The expected value for the specific info
|
||||||
|
"""
|
||||||
|
room_info_event = await self._storage_controllers.state.get_current_state_event(
|
||||||
|
room_id,
|
||||||
|
info_event_type,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_info_value = None
|
||||||
|
if room_info_event:
|
||||||
|
existing_info_value = room_info_event.get(info_content_key)
|
||||||
|
if existing_info_value == info_value:
|
||||||
|
return
|
||||||
|
if not existing_info_value and not info_value:
|
||||||
|
# A missing `info_value` can either be represented by a None
|
||||||
|
# or an empty string, so we assume that if they're both falsey
|
||||||
|
# they're equivalent.
|
||||||
|
return
|
||||||
|
|
||||||
|
if info_value is None:
|
||||||
|
info_value = ""
|
||||||
|
|
||||||
|
room_info_event_dict = {
|
||||||
|
"type": info_event_type,
|
||||||
|
"room_id": room_id,
|
||||||
|
"sender": requester.user.to_string(),
|
||||||
|
"state_key": "",
|
||||||
|
"content": {
|
||||||
|
info_content_key: info_value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
event, _ = await self._event_creation_handler.create_and_send_nonmember_event(
|
||||||
|
requester, room_info_event_dict, ratelimit=False
|
||||||
|
)
|
||||||
|
|
|
@ -596,6 +596,115 @@ class ServerNoticeTestCase(unittest.HomeserverTestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(notice_user_state["avatar_url"], new_avatar_url)
|
self.assertEqual(notice_user_state["avatar_url"], new_avatar_url)
|
||||||
|
|
||||||
|
@override_config(
|
||||||
|
{
|
||||||
|
"server_notices": {
|
||||||
|
"system_mxid_localpart": "notices",
|
||||||
|
"room_avatar_url": "test/url",
|
||||||
|
"room_topic": "Test Topic",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def test_notice_room_avatar_and_topic(self) -> None:
|
||||||
|
"""
|
||||||
|
Tests that using `room_avatar_url` and `room_topic` config properly sets
|
||||||
|
those properties for the created notice rooms.
|
||||||
|
"""
|
||||||
|
server_notice_request_content = {
|
||||||
|
"user_id": self.other_user,
|
||||||
|
"content": {"msgtype": "m.text", "body": "test msg one"},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.make_request(
|
||||||
|
"POST",
|
||||||
|
self.url,
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
content=server_notice_request_content,
|
||||||
|
)
|
||||||
|
|
||||||
|
invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
|
||||||
|
notice_room_id = invited_rooms[0].room_id
|
||||||
|
self.helper.join(
|
||||||
|
room=notice_room_id, user=self.other_user, tok=self.other_user_token
|
||||||
|
)
|
||||||
|
|
||||||
|
room_avatar_state = self.helper.get_state(
|
||||||
|
notice_room_id,
|
||||||
|
"m.room.avatar",
|
||||||
|
self.other_user_token,
|
||||||
|
state_key="",
|
||||||
|
)
|
||||||
|
self.assertEqual(room_avatar_state["url"], "test/url")
|
||||||
|
|
||||||
|
room_topic_state = self.helper.get_state(
|
||||||
|
notice_room_id,
|
||||||
|
"m.room.topic",
|
||||||
|
self.other_user_token,
|
||||||
|
state_key="",
|
||||||
|
)
|
||||||
|
self.assertEqual(room_topic_state["topic"], "Test Topic")
|
||||||
|
|
||||||
|
@override_config(
|
||||||
|
{
|
||||||
|
"server_notices": {
|
||||||
|
"system_mxid_localpart": "notices",
|
||||||
|
"room_avatar_url": "test/url",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def test_update_room_avatar_when_changed(self) -> None:
|
||||||
|
"""
|
||||||
|
Tests that existing server notices room avatar is updated when it is
|
||||||
|
different from the one in homeserver config.
|
||||||
|
"""
|
||||||
|
server_notice_request_content = {
|
||||||
|
"user_id": self.other_user,
|
||||||
|
"content": {"msgtype": "m.text", "body": "test msg one"},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.make_request(
|
||||||
|
"POST",
|
||||||
|
self.url,
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
content=server_notice_request_content,
|
||||||
|
)
|
||||||
|
|
||||||
|
invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
|
||||||
|
notice_room_id = invited_rooms[0].room_id
|
||||||
|
self.helper.join(
|
||||||
|
room=notice_room_id, user=self.other_user, tok=self.other_user_token
|
||||||
|
)
|
||||||
|
|
||||||
|
room_avatar_state = self.helper.get_state(
|
||||||
|
notice_room_id,
|
||||||
|
"m.room.avatar",
|
||||||
|
self.other_user_token,
|
||||||
|
state_key="",
|
||||||
|
)
|
||||||
|
self.assertEqual(room_avatar_state["url"], "test/url")
|
||||||
|
|
||||||
|
# simulate a change in server config after a server restart.
|
||||||
|
new_avatar_url = "test/new-url"
|
||||||
|
self.server_notices_manager._config.servernotices.server_notices_room_avatar_url = (
|
||||||
|
new_avatar_url
|
||||||
|
)
|
||||||
|
self.server_notices_manager.get_or_create_notice_room_for_user.cache.invalidate_all()
|
||||||
|
|
||||||
|
self.make_request(
|
||||||
|
"POST",
|
||||||
|
self.url,
|
||||||
|
access_token=self.admin_user_tok,
|
||||||
|
content=server_notice_request_content,
|
||||||
|
)
|
||||||
|
|
||||||
|
room_avatar_state = self.helper.get_state(
|
||||||
|
notice_room_id,
|
||||||
|
"m.room.avatar",
|
||||||
|
self.other_user_token,
|
||||||
|
state_key="",
|
||||||
|
)
|
||||||
|
self.assertEqual(room_avatar_state["url"], new_avatar_url)
|
||||||
|
|
||||||
def _check_invite_and_join_status(
|
def _check_invite_and_join_status(
|
||||||
self, user_id: str, expected_invites: int, expected_memberships: int
|
self, user_id: str, expected_invites: int, expected_memberships: int
|
||||||
) -> Sequence[RoomsForUser]:
|
) -> Sequence[RoomsForUser]:
|
||||||
|
|
Loading…
Reference in a new issue