Skip to content
Snippets Groups Projects
directory.rs 13.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • Timo Kösters's avatar
    Timo Kösters committed
    use crate::{services, Error, Result, Ruma};
    
    use ruma::{
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
                directory::{
                    get_public_rooms, get_public_rooms_filtered, get_room_visibility,
                    set_room_visibility,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
                error::ErrorKind,
                room,
    
    chenyuqide's avatar
    chenyuqide committed
        directory::{
            Filter, IncomingFilter, IncomingRoomNetwork, PublicRoomJoinRule, PublicRoomsChunk,
            RoomNetwork,
        },
    
    Jonas Platte's avatar
    Jonas Platte committed
            room::{
                avatar::RoomAvatarEventContent,
                canonical_alias::RoomCanonicalAliasEventContent,
    
    Timo Kösters's avatar
    Timo Kösters committed
                create::RoomCreateEventContent,
    
    Jonas Platte's avatar
    Jonas Platte committed
                guest_access::{GuestAccess, RoomGuestAccessEventContent},
                history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
    
    chenyuqide's avatar
    chenyuqide committed
                join_rules::{JoinRule, RoomJoinRulesEventContent},
    
    Jonas Platte's avatar
    Jonas Platte committed
                name::RoomNameEventContent,
                topic::RoomTopicEventContent,
            },
    
    Timo Kösters's avatar
    Timo Kösters committed
            StateEventType,
    
    Timo Kösters's avatar
    Timo Kösters committed
    use tracing::{error, info, warn};
    
    /// # `POST /_matrix/client/r0/publicRooms`
    ///
    /// Lists the public rooms on this server.
    ///
    /// - Rooms are ordered by the number of joined members
    
    pub async fn get_public_rooms_filtered_route(
    
    Timo Kösters's avatar
    Timo Kösters committed
        body: Ruma<get_public_rooms_filtered::v3::IncomingRequest>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<get_public_rooms_filtered::v3::Response> {
    
        get_public_rooms_filtered_helper(
    
            body.server.as_deref(),
            body.limit,
            body.since.as_deref(),
            &body.filter,
            &body.room_network,
    
    /// # `GET /_matrix/client/r0/publicRooms`
    ///
    /// Lists the public rooms on this server.
    ///
    /// - Rooms are ordered by the number of joined members
    
    pub async fn get_public_rooms_route(
    
    Timo Kösters's avatar
    Timo Kösters committed
        body: Ruma<get_public_rooms::v3::IncomingRequest>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<get_public_rooms::v3::Response> {
    
        let response = get_public_rooms_filtered_helper(
    
            body.server.as_deref(),
            body.limit,
            body.since.as_deref(),
    
            &IncomingFilter::default(),
            &IncomingRoomNetwork::Matrix,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(get_public_rooms::v3::Response {
    
            chunk: response.chunk,
            prev_batch: response.prev_batch,
            next_batch: response.next_batch,
            total_room_count_estimate: response.total_room_count_estimate,
    
    /// # `PUT /_matrix/client/r0/directory/list/room/{roomId}`
    ///
    /// Sets the visibility of a given room in the room directory.
    ///
    /// - TODO: Access control checks
    
    pub async fn set_room_visibility_route(
    
    Timo Kösters's avatar
    Timo Kösters committed
        body: Ruma<set_room_visibility::v3::IncomingRequest>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<set_room_visibility::v3::Response> {
    
        let sender_user = body.sender_user.as_ref().expect("user is authenticated");
    
    
            room::Visibility::Public => {
    
                services().rooms.directory.set_public(&body.room_id)?;
    
                info!("{} made {} public", sender_user, body.room_id);
            }
    
            room::Visibility::Private => services().rooms.directory.set_not_public(&body.room_id)?,
    
    Timo Kösters's avatar
    Timo Kösters committed
            _ => {
                return Err(Error::BadRequest(
                    ErrorKind::InvalidParam,
                    "Room visibility type is not supported.",
                ));
            }
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(set_room_visibility::v3::Response {})
    
    /// # `GET /_matrix/client/r0/directory/list/room/{roomId}`
    ///
    /// Gets the visibility of a given room in the room directory.
    
    pub async fn get_room_visibility_route(
    
    Timo Kösters's avatar
    Timo Kösters committed
        body: Ruma<get_room_visibility::v3::IncomingRequest>,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<get_room_visibility::v3::Response> {
        Ok(get_room_visibility::v3::Response {
    
            visibility: if services().rooms.directory.is_public_room(&body.room_id)? {
    
                room::Visibility::Public
            } else {
                room::Visibility::Private
            },
    
    pub(crate) async fn get_public_rooms_filtered_helper(
    
        limit: Option<UInt>,
    
        filter: &IncomingFilter,
    
        _network: &IncomingRoomNetwork,
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
    ) -> Result<get_public_rooms_filtered::v3::Response> {
    
    Timo Kösters's avatar
    Timo Kösters committed
        if let Some(other_server) =
            server.filter(|server| *server != services().globals.server_name().as_str())
    
            let response = services()
    
                .sending
                .send_federation_request(
    
                    federation::directory::get_public_rooms_filtered::v1::Request {
                        limit,
    
    Jonas Platte's avatar
    Jonas Platte committed
                        since,
    
                        filter: Filter {
                            generic_search_term: filter.generic_search_term.as_deref(),
    
    Timo Kösters's avatar
    Timo Kösters committed
                            room_types: filter.room_types.clone(),
    
                        },
                        room_network: RoomNetwork::Matrix,
    
                )
                .await?;
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
            return Ok(get_public_rooms_filtered::v3::Response {
    
                chunk: response.chunk,
    
                prev_batch: response.prev_batch,
                next_batch: response.next_batch,
                total_room_count_estimate: response.total_room_count_estimate,
    
        let limit = limit.map_or(10, u64::from);
        let mut num_since = 0_u64;
    
            let mut characters = s.chars();
            let backwards = match characters.next() {
                Some('n') => false,
                Some('p') => true,
                _ => {
                    return Err(Error::BadRequest(
                        ErrorKind::InvalidParam,
                        "Invalid `since` token",
                    ))
                }
            };
    
                .collect::<String>()
                .parse()
                .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token."))?;
    
            if backwards {
    
                num_since = num_since.saturating_sub(limit);
    
        let mut all_rooms: Vec<_> = services()
    
    Jonas Platte's avatar
    Jonas Platte committed
            .rooms
    
            .directory
    
    Jonas Platte's avatar
    Jonas Platte committed
            .public_rooms()
            .map(|room_id| {
                let room_id = room_id?;
    
    Jonas Platte's avatar
    Jonas Platte committed
                let chunk = PublicRoomsChunk {
    
                    canonical_alias: services()
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .rooms
    
                        .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .room_state_get(&room_id, &StateEventType::RoomCanonicalAlias, "")?
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .map_or(Ok(None), |s| {
                            serde_json::from_str(s.content.get())
                                .map(|c: RoomCanonicalAliasEventContent| c.alias)
                                .map_err(|_| {
                                    Error::bad_database("Invalid canonical alias event in database.")
                                })
    
    Jonas Platte's avatar
    Jonas Platte committed
                        })?,
    
                    name: services()
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .rooms
    
                        .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .room_state_get(&room_id, &StateEventType::RoomName, "")?
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .map_or(Ok(None), |s| {
                            serde_json::from_str(s.content.get())
                                .map(|c: RoomNameEventContent| c.name)
                                .map_err(|_| {
                                    Error::bad_database("Invalid room name event in database.")
                                })
    
    Jonas Platte's avatar
    Jonas Platte committed
                        })?,
    
                    num_joined_members: services()
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .rooms
    
                        .state_cache
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .room_joined_count(&room_id)?
                        .unwrap_or_else(|| {
                            warn!("Room {} has no member count", room_id);
                            0
                        })
                        .try_into()
                        .expect("user count should not be that big"),
    
                    topic: services()
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .rooms
    
                        .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .room_state_get(&room_id, &StateEventType::RoomTopic, "")?
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .map_or(Ok(None), |s| {
                            serde_json::from_str(s.content.get())
                                .map(|c: RoomTopicEventContent| Some(c.topic))
                                .map_err(|_| {
                                    Error::bad_database("Invalid room topic event in database.")
                                })
    
    Jonas Platte's avatar
    Jonas Platte committed
                        })?,
    
                    world_readable: services()
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .rooms
    
                        .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .room_state_get(&room_id, &StateEventType::RoomHistoryVisibility, "")?
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .map_or(Ok(false), |s| {
                            serde_json::from_str(s.content.get())
                                .map(|c: RoomHistoryVisibilityEventContent| {
                                    c.history_visibility == HistoryVisibility::WorldReadable
                                })
                                .map_err(|_| {
                                    Error::bad_database(
                                        "Invalid room history visibility event in database.",
                                    )
                                })
    
    Jonas Platte's avatar
    Jonas Platte committed
                        })?,
    
                    guest_can_join: services()
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .rooms
    
                        .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .room_state_get(&room_id, &StateEventType::RoomGuestAccess, "")?
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .map_or(Ok(false), |s| {
                            serde_json::from_str(s.content.get())
                                .map(|c: RoomGuestAccessEventContent| {
                                    c.guest_access == GuestAccess::CanJoin
                                })
                                .map_err(|_| {
                                    Error::bad_database("Invalid room guest access event in database.")
                                })
    
    Jonas Platte's avatar
    Jonas Platte committed
                        })?,
    
                    avatar_url: services()
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .rooms
    
                        .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .room_state_get(&room_id, &StateEventType::RoomAvatar, "")?
    
    Jonas Platte's avatar
    Jonas Platte committed
                        .map(|s| {
    
    Jonas Platte's avatar
    Jonas Platte committed
                            serde_json::from_str(s.content.get())
                                .map(|c: RoomAvatarEventContent| c.url)
                                .map_err(|_| {
                                    Error::bad_database("Invalid room avatar event in database.")
                                })
    
    Jonas Platte's avatar
    Jonas Platte committed
                        })
                        .transpose()?
                        // url is now an Option<String> so we must flatten
                        .flatten(),
    
                    join_rule: services()
    
    chenyuqide's avatar
    chenyuqide committed
                        .rooms
    
                        .state_accessor
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .room_state_get(&room_id, &StateEventType::RoomJoinRules, "")?
    
    chenyuqide's avatar
    chenyuqide committed
                        .map(|s| {
                            serde_json::from_str(s.content.get())
                                .map(|c: RoomJoinRulesEventContent| match c.join_rule {
                                    JoinRule::Public => Some(PublicRoomJoinRule::Public),
                                    JoinRule::Knock => Some(PublicRoomJoinRule::Knock),
                                    _ => None,
                                })
    
    Timo Kösters's avatar
    Timo Kösters committed
                                .map_err(|e| {
                                    error!("Invalid room join rule event in database: {}", e);
                                    Error::BadDatabase("Invalid room join rule event in database.")
    
    chenyuqide's avatar
    chenyuqide committed
                                })
                        })
                        .transpose()?
                        .flatten()
    
    Timo Kösters's avatar
    Timo Kösters committed
                        .ok_or_else(|| Error::bad_database("Missing room join rule event for room."))?,
    
    Timo Kösters's avatar
    Timo Kösters committed
                    room_type: services()
                        .rooms
                        .state_accessor
                        .room_state_get(&room_id, &StateEventType::RoomCreate, "")?
                        .map(|s| {
                            serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(
                                |e| {
                                    error!("Invalid room create event in database: {}", e);
                                    Error::BadDatabase("Invalid room create event in database.")
                                },
                            )
                        })
                        .transpose()?
                        .and_then(|e| e.room_type),
    
    Jonas Platte's avatar
    Jonas Platte committed
                    room_id,
                };
                Ok(chunk)
            })
            .filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms
            .filter(|chunk| {
                if let Some(query) = filter
                    .generic_search_term
                    .as_ref()
                    .map(|q| q.to_lowercase())
                {
                    if let Some(name) = &chunk.name {
                        if name.as_str().to_lowercase().contains(&query) {
                            return true;
    
    Jonas Platte's avatar
    Jonas Platte committed
                    }
    
    Jonas Platte's avatar
    Jonas Platte committed
                    if let Some(topic) = &chunk.topic {
                        if topic.to_lowercase().contains(&query) {
                            return true;
    
    Jonas Platte's avatar
    Jonas Platte committed
                    }
    
    Jonas Platte's avatar
    Jonas Platte committed
                    if let Some(canonical_alias) = &chunk.canonical_alias {
                        if canonical_alias.as_str().to_lowercase().contains(&query) {
                            return true;
    
    Jonas Platte's avatar
    Jonas Platte committed
    
                    false
                } else {
                    // No search term
                    true
                }
            })
            // We need to collect all, so we can sort by member count
    
    Jonas Platte's avatar
    Jonas Platte committed
            .collect();
    
        all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members));
    
        let total_room_count_estimate = (all_rooms.len() as u32).into();
    
    
    Jonas Platte's avatar
    Jonas Platte committed
        let chunk: Vec<_> = all_rooms
    
            .into_iter()
    
            .take(limit as usize)
    
    Jonas Platte's avatar
    Jonas Platte committed
            .collect();
    
        let prev_batch = if num_since == 0 {
    
            Some(format!("p{}", num_since))
    
        };
    
        let next_batch = if chunk.len() < limit as usize {
            None
        } else {
    
            Some(format!("n{}", num_since + limit))
    
    Jonathan de Jong's avatar
    Jonathan de Jong committed
        Ok(get_public_rooms_filtered::v3::Response {
    
            prev_batch,
            next_batch,
    
            total_room_count_estimate: Some(total_room_count_estimate),