mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-24 18:45:52 +03:00
Add heroes
and room summary fields to Sliding Sync /sync
(#17419)
Some checks are pending
Build docker images / build (push) Waiting to run
Deploy the documentation / Calculate variables for GitHub Pages deployment (push) Waiting to run
Deploy the documentation / GitHub Pages (push) Blocked by required conditions
Build release artifacts / Calculate list of debian distros (push) Waiting to run
Build release artifacts / Build .deb packages (push) Blocked by required conditions
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (aarch64, ${{ startsWith(github.ref, 'refs/pull/') }}, ubuntu-20.04) (push) Waiting to run
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (x86_64, ${{ startsWith(github.ref, 'refs/pull/') }}, macos-12) (push) Waiting to run
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (x86_64, ${{ startsWith(github.ref, 'refs/pull/') }}, ubuntu-20.04) (push) Waiting to run
Build release artifacts / Build sdist (push) Waiting to run
Build release artifacts / Attach assets to release (push) Blocked by required conditions
Tests / calculate-test-jobs (push) Blocked by required conditions
Tests / changes (push) Waiting to run
Tests / check-sampleconfig (push) Blocked by required conditions
Tests / check-schema-delta (push) Blocked by required conditions
Tests / check-lockfile (push) Waiting to run
Tests / lint (push) Blocked by required conditions
Tests / Typechecking (push) Blocked by required conditions
Tests / lint-crlf (push) Waiting to run
Tests / lint-newsfile (push) Waiting to run
Tests / lint-pydantic (push) Blocked by required conditions
Tests / lint-clippy (push) Blocked by required conditions
Tests / lint-clippy-nightly (push) Blocked by required conditions
Tests / lint-rustfmt (push) Blocked by required conditions
Tests / lint-readme (push) Blocked by required conditions
Tests / linting-done (push) Blocked by required conditions
Tests / trial (push) Blocked by required conditions
Tests / trial-olddeps (push) Blocked by required conditions
Tests / trial-pypy (all, pypy-3.8) (push) Blocked by required conditions
Tests / sytest (push) Blocked by required conditions
Tests / export-data (push) Blocked by required conditions
Tests / portdb (11, 3.8) (push) Blocked by required conditions
Tests / portdb (15, 3.11) (push) Blocked by required conditions
Tests / complement (monolith, SQLite) (push) Blocked by required conditions
Tests / complement (workers, Postgres) (push) Blocked by required conditions
Tests / complement (monolith, Postgres) (push) Blocked by required conditions
Tests / cargo-test (push) Blocked by required conditions
Tests / cargo-bench (push) Blocked by required conditions
Tests / tests-done (push) Blocked by required conditions
Some checks are pending
Build docker images / build (push) Waiting to run
Deploy the documentation / Calculate variables for GitHub Pages deployment (push) Waiting to run
Deploy the documentation / GitHub Pages (push) Blocked by required conditions
Build release artifacts / Calculate list of debian distros (push) Waiting to run
Build release artifacts / Build .deb packages (push) Blocked by required conditions
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (aarch64, ${{ startsWith(github.ref, 'refs/pull/') }}, ubuntu-20.04) (push) Waiting to run
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (x86_64, ${{ startsWith(github.ref, 'refs/pull/') }}, macos-12) (push) Waiting to run
Build release artifacts / Build wheels on ${{ matrix.os }} for ${{ matrix.arch }} (x86_64, ${{ startsWith(github.ref, 'refs/pull/') }}, ubuntu-20.04) (push) Waiting to run
Build release artifacts / Build sdist (push) Waiting to run
Build release artifacts / Attach assets to release (push) Blocked by required conditions
Tests / calculate-test-jobs (push) Blocked by required conditions
Tests / changes (push) Waiting to run
Tests / check-sampleconfig (push) Blocked by required conditions
Tests / check-schema-delta (push) Blocked by required conditions
Tests / check-lockfile (push) Waiting to run
Tests / lint (push) Blocked by required conditions
Tests / Typechecking (push) Blocked by required conditions
Tests / lint-crlf (push) Waiting to run
Tests / lint-newsfile (push) Waiting to run
Tests / lint-pydantic (push) Blocked by required conditions
Tests / lint-clippy (push) Blocked by required conditions
Tests / lint-clippy-nightly (push) Blocked by required conditions
Tests / lint-rustfmt (push) Blocked by required conditions
Tests / lint-readme (push) Blocked by required conditions
Tests / linting-done (push) Blocked by required conditions
Tests / trial (push) Blocked by required conditions
Tests / trial-olddeps (push) Blocked by required conditions
Tests / trial-pypy (all, pypy-3.8) (push) Blocked by required conditions
Tests / sytest (push) Blocked by required conditions
Tests / export-data (push) Blocked by required conditions
Tests / portdb (11, 3.8) (push) Blocked by required conditions
Tests / portdb (15, 3.11) (push) Blocked by required conditions
Tests / complement (monolith, SQLite) (push) Blocked by required conditions
Tests / complement (workers, Postgres) (push) Blocked by required conditions
Tests / complement (monolith, Postgres) (push) Blocked by required conditions
Tests / cargo-test (push) Blocked by required conditions
Tests / cargo-bench (push) Blocked by required conditions
Tests / tests-done (push) Blocked by required conditions
Additional room summary fields: `joined_count`, `invited_count` Based on [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575): Sliding Sync
This commit is contained in:
parent
606da398fc
commit
5a97bbd895
6 changed files with 528 additions and 109 deletions
1
changelog.d/17419.feature
Normal file
1
changelog.d/17419.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Populate `heroes` and room summary fields (`joined_count`, `invited_count`) in experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint.
|
|
@ -19,7 +19,7 @@
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import TYPE_CHECKING, Any, Dict, Final, List, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Any, Dict, Final, List, Mapping, Optional, Set, Tuple
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
from immutabledict import immutabledict
|
from immutabledict import immutabledict
|
||||||
|
@ -28,7 +28,9 @@ from synapse.api.constants import AccountDataTypes, Direction, EventTypes, Membe
|
||||||
from synapse.events import EventBase
|
from synapse.events import EventBase
|
||||||
from synapse.events.utils import strip_event
|
from synapse.events.utils import strip_event
|
||||||
from synapse.handlers.relations import BundledAggregations
|
from synapse.handlers.relations import BundledAggregations
|
||||||
|
from synapse.storage.databases.main.roommember import extract_heroes_from_room_summary
|
||||||
from synapse.storage.databases.main.stream import CurrentStateDeltaMembership
|
from synapse.storage.databases.main.stream import CurrentStateDeltaMembership
|
||||||
|
from synapse.storage.roommember import MemberSummary
|
||||||
from synapse.types import (
|
from synapse.types import (
|
||||||
JsonDict,
|
JsonDict,
|
||||||
PersistedEventPosition,
|
PersistedEventPosition,
|
||||||
|
@ -1043,6 +1045,103 @@ class SlidingSyncHandler:
|
||||||
reverse=True,
|
reverse=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def get_current_state_ids_at(
|
||||||
|
self,
|
||||||
|
room_id: str,
|
||||||
|
room_membership_for_user_at_to_token: _RoomMembershipForUser,
|
||||||
|
state_filter: StateFilter,
|
||||||
|
to_token: StreamToken,
|
||||||
|
) -> StateMap[str]:
|
||||||
|
"""
|
||||||
|
Get current state IDs for the user in the room according to their membership. This
|
||||||
|
will be the current state at the time of their LEAVE/BAN, otherwise will be the
|
||||||
|
current state <= to_token.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id: The room ID to fetch data for
|
||||||
|
room_membership_for_user_at_token: Membership information for the user
|
||||||
|
in the room at the time of `to_token`.
|
||||||
|
to_token: The point in the stream to sync up to.
|
||||||
|
"""
|
||||||
|
|
||||||
|
room_state_ids: StateMap[str]
|
||||||
|
# People shouldn't see past their leave/ban event
|
||||||
|
if room_membership_for_user_at_to_token.membership in (
|
||||||
|
Membership.LEAVE,
|
||||||
|
Membership.BAN,
|
||||||
|
):
|
||||||
|
# TODO: `get_state_ids_at(...)` doesn't take into account the "current state"
|
||||||
|
room_state_ids = await self.storage_controllers.state.get_state_ids_at(
|
||||||
|
room_id,
|
||||||
|
stream_position=to_token.copy_and_replace(
|
||||||
|
StreamKeyType.ROOM,
|
||||||
|
room_membership_for_user_at_to_token.event_pos.to_room_stream_token(),
|
||||||
|
),
|
||||||
|
state_filter=state_filter,
|
||||||
|
# Partially-stated rooms should have all state events except for
|
||||||
|
# remote membership events. Since we've already excluded
|
||||||
|
# partially-stated rooms unless `required_state` only has
|
||||||
|
# `["m.room.member", "$LAZY"]` for membership, we should be able to
|
||||||
|
# retrieve everything requested. When we're lazy-loading, if there
|
||||||
|
# are some remote senders in the timeline, we should also have their
|
||||||
|
# membership event because we had to auth that timeline event. Plus
|
||||||
|
# we don't want to block the whole sync waiting for this one room.
|
||||||
|
await_full_state=False,
|
||||||
|
)
|
||||||
|
# Otherwise, we can get the latest current state in the room
|
||||||
|
else:
|
||||||
|
room_state_ids = await self.storage_controllers.state.get_current_state_ids(
|
||||||
|
room_id,
|
||||||
|
state_filter,
|
||||||
|
# Partially-stated rooms should have all state events except for
|
||||||
|
# remote membership events. Since we've already excluded
|
||||||
|
# partially-stated rooms unless `required_state` only has
|
||||||
|
# `["m.room.member", "$LAZY"]` for membership, we should be able to
|
||||||
|
# retrieve everything requested. When we're lazy-loading, if there
|
||||||
|
# are some remote senders in the timeline, we should also have their
|
||||||
|
# membership event because we had to auth that timeline event. Plus
|
||||||
|
# we don't want to block the whole sync waiting for this one room.
|
||||||
|
await_full_state=False,
|
||||||
|
)
|
||||||
|
# TODO: Query `current_state_delta_stream` and reverse/rewind back to the `to_token`
|
||||||
|
|
||||||
|
return room_state_ids
|
||||||
|
|
||||||
|
async def get_current_state_at(
|
||||||
|
self,
|
||||||
|
room_id: str,
|
||||||
|
room_membership_for_user_at_to_token: _RoomMembershipForUser,
|
||||||
|
state_filter: StateFilter,
|
||||||
|
to_token: StreamToken,
|
||||||
|
) -> StateMap[EventBase]:
|
||||||
|
"""
|
||||||
|
Get current state for the user in the room according to their membership. This
|
||||||
|
will be the current state at the time of their LEAVE/BAN, otherwise will be the
|
||||||
|
current state <= to_token.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id: The room ID to fetch data for
|
||||||
|
room_membership_for_user_at_token: Membership information for the user
|
||||||
|
in the room at the time of `to_token`.
|
||||||
|
to_token: The point in the stream to sync up to.
|
||||||
|
"""
|
||||||
|
room_state_ids = await self.get_current_state_ids_at(
|
||||||
|
room_id=room_id,
|
||||||
|
room_membership_for_user_at_to_token=room_membership_for_user_at_to_token,
|
||||||
|
state_filter=state_filter,
|
||||||
|
to_token=to_token,
|
||||||
|
)
|
||||||
|
|
||||||
|
event_map = await self.store.get_events(list(room_state_ids.values()))
|
||||||
|
|
||||||
|
state_map = {}
|
||||||
|
for key, event_id in room_state_ids.items():
|
||||||
|
event = event_map.get(event_id)
|
||||||
|
if event:
|
||||||
|
state_map[key] = event
|
||||||
|
|
||||||
|
return state_map
|
||||||
|
|
||||||
async def get_room_sync_data(
|
async def get_room_sync_data(
|
||||||
self,
|
self,
|
||||||
user: UserID,
|
user: UserID,
|
||||||
|
@ -1074,7 +1173,7 @@ class SlidingSyncHandler:
|
||||||
# membership. Currently, we have to make all of these optional because
|
# membership. Currently, we have to make all of these optional because
|
||||||
# `invite`/`knock` rooms only have `stripped_state`. See
|
# `invite`/`knock` rooms only have `stripped_state`. See
|
||||||
# https://github.com/matrix-org/matrix-spec-proposals/pull/3575#discussion_r1653045932
|
# https://github.com/matrix-org/matrix-spec-proposals/pull/3575#discussion_r1653045932
|
||||||
timeline_events: Optional[List[EventBase]] = None
|
timeline_events: List[EventBase] = []
|
||||||
bundled_aggregations: Optional[Dict[str, BundledAggregations]] = None
|
bundled_aggregations: Optional[Dict[str, BundledAggregations]] = None
|
||||||
limited: Optional[bool] = None
|
limited: Optional[bool] = None
|
||||||
prev_batch_token: Optional[StreamToken] = None
|
prev_batch_token: Optional[StreamToken] = None
|
||||||
|
@ -1206,7 +1305,7 @@ class SlidingSyncHandler:
|
||||||
|
|
||||||
# Figure out any stripped state events for invite/knocks. This allows the
|
# Figure out any stripped state events for invite/knocks. This allows the
|
||||||
# potential joiner to identify the room.
|
# potential joiner to identify the room.
|
||||||
stripped_state: Optional[List[JsonDict]] = None
|
stripped_state: List[JsonDict] = []
|
||||||
if room_membership_for_user_at_to_token.membership in (
|
if room_membership_for_user_at_to_token.membership in (
|
||||||
Membership.INVITE,
|
Membership.INVITE,
|
||||||
Membership.KNOCK,
|
Membership.KNOCK,
|
||||||
|
@ -1243,6 +1342,44 @@ class SlidingSyncHandler:
|
||||||
# updates.
|
# updates.
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
|
# Check whether the room has a name set
|
||||||
|
name_state_ids = await self.get_current_state_ids_at(
|
||||||
|
room_id=room_id,
|
||||||
|
room_membership_for_user_at_to_token=room_membership_for_user_at_to_token,
|
||||||
|
state_filter=StateFilter.from_types([(EventTypes.Name, "")]),
|
||||||
|
to_token=to_token,
|
||||||
|
)
|
||||||
|
name_event_id = name_state_ids.get((EventTypes.Name, ""))
|
||||||
|
|
||||||
|
room_membership_summary: Mapping[str, MemberSummary]
|
||||||
|
empty_membership_summary = MemberSummary([], 0)
|
||||||
|
if room_membership_for_user_at_to_token.membership in (
|
||||||
|
Membership.LEAVE,
|
||||||
|
Membership.BAN,
|
||||||
|
):
|
||||||
|
# TODO: Figure out how to get the membership summary for left/banned rooms
|
||||||
|
room_membership_summary = {}
|
||||||
|
else:
|
||||||
|
room_membership_summary = await self.store.get_room_summary(room_id)
|
||||||
|
# TODO: Reverse/rewind back to the `to_token`
|
||||||
|
|
||||||
|
# `heroes` are required if the room name is not set.
|
||||||
|
#
|
||||||
|
# Note: When you're the first one on your server to be invited to a new room
|
||||||
|
# over federation, we only have access to some stripped state in
|
||||||
|
# `event.unsigned.invite_room_state` which currently doesn't include `heroes`,
|
||||||
|
# see https://github.com/matrix-org/matrix-spec/issues/380. This means that
|
||||||
|
# clients won't be able to calculate the room name when necessary and just a
|
||||||
|
# pitfall we have to deal with until that spec issue is resolved.
|
||||||
|
hero_user_ids: List[str] = []
|
||||||
|
# TODO: Should we also check for `EventTypes.CanonicalAlias`
|
||||||
|
# (`m.room.canonical_alias`) as a fallback for the room name? see
|
||||||
|
# https://github.com/matrix-org/matrix-spec-proposals/pull/3575#discussion_r1671260153
|
||||||
|
if name_event_id is None:
|
||||||
|
hero_user_ids = extract_heroes_from_room_summary(
|
||||||
|
room_membership_summary, me=user.to_string()
|
||||||
|
)
|
||||||
|
|
||||||
# Fetch the `required_state` for the room
|
# Fetch the `required_state` for the room
|
||||||
#
|
#
|
||||||
# No `required_state` for invite/knock rooms (just `stripped_state`)
|
# No `required_state` for invite/knock rooms (just `stripped_state`)
|
||||||
|
@ -1253,13 +1390,11 @@ class SlidingSyncHandler:
|
||||||
# https://github.com/matrix-org/matrix-spec-proposals/pull/3575#discussion_r1653045932
|
# https://github.com/matrix-org/matrix-spec-proposals/pull/3575#discussion_r1653045932
|
||||||
#
|
#
|
||||||
# Calculate the `StateFilter` based on the `required_state` for the room
|
# Calculate the `StateFilter` based on the `required_state` for the room
|
||||||
room_state: Optional[StateMap[EventBase]] = None
|
required_state_filter = StateFilter.none()
|
||||||
required_room_state: Optional[StateMap[EventBase]] = None
|
|
||||||
if room_membership_for_user_at_to_token.membership not in (
|
if room_membership_for_user_at_to_token.membership not in (
|
||||||
Membership.INVITE,
|
Membership.INVITE,
|
||||||
Membership.KNOCK,
|
Membership.KNOCK,
|
||||||
):
|
):
|
||||||
required_state_filter = StateFilter.none()
|
|
||||||
# If we have a double wildcard ("*", "*") in the `required_state`, we need
|
# If we have a double wildcard ("*", "*") in the `required_state`, we need
|
||||||
# to fetch all state for the room
|
# to fetch all state for the room
|
||||||
#
|
#
|
||||||
|
@ -1325,86 +1460,65 @@ class SlidingSyncHandler:
|
||||||
|
|
||||||
required_state_filter = StateFilter.from_types(required_state_types)
|
required_state_filter = StateFilter.from_types(required_state_types)
|
||||||
|
|
||||||
# We need this base set of info for the response so let's just fetch it along
|
# We need this base set of info for the response so let's just fetch it along
|
||||||
# with the `required_state` for the room
|
# with the `required_state` for the room
|
||||||
META_ROOM_STATE = [(EventTypes.Name, ""), (EventTypes.RoomAvatar, "")]
|
meta_room_state = [(EventTypes.Name, ""), (EventTypes.RoomAvatar, "")] + [
|
||||||
|
(EventTypes.Member, hero_user_id) for hero_user_id in hero_user_ids
|
||||||
|
]
|
||||||
|
state_filter = StateFilter.all()
|
||||||
|
if required_state_filter != StateFilter.all():
|
||||||
state_filter = StateFilter(
|
state_filter = StateFilter(
|
||||||
types=StateFilter.from_types(
|
types=StateFilter.from_types(
|
||||||
chain(META_ROOM_STATE, required_state_filter.to_types())
|
chain(meta_room_state, required_state_filter.to_types())
|
||||||
).types,
|
).types,
|
||||||
include_others=required_state_filter.include_others,
|
include_others=required_state_filter.include_others,
|
||||||
)
|
)
|
||||||
|
|
||||||
# We can return all of the state that was requested if this was the first
|
# We can return all of the state that was requested if this was the first
|
||||||
# time we've sent the room down this connection.
|
# time we've sent the room down this connection.
|
||||||
if initial:
|
room_state: StateMap[EventBase] = {}
|
||||||
# People shouldn't see past their leave/ban event
|
if initial:
|
||||||
if room_membership_for_user_at_to_token.membership in (
|
room_state = await self.get_current_state_at(
|
||||||
Membership.LEAVE,
|
room_id=room_id,
|
||||||
Membership.BAN,
|
room_membership_for_user_at_to_token=room_membership_for_user_at_to_token,
|
||||||
):
|
state_filter=state_filter,
|
||||||
room_state = await self.storage_controllers.state.get_state_at(
|
to_token=to_token,
|
||||||
room_id,
|
)
|
||||||
stream_position=to_token.copy_and_replace(
|
else:
|
||||||
StreamKeyType.ROOM,
|
# TODO: Once we can figure out if we've sent a room down this connection before,
|
||||||
room_membership_for_user_at_to_token.event_pos.to_room_stream_token(),
|
# we can return updates instead of the full required state.
|
||||||
),
|
raise NotImplementedError()
|
||||||
state_filter=state_filter,
|
|
||||||
# Partially-stated rooms should have all state events except for
|
|
||||||
# remote membership events. Since we've already excluded
|
|
||||||
# partially-stated rooms unless `required_state` only has
|
|
||||||
# `["m.room.member", "$LAZY"]` for membership, we should be able to
|
|
||||||
# retrieve everything requested. When we're lazy-loading, if there
|
|
||||||
# are some remote senders in the timeline, we should also have their
|
|
||||||
# membership event because we had to auth that timeline event. Plus
|
|
||||||
# we don't want to block the whole sync waiting for this one room.
|
|
||||||
await_full_state=False,
|
|
||||||
)
|
|
||||||
# Otherwise, we can get the latest current state in the room
|
|
||||||
else:
|
|
||||||
room_state = await self.storage_controllers.state.get_current_state(
|
|
||||||
room_id,
|
|
||||||
state_filter,
|
|
||||||
# Partially-stated rooms should have all state events except for
|
|
||||||
# remote membership events. Since we've already excluded
|
|
||||||
# partially-stated rooms unless `required_state` only has
|
|
||||||
# `["m.room.member", "$LAZY"]` for membership, we should be able to
|
|
||||||
# retrieve everything requested. When we're lazy-loading, if there
|
|
||||||
# are some remote senders in the timeline, we should also have their
|
|
||||||
# membership event because we had to auth that timeline event. Plus
|
|
||||||
# we don't want to block the whole sync waiting for this one room.
|
|
||||||
await_full_state=False,
|
|
||||||
)
|
|
||||||
# TODO: Query `current_state_delta_stream` and reverse/rewind back to the `to_token`
|
|
||||||
else:
|
|
||||||
# TODO: Once we can figure out if we've sent a room down this connection before,
|
|
||||||
# we can return updates instead of the full required state.
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
if required_state_filter != StateFilter.none():
|
required_room_state: StateMap[EventBase] = {}
|
||||||
required_room_state = required_state_filter.filter_state(room_state)
|
if required_state_filter != StateFilter.none():
|
||||||
|
required_room_state = required_state_filter.filter_state(room_state)
|
||||||
|
|
||||||
# Find the room name and avatar from the state
|
# Find the room name and avatar from the state
|
||||||
room_name: Optional[str] = None
|
room_name: Optional[str] = None
|
||||||
|
# TODO: Should we also check for `EventTypes.CanonicalAlias`
|
||||||
|
# (`m.room.canonical_alias`) as a fallback for the room name? see
|
||||||
|
# https://github.com/matrix-org/matrix-spec-proposals/pull/3575#discussion_r1671260153
|
||||||
|
name_event = room_state.get((EventTypes.Name, ""))
|
||||||
|
if name_event is not None:
|
||||||
|
room_name = name_event.content.get("name")
|
||||||
|
|
||||||
room_avatar: Optional[str] = None
|
room_avatar: Optional[str] = None
|
||||||
if room_state is not None:
|
avatar_event = room_state.get((EventTypes.RoomAvatar, ""))
|
||||||
name_event = room_state.get((EventTypes.Name, ""))
|
if avatar_event is not None:
|
||||||
if name_event is not None:
|
room_avatar = avatar_event.content.get("url")
|
||||||
room_name = name_event.content.get("name")
|
|
||||||
|
|
||||||
avatar_event = room_state.get((EventTypes.RoomAvatar, ""))
|
# Assemble heroes: extract the info from the state we just fetched
|
||||||
if avatar_event is not None:
|
heroes: List[SlidingSyncResult.RoomResult.StrippedHero] = []
|
||||||
room_avatar = avatar_event.content.get("url")
|
for hero_user_id in hero_user_ids:
|
||||||
elif stripped_state is not None:
|
member_event = room_state.get((EventTypes.Member, hero_user_id))
|
||||||
for event in stripped_state:
|
if member_event is not None:
|
||||||
if event["type"] == EventTypes.Name:
|
heroes.append(
|
||||||
room_name = event.get("content", {}).get("name")
|
SlidingSyncResult.RoomResult.StrippedHero(
|
||||||
elif event["type"] == EventTypes.RoomAvatar:
|
user_id=hero_user_id,
|
||||||
room_avatar = event.get("content", {}).get("url")
|
display_name=member_event.content.get("displayname"),
|
||||||
|
avatar_url=member_event.content.get("avatar_url"),
|
||||||
# Found everything so we can stop looking
|
)
|
||||||
if room_name is not None and room_avatar is not None:
|
)
|
||||||
break
|
|
||||||
|
|
||||||
# Figure out the last bump event in the room
|
# Figure out the last bump event in the room
|
||||||
last_bump_event_result = (
|
last_bump_event_result = (
|
||||||
|
@ -1423,14 +1537,11 @@ class SlidingSyncHandler:
|
||||||
return SlidingSyncResult.RoomResult(
|
return SlidingSyncResult.RoomResult(
|
||||||
name=room_name,
|
name=room_name,
|
||||||
avatar=room_avatar,
|
avatar=room_avatar,
|
||||||
# TODO: Dummy value
|
heroes=heroes,
|
||||||
heroes=None,
|
|
||||||
# TODO: Dummy value
|
# TODO: Dummy value
|
||||||
is_dm=False,
|
is_dm=False,
|
||||||
initial=initial,
|
initial=initial,
|
||||||
required_state=(
|
required_state=list(required_room_state.values()),
|
||||||
list(required_room_state.values()) if required_room_state else None
|
|
||||||
),
|
|
||||||
timeline_events=timeline_events,
|
timeline_events=timeline_events,
|
||||||
bundled_aggregations=bundled_aggregations,
|
bundled_aggregations=bundled_aggregations,
|
||||||
stripped_state=stripped_state,
|
stripped_state=stripped_state,
|
||||||
|
@ -1438,9 +1549,12 @@ class SlidingSyncHandler:
|
||||||
limited=limited,
|
limited=limited,
|
||||||
num_live=num_live,
|
num_live=num_live,
|
||||||
bump_stamp=bump_stamp,
|
bump_stamp=bump_stamp,
|
||||||
# TODO: Dummy values
|
joined_count=room_membership_summary.get(
|
||||||
joined_count=0,
|
Membership.JOIN, empty_membership_summary
|
||||||
invited_count=0,
|
).count,
|
||||||
|
invited_count=room_membership_summary.get(
|
||||||
|
Membership.INVITE, empty_membership_summary
|
||||||
|
).count,
|
||||||
# TODO: These are just dummy values. We could potentially just remove these
|
# TODO: These are just dummy values. We could potentially just remove these
|
||||||
# since notifications can only really be done correctly on the client anyway
|
# since notifications can only really be done correctly on the client anyway
|
||||||
# (encrypted rooms).
|
# (encrypted rooms).
|
||||||
|
|
|
@ -997,8 +997,21 @@ class SlidingSyncRestServlet(RestServlet):
|
||||||
if room_result.avatar:
|
if room_result.avatar:
|
||||||
serialized_rooms[room_id]["avatar"] = room_result.avatar
|
serialized_rooms[room_id]["avatar"] = room_result.avatar
|
||||||
|
|
||||||
if room_result.heroes:
|
if room_result.heroes is not None and len(room_result.heroes) > 0:
|
||||||
serialized_rooms[room_id]["heroes"] = room_result.heroes
|
serialized_heroes = []
|
||||||
|
for hero in room_result.heroes:
|
||||||
|
serialized_hero = {
|
||||||
|
"user_id": hero.user_id,
|
||||||
|
}
|
||||||
|
if hero.display_name is not None:
|
||||||
|
# Not a typo, just how "displayname" is spelled in the spec
|
||||||
|
serialized_hero["displayname"] = hero.display_name
|
||||||
|
|
||||||
|
if hero.avatar_url is not None:
|
||||||
|
serialized_hero["avatar_url"] = hero.avatar_url
|
||||||
|
|
||||||
|
serialized_heroes.append(serialized_hero)
|
||||||
|
serialized_rooms[room_id]["heroes"] = serialized_heroes
|
||||||
|
|
||||||
# We should only include the `initial` key if it's `True` to save bandwidth.
|
# We should only include the `initial` key if it's `True` to save bandwidth.
|
||||||
# The absense of this flag means `False`.
|
# The absense of this flag means `False`.
|
||||||
|
@ -1006,7 +1019,10 @@ class SlidingSyncRestServlet(RestServlet):
|
||||||
serialized_rooms[room_id]["initial"] = room_result.initial
|
serialized_rooms[room_id]["initial"] = room_result.initial
|
||||||
|
|
||||||
# This will be omitted for invite/knock rooms with `stripped_state`
|
# This will be omitted for invite/knock rooms with `stripped_state`
|
||||||
if room_result.required_state is not None:
|
if (
|
||||||
|
room_result.required_state is not None
|
||||||
|
and len(room_result.required_state) > 0
|
||||||
|
):
|
||||||
serialized_required_state = (
|
serialized_required_state = (
|
||||||
await self.event_serializer.serialize_events(
|
await self.event_serializer.serialize_events(
|
||||||
room_result.required_state,
|
room_result.required_state,
|
||||||
|
@ -1017,7 +1033,10 @@ class SlidingSyncRestServlet(RestServlet):
|
||||||
serialized_rooms[room_id]["required_state"] = serialized_required_state
|
serialized_rooms[room_id]["required_state"] = serialized_required_state
|
||||||
|
|
||||||
# This will be omitted for invite/knock rooms with `stripped_state`
|
# This will be omitted for invite/knock rooms with `stripped_state`
|
||||||
if room_result.timeline_events is not None:
|
if (
|
||||||
|
room_result.timeline_events is not None
|
||||||
|
and len(room_result.timeline_events) > 0
|
||||||
|
):
|
||||||
serialized_timeline = await self.event_serializer.serialize_events(
|
serialized_timeline = await self.event_serializer.serialize_events(
|
||||||
room_result.timeline_events,
|
room_result.timeline_events,
|
||||||
time_now,
|
time_now,
|
||||||
|
@ -1045,7 +1064,10 @@ class SlidingSyncRestServlet(RestServlet):
|
||||||
serialized_rooms[room_id]["is_dm"] = room_result.is_dm
|
serialized_rooms[room_id]["is_dm"] = room_result.is_dm
|
||||||
|
|
||||||
# Stripped state only applies to invite/knock rooms
|
# Stripped state only applies to invite/knock rooms
|
||||||
if room_result.stripped_state is not None:
|
if (
|
||||||
|
room_result.stripped_state is not None
|
||||||
|
and len(room_result.stripped_state) > 0
|
||||||
|
):
|
||||||
# TODO: `knocked_state` but that isn't specced yet.
|
# TODO: `knocked_state` but that isn't specced yet.
|
||||||
#
|
#
|
||||||
# TODO: Instead of adding `knocked_state`, it would be good to rename
|
# TODO: Instead of adding `knocked_state`, it would be good to rename
|
||||||
|
|
|
@ -200,18 +200,24 @@ class SlidingSyncResult:
|
||||||
flag set. (same as sync v2)
|
flag set. (same as sync v2)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
||||||
|
class StrippedHero:
|
||||||
|
user_id: str
|
||||||
|
display_name: Optional[str]
|
||||||
|
avatar_url: Optional[str]
|
||||||
|
|
||||||
name: Optional[str]
|
name: Optional[str]
|
||||||
avatar: Optional[str]
|
avatar: Optional[str]
|
||||||
heroes: Optional[List[EventBase]]
|
heroes: Optional[List[StrippedHero]]
|
||||||
is_dm: bool
|
is_dm: bool
|
||||||
initial: bool
|
initial: bool
|
||||||
# Only optional because it won't be included for invite/knock rooms with `stripped_state`
|
# Should be empty for invite/knock rooms with `stripped_state`
|
||||||
required_state: Optional[List[EventBase]]
|
required_state: List[EventBase]
|
||||||
# Only optional because it won't be included for invite/knock rooms with `stripped_state`
|
# Should be empty for invite/knock rooms with `stripped_state`
|
||||||
timeline_events: Optional[List[EventBase]]
|
timeline_events: List[EventBase]
|
||||||
bundled_aggregations: Optional[Dict[str, "BundledAggregations"]]
|
bundled_aggregations: Optional[Dict[str, "BundledAggregations"]]
|
||||||
# Optional because it's only relevant to invite/knock rooms
|
# Optional because it's only relevant to invite/knock rooms
|
||||||
stripped_state: Optional[List[JsonDict]]
|
stripped_state: List[JsonDict]
|
||||||
# Only optional because it won't be included for invite/knock rooms with `stripped_state`
|
# Only optional because it won't be included for invite/knock rooms with `stripped_state`
|
||||||
prev_batch: Optional[StreamToken]
|
prev_batch: Optional[StreamToken]
|
||||||
# Only optional because it won't be included for invite/knock rooms with `stripped_state`
|
# Only optional because it won't be included for invite/knock rooms with `stripped_state`
|
||||||
|
|
|
@ -200,9 +200,6 @@ class SlidingSyncBody(RequestBodyModel):
|
||||||
}
|
}
|
||||||
|
|
||||||
timeline_limit: The maximum number of timeline events to return per response.
|
timeline_limit: The maximum number of timeline events to return per response.
|
||||||
include_heroes: Return a stripped variant of membership events (containing
|
|
||||||
`user_id` and optionally `avatar_url` and `displayname`) for the users used
|
|
||||||
to calculate the room name.
|
|
||||||
filters: Filters to apply to the list before sorting.
|
filters: Filters to apply to the list before sorting.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -270,7 +267,6 @@ class SlidingSyncBody(RequestBodyModel):
|
||||||
else:
|
else:
|
||||||
ranges: Optional[List[Tuple[conint(ge=0, strict=True), conint(ge=0, strict=True)]]] = None # type: ignore[valid-type]
|
ranges: Optional[List[Tuple[conint(ge=0, strict=True), conint(ge=0, strict=True)]]] = None # type: ignore[valid-type]
|
||||||
slow_get_all_rooms: Optional[StrictBool] = False
|
slow_get_all_rooms: Optional[StrictBool] = False
|
||||||
include_heroes: Optional[StrictBool] = False
|
|
||||||
filters: Optional[Filters] = None
|
filters: Optional[Filters] = None
|
||||||
|
|
||||||
class RoomSubscription(CommonRoomParameters):
|
class RoomSubscription(CommonRoomParameters):
|
||||||
|
|
|
@ -1813,8 +1813,8 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
def test_rooms_meta_when_joined(self) -> None:
|
def test_rooms_meta_when_joined(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test that the `rooms` `name` and `avatar` (soon to test `heroes`) are included
|
Test that the `rooms` `name` and `avatar` are included in the response and
|
||||||
in the response when the user is joined to the room.
|
reflect the current state of the room when the user is joined to the room.
|
||||||
"""
|
"""
|
||||||
user1_id = self.register_user("user1", "pass")
|
user1_id = self.register_user("user1", "pass")
|
||||||
user1_tok = self.login(user1_id, "pass")
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
@ -1866,11 +1866,19 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
"mxc://DUMMY_MEDIA_ID",
|
"mxc://DUMMY_MEDIA_ID",
|
||||||
channel.json_body["rooms"][room_id1],
|
channel.json_body["rooms"][room_id1],
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["joined_count"],
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["invited_count"],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
def test_rooms_meta_when_invited(self) -> None:
|
def test_rooms_meta_when_invited(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test that the `rooms` `name` and `avatar` (soon to test `heroes`) are included
|
Test that the `rooms` `name` and `avatar` are included in the response and
|
||||||
in the response when the user is invited to the room.
|
reflect the current state of the room when the user is invited to the room.
|
||||||
"""
|
"""
|
||||||
user1_id = self.register_user("user1", "pass")
|
user1_id = self.register_user("user1", "pass")
|
||||||
user1_tok = self.login(user1_id, "pass")
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
@ -1892,7 +1900,8 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
tok=user2_tok,
|
tok=user2_tok,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.helper.join(room_id1, user1_id, tok=user1_tok)
|
# User1 is invited to the room
|
||||||
|
self.helper.invite(room_id1, src=user2_id, targ=user1_id, tok=user2_tok)
|
||||||
|
|
||||||
# Update the room name after user1 has left
|
# Update the room name after user1 has left
|
||||||
self.helper.send_state(
|
self.helper.send_state(
|
||||||
|
@ -1938,11 +1947,19 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
"mxc://UPDATED_DUMMY_MEDIA_ID",
|
"mxc://UPDATED_DUMMY_MEDIA_ID",
|
||||||
channel.json_body["rooms"][room_id1],
|
channel.json_body["rooms"][room_id1],
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["joined_count"],
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["invited_count"],
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
def test_rooms_meta_when_banned(self) -> None:
|
def test_rooms_meta_when_banned(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test that the `rooms` `name` and `avatar` (soon to test `heroes`) reflect the
|
Test that the `rooms` `name` and `avatar` reflect the state of the room when the
|
||||||
state of the room when the user was banned (do not leak current state).
|
user was banned (do not leak current state).
|
||||||
"""
|
"""
|
||||||
user1_id = self.register_user("user1", "pass")
|
user1_id = self.register_user("user1", "pass")
|
||||||
user1_tok = self.login(user1_id, "pass")
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
@ -2010,6 +2027,273 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
"mxc://DUMMY_MEDIA_ID",
|
"mxc://DUMMY_MEDIA_ID",
|
||||||
channel.json_body["rooms"][room_id1],
|
channel.json_body["rooms"][room_id1],
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["joined_count"],
|
||||||
|
# FIXME: The actual number should be "1" (user2) but we currently don't
|
||||||
|
# support this for rooms where the user has left/been banned.
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["invited_count"],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rooms_meta_heroes(self) -> None:
|
||||||
|
"""
|
||||||
|
Test that the `rooms` `heroes` are included in the response when the room
|
||||||
|
doesn't have a room name set.
|
||||||
|
"""
|
||||||
|
user1_id = self.register_user("user1", "pass")
|
||||||
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
user2_id = self.register_user("user2", "pass")
|
||||||
|
user2_tok = self.login(user2_id, "pass")
|
||||||
|
user3_id = self.register_user("user3", "pass")
|
||||||
|
_user3_tok = self.login(user3_id, "pass")
|
||||||
|
|
||||||
|
room_id1 = self.helper.create_room_as(
|
||||||
|
user2_id,
|
||||||
|
tok=user2_tok,
|
||||||
|
extra_content={
|
||||||
|
"name": "my super room",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.helper.join(room_id1, user1_id, tok=user1_tok)
|
||||||
|
# User3 is invited
|
||||||
|
self.helper.invite(room_id1, src=user2_id, targ=user3_id, tok=user2_tok)
|
||||||
|
|
||||||
|
room_id2 = self.helper.create_room_as(
|
||||||
|
user2_id,
|
||||||
|
tok=user2_tok,
|
||||||
|
extra_content={
|
||||||
|
# No room name set so that `heroes` is populated
|
||||||
|
#
|
||||||
|
# "name": "my super room2",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.helper.join(room_id2, user1_id, tok=user1_tok)
|
||||||
|
# User3 is invited
|
||||||
|
self.helper.invite(room_id2, src=user2_id, targ=user3_id, tok=user2_tok)
|
||||||
|
|
||||||
|
# Make the Sliding Sync request
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
self.sync_endpoint,
|
||||||
|
{
|
||||||
|
"lists": {
|
||||||
|
"foo-list": {
|
||||||
|
"ranges": [[0, 1]],
|
||||||
|
"required_state": [],
|
||||||
|
"timeline_limit": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
access_token=user1_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200, channel.json_body)
|
||||||
|
|
||||||
|
# Room1 has a name so we shouldn't see any `heroes` which the client would use
|
||||||
|
# the calculate the room name themselves.
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["name"],
|
||||||
|
"my super room",
|
||||||
|
channel.json_body["rooms"][room_id1],
|
||||||
|
)
|
||||||
|
self.assertIsNone(channel.json_body["rooms"][room_id1].get("heroes"))
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["joined_count"],
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["invited_count"],
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Room2 doesn't have a name so we should see `heroes` populated
|
||||||
|
self.assertIsNone(channel.json_body["rooms"][room_id2].get("name"))
|
||||||
|
self.assertCountEqual(
|
||||||
|
[
|
||||||
|
hero["user_id"]
|
||||||
|
for hero in channel.json_body["rooms"][room_id2].get("heroes", [])
|
||||||
|
],
|
||||||
|
# Heroes shouldn't include the user themselves (we shouldn't see user1)
|
||||||
|
[user2_id, user3_id],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id2]["joined_count"],
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id2]["invited_count"],
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# We didn't request any state so we shouldn't see any `required_state`
|
||||||
|
self.assertIsNone(channel.json_body["rooms"][room_id1].get("required_state"))
|
||||||
|
self.assertIsNone(channel.json_body["rooms"][room_id2].get("required_state"))
|
||||||
|
|
||||||
|
def test_rooms_meta_heroes_max(self) -> None:
|
||||||
|
"""
|
||||||
|
Test that the `rooms` `heroes` only includes the first 5 users (not including
|
||||||
|
yourself).
|
||||||
|
"""
|
||||||
|
user1_id = self.register_user("user1", "pass")
|
||||||
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
user2_id = self.register_user("user2", "pass")
|
||||||
|
user2_tok = self.login(user2_id, "pass")
|
||||||
|
user3_id = self.register_user("user3", "pass")
|
||||||
|
user3_tok = self.login(user3_id, "pass")
|
||||||
|
user4_id = self.register_user("user4", "pass")
|
||||||
|
user4_tok = self.login(user4_id, "pass")
|
||||||
|
user5_id = self.register_user("user5", "pass")
|
||||||
|
user5_tok = self.login(user5_id, "pass")
|
||||||
|
user6_id = self.register_user("user6", "pass")
|
||||||
|
user6_tok = self.login(user6_id, "pass")
|
||||||
|
user7_id = self.register_user("user7", "pass")
|
||||||
|
user7_tok = self.login(user7_id, "pass")
|
||||||
|
|
||||||
|
room_id1 = self.helper.create_room_as(
|
||||||
|
user2_id,
|
||||||
|
tok=user2_tok,
|
||||||
|
extra_content={
|
||||||
|
# No room name set so that `heroes` is populated
|
||||||
|
#
|
||||||
|
# "name": "my super room",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.helper.join(room_id1, user1_id, tok=user1_tok)
|
||||||
|
self.helper.join(room_id1, user3_id, tok=user3_tok)
|
||||||
|
self.helper.join(room_id1, user4_id, tok=user4_tok)
|
||||||
|
self.helper.join(room_id1, user5_id, tok=user5_tok)
|
||||||
|
self.helper.join(room_id1, user6_id, tok=user6_tok)
|
||||||
|
self.helper.join(room_id1, user7_id, tok=user7_tok)
|
||||||
|
|
||||||
|
# Make the Sliding Sync request
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
self.sync_endpoint,
|
||||||
|
{
|
||||||
|
"lists": {
|
||||||
|
"foo-list": {
|
||||||
|
"ranges": [[0, 1]],
|
||||||
|
"required_state": [],
|
||||||
|
"timeline_limit": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
access_token=user1_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200, channel.json_body)
|
||||||
|
|
||||||
|
# Room2 doesn't have a name so we should see `heroes` populated
|
||||||
|
self.assertIsNone(channel.json_body["rooms"][room_id1].get("name"))
|
||||||
|
# FIXME: Remove this basic assertion and uncomment the better assertion below
|
||||||
|
# after https://github.com/element-hq/synapse/pull/17435 merges
|
||||||
|
self.assertEqual(len(channel.json_body["rooms"][room_id1].get("heroes", [])), 5)
|
||||||
|
# self.assertCountEqual(
|
||||||
|
# [
|
||||||
|
# hero["user_id"]
|
||||||
|
# for hero in channel.json_body["rooms"][room_id1].get("heroes", [])
|
||||||
|
# ],
|
||||||
|
# # Heroes should be the first 5 users in the room (excluding the user
|
||||||
|
# # themselves, we shouldn't see `user1`)
|
||||||
|
# [user2_id, user3_id, user4_id, user5_id, user6_id],
|
||||||
|
# )
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["joined_count"],
|
||||||
|
7,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["invited_count"],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# We didn't request any state so we shouldn't see any `required_state`
|
||||||
|
self.assertIsNone(channel.json_body["rooms"][room_id1].get("required_state"))
|
||||||
|
|
||||||
|
def test_rooms_meta_heroes_when_banned(self) -> None:
|
||||||
|
"""
|
||||||
|
Test that the `rooms` `heroes` are included in the response when the room
|
||||||
|
doesn't have a room name set but doesn't leak information past their ban.
|
||||||
|
"""
|
||||||
|
user1_id = self.register_user("user1", "pass")
|
||||||
|
user1_tok = self.login(user1_id, "pass")
|
||||||
|
user2_id = self.register_user("user2", "pass")
|
||||||
|
user2_tok = self.login(user2_id, "pass")
|
||||||
|
user3_id = self.register_user("user3", "pass")
|
||||||
|
_user3_tok = self.login(user3_id, "pass")
|
||||||
|
user4_id = self.register_user("user4", "pass")
|
||||||
|
user4_tok = self.login(user4_id, "pass")
|
||||||
|
user5_id = self.register_user("user5", "pass")
|
||||||
|
_user5_tok = self.login(user5_id, "pass")
|
||||||
|
|
||||||
|
room_id1 = self.helper.create_room_as(
|
||||||
|
user2_id,
|
||||||
|
tok=user2_tok,
|
||||||
|
extra_content={
|
||||||
|
# No room name set so that `heroes` is populated
|
||||||
|
#
|
||||||
|
# "name": "my super room",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# User1 joins the room
|
||||||
|
self.helper.join(room_id1, user1_id, tok=user1_tok)
|
||||||
|
# User3 is invited
|
||||||
|
self.helper.invite(room_id1, src=user2_id, targ=user3_id, tok=user2_tok)
|
||||||
|
|
||||||
|
# User1 is banned from the room
|
||||||
|
self.helper.ban(room_id1, src=user2_id, targ=user1_id, tok=user2_tok)
|
||||||
|
|
||||||
|
# User4 joins the room after user1 is banned
|
||||||
|
self.helper.join(room_id1, user4_id, tok=user4_tok)
|
||||||
|
# User5 is invited after user1 is banned
|
||||||
|
self.helper.invite(room_id1, src=user2_id, targ=user5_id, tok=user2_tok)
|
||||||
|
|
||||||
|
# Make the Sliding Sync request
|
||||||
|
channel = self.make_request(
|
||||||
|
"POST",
|
||||||
|
self.sync_endpoint,
|
||||||
|
{
|
||||||
|
"lists": {
|
||||||
|
"foo-list": {
|
||||||
|
"ranges": [[0, 1]],
|
||||||
|
"required_state": [],
|
||||||
|
"timeline_limit": 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
access_token=user1_tok,
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, 200, channel.json_body)
|
||||||
|
|
||||||
|
# Room2 doesn't have a name so we should see `heroes` populated
|
||||||
|
self.assertIsNone(channel.json_body["rooms"][room_id1].get("name"))
|
||||||
|
self.assertCountEqual(
|
||||||
|
[
|
||||||
|
hero["user_id"]
|
||||||
|
for hero in channel.json_body["rooms"][room_id1].get("heroes", [])
|
||||||
|
],
|
||||||
|
# Heroes shouldn't include the user themselves (we shouldn't see user1). We
|
||||||
|
# also shouldn't see user4 since they joined after user1 was banned.
|
||||||
|
#
|
||||||
|
# FIXME: The actual result should be `[user2_id, user3_id]` but we currently
|
||||||
|
# don't support this for rooms where the user has left/been banned.
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["joined_count"],
|
||||||
|
# FIXME: The actual number should be "1" (user2) but we currently don't
|
||||||
|
# support this for rooms where the user has left/been banned.
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["rooms"][room_id1]["invited_count"],
|
||||||
|
# We shouldn't see user5 since they were invited after user1 was banned.
|
||||||
|
#
|
||||||
|
# FIXME: The actual number should be "1" (user3) but we currently don't
|
||||||
|
# support this for rooms where the user has left/been banned.
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
def test_rooms_limited_initial_sync(self) -> None:
|
def test_rooms_limited_initial_sync(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -3081,11 +3365,7 @@ class SlidingSyncTestCase(unittest.HomeserverTestCase):
|
||||||
self.assertEqual(channel.code, 200, channel.json_body)
|
self.assertEqual(channel.code, 200, channel.json_body)
|
||||||
|
|
||||||
# Nothing to see for this banned user in the room in the token range
|
# Nothing to see for this banned user in the room in the token range
|
||||||
self.assertEqual(
|
self.assertIsNone(channel.json_body["rooms"][room_id1].get("timeline"))
|
||||||
channel.json_body["rooms"][room_id1]["timeline"],
|
|
||||||
[],
|
|
||||||
channel.json_body["rooms"][room_id1]["timeline"],
|
|
||||||
)
|
|
||||||
# No events returned in the timeline so nothing is "live"
|
# No events returned in the timeline so nothing is "live"
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
channel.json_body["rooms"][room_id1]["num_live"],
|
channel.json_body["rooms"][room_id1]["num_live"],
|
||||||
|
|
Loading…
Reference in a new issue