tuwunel_service/rooms/state_accessor/
mod.rs1mod erased;
7mod room_state;
8mod server_can;
9mod state;
10mod user_can;
11
12use std::sync::Arc;
13
14use async_trait::async_trait;
15use futures::{FutureExt, TryFutureExt, future::try_join};
16use ruma::{
17 EventEncryptionAlgorithm, OwnedRoomAliasId, RoomId, UserId,
18 events::{
19 StateEventType,
20 room::{
21 avatar::RoomAvatarEventContent,
22 canonical_alias::RoomCanonicalAliasEventContent,
23 create::RoomCreateEventContent,
24 encryption::RoomEncryptionEventContent,
25 guest_access::{GuestAccess, RoomGuestAccessEventContent},
26 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
27 join_rules::{JoinRule, RoomJoinRulesEventContent},
28 member::RoomMemberEventContent,
29 name::RoomNameEventContent,
30 power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
31 topic::RoomTopicEventContent,
32 },
33 },
34 room::RoomType,
35};
36use tuwunel_core::{
37 Result, err, implement,
38 matrix::{Pdu, room_version},
39 utils::BoolExt,
40};
41
42use crate::rooms::state_res::events::RoomCreateEvent;
43
44pub struct Service {
49 services: Arc<crate::services::OnceServices>,
50}
51
52#[async_trait]
53impl crate::Service for Service {
54 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
55 Ok(Arc::new(Self { services: args.services.clone() }))
56 }
57
58 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
59}
60
61impl Service {
62 pub async fn get_power_levels(&self, room_id: &RoomId) -> Result<RoomPowerLevels> {
67 let create = self.get_create(room_id);
68 let power_levels = self
69 .room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "")
70 .map_ok(|c: RoomPowerLevelsEventContent| c)
71 .map(Result::ok)
72 .map(Ok);
73
74 let (create, power_levels) = try_join(create, power_levels).await?;
75
76 let room_version = create.room_version()?;
77 let rules = room_version::rules(&room_version)?;
78 let creators = create.creators(&rules.authorization)?;
79
80 Ok(RoomPowerLevels::new(power_levels.into(), &rules.authorization, creators))
81 }
82
83 pub async fn get_create(&self, room_id: &RoomId) -> Result<RoomCreateEvent<Pdu>> {
88 self.room_state_get(room_id, &StateEventType::RoomCreate, "")
89 .await
90 .map(RoomCreateEvent::new)
91 }
92
93 pub async fn get_name(&self, room_id: &RoomId) -> Result<String> {
97 self.room_state_get_content(room_id, &StateEventType::RoomName, "")
98 .await
99 .and_then(|c: RoomNameEventContent| {
100 c.name
101 .is_empty()
102 .is_false()
103 .then_some(c.name)
104 .ok_or_else(|| err!(Request(NotFound("Empty name found in event content."))))
105 })
106 }
107
108 pub async fn get_avatar(&self, room_id: &RoomId) -> Result<RoomAvatarEventContent> {
112 self.room_state_get_content(room_id, &StateEventType::RoomAvatar, "")
113 .await
114 }
115
116 pub async fn get_member(
121 &self,
122 room_id: &RoomId,
123 user_id: &UserId,
124 ) -> Result<RoomMemberEventContent> {
125 self.room_state_get_content(room_id, &StateEventType::RoomMember, user_id.as_str())
126 .await
127 }
128
129 pub async fn is_world_readable(&self, room_id: &RoomId) -> bool {
134 self.room_state_get_content(room_id, &StateEventType::RoomHistoryVisibility, "")
135 .await
136 .map(|c: RoomHistoryVisibilityEventContent| {
137 c.history_visibility == HistoryVisibility::WorldReadable
138 })
139 .unwrap_or(false)
140 }
141
142 pub async fn guest_can_join(&self, room_id: &RoomId) -> bool {
147 self.room_state_get_content(room_id, &StateEventType::RoomGuestAccess, "")
148 .await
149 .map(|c: RoomGuestAccessEventContent| c.guest_access == GuestAccess::CanJoin)
150 .unwrap_or(false)
151 }
152
153 pub async fn get_canonical_alias(&self, room_id: &RoomId) -> Result<OwnedRoomAliasId> {
158 self.room_state_get_content(room_id, &StateEventType::RoomCanonicalAlias, "")
159 .await
160 .and_then(|c: RoomCanonicalAliasEventContent| {
161 c.alias
162 .ok_or_else(|| err!(Request(NotFound("No alias found in event content."))))
163 })
164 }
165
166 pub async fn get_room_topic(&self, room_id: &RoomId) -> Result<String> {
171 self.room_state_get_content(room_id, &StateEventType::RoomTopic, "")
172 .await
173 .and_then(|content: RoomTopicEventContent| {
174 plain_text_topic(content)
175 .ok_or_else(|| err!(Request(NotFound("Empty topic found in event content."))))
176 })
177 }
178
179 pub async fn get_join_rules(&self, room_id: &RoomId) -> JoinRule {
184 self.room_state_get_content(room_id, &StateEventType::RoomJoinRules, "")
185 .await
186 .map_or(JoinRule::Invite, |c: RoomJoinRulesEventContent| c.join_rule)
187 }
188
189 pub async fn get_room_type(&self, room_id: &RoomId) -> Result<RoomType> {
194 self.room_state_get_content(room_id, &StateEventType::RoomCreate, "")
195 .await
196 .and_then(|content: RoomCreateEventContent| {
197 content
198 .room_type
199 .ok_or_else(|| err!(Request(NotFound("No type found in event content"))))
200 })
201 }
202
203 pub async fn get_room_encryption(
207 &self,
208 room_id: &RoomId,
209 ) -> Result<EventEncryptionAlgorithm> {
210 self.room_state_get_content(room_id, &StateEventType::RoomEncryption, "")
211 .await
212 .map(|content: RoomEncryptionEventContent| content.algorithm)
213 }
214
215 pub async fn is_encrypted_room(&self, room_id: &RoomId) -> bool {
220 self.room_state_get(room_id, &StateEventType::RoomEncryption, "")
221 .await
222 .is_ok()
223 }
224}
225
226#[implement(Service)]
232pub async fn is_federating(&self, room_id: &RoomId) -> bool {
233 self.get_create(room_id)
234 .await
235 .and_then(|create| create.federate())
236 .unwrap_or(true)
237}
238
239pub(crate) fn plain_text_topic(content: RoomTopicEventContent) -> Option<String> {
244 let topic = content
245 .topic_block
246 .text
247 .find_plain()
248 .map(ToOwned::to_owned)
249 .unwrap_or(content.topic);
250
251 topic.is_empty().is_false().then_some(topic)
252}