Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit e108c31

Browse files
authored
Add avatar and topic settings for server notice room (#16679)
1 parent 9f6c644 commit e108c31

File tree

6 files changed

+235
-14
lines changed

6 files changed

+235
-14
lines changed

changelog.d/16679.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add config options to set the avatar and the topic of the server notices room.

docs/server_notices.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,16 @@ section, which should look like this:
4444
server_notices:
4545
system_mxid_localpart: server
4646
system_mxid_display_name: "Server Notices"
47-
system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
47+
system_mxid_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
4848
room_name: "Server Notices"
49+
room_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
50+
room_topic: "Room used by your server admin to notice you of important information"
4951
auto_join: true
5052
```
5153
5254
The only compulsory setting is `system_mxid_localpart`, which defines the user
5355
id of the Server Notices user, as above. `room_name` defines the name of the
54-
room which will be created.
56+
room which will be created, `room_avatar_url` its avatar and `room_topic` its topic.
5557

5658
`system_mxid_display_name` and `system_mxid_avatar_url` can be used to set the
5759
displayname and avatar of the Server Notices user.

docs/usage/configuration/config_documentation.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -3837,16 +3837,22 @@ Sub-options for this setting include:
38373837
* `system_mxid_display_name`: set the display name of the "notices" user
38383838
* `system_mxid_avatar_url`: set the avatar for the "notices" user
38393839
* `room_name`: set the room name of the server notices room
3840+
* `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._
3841+
* `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._
38403842
* `auto_join`: boolean. If true, the user will be automatically joined to the room instead of being invited.
38413843
Defaults to false. _Added in Synapse 1.98.0._
38423844

3845+
Note that the name, topic and avatar of existing server notice rooms will only be updated when a new notice event is sent.
3846+
38433847
Example configuration:
38443848
```yaml
38453849
server_notices:
38463850
system_mxid_localpart: notices
38473851
system_mxid_display_name: "Server Notices"
3848-
system_mxid_avatar_url: "mxc://server.com/oumMVlgDnLYFaPVkExemNVVZ"
3852+
system_mxid_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
38493853
room_name: "Server Notices"
3854+
room_avatar_url: "mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ"
3855+
room_topic: "Room used by your server admin to notice you of important information"
38503856
auto_join: true
38513857
```
38523858
---

synapse/config/server_notices.py

+12
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ class ServerNoticesConfig(Config):
3838
server_notices_room_name (str|None):
3939
The name to use for the server notices room.
4040
None if server notices are not enabled.
41+
42+
server_notices_room_avatar_url (str|None):
43+
The avatar URL to use for the server notices room.
44+
None if server notices are not enabled.
45+
46+
server_notices_room_topic (str|None):
47+
The topic to use for the server notices room.
48+
None if server notices are not enabled.
4149
"""
4250

4351
section = "servernotices"
@@ -48,6 +56,8 @@ def __init__(self, *args: Any):
4856
self.server_notices_mxid_display_name: Optional[str] = None
4957
self.server_notices_mxid_avatar_url: Optional[str] = None
5058
self.server_notices_room_name: Optional[str] = None
59+
self.server_notices_room_avatar_url: Optional[str] = None
60+
self.server_notices_room_topic: Optional[str] = None
5161
self.server_notices_auto_join: bool = False
5262

5363
def read_config(self, config: JsonDict, **kwargs: Any) -> None:
@@ -63,4 +73,6 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
6373
self.server_notices_mxid_avatar_url = c.get("system_mxid_avatar_url", None)
6474
# todo: i18n
6575
self.server_notices_room_name = c.get("room_name", "Server Notices")
76+
self.server_notices_room_avatar_url = c.get("room_avatar_url", None)
77+
self.server_notices_room_topic = c.get("room_topic", None)
6678
self.server_notices_auto_join = c.get("auto_join", False)

synapse/server_notices/server_notices_manager.py

+102-11
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from synapse.api.constants import EventTypes, Membership, RoomCreationPreset
1818
from synapse.events import EventBase
19-
from synapse.types import Requester, StreamKeyType, UserID, create_requester
19+
from synapse.types import JsonDict, Requester, StreamKeyType, UserID, create_requester
2020
from synapse.util.caches.descriptors import cached
2121

2222
if TYPE_CHECKING:
@@ -36,6 +36,7 @@ def __init__(self, hs: "HomeServer"):
3636
self._room_member_handler = hs.get_room_member_handler()
3737
self._event_creation_handler = hs.get_event_creation_handler()
3838
self._message_handler = hs.get_message_handler()
39+
self._storage_controllers = hs.get_storage_controllers()
3940
self._is_mine_id = hs.is_mine_id
4041
self._server_name = hs.hostname
4142

@@ -160,6 +161,27 @@ async def get_or_create_notice_room_for_user(self, user_id: str) -> str:
160161
self._config.servernotices.server_notices_mxid_display_name,
161162
self._config.servernotices.server_notices_mxid_avatar_url,
162163
)
164+
await self._update_room_info(
165+
requester,
166+
room_id,
167+
EventTypes.Name,
168+
"name",
169+
self._config.servernotices.server_notices_room_name,
170+
)
171+
await self._update_room_info(
172+
requester,
173+
room_id,
174+
EventTypes.RoomAvatar,
175+
"url",
176+
self._config.servernotices.server_notices_room_avatar_url,
177+
)
178+
await self._update_room_info(
179+
requester,
180+
room_id,
181+
EventTypes.Topic,
182+
"topic",
183+
self._config.servernotices.server_notices_room_topic,
184+
)
163185
return room_id
164186

165187
# apparently no existing notice room: create a new one
@@ -178,15 +200,31 @@ async def get_or_create_notice_room_for_user(self, user_id: str) -> str:
178200
"avatar_url": self._config.servernotices.server_notices_mxid_avatar_url,
179201
}
180202

203+
room_config: JsonDict = {
204+
"preset": RoomCreationPreset.PRIVATE_CHAT,
205+
"power_level_content_override": {"users_default": -10},
206+
}
207+
208+
if self._config.servernotices.server_notices_room_name:
209+
room_config["name"] = self._config.servernotices.server_notices_room_name
210+
if self._config.servernotices.server_notices_room_topic:
211+
room_config["topic"] = self._config.servernotices.server_notices_room_topic
212+
if self._config.servernotices.server_notices_room_avatar_url:
213+
room_config["initial_state"] = [
214+
{
215+
"type": EventTypes.RoomAvatar,
216+
"state_key": "",
217+
"content": {
218+
"url": self._config.servernotices.server_notices_room_avatar_url,
219+
},
220+
}
221+
]
222+
181223
# `ignore_forced_encryption` is used to bypass `encryption_enabled_by_default_for_room_type`
182224
# setting if it set, since the server notices will not be encrypted anyway.
183225
room_id, _, _ = await self._room_creation_handler.create_room(
184226
requester,
185-
config={
186-
"preset": RoomCreationPreset.PRIVATE_CHAT,
187-
"name": self._config.servernotices.server_notices_room_name,
188-
"power_level_content_override": {"users_default": -10},
189-
},
227+
config=room_config,
190228
ratelimit=False,
191229
creator_join_profile=join_profile,
192230
ignore_forced_encryption=True,
@@ -265,11 +303,12 @@ async def _update_notice_user_profile_if_changed(
265303

266304
assert self.server_notices_mxid is not None
267305

268-
notice_user_data_in_room = await self._message_handler.get_room_data(
269-
create_requester(self.server_notices_mxid),
270-
room_id,
271-
EventTypes.Member,
272-
self.server_notices_mxid,
306+
notice_user_data_in_room = (
307+
await self._storage_controllers.state.get_current_state_event(
308+
room_id,
309+
EventTypes.Member,
310+
self.server_notices_mxid,
311+
)
273312
)
274313

275314
assert notice_user_data_in_room is not None
@@ -288,3 +327,55 @@ async def _update_notice_user_profile_if_changed(
288327
ratelimit=False,
289328
content={"displayname": display_name, "avatar_url": avatar_url},
290329
)
330+
331+
async def _update_room_info(
332+
self,
333+
requester: Requester,
334+
room_id: str,
335+
info_event_type: str,
336+
info_content_key: str,
337+
info_value: Optional[str],
338+
) -> None:
339+
"""
340+
Updates a specific notice room's info if it's different from what is set.
341+
342+
Args:
343+
requester: The user who is performing the update.
344+
room_id: The ID of the server notice room
345+
info_event_type: The event type holding the specific info
346+
info_content_key: The key containing the specific info in the event's content
347+
info_value: The expected value for the specific info
348+
"""
349+
room_info_event = await self._storage_controllers.state.get_current_state_event(
350+
room_id,
351+
info_event_type,
352+
"",
353+
)
354+
355+
existing_info_value = None
356+
if room_info_event:
357+
existing_info_value = room_info_event.get(info_content_key)
358+
if existing_info_value == info_value:
359+
return
360+
if not existing_info_value and not info_value:
361+
# A missing `info_value` can either be represented by a None
362+
# or an empty string, so we assume that if they're both falsey
363+
# they're equivalent.
364+
return
365+
366+
if info_value is None:
367+
info_value = ""
368+
369+
room_info_event_dict = {
370+
"type": info_event_type,
371+
"room_id": room_id,
372+
"sender": requester.user.to_string(),
373+
"state_key": "",
374+
"content": {
375+
info_content_key: info_value,
376+
},
377+
}
378+
379+
event, _ = await self._event_creation_handler.create_and_send_nonmember_event(
380+
requester, room_info_event_dict, ratelimit=False
381+
)

tests/rest/admin/test_server_notice.py

+109
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,115 @@ def test_update_notice_user_avatar_when_changed(self) -> None:
596596
)
597597
self.assertEqual(notice_user_state["avatar_url"], new_avatar_url)
598598

599+
@override_config(
600+
{
601+
"server_notices": {
602+
"system_mxid_localpart": "notices",
603+
"room_avatar_url": "test/url",
604+
"room_topic": "Test Topic",
605+
}
606+
}
607+
)
608+
def test_notice_room_avatar_and_topic(self) -> None:
609+
"""
610+
Tests that using `room_avatar_url` and `room_topic` config properly sets
611+
those properties for the created notice rooms.
612+
"""
613+
server_notice_request_content = {
614+
"user_id": self.other_user,
615+
"content": {"msgtype": "m.text", "body": "test msg one"},
616+
}
617+
618+
self.make_request(
619+
"POST",
620+
self.url,
621+
access_token=self.admin_user_tok,
622+
content=server_notice_request_content,
623+
)
624+
625+
invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
626+
notice_room_id = invited_rooms[0].room_id
627+
self.helper.join(
628+
room=notice_room_id, user=self.other_user, tok=self.other_user_token
629+
)
630+
631+
room_avatar_state = self.helper.get_state(
632+
notice_room_id,
633+
"m.room.avatar",
634+
self.other_user_token,
635+
state_key="",
636+
)
637+
self.assertEqual(room_avatar_state["url"], "test/url")
638+
639+
room_topic_state = self.helper.get_state(
640+
notice_room_id,
641+
"m.room.topic",
642+
self.other_user_token,
643+
state_key="",
644+
)
645+
self.assertEqual(room_topic_state["topic"], "Test Topic")
646+
647+
@override_config(
648+
{
649+
"server_notices": {
650+
"system_mxid_localpart": "notices",
651+
"room_avatar_url": "test/url",
652+
}
653+
}
654+
)
655+
def test_update_room_avatar_when_changed(self) -> None:
656+
"""
657+
Tests that existing server notices room avatar is updated when it is
658+
different from the one in homeserver config.
659+
"""
660+
server_notice_request_content = {
661+
"user_id": self.other_user,
662+
"content": {"msgtype": "m.text", "body": "test msg one"},
663+
}
664+
665+
self.make_request(
666+
"POST",
667+
self.url,
668+
access_token=self.admin_user_tok,
669+
content=server_notice_request_content,
670+
)
671+
672+
invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
673+
notice_room_id = invited_rooms[0].room_id
674+
self.helper.join(
675+
room=notice_room_id, user=self.other_user, tok=self.other_user_token
676+
)
677+
678+
room_avatar_state = self.helper.get_state(
679+
notice_room_id,
680+
"m.room.avatar",
681+
self.other_user_token,
682+
state_key="",
683+
)
684+
self.assertEqual(room_avatar_state["url"], "test/url")
685+
686+
# simulate a change in server config after a server restart.
687+
new_avatar_url = "test/new-url"
688+
self.server_notices_manager._config.servernotices.server_notices_room_avatar_url = (
689+
new_avatar_url
690+
)
691+
self.server_notices_manager.get_or_create_notice_room_for_user.cache.invalidate_all()
692+
693+
self.make_request(
694+
"POST",
695+
self.url,
696+
access_token=self.admin_user_tok,
697+
content=server_notice_request_content,
698+
)
699+
700+
room_avatar_state = self.helper.get_state(
701+
notice_room_id,
702+
"m.room.avatar",
703+
self.other_user_token,
704+
state_key="",
705+
)
706+
self.assertEqual(room_avatar_state["url"], new_avatar_url)
707+
599708
def _check_invite_and_join_status(
600709
self, user_id: str, expected_invites: int, expected_memberships: int
601710
) -> Sequence[RoomsForUser]:

0 commit comments

Comments
 (0)