wip: call the public room callback

This commit is contained in:
Andrew Morgan 2023-03-15 17:19:05 +00:00
parent 2436153e8f
commit 5c1e9f24da
5 changed files with 91 additions and 49 deletions

View File

@ -27,6 +27,7 @@ from synapse.api.constants import (
JoinRules, JoinRules,
PublicRoomsFilterFields, PublicRoomsFilterFields,
) )
from synapse.types import Requester
from synapse.api.errors import ( from synapse.api.errors import (
Codes, Codes,
HttpResponseException, HttpResponseException,
@ -60,6 +61,7 @@ class RoomListHandler:
self.remote_response_cache: ResponseCache[ self.remote_response_cache: ResponseCache[
Tuple[str, Optional[int], Optional[str], bool, Optional[str]] Tuple[str, Optional[int], Optional[str], bool, Optional[str]]
] = ResponseCache(hs.get_clock(), "remote_room_list", timeout_ms=30 * 1000) ] = ResponseCache(hs.get_clock(), "remote_room_list", timeout_ms=30 * 1000)
self._module_api_callbacks = hs.get_module_api_callbacks().public_rooms
async def get_local_public_room_list( async def get_local_public_room_list(
self, self,
@ -67,7 +69,8 @@ class RoomListHandler:
since_token: Optional[str] = None, since_token: Optional[str] = None,
search_filter: Optional[dict] = None, search_filter: Optional[dict] = None,
network_tuple: Optional[ThirdPartyInstanceID] = EMPTY_THIRD_PARTY_ID, network_tuple: Optional[ThirdPartyInstanceID] = EMPTY_THIRD_PARTY_ID,
from_federation: bool = False, from_client_mxid: Optional[str] = None,
from_remote_server_name: Optional[str] = None,
) -> JsonDict: ) -> JsonDict:
"""Generate a local public room list. """Generate a local public room list.
@ -75,14 +78,20 @@ class RoomListHandler:
party network. A client can ask for a specific list or to return all. party network. A client can ask for a specific list or to return all.
Args: Args:
limit limit: The maximum number of rooms to return, or None to return all rooms.
since_token since_token: A pagination token, or None to return the head of the public
search_filter rooms list.
search_filter: An optional dictionary with the following keys:
* generic_search_term: A string to search for in room ...
* room_types: A list to filter returned rooms by their type. If None or
an empty list is passed, rooms will not be filtered by type.
network_tuple: Which public list to use. network_tuple: Which public list to use.
This can be (None, None) to indicate the main list, or a particular This can be (None, None) to indicate the main list, or a particular
appservice and network id to use an appservice specific one. appservice and network id to use an appservice specific one.
Setting to None returns all public rooms across all lists. Setting to None returns all public rooms across all lists.
from_federation: true iff the request comes from the federation API from_client_mxid: A user's MXID if this request came from a registered user.
from_remote_server_name: A remote homeserver's server name, if this
request came from the federation API.
""" """
if not self.enable_room_list_search: if not self.enable_room_list_search:
return {"chunk": [], "total_room_count_estimate": 0} return {"chunk": [], "total_room_count_estimate": 0}
@ -105,7 +114,8 @@ class RoomListHandler:
since_token, since_token,
search_filter, search_filter,
network_tuple=network_tuple, network_tuple=network_tuple,
from_federation=from_federation, from_client_mxid=from_client_mxid,
from_remote_server_name=from_remote_server_name,
) )
key = (limit, since_token, network_tuple) key = (limit, since_token, network_tuple)
@ -115,7 +125,8 @@ class RoomListHandler:
limit, limit,
since_token, since_token,
network_tuple=network_tuple, network_tuple=network_tuple,
from_federation=from_federation, from_client_mxid=from_client_mxid,
from_remote_server_name=from_remote_server_name,
) )
async def _get_public_room_list( async def _get_public_room_list(
@ -124,7 +135,8 @@ class RoomListHandler:
since_token: Optional[str] = None, since_token: Optional[str] = None,
search_filter: Optional[dict] = None, search_filter: Optional[dict] = None,
network_tuple: Optional[ThirdPartyInstanceID] = EMPTY_THIRD_PARTY_ID, network_tuple: Optional[ThirdPartyInstanceID] = EMPTY_THIRD_PARTY_ID,
from_federation: bool = False, from_client_mxid: Optional[str] = None,
from_remote_server_name: Optional[str] = None,
) -> JsonDict: ) -> JsonDict:
"""Generate a public room list. """Generate a public room list.
Args: Args:
@ -135,8 +147,9 @@ class RoomListHandler:
This can be (None, None) to indicate the main list, or a particular This can be (None, None) to indicate the main list, or a particular
appservice and network id to use an appservice specific one. appservice and network id to use an appservice specific one.
Setting to None returns all public rooms across all lists. Setting to None returns all public rooms across all lists.
from_federation: Whether this request originated from a from_client_mxid: A user's MXID if this request came from a registered user.
federating server or a client. Used for room filtering. from_remote_server_name: A remote homeserver's server name, if this
request came from the federation API.
""" """
# Pagination tokens work by storing the room ID sent in the last batch, # Pagination tokens work by storing the room ID sent in the last batch,
@ -145,50 +158,38 @@ class RoomListHandler:
if since_token: if since_token:
batch_token = RoomListNextBatch.from_token(since_token) batch_token = RoomListNextBatch.from_token(since_token)
bounds: Optional[Tuple[int, str]] = (
batch_token.last_joined_members,
batch_token.last_room_id,
)
forwards = batch_token.direction_is_forward forwards = batch_token.direction_is_forward
has_batch_token = True
else: else:
bounds = None batch_token = None
forwards = True forwards = True
has_batch_token = False
# we request one more than wanted to see if there are more pages to come # we request one more than wanted to see if there are more pages to come
probing_limit = limit + 1 if limit is not None else None probing_limit = limit + 1 if limit is not None else None
results = await self.store.get_largest_public_rooms( public_rooms = await self.store.get_largest_public_rooms(
network_tuple, network_tuple,
search_filter, search_filter,
probing_limit, probing_limit,
bounds=bounds, bounds=(
[batch_token.last_joined_members, batch_token.last_room_id]
if batch_token else None
),
forwards=forwards, forwards=forwards,
ignore_non_federatable=from_federation, ignore_non_federatable=bool(from_remote_server_name),
) )
def build_room_entry(room: JsonDict) -> JsonDict: for fetch_public_rooms in self._module_api_callbacks.fetch_public_rooms_callbacks:
entry = { # Ask each module for a list of public rooms given the last_joined_members
"room_id": room["room_id"], # value from the since token and the probing limit.
"name": room["name"], module_public_rooms = await fetch_public_rooms(
"topic": room["topic"], limit=probing_limit,
"canonical_alias": room["canonical_alias"], max_member_count=(
"num_joined_members": room["joined_members"], batch_token.last_joined_members
"avatar_url": room["avatar"], if batch_token else None
"world_readable": room["history_visibility"] ),
== HistoryVisibility.WORLD_READABLE, )
"guest_can_join": room["guest_access"] == "can_join",
"join_rule": room["join_rules"],
"room_type": room["room_type"],
}
# Filter out Nones rather omit the field altogether # Insert the module's reported public rooms into the list
return {k: v for k, v in entry.items() if v is not None}
results = [build_room_entry(r) for r in results]
response: JsonDict = {} response: JsonDict = {}
num_results = len(results) num_results = len(results)
@ -208,7 +209,7 @@ class RoomListHandler:
initial_entry = results[0] initial_entry = results[0]
if forwards: if forwards:
if has_batch_token: if batch_token is not None:
# If there was a token given then we assume that there # If there was a token given then we assume that there
# must be previous results. # must be previous results.
response["prev_batch"] = RoomListNextBatch( response["prev_batch"] = RoomListNextBatch(
@ -224,7 +225,7 @@ class RoomListHandler:
direction_is_forward=True, direction_is_forward=True,
).to_token() ).to_token()
else: else:
if has_batch_token: if batch_token is not None:
response["next_batch"] = RoomListNextBatch( response["next_batch"] = RoomListNextBatch(
last_joined_members=final_entry["num_joined_members"], last_joined_members=final_entry["num_joined_members"],
last_room_id=final_entry["room_id"], last_room_id=final_entry["room_id"],
@ -242,7 +243,7 @@ class RoomListHandler:
response["total_room_count_estimate"] = await self.store.count_public_rooms( response["total_room_count_estimate"] = await self.store.count_public_rooms(
network_tuple, network_tuple,
ignore_non_federatable=from_federation, ignore_non_federatable=bool(from_remote_server_name),
search_filter=search_filter, search_filter=search_filter,
) )

View File

@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
@attr.s(auto_attribs=True) @attr.s(auto_attribs=True)
class PublicRoomChunk: class PublicRoom:
room_id: str room_id: str
name: str name: str
topic: str topic: str
@ -36,8 +36,8 @@ class PublicRoomChunk:
# Types for callbacks to be registered via the module api # Types for callbacks to be registered via the module api
FETCH_PUBLIC_ROOMS_CALLBACK = Callable[ FETCH_PUBLIC_ROOMS_CALLBACK = Callable[
[int, Optional[int], Optional[dict], Optional[str], Optional[str]], [int, Optional[Tuple[int, bool]], Optional[dict], Optional[str], Optional[str]],
Awaitable[Tuple[Iterable[PublicRoomChunk], bool]], Awaitable[Tuple[Iterable[PublicRoom], bool]],
] ]

View File

@ -476,8 +476,9 @@ class PublicRoomListRestServlet(RestServlet):
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
server = parse_string(request, "server") server = parse_string(request, "server")
requester: Optional[Requester] = None
try: try:
await self.auth.get_user_by_req(request, allow_guest=True) requester = await self.auth.get_user_by_req(request, allow_guest=True)
except InvalidClientCredentialsError as e: except InvalidClientCredentialsError as e:
# Option to allow servers to require auth when accessing # Option to allow servers to require auth when accessing
# /publicRooms via CS API. This is especially helpful in private # /publicRooms via CS API. This is especially helpful in private
@ -516,8 +517,15 @@ class PublicRoomListRestServlet(RestServlet):
server, limit=limit, since_token=since_token server, limit=limit, since_token=since_token
) )
else: else:
# If a user we know made this request, pass that information to the
# public rooms list handler.
if requester is None:
from_client_mxid = None
else:
from_client_mxid = requester.user.to_string()
data = await handler.get_local_public_room_list( data = await handler.get_local_public_room_list(
limit=limit, since_token=since_token limit=limit, since_token=since_token, from_client_mxid=from_client_mxid
) )
return 200, data return 200, data

View File

@ -16,6 +16,7 @@
import logging import logging
from abc import abstractmethod from abc import abstractmethod
from enum import Enum from enum import Enum
from synapse.api.constants import HistoryVisibility
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
AbstractSet, AbstractSet,
@ -518,7 +519,26 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
ret_val = await self.db_pool.runInteraction( ret_val = await self.db_pool.runInteraction(
"get_largest_public_rooms", _get_largest_public_rooms_txn "get_largest_public_rooms", _get_largest_public_rooms_txn
) )
return ret_val
def build_room_entry(room: JsonDict) -> JsonDict:
entry = {
"room_id": room["room_id"],
"name": room["name"],
"topic": room["topic"],
"canonical_alias": room["canonical_alias"],
"num_joined_members": room["joined_members"],
"avatar_url": room["avatar"],
"world_readable": room["history_visibility"]
== HistoryVisibility.WORLD_READABLE,
"guest_can_join": room["guest_access"] == "can_join",
"join_rule": room["join_rules"],
"room_type": room["room_type"],
}
# Filter out Nones rather omit the field altogether
return {k: v for k, v in entry.items() if v is not None}
return [build_room_entry(r) for r in ret_val]
@cached(max_entries=10000) @cached(max_entries=10000)
async def is_room_blocked(self, room_id: str) -> Optional[bool]: async def is_room_blocked(self, room_id: str) -> Optional[bool]:

View File

@ -936,6 +936,19 @@ class UserInfo:
is_shadow_banned: bool is_shadow_banned: bool
class PublicRoomsChunk:
room_id: str
name: str
topic: str
num_joined_members: int
canonical_alias: str
avatar_url: str
world_readable: bool
guest_can_join: bool
join_rule: str
room_type: str
class UserProfile(TypedDict): class UserProfile(TypedDict):
user_id: str user_id: str
display_name: Optional[str] display_name: Optional[str]