1use matrix_sdk_base::Room as BaseRoom;
2use ruma::{
3 api::client::{
4 directory::{get_room_visibility, set_room_visibility},
5 room::Visibility,
6 state::send_state_event,
7 },
8 assign,
9 events::{
10 room::{
11 canonical_alias::RoomCanonicalAliasEventContent,
12 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
13 join_rules::{JoinRule, RoomJoinRulesEventContent},
14 },
15 EmptyStateKey,
16 },
17 OwnedRoomAliasId, RoomAliasId,
18};
1920use crate::{Client, Result};
2122/// A helper to group the methods in [Room](crate::Room) related to the room's
23/// visibility and access.
24#[derive(Debug)]
25pub struct RoomPrivacySettings<'a> {
26 room: &'a BaseRoom,
27 client: &'a Client,
28}
2930impl<'a> RoomPrivacySettings<'a> {
31pub(crate) fn new(room: &'a BaseRoom, client: &'a Client) -> Self {
32Self { room, client }
33 }
3435/// Publish a new room alias for this room in the room directory.
36 ///
37 /// Returns:
38 /// - `true` if the room alias didn't exist and it's now published.
39 /// - `false` if the room alias was already present so it couldn't be
40 /// published.
41pub async fn publish_room_alias_in_room_directory(
42&'a self,
43 alias: &RoomAliasId,
44 ) -> Result<bool> {
45if self.client.is_room_alias_available(alias).await? {
46self.client.create_room_alias(alias, self.room.room_id()).await?;
47return Ok(true);
48 }
4950Ok(false)
51 }
5253/// Remove an existing room alias for this room in the room directory.
54 ///
55 /// Returns:
56 /// - `true` if the room alias was present and it's now removed from the
57 /// room directory.
58 /// - `false` if the room alias didn't exist so it couldn't be removed.
59pub async fn remove_room_alias_from_room_directory(
60&'a self,
61 alias: &RoomAliasId,
62 ) -> Result<bool> {
63if self.client.resolve_room_alias(alias).await.is_ok() {
64self.client.remove_room_alias(alias).await?;
65return Ok(true);
66 }
6768Ok(false)
69 }
7071/// Update the canonical alias of the room.
72 ///
73 /// # Arguments:
74 /// * `alias` - The new main alias to use for the room. A `None` value
75 /// removes the existing main canonical alias.
76 /// * `alt_aliases` - The list of alternative aliases for this room.
77 ///
78 /// See <https://spec.matrix.org/v1.12/client-server-api/#mroomcanonical_alias> for more info about the canonical alias.
79 ///
80 /// Note that publishing the alias in the room directory is done separately,
81 /// and a room alias must have already been published before it can be set
82 /// as the canonical alias.
83pub async fn update_canonical_alias(
84&'a self,
85 alias: Option<OwnedRoomAliasId>,
86 alt_aliases: Vec<OwnedRoomAliasId>,
87 ) -> Result<()> {
88// Create a new alias event combining both the new and previous values
89let content = assign!(
90 RoomCanonicalAliasEventContent::new(),
91 { alias, alt_aliases }
92 );
9394// Send the state event
95let request = send_state_event::v3::Request::new(
96self.room.room_id().to_owned(),
97&EmptyStateKey,
98&content,
99 )?;
100self.client.send(request).await?;
101102Ok(())
103 }
104105/// Update room history visibility for this room.
106 ///
107 /// The history visibility controls whether a user can see the events that
108 /// happened in a room before they joined.
109 ///
110 /// See <https://spec.matrix.org/v1.12/client-server-api/#mroomcanonical_alias> for more info.
111pub async fn update_room_history_visibility(
112&'a self,
113 new_value: HistoryVisibility,
114 ) -> Result<()> {
115let request = send_state_event::v3::Request::new(
116self.room.room_id().to_owned(),
117&EmptyStateKey,
118&RoomHistoryVisibilityEventContent::new(new_value),
119 )?;
120self.client.send(request).await?;
121Ok(())
122 }
123124/// Update the join rule for this room.
125 ///
126 /// The join rules controls if and how a new user can get access to the
127 /// room.
128 ///
129 /// See <https://spec.matrix.org/v1.12/client-server-api/#mroomjoin_rules> for more info.
130pub async fn update_join_rule(&'a self, new_rule: JoinRule) -> Result<()> {
131let request = send_state_event::v3::Request::new(
132self.room.room_id().to_owned(),
133&EmptyStateKey,
134&RoomJoinRulesEventContent::new(new_rule),
135 )?;
136self.client.send(request).await?;
137Ok(())
138 }
139140/// Returns the visibility for this room in the room directory.
141 ///
142 /// [Public](`Visibility::Public`) rooms are listed in the room directory
143 /// and can be found using it.
144pub async fn get_room_visibility(&'a self) -> Result<Visibility> {
145let request = get_room_visibility::v3::Request::new(self.room.room_id().to_owned());
146let response = self.client.send(request).await?;
147Ok(response.visibility)
148 }
149150/// Update the visibility for this room in the room directory.
151 ///
152 /// [Public](`Visibility::Public`) rooms are listed in the room directory
153 /// and can be found using it.
154pub async fn update_room_visibility(&'a self, visibility: Visibility) -> Result<()> {
155let request =
156 set_room_visibility::v3::Request::new(self.room.room_id().to_owned(), visibility);
157158self.client.send(request).await?;
159160Ok(())
161 }
162}
163164#[cfg(all(test, not(target_arch = "wasm32")))]
165mod tests {
166use std::ops::Not;
167168use matrix_sdk_test::{async_test, JoinedRoomBuilder, StateTestEvent};
169use ruma::{
170 api::client::room::Visibility,
171 event_id,
172 events::{
173 room::{history_visibility::HistoryVisibility, join_rules::JoinRule},
174 StateEventType,
175 },
176 owned_room_alias_id, room_id,
177 };
178179use crate::test_utils::mocks::MatrixMockServer;
180181#[async_test]
182async fn test_publish_room_alias_to_room_directory() {
183let server = MatrixMockServer::new().await;
184let client = server.client_builder().build().await;
185186let room_id = room_id!("!a:b.c");
187let room = server.sync_joined_room(&client, room_id).await;
188189let room_alias = owned_room_alias_id!("#a:b.c");
190191// First we'd check if the new alias needs to be created
192server
193 .mock_room_directory_resolve_alias()
194 .for_alias(room_alias.to_string())
195 .not_found()
196 .mock_once()
197 .mount()
198 .await;
199200// After that, we'd create a new room alias association in the room directory
201server.mock_room_directory_create_room_alias().ok().mock_once().mount().await;
202203let published = room
204 .privacy_settings()
205 .publish_room_alias_in_room_directory(&room_alias)
206 .await
207.expect("we should get a result value, not an error");
208assert!(published);
209 }
210211#[async_test]
212async fn test_publish_room_alias_to_room_directory_when_alias_exists() {
213let server = MatrixMockServer::new().await;
214let client = server.client_builder().build().await;
215216let room_id = room_id!("!a:b.c");
217let room = server.sync_joined_room(&client, room_id).await;
218219let room_alias = owned_room_alias_id!("#a:b.c");
220221// First we'd check if the new alias needs to be created. It does not.
222server
223 .mock_room_directory_resolve_alias()
224 .for_alias(room_alias.to_string())
225 .ok(room_id.as_ref(), Vec::new())
226 .mock_once()
227 .mount()
228 .await;
229230// Since the room alias already exists we won't create it again.
231server.mock_room_directory_create_room_alias().ok().never().mount().await;
232233let published = room
234 .privacy_settings()
235 .publish_room_alias_in_room_directory(&room_alias)
236 .await
237.expect("we should get a result value, not an error");
238assert!(published.not());
239 }
240241#[async_test]
242async fn test_remove_room_alias() {
243let server = MatrixMockServer::new().await;
244let client = server.client_builder().build().await;
245246let room_id = room_id!("!a:b.c");
247let joined_room_builder =
248 JoinedRoomBuilder::new(room_id).add_state_event(StateTestEvent::Alias);
249let room = server.sync_room(&client, joined_room_builder).await;
250251let room_alias = owned_room_alias_id!("#a:b.c");
252253// First we'd check if the alias exists
254server
255 .mock_room_directory_resolve_alias()
256 .for_alias(room_alias.to_string())
257 .ok(room_id.as_ref(), Vec::new())
258 .mock_once()
259 .mount()
260 .await;
261262// After that we'd remove it
263server.mock_room_directory_remove_room_alias().ok().mock_once().mount().await;
264265let removed = room
266 .privacy_settings()
267 .remove_room_alias_from_room_directory(&room_alias)
268 .await
269.expect("we should get a result value, not an error");
270assert!(removed);
271 }
272273#[async_test]
274async fn test_remove_room_alias_if_it_does_not_exist() {
275let server = MatrixMockServer::new().await;
276let client = server.client_builder().build().await;
277278let room_id = room_id!("!a:b.c");
279let joined_room_builder =
280 JoinedRoomBuilder::new(room_id).add_state_event(StateTestEvent::Alias);
281let room = server.sync_room(&client, joined_room_builder).await;
282283let room_alias = owned_room_alias_id!("#a:b.c");
284285// First we'd check if the alias exists. It doesn't.
286server
287 .mock_room_directory_resolve_alias()
288 .for_alias(room_alias.to_string())
289 .not_found()
290 .mock_once()
291 .mount()
292 .await;
293294// So we can't remove it after the check.
295server.mock_room_directory_remove_room_alias().ok().never().mount().await;
296297let removed = room
298 .privacy_settings()
299 .remove_room_alias_from_room_directory(&room_alias)
300 .await
301.expect("we should get a result value, not an error");
302assert!(removed.not());
303 }
304305#[async_test]
306async fn test_update_canonical_alias_with_some_value() {
307let server = MatrixMockServer::new().await;
308let client = server.client_builder().build().await;
309310let room_id = room_id!("!a:b.c");
311let room = server.sync_joined_room(&client, room_id).await;
312313 server
314 .mock_room_send_state()
315 .for_type(StateEventType::RoomCanonicalAlias)
316 .ok(event_id!("$a:b.c"))
317 .mock_once()
318 .mount()
319 .await;
320321let room_alias = owned_room_alias_id!("#a:b.c");
322let ret = room
323 .privacy_settings()
324 .update_canonical_alias(Some(room_alias.clone()), Vec::new())
325 .await;
326assert!(ret.is_ok());
327 }
328329#[async_test]
330async fn test_update_canonical_alias_with_no_value() {
331let server = MatrixMockServer::new().await;
332let client = server.client_builder().build().await;
333334let room_id = room_id!("!a:b.c");
335let room = server.sync_joined_room(&client, room_id).await;
336337 server
338 .mock_room_send_state()
339 .for_type(StateEventType::RoomCanonicalAlias)
340 .ok(event_id!("$a:b.c"))
341 .mock_once()
342 .mount()
343 .await;
344345let ret = room.privacy_settings().update_canonical_alias(None, Vec::new()).await;
346assert!(ret.is_ok());
347 }
348349#[async_test]
350async fn test_update_room_history_visibility() {
351let server = MatrixMockServer::new().await;
352let client = server.client_builder().build().await;
353354let room_id = room_id!("!a:b.c");
355let room = server.sync_joined_room(&client, room_id).await;
356357 server
358 .mock_room_send_state()
359 .for_type(StateEventType::RoomHistoryVisibility)
360 .ok(event_id!("$a:b.c"))
361 .mock_once()
362 .mount()
363 .await;
364365let ret =
366 room.privacy_settings().update_room_history_visibility(HistoryVisibility::Joined).await;
367assert!(ret.is_ok());
368 }
369370#[async_test]
371async fn test_update_join_rule() {
372let server = MatrixMockServer::new().await;
373let client = server.client_builder().build().await;
374375let room_id = room_id!("!a:b.c");
376let room = server.sync_joined_room(&client, room_id).await;
377378 server
379 .mock_room_send_state()
380 .for_type(StateEventType::RoomJoinRules)
381 .ok(event_id!("$a:b.c"))
382 .mock_once()
383 .mount()
384 .await;
385386let ret = room.privacy_settings().update_join_rule(JoinRule::Public).await;
387assert!(ret.is_ok());
388 }
389390#[async_test]
391async fn test_get_room_visibility() {
392let server = MatrixMockServer::new().await;
393let client = server.client_builder().build().await;
394395let room_id = room_id!("!a:b.c");
396let room = server.sync_joined_room(&client, room_id).await;
397398 server
399 .mock_room_send_state()
400 .for_type(StateEventType::RoomJoinRules)
401 .ok(event_id!("$a:b.c"))
402 .mock_once()
403 .mount()
404 .await;
405406let ret = room.privacy_settings().update_join_rule(JoinRule::Public).await;
407assert!(ret.is_ok());
408 }
409410#[async_test]
411async fn test_update_room_visibility() {
412let server = MatrixMockServer::new().await;
413let client = server.client_builder().build().await;
414415let room_id = room_id!("!a:b.c");
416let room = server.sync_joined_room(&client, room_id).await;
417418 server.mock_room_directory_set_room_visibility().ok().mock_once().mount().await;
419420let ret = room.privacy_settings().update_room_visibility(Visibility::Private).await;
421assert!(ret.is_ok());
422 }
423}