[Server Announcements] Implement excluded_user_ids option

Adds a new config option to exclude a given list of users from being
proactively pushed events from a given announcement room.

Requested by the ESS team at
https://github.com/element-hq/synapse/issues/20344#issuecomment-6217038992
This commit is contained in:
Andrew Morgan authored and GitHub committed 2026-10-28 18:13:38 +00:00
1 parent 80bc844b2b
commit 68ad4b2eef
2 files changed
+48 -3

No files matched your search

+36 -3
View File
@@ -21,9 +21,8 @@ incomplete.
## server_announcements
This feature allows homeserver admins to create and manage "announcement" rooms. These
rooms will be visible to *all* users on the homeserver. These are real rooms, which a
configured list of users may join, where the events are streamed to all users when they
fetch events from the homeserver.
rooms and their events will be visible to all users on the homeserver, except those
listed in the room's `excluded_user_ids`.
Read-only users don't *actually* join announcement rooms, but they will see it in their
room list.
@@ -76,6 +75,15 @@ Options for each entry:
Each must be a valid, local user; remote users are not supported because these
rooms are not federated. Defaults to an empty list, allowing no users to send
announcements. Sender accounts must already exist and must not be deactivated.
* `excluded_user_ids` (array): Full Matrix user IDs excluded from automatically
receiving announcements from this room, for example appservice users or audit
bots.
Each must be a valid, local user. Defaults to an empty list (excluding nobody).
Accounts do not need to exist yet. Users on this list may still view announcement
rooms manually (as they are world-readable). But they will not automatically receive
announcement events.
The internal room manager user (which is used to create announcement rooms and update
their room state) is configured with `room_management_localpart` below. It is a
@@ -122,6 +130,8 @@ Example configuration:
room_avatar_url: mxc://example.com/oumMVlgDnLYFaPVkExemNVVZ
allowed_user_ids:
- "@alice:example.com"
excluded_user_ids:
- "@audit_bot:example.com"
- id: maintenance
room_name: Maintenance announcements
@@ -151,6 +161,7 @@ class ServerAnnouncementRoom:
room_avatar_url: str | None
room_avatar_path: str | None
allowed_user_ids: list[str]
excluded_user_ids: list[str] = attr.ib(factory=list)
class ServerAnnouncementsConfig(Config):
@@ -334,6 +345,27 @@ class ServerAnnouncementsConfig(Config):
user_config_path,
)
excluded_user_ids = room.get("excluded_user_ids", [])
if not isinstance(excluded_user_ids, list):
raise ConfigError(
"Must be a list", config_path + ("excluded_user_ids",)
)
for excluded_user_index, user_id in enumerate(excluded_user_ids):
user_config_path = config_path + (
"excluded_user_ids",
f"<item {excluded_user_index}>",
)
if not isinstance(user_id, str) or not UserID.is_valid_strict(user_id):
raise ConfigError(
"Must be a valid Matrix user ID", user_config_path
)
if UserID.from_string(user_id).domain != server_name:
raise ConfigError(
"Must be a local user: server announcement rooms are not federated",
user_config_path,
)
self.rooms.append(
ServerAnnouncementRoom(
config_id=room_unique_identifier,
@@ -342,5 +374,6 @@ class ServerAnnouncementsConfig(Config):
room_avatar_url=room_avatar_url,
room_avatar_path=room_avatar_path,
allowed_user_ids=list(allowed_user_ids),
excluded_user_ids=list(excluded_user_ids),
)
)
+12
View File
@@ -155,6 +155,10 @@ class ServerAnnouncementsConfigTestCase(TestCase):
"room_topic": "Server news",
"room_avatar_url": "mxc://media.example.com/avatar",
"allowed_user_ids": ["@alice:example.com", "@Bob:example.com"],
"excluded_user_ids": [
"@audit_bot:example.com",
"@_appservice:example.com",
],
},
{"id": "maintenance", "room_name": "Announcements"},
]
@@ -169,6 +173,7 @@ class ServerAnnouncementsConfigTestCase(TestCase):
"mxc://media.example.com/avatar",
None,
["@alice:example.com", "@Bob:example.com"],
["@audit_bot:example.com", "@_appservice:example.com"],
),
ServerAnnouncementRoom(
"maintenance", "Announcements", None, None, None, []
@@ -176,6 +181,13 @@ class ServerAnnouncementsConfigTestCase(TestCase):
],
)
def test_excluded_user_ids_default(self) -> None:
"""Test the defaults of the `excluded_user_ids` option are as expected."""
self.assertEqual(self._read_room({}).rooms[0].excluded_user_ids, [])
self.assertEqual(
self._read_room({"excluded_user_ids": []}).rooms[0].excluded_user_ids, []
)
def test_duplicate_id(self) -> None:
with self.assertRaisesRegex(ConfigError, "Duplicate") as err:
self._read_config(