Skip to main content

tuwunel_service/rooms/state_accessor/
mod.rs

1mod room_state;
2mod server_can;
3mod state;
4mod user_can;
5
6use std::sync::Arc;
7
8use async_trait::async_trait;
9use futures::{FutureExt, TryFutureExt, future::try_join};
10use ruma::{
11	EventEncryptionAlgorithm, OwnedRoomAliasId, RoomId, UserId,
12	events::{
13		StateEventType,
14		room::{
15			avatar::RoomAvatarEventContent,
16			canonical_alias::RoomCanonicalAliasEventContent,
17			create::RoomCreateEventContent,
18			encryption::RoomEncryptionEventContent,
19			guest_access::{GuestAccess, RoomGuestAccessEventContent},
20			history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
21			join_rules::{JoinRule, RoomJoinRulesEventContent},
22			member::RoomMemberEventContent,
23			name::RoomNameEventContent,
24			power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
25			topic::RoomTopicEventContent,
26		},
27	},
28	room::RoomType,
29};
30use tuwunel_core::{
31	Result, err, is_true,
32	matrix::{Pdu, room_version},
33	utils::BoolExt,
34};
35
36use crate::rooms::state_res::events::RoomCreateEvent;
37
38pub struct Service {
39	services: Arc<crate::services::OnceServices>,
40}
41
42#[async_trait]
43impl crate::Service for Service {
44	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
45		Ok(Arc::new(Self { services: args.services.clone() }))
46	}
47
48	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
49}
50
51impl Service {
52	/// Gets the effective power levels of a room, regardless of if there is an
53	/// `m.room.power_levels` state.
54	pub async fn get_power_levels(&self, room_id: &RoomId) -> Result<RoomPowerLevels> {
55		let create = self.get_create(room_id);
56		let power_levels = self
57			.room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "")
58			.map_ok(|c: RoomPowerLevelsEventContent| c)
59			.map(Result::ok)
60			.map(Ok);
61
62		let (create, power_levels) = try_join(create, power_levels).await?;
63
64		let room_version = create.room_version()?;
65		let rules = room_version::rules(&room_version)?;
66		let creators = create.creators(&rules.authorization)?;
67
68		Ok(RoomPowerLevels::new(power_levels.into(), &rules.authorization, creators))
69	}
70
71	pub async fn get_create(&self, room_id: &RoomId) -> Result<RoomCreateEvent<Pdu>> {
72		self.room_state_get(room_id, &StateEventType::RoomCreate, "")
73			.await
74			.map(RoomCreateEvent::new)
75	}
76
77	pub async fn get_name(&self, room_id: &RoomId) -> Result<String> {
78		self.room_state_get_content(room_id, &StateEventType::RoomName, "")
79			.await
80			.and_then(|c: RoomNameEventContent| {
81				c.name
82					.is_empty()
83					.is_false()
84					.then_some(c.name)
85					.ok_or_else(|| err!(Request(NotFound("Empty name found in event content."))))
86			})
87	}
88
89	pub async fn get_avatar(&self, room_id: &RoomId) -> Result<RoomAvatarEventContent> {
90		self.room_state_get_content(room_id, &StateEventType::RoomAvatar, "")
91			.await
92	}
93
94	pub async fn is_direct(&self, room_id: &RoomId, user_id: &UserId) -> bool {
95		self.get_member(room_id, user_id)
96			.await
97			.ok()
98			.and_then(|content| content.is_direct)
99			.is_some_and(is_true!())
100	}
101
102	pub async fn get_member(
103		&self,
104		room_id: &RoomId,
105		user_id: &UserId,
106	) -> Result<RoomMemberEventContent> {
107		self.room_state_get_content(room_id, &StateEventType::RoomMember, user_id.as_str())
108			.await
109	}
110
111	/// Checks if guests are able to view room content without joining
112	pub async fn is_world_readable(&self, room_id: &RoomId) -> bool {
113		self.room_state_get_content(room_id, &StateEventType::RoomHistoryVisibility, "")
114			.await
115			.map(|c: RoomHistoryVisibilityEventContent| {
116				c.history_visibility == HistoryVisibility::WorldReadable
117			})
118			.unwrap_or(false)
119	}
120
121	/// Checks if guests are able to join a given room
122	pub async fn guest_can_join(&self, room_id: &RoomId) -> bool {
123		self.room_state_get_content(room_id, &StateEventType::RoomGuestAccess, "")
124			.await
125			.map(|c: RoomGuestAccessEventContent| c.guest_access == GuestAccess::CanJoin)
126			.unwrap_or(false)
127	}
128
129	/// Gets the primary alias from canonical alias event
130	pub async fn get_canonical_alias(&self, room_id: &RoomId) -> Result<OwnedRoomAliasId> {
131		self.room_state_get_content(room_id, &StateEventType::RoomCanonicalAlias, "")
132			.await
133			.and_then(|c: RoomCanonicalAliasEventContent| {
134				c.alias
135					.ok_or_else(|| err!(Request(NotFound("No alias found in event content."))))
136			})
137	}
138
139	/// Gets the room topic
140	pub async fn get_room_topic(&self, room_id: &RoomId) -> Result<String> {
141		self.room_state_get_content(room_id, &StateEventType::RoomTopic, "")
142			.await
143			.map(|c: RoomTopicEventContent| c.topic)
144	}
145
146	/// Returns the join rules for a given room (`JoinRule` type). Will default
147	/// to Invite if doesnt exist or invalid
148	pub async fn get_join_rules(&self, room_id: &RoomId) -> JoinRule {
149		self.room_state_get_content(room_id, &StateEventType::RoomJoinRules, "")
150			.await
151			.map_or(JoinRule::Invite, |c: RoomJoinRulesEventContent| c.join_rule)
152	}
153
154	pub async fn get_room_type(&self, room_id: &RoomId) -> Result<RoomType> {
155		self.room_state_get_content(room_id, &StateEventType::RoomCreate, "")
156			.await
157			.and_then(|content: RoomCreateEventContent| {
158				content
159					.room_type
160					.ok_or_else(|| err!(Request(NotFound("No type found in event content"))))
161			})
162	}
163
164	/// Gets the room's encryption algorithm if `m.room.encryption` state event
165	/// is found
166	pub async fn get_room_encryption(
167		&self,
168		room_id: &RoomId,
169	) -> Result<EventEncryptionAlgorithm> {
170		self.room_state_get_content(room_id, &StateEventType::RoomEncryption, "")
171			.await
172			.map(|content: RoomEncryptionEventContent| content.algorithm)
173	}
174
175	pub async fn is_encrypted_room(&self, room_id: &RoomId) -> bool {
176		self.room_state_get(room_id, &StateEventType::RoomEncryption, "")
177			.await
178			.is_ok()
179	}
180}