Add support for tagging as synthetic member

This commit is contained in:
Paul Chobert committed 2026-10-30 10:53:57 +01:00
1 parent 90f45d5b99
commit 8ff205b754
3 files changed
+66 -10

No files matched your search

+8 -7
View File
@@ -2981,13 +2981,13 @@ class SyncHandler:
# 3. Work out which rooms need reporting in the sync response.
ignored_users = await self.store.ignored_users(user_id)
if since_token:
tags_by_room = await self.store.get_updated_tags(
user_id, since_token.account_data_key
)
room_changes = await self._get_room_changes_for_incremental_sync(
sync_result_builder,
ignored_users,
account_data_room_ids=account_data_by_room.keys(),
)
tags_by_room = await self.store.get_updated_tags(
user_id, since_token.account_data_key
account_data_room_ids=account_data_by_room.keys() | tags_by_room.keys(),
)
else:
room_changes = await self._get_room_changes_for_initial_sync(
@@ -3382,9 +3382,10 @@ class SyncHandler:
room_ids: The announcement rooms to consider.
left_room_ids: Rooms among `room_ids` that the user left or was
removed from since the `since_token` of `sync_result_builder`.
account_data_room_ids: Rooms whose room account data for the user
changed since the `since_token` of `sync_result_builder`.
They may include rooms that are not in `room_ids`.
account_data_room_ids: Rooms whose room account data for the user,
including tags, changed since the `since_token` of
`sync_result_builder`. They may include rooms that are not in
`room_ids`.
"""
since_token = sync_result_builder.since_token
now_token = sync_result_builder.now_token
+5 -3
View File
@@ -82,6 +82,7 @@ class TagServlet(RestServlet):
self.auth = hs.get_auth()
self.handler = hs.get_account_data_handler()
self.room_member_handler = hs.get_room_member_handler()
self.store = hs.get_datastores().main
async def on_PUT(
self, request: SynapseRequest, user_id: str, room_id: str, tag: str
@@ -102,9 +103,10 @@ class TagServlet(RestServlet):
# Check if the user has any membership in the room and raise error if not.
# Although it's not harmful for users to tag random rooms, it's just superfluous
# data we don't need to track or allow.
await self.room_member_handler.check_for_any_membership_in_room(
user_id=user_id, room_id=room_id
)
if await self.store.get_server_announcement_id(room_id) is None:
await self.room_member_handler.check_for_any_membership_in_room(
user_id=user_id, room_id=room_id
)
body = parse_json_object_from_request(request)
+53
View File
@@ -47,6 +47,7 @@ from synapse.rest.client import (
receipts,
room,
sync,
tags,
)
from synapse.server import HomeServer
from synapse.types import JsonDict, RoomStreamToken, StreamKeyType, StreamToken
@@ -2212,6 +2213,7 @@ class ServerAnnouncementsSyncTestCase(unittest.HomeserverTestCase):
room.register_servlets,
sync.register_servlets,
account_data.register_servlets,
tags.register_servlets,
]
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
@@ -2611,6 +2613,57 @@ class ServerAnnouncementsSyncTestCase(unittest.HomeserverTestCase):
[{"type": "m.marked_unread", "content": {"unread": True}}],
)
def test_tag_without_new_events(self) -> None:
"""A non-member who tags an announcement room gets `m.tag` on their next
incremental sync, even when nothing else happened in the room.
"""
self._announce("hello")
since = self._sync(self.bob_tok)["next_batch"]
channel = self.make_request(
"PUT",
f"/user/{self.bob}/rooms/{self.room_id}/tags/m.favourite",
{"order": 0.5},
access_token=self.bob_tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
room = self._assert_delivered(self._sync(self.bob_tok, since), self.bob)
self.assertEqual(self._timeline_ids(room), [])
self.assertEqual(
room["account_data"]["events"],
[{"type": "m.tag", "content": {"tags": {"m.favourite": {"order": 0.5}}}}],
)
def test_tag_removed_without_new_events(self) -> None:
"""A non-member who removes their last tag from an announcement room gets
an empty `m.tag` on their next incremental sync, even when nothing else
happened in the room.
"""
self._announce("hello")
channel = self.make_request(
"PUT",
f"/user/{self.bob}/rooms/{self.room_id}/tags/m.favourite",
{"order": 0.5},
access_token=self.bob_tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
since = self._sync(self.bob_tok)["next_batch"]
channel = self.make_request(
"DELETE",
f"/user/{self.bob}/rooms/{self.room_id}/tags/m.favourite",
access_token=self.bob_tok,
)
self.assertEqual(channel.code, 200, channel.json_body)
room = self._assert_delivered(self._sync(self.bob_tok, since), self.bob)
self.assertEqual(self._timeline_ids(room), [])
self.assertEqual(
room["account_data"]["events"],
[{"type": "m.tag", "content": {"tags": {}}}],
)
def test_state_after(self) -> None:
"""With `use_state_after`, a non-member gets the synthetic join in
`state_after`, including a former member whose real leave is in the