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