tuwunel_service/rooms/state_accessor/
mod.rs1mod 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 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 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 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 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 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 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 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}