matrix_sdk_ffi/
room_preview.rs

1use anyhow::Context as _;
2use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, Client};
3use ruma::room::{JoinRuleSummary, RoomType as RumaRoomType};
4
5use crate::{
6    client::{AllowRule, JoinRule},
7    error::ClientError,
8    room::{Membership, RoomHero},
9    room_member::{RoomMember, RoomMemberWithSenderInfo},
10    utils::AsyncRuntimeDropped,
11};
12
13/// A room preview for a room. It's intended to be used to represent rooms that
14/// aren't joined yet.
15#[derive(uniffi::Object)]
16pub struct RoomPreview {
17    inner: SdkRoomPreview,
18    client: AsyncRuntimeDropped<Client>,
19}
20
21#[matrix_sdk_ffi_macros::export]
22impl RoomPreview {
23    /// Returns the room info the preview contains.
24    pub fn info(&self) -> RoomPreviewInfo {
25        let info = &self.inner;
26        RoomPreviewInfo {
27            room_id: info.room_id.to_string(),
28            canonical_alias: info.canonical_alias.as_ref().map(|alias| alias.to_string()),
29            name: info.name.clone(),
30            topic: info.topic.clone(),
31            avatar_url: info.avatar_url.as_ref().map(|url| url.to_string()),
32            num_joined_members: info.num_joined_members,
33            num_active_members: info.num_active_members,
34            room_type: info.room_type.clone().into(),
35            is_history_world_readable: info.is_world_readable,
36            membership: info.state.map(|state| state.into()),
37            join_rule: info.join_rule.clone().map(Into::into),
38            is_direct: info.is_direct,
39            heroes: info
40                .heroes
41                .as_ref()
42                .map(|heroes| heroes.iter().map(|h| h.to_owned().into()).collect()),
43        }
44    }
45
46    /// Leave the room if the room preview state is either joined, invited or
47    /// knocked.
48    ///
49    /// If rejecting an invite then also forget it as an extra layer of
50    /// protection against spam attacks.
51    ///
52    /// Will return an error otherwise.
53    pub async fn leave(&self) -> Result<(), ClientError> {
54        let room =
55            self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
56
57        Ok(room.leave().await?)
58    }
59
60    /// Get the user who created the invite, if any.
61    pub async fn inviter(&self) -> Option<RoomMember> {
62        let room = self.client.get_room(&self.inner.room_id)?;
63        let invite_details = room.invite_details().await.ok()?;
64        invite_details.inviter.and_then(|m| m.try_into().ok())
65    }
66
67    /// Forget the room if we had access to it, and it was left or banned.
68    pub async fn forget(&self) -> Result<(), ClientError> {
69        let room =
70            self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
71        room.forget().await?;
72        Ok(())
73    }
74
75    /// Get the membership details for the current user.
76    pub async fn own_membership_details(&self) -> Option<RoomMemberWithSenderInfo> {
77        let room = self.client.get_room(&self.inner.room_id)?;
78        room.member_with_sender_info(self.client.user_id()?).await.ok()?.try_into().ok()
79    }
80}
81
82impl RoomPreview {
83    pub(crate) fn new(client: AsyncRuntimeDropped<Client>, inner: SdkRoomPreview) -> Self {
84        Self { client, inner }
85    }
86}
87
88/// The preview of a room, be it invited/joined/left, or not.
89#[derive(uniffi::Record)]
90pub struct RoomPreviewInfo {
91    /// The room id for this room.
92    pub room_id: String,
93    /// The canonical alias for the room.
94    pub canonical_alias: Option<String>,
95    /// The room's name, if set.
96    pub name: Option<String>,
97    /// The room's topic, if set.
98    pub topic: Option<String>,
99    /// The MXC URI to the room's avatar, if set.
100    pub avatar_url: Option<String>,
101    /// The number of joined members.
102    pub num_joined_members: u64,
103    /// The number of active members, if known (joined + invited).
104    pub num_active_members: Option<u64>,
105    /// The room type (space, custom) or nothing, if it's a regular room.
106    pub room_type: RoomType,
107    /// Is the history world-readable for this room?
108    pub is_history_world_readable: Option<bool>,
109    /// The membership state for the current user, if known.
110    pub membership: Option<Membership>,
111    /// The join rule for this room (private, public, knock, etc.).
112    pub join_rule: Option<JoinRule>,
113    /// Whether the room is direct or not, if known.
114    pub is_direct: Option<bool>,
115    /// Room heroes.
116    pub heroes: Option<Vec<RoomHero>>,
117}
118
119impl From<JoinRuleSummary> for JoinRule {
120    fn from(join_rule: JoinRuleSummary) -> Self {
121        match join_rule {
122            JoinRuleSummary::Invite => JoinRule::Invite,
123            JoinRuleSummary::Knock => JoinRule::Knock,
124            JoinRuleSummary::Private => JoinRule::Private,
125            JoinRuleSummary::Restricted(summary) => JoinRule::Restricted {
126                rules: summary
127                    .allowed_room_ids
128                    .iter()
129                    .map(|room_id| AllowRule::RoomMembership { room_id: room_id.to_string() })
130                    .collect(),
131            },
132            JoinRuleSummary::KnockRestricted(summary) => JoinRule::KnockRestricted {
133                rules: summary
134                    .allowed_room_ids
135                    .iter()
136                    .map(|room_id| AllowRule::RoomMembership { room_id: room_id.to_string() })
137                    .collect(),
138            },
139            JoinRuleSummary::Public => JoinRule::Public,
140            _ => JoinRule::Custom { repr: join_rule.as_str().to_owned() },
141        }
142    }
143}
144
145/// The type of room for a [`RoomPreviewInfo`].
146#[derive(Debug, Clone, uniffi::Enum)]
147pub enum RoomType {
148    /// It's a plain chat room.
149    Room,
150    /// It's a space that can group several rooms.
151    Space,
152    /// It's a custom implementation.
153    Custom { value: String },
154}
155
156impl From<Option<RumaRoomType>> for RoomType {
157    fn from(value: Option<RumaRoomType>) -> Self {
158        match value {
159            Some(RumaRoomType::Space) => RoomType::Space,
160            Some(RumaRoomType::_Custom(_)) => RoomType::Custom {
161                // SAFETY: this was checked in the match branch above
162                value: value.unwrap().to_string(),
163            },
164            _ => RoomType::Room,
165        }
166    }
167}