1use anyhow::Context as _;
2use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, Client};
3use ruma::{room::RoomType as RumaRoomType, space::SpaceRoomJoinRule};
4use tracing::warn;
56use crate::{
7 client::JoinRule,
8 error::ClientError,
9 room::{Membership, RoomHero},
10 room_member::RoomMember,
11 utils::AsyncRuntimeDropped,
12};
1314/// A room preview for a room. It's intended to be used to represent rooms that
15/// aren't joined yet.
16#[derive(uniffi::Object)]
17pub struct RoomPreview {
18 inner: SdkRoomPreview,
19 client: AsyncRuntimeDropped<Client>,
20}
2122#[matrix_sdk_ffi_macros::export]
23impl RoomPreview {
24/// Returns the room info the preview contains.
25pub fn info(&self) -> Result<RoomPreviewInfo, ClientError> {
26let info = &self.inner;
27Ok(RoomPreviewInfo {
28 room_id: info.room_id.to_string(),
29 canonical_alias: info.canonical_alias.as_ref().map(|alias| alias.to_string()),
30 name: info.name.clone(),
31 topic: info.topic.clone(),
32 avatar_url: info.avatar_url.as_ref().map(|url| url.to_string()),
33 num_joined_members: info.num_joined_members,
34 num_active_members: info.num_active_members,
35 room_type: info.room_type.as_ref().into(),
36 is_history_world_readable: info.is_world_readable,
37 membership: info.state.map(|state| state.into()),
38 join_rule: info
39 .join_rule
40 .clone()
41 .try_into()
42 .map_err(|_| anyhow::anyhow!("unhandled SpaceRoomJoinRule kind"))?,
43 is_direct: info.is_direct,
44 heroes: info
45 .heroes
46 .as_ref()
47 .map(|heroes| heroes.iter().map(|h| h.to_owned().into()).collect()),
48 })
49 }
5051/// Leave the room if the room preview state is either joined, invited or
52 /// knocked.
53 ///
54 /// If rejecting an invite then also forget it as an extra layer of
55 /// protection against spam attacks.
56 ///
57 /// Will return an error otherwise.
58pub async fn leave(&self) -> Result<(), ClientError> {
59let room =
60self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
6162let should_forget = matches!(room.state(), matrix_sdk::RoomState::Invited);
6364 room.leave().await.map_err(ClientError::from)?;
6566if should_forget {
67_ = self.forget().await;
68 }
6970Ok(())
71 }
7273/// Get the user who created the invite, if any.
74pub async fn inviter(&self) -> Option<RoomMember> {
75let room = self.client.get_room(&self.inner.room_id)?;
76let invite_details = room.invite_details().await.ok()?;
77 invite_details.inviter.and_then(|m| m.try_into().ok())
78 }
7980/// Forget the room if we had access to it, and it was left or banned.
81pub async fn forget(&self) -> Result<(), ClientError> {
82let room =
83self.client.get_room(&self.inner.room_id).context("missing room for a room preview")?;
84 room.forget().await?;
85Ok(())
86 }
8788/// Get the membership details for the current user.
89pub async fn own_membership_details(&self) -> Option<RoomMembershipDetails> {
90let room = self.client.get_room(&self.inner.room_id)?;
9192let (own_member, sender_member) = match room.own_membership_details().await {
93Ok(memberships) => memberships,
94Err(error) => {
95warn!("Couldn't get membership info: {error}");
96return None;
97 }
98 };
99100Some(RoomMembershipDetails {
101 own_room_member: own_member.try_into().ok()?,
102 sender_room_member: sender_member.and_then(|member| member.try_into().ok()),
103 })
104 }
105}
106107/// Contains the current user's room member info and the optional room member
108/// info of the sender of the `m.room.member` event that this info represents.
109#[derive(uniffi::Record)]
110pub struct RoomMembershipDetails {
111pub own_room_member: RoomMember,
112pub sender_room_member: Option<RoomMember>,
113}
114115impl RoomPreview {
116pub(crate) fn new(client: AsyncRuntimeDropped<Client>, inner: SdkRoomPreview) -> Self {
117Self { client, inner }
118 }
119}
120121/// The preview of a room, be it invited/joined/left, or not.
122#[derive(uniffi::Record)]
123pub struct RoomPreviewInfo {
124/// The room id for this room.
125pub room_id: String,
126/// The canonical alias for the room.
127pub canonical_alias: Option<String>,
128/// The room's name, if set.
129pub name: Option<String>,
130/// The room's topic, if set.
131pub topic: Option<String>,
132/// The MXC URI to the room's avatar, if set.
133pub avatar_url: Option<String>,
134/// The number of joined members.
135pub num_joined_members: u64,
136/// The number of active members, if known (joined + invited).
137pub num_active_members: Option<u64>,
138/// The room type (space, custom) or nothing, if it's a regular room.
139pub room_type: RoomType,
140/// Is the history world-readable for this room?
141pub is_history_world_readable: Option<bool>,
142/// The membership state for the current user, if known.
143pub membership: Option<Membership>,
144/// The join rule for this room (private, public, knock, etc.).
145pub join_rule: JoinRule,
146/// Whether the room is direct or not, if known.
147pub is_direct: Option<bool>,
148/// Room heroes.
149pub heroes: Option<Vec<RoomHero>>,
150}
151152impl TryFrom<SpaceRoomJoinRule> for JoinRule {
153type Error = ();
154155fn try_from(join_rule: SpaceRoomJoinRule) -> Result<Self, ()> {
156Ok(match join_rule {
157 SpaceRoomJoinRule::Invite => JoinRule::Invite,
158 SpaceRoomJoinRule::Knock => JoinRule::Knock,
159 SpaceRoomJoinRule::Private => JoinRule::Private,
160 SpaceRoomJoinRule::Restricted => JoinRule::Restricted { rules: Vec::new() },
161 SpaceRoomJoinRule::KnockRestricted => JoinRule::KnockRestricted { rules: Vec::new() },
162 SpaceRoomJoinRule::Public => JoinRule::Public,
163 SpaceRoomJoinRule::_Custom(_) => JoinRule::Custom { repr: join_rule.to_string() },
164_ => {
165warn!("unhandled SpaceRoomJoinRule: {join_rule}");
166return Err(());
167 }
168 })
169 }
170}
171172/// The type of room for a [`RoomPreviewInfo`].
173#[derive(Debug, Clone, uniffi::Enum)]
174pub enum RoomType {
175/// It's a plain chat room.
176Room,
177/// It's a space that can group several rooms.
178Space,
179/// It's a custom implementation.
180Custom { value: String },
181}
182183impl From<Option<&RumaRoomType>> for RoomType {
184fn from(value: Option<&RumaRoomType>) -> Self {
185match value {
186Some(RumaRoomType::Space) => RoomType::Space,
187Some(RumaRoomType::_Custom(_)) => RoomType::Custom {
188// SAFETY: this was checked in the match branch above
189value: value.unwrap().to_string(),
190 },
191_ => RoomType::Room,
192 }
193 }
194}