mod room_state; mod server_can; mod state; mod user_can; use std::sync::Arc; use async_trait::async_trait; use conduwuit::{Result, err}; use database::Map; use ruma::{ EventEncryptionAlgorithm, JsOption, OwnedRoomAliasId, OwnedRoomId, RoomId, UserId, events::{ StateEventType, room::{ avatar::RoomAvatarEventContent, canonical_alias::RoomCanonicalAliasEventContent, create::RoomCreateEventContent, encryption::RoomEncryptionEventContent, guest_access::{GuestAccess, RoomGuestAccessEventContent}, history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent, RoomMembership}, member::RoomMemberEventContent, name::RoomNameEventContent, topic::RoomTopicEventContent, }, }, room::RoomType, space::SpaceRoomJoinRule, }; use crate::{Dep, rooms}; pub struct Service { services: Services, db: Data, } struct Services { short: Dep, state: Dep, state_compressor: Dep, state_cache: Dep, timeline: Dep, } struct Data { shorteventid_shortstatehash: Arc, } #[async_trait] impl crate::Service for Service { fn build(args: crate::Args<'_>) -> Result> { Ok(Arc::new(Self { services: Services { state_cache: args.depend::("rooms::state_cache"), timeline: args.depend::("rooms::timeline"), short: args.depend::("rooms::short"), state: args.depend::("rooms::state"), state_compressor: args .depend::("rooms::state_compressor"), }, db: Data { shorteventid_shortstatehash: args.db["shorteventid_shortstatehash"].clone(), }, })) } fn name(&self) -> &str { crate::service::make_name(std::module_path!()) } } impl Service { pub async fn get_name(&self, room_id: &RoomId) -> Result { self.room_state_get_content(room_id, &StateEventType::RoomName, "") .await .map(|c: RoomNameEventContent| c.name) } pub async fn get_avatar(&self, room_id: &RoomId) -> JsOption { let content = self .room_state_get_content(room_id, &StateEventType::RoomAvatar, "") .await .ok(); JsOption::from_option(content) } pub async fn get_member( &self, room_id: &RoomId, user_id: &UserId, ) -> Result { self.room_state_get_content(room_id, &StateEventType::RoomMember, user_id.as_str()) .await } /// Checks if guests are able to view room content without joining pub async fn is_world_readable(&self, room_id: &RoomId) -> bool { self.room_state_get_content(room_id, &StateEventType::RoomHistoryVisibility, "") .await .map(|c: RoomHistoryVisibilityEventContent| { c.history_visibility == HistoryVisibility::WorldReadable }) .unwrap_or(false) } /// Checks if guests are able to join a given room pub async fn guest_can_join(&self, room_id: &RoomId) -> bool { self.room_state_get_content(room_id, &StateEventType::RoomGuestAccess, "") .await .map(|c: RoomGuestAccessEventContent| c.guest_access == GuestAccess::CanJoin) .unwrap_or(false) } /// Gets the primary alias from canonical alias event pub async fn get_canonical_alias(&self, room_id: &RoomId) -> Result { self.room_state_get_content(room_id, &StateEventType::RoomCanonicalAlias, "") .await .and_then(|c: RoomCanonicalAliasEventContent| { c.alias .ok_or_else(|| err!(Request(NotFound("No alias found in event content.")))) }) } /// Gets the room topic pub async fn get_room_topic(&self, room_id: &RoomId) -> Result { self.room_state_get_content(room_id, &StateEventType::RoomTopic, "") .await .map(|c: RoomTopicEventContent| c.topic) } /// Returns the space join rule (`SpaceRoomJoinRule`) for a given room and /// any allowed room IDs if available. Will default to Invite and empty vec /// if doesnt exist or invalid, pub async fn get_space_join_rule( &self, room_id: &RoomId, ) -> (SpaceRoomJoinRule, Vec) { self.room_state_get_content(room_id, &StateEventType::RoomJoinRules, "") .await .map_or_else( |_| (SpaceRoomJoinRule::Invite, vec![]), |c: RoomJoinRulesEventContent| { (c.join_rule.clone().into(), self.allowed_room_ids(c.join_rule)) }, ) } /// Returns the join rules for a given room (`JoinRule` type). Will default /// to Invite if doesnt exist or invalid pub async fn get_join_rules(&self, room_id: &RoomId) -> JoinRule { self.room_state_get_content(room_id, &StateEventType::RoomJoinRules, "") .await .map_or_else(|_| JoinRule::Invite, |c: RoomJoinRulesEventContent| (c.join_rule)) } /// Returns an empty vec if not a restricted room pub fn allowed_room_ids(&self, join_rule: JoinRule) -> Vec { let mut room_ids = Vec::with_capacity(1); // restricted rooms generally only have 1 allowed room ID if let JoinRule::Restricted(r) | JoinRule::KnockRestricted(r) = join_rule { for rule in r.allow { if let AllowRule::RoomMembership(RoomMembership { room_id: membership }) = rule { room_ids.push(membership.clone()); } } } room_ids } pub async fn get_room_type(&self, room_id: &RoomId) -> Result { self.room_state_get_content(room_id, &StateEventType::RoomCreate, "") .await .and_then(|content: RoomCreateEventContent| { content .room_type .ok_or_else(|| err!(Request(NotFound("No type found in event content")))) }) } /// Gets the room's encryption algorithm if `m.room.encryption` state event /// is found pub async fn get_room_encryption( &self, room_id: &RoomId, ) -> Result { self.room_state_get_content(room_id, &StateEventType::RoomEncryption, "") .await .map(|content: RoomEncryptionEventContent| content.algorithm) } pub async fn is_encrypted_room(&self, room_id: &RoomId) -> bool { self.room_state_get(room_id, &StateEventType::RoomEncryption, "") .await .is_ok() } }