diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 43653cd5787..faca04ca12f 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -49,7 +49,7 @@ use ruma::{ room::power_levels::RoomPowerLevelsEventContent, GlobalAccountDataEventType, }, push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat}, - OwnedServerName, RoomAliasId, RoomOrAliasId, + OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName, }; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -771,25 +771,48 @@ impl Client { Ok(response.into()) } - /// Get the preview of a room, to interact with it. - pub async fn get_room_preview( + /// Given a room id, get the preview of a room, to interact with it. + /// + /// The list of `via_servers` must be a list of servers that know + /// about the room and can resolve it, and that may appear as a `via` + /// parameter in e.g. a permalink URL. This list can be empty. + pub async fn get_room_preview_from_room_id( &self, - room_id_or_alias: String, + room_id: String, + via_servers: Vec, ) -> Result { - let room_id = if let Ok(parsed_room_id) = RoomId::parse(&room_id_or_alias) { - parsed_room_id - } else { - // Try to resolve it as an alias. - let Ok(room_alias) = RoomAliasId::parse(&room_id_or_alias) else { - return Err(anyhow!("room_id_or_alias is neither a room id or an alias").into()); - }; - let response = self.inner.resolve_room_alias(&room_alias).await?; - response.room_id - }; + let room_id = RoomId::parse(&room_id).context("room_id is not a valid room id")?; + + let via_servers = via_servers + .into_iter() + .map(ServerName::parse) + .collect::, _>>() + .context("at least one `via` server name is invalid")?; + + // The `into()` call below doesn't work if I do `(&room_id).into()`, so I let + // rustc win that one fight. + let room_id: &RoomId = &room_id; + + let sdk_room_preview = self.inner.get_room_preview(room_id.into(), via_servers).await?; + + Ok(RoomPreview::from_sdk(sdk_room_preview)) + } + + /// Given a room alias, get the preview of a room, to interact with it. + pub async fn get_room_preview_from_room_alias( + &self, + room_alias: String, + ) -> Result { + let room_alias = + RoomAliasId::parse(&room_alias).context("room_alias is not a valid room alias")?; + + // The `into()` call below doesn't work if I do `(&room_id).into()`, so I let + // rustc win that one fight. + let room_alias: &RoomAliasId = &room_alias; - let sdk_room_preview = self.inner.get_room_preview(&room_id).await?; + let sdk_room_preview = self.inner.get_room_preview(room_alias.into(), Vec::new()).await?; - Ok(RoomPreview::from_sdk(room_id, sdk_room_preview)) + Ok(RoomPreview::from_sdk(sdk_room_preview)) } } diff --git a/bindings/matrix-sdk-ffi/src/room_preview.rs b/bindings/matrix-sdk-ffi/src/room_preview.rs index 112a6732fc5..68214bb11ec 100644 --- a/bindings/matrix-sdk-ffi/src/room_preview.rs +++ b/bindings/matrix-sdk-ffi/src/room_preview.rs @@ -1,5 +1,5 @@ use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, RoomState}; -use ruma::{space::SpaceRoomJoinRule, OwnedRoomId}; +use ruma::space::SpaceRoomJoinRule; /// The preview of a room, be it invited/joined/left, or not. #[derive(uniffi::Record)] @@ -31,9 +31,9 @@ pub struct RoomPreview { } impl RoomPreview { - pub(crate) fn from_sdk(room_id: OwnedRoomId, preview: SdkRoomPreview) -> Self { + pub(crate) fn from_sdk(preview: SdkRoomPreview) -> Self { Self { - room_id: room_id.to_string(), + room_id: preview.room_id.to_string(), canonical_alias: preview.canonical_alias.map(|alias| alias.to_string()), name: preview.name, topic: preview.topic, diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 91470963e93..692c7b8b355 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -944,11 +944,21 @@ impl Client { /// Gets the preview of a room, whether the current user knows it (because /// they've joined/left/been invited to it) or not. - pub async fn get_room_preview(&self, room_id: &RoomId) -> Result { - if let Some(room) = self.get_room(room_id) { + pub async fn get_room_preview( + &self, + room_or_alias_id: &RoomOrAliasId, + via: Vec, + ) -> Result { + let room_id = match <&RoomId>::try_from(room_or_alias_id) { + Ok(room_id) => room_id.to_owned(), + Err(alias) => self.resolve_room_alias(alias).await?.room_id, + }; + + if let Some(room) = self.get_room(&room_id) { return Ok(RoomPreview::from_known(&room)); } - RoomPreview::from_unknown(self, room_id).await + + RoomPreview::from_unknown(self, room_id, room_or_alias_id, via).await } /// Resolve a room alias to a room id and a list of servers which know diff --git a/crates/matrix-sdk/src/room_preview.rs b/crates/matrix-sdk/src/room_preview.rs index a113f80e152..20fc29c0093 100644 --- a/crates/matrix-sdk/src/room_preview.rs +++ b/crates/matrix-sdk/src/room_preview.rs @@ -24,7 +24,7 @@ use ruma::{ events::room::{history_visibility::HistoryVisibility, join_rules::JoinRule}, room::RoomType, space::SpaceRoomJoinRule, - OwnedMxcUri, OwnedRoomAliasId, RoomId, + OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedServerName, RoomId, RoomOrAliasId, }; use tokio::try_join; use tracing::{instrument, warn}; @@ -34,6 +34,12 @@ use crate::{Client, Room}; /// The preview of a room, be it invited/joined/left, or not. #[derive(Debug)] pub struct RoomPreview { + /// The actual room id for this room. + /// + /// Remember the room preview can be fetched from a room alias id, so we + /// might not know ahead of time what the room id is. + pub room_id: OwnedRoomId, + /// The canonical alias for the room. pub canonical_alias: Option, @@ -76,6 +82,7 @@ impl RoomPreview { state: Option, ) -> Self { RoomPreview { + room_id: room_info.room_id().to_owned(), canonical_alias: room_info.canonical_alias().map(ToOwned::to_owned), name: room_info.name().map(ToOwned::to_owned), topic: room_info.topic().map(ToOwned::to_owned), @@ -108,10 +115,15 @@ impl RoomPreview { } #[instrument(skip(client))] - pub(crate) async fn from_unknown(client: &Client, room_id: &RoomId) -> crate::Result { + pub(crate) async fn from_unknown( + client: &Client, + room_id: OwnedRoomId, + room_or_alias_id: &RoomOrAliasId, + via: Vec, + ) -> crate::Result { // Use the room summary endpoint, if available, as described in // https://github.com/deepbluev7/matrix-doc/blob/room-summaries/proposals/3266-room-summary.md - match Self::from_room_summary(client, room_id).await { + match Self::from_room_summary(client, room_id.clone(), room_or_alias_id, via).await { Ok(res) => return Ok(res), Err(err) => { warn!("error when previewing room from the room summary endpoint: {err}"); @@ -123,7 +135,7 @@ impl RoomPreview { // - then use a public room filter set to this room id // Resort to using the room state endpoint, as well as the joined members one. - Self::from_state_events(client, room_id).await + Self::from_state_events(client, &room_id).await } /// Get a [`RoomPreview`] using MSC3266, if available on the remote server. @@ -132,10 +144,15 @@ impl RoomPreview { /// /// This method is exposed for testing purposes; clients should prefer /// `Client::get_room_preview` in general over this. - pub async fn from_room_summary(client: &Client, room_id: &RoomId) -> crate::Result { + pub async fn from_room_summary( + client: &Client, + room_id: OwnedRoomId, + room_or_alias_id: &RoomOrAliasId, + via: Vec, + ) -> crate::Result { let request = ruma::api::client::room::get_summary::msc3266::Request::new( - room_id.to_owned().into(), - Vec::new(), + room_or_alias_id.to_owned(), + via, ); let response = client.send(request, None).await?; @@ -143,13 +160,14 @@ impl RoomPreview { // The server returns a `Left` room state for rooms the user has not joined. Be // more precise than that, and set it to `None` if we haven't joined // that room. - let state = if client.get_room(room_id).is_none() { + let state = if client.get_room(&room_id).is_none() { None } else { response.membership.map(|membership| RoomState::from(&membership)) }; Ok(RoomPreview { + room_id, canonical_alias: response.canonical_alias, name: response.name, topic: response.topic, diff --git a/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs b/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs index d3251c4f8d4..60a08adaaf6 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/sliding_sync/room.rs @@ -1118,7 +1118,7 @@ async fn test_room_preview() -> Result<()> { { // Dummy test for `Client::get_room_preview` which may call one or the other // methods. - let preview = alice.get_room_preview(room_id).await.unwrap(); + let preview = alice.get_room_preview(room_id.into(), Vec::new()).await.unwrap(); assert_room_preview(&preview, &room_alias); assert_eq!(preview.state, Some(RoomState::Joined)); } @@ -1169,20 +1169,47 @@ async fn get_room_preview_with_room_summary( public_no_history_room_id: &RoomId, ) { // Alice has joined the room, so they get the full details. - let preview = RoomPreview::from_room_summary(alice, room_id).await.unwrap(); + let preview = + RoomPreview::from_room_summary(alice, room_id.to_owned(), room_id.into(), Vec::new()) + .await + .unwrap(); + + assert_room_preview(&preview, room_alias); + assert_eq!(preview.state, Some(RoomState::Joined)); + + // The preview also works when using the room alias parameter. + let full_alias = format!("#{room_alias}:{}", alice.user_id().unwrap().server_name()); + let preview = RoomPreview::from_room_summary( + alice, + room_id.to_owned(), + <_>::try_from(full_alias.as_str()).unwrap(), + Vec::new(), + ) + .await + .unwrap(); assert_room_preview(&preview, room_alias); assert_eq!(preview.state, Some(RoomState::Joined)); // Bob definitely doesn't know about the room, but they can get a preview of the // room too. - let preview = RoomPreview::from_room_summary(bob, room_id).await.unwrap(); + let preview = + RoomPreview::from_room_summary(bob, room_id.to_owned(), room_id.into(), Vec::new()) + .await + .unwrap(); assert_room_preview(&preview, room_alias); assert!(preview.state.is_none()); // Bob can preview the second room with the room summary (because its join rule // is set to public, or because Alice is a member of that room). - let preview = RoomPreview::from_room_summary(bob, public_no_history_room_id).await.unwrap(); + let preview = RoomPreview::from_room_summary( + bob, + public_no_history_room_id.to_owned(), + public_no_history_room_id.into(), + Vec::new(), + ) + .await + .unwrap(); assert_eq!(preview.name.unwrap(), "Alice's Room 2"); assert!(preview.state.is_none()); }