matrix_sdk_ffi/
event.rs

1use anyhow::{bail, Context};
2use matrix_sdk::IdParseError;
3use matrix_sdk_ui::timeline::TimelineEventItemId;
4use ruma::{
5    events::{
6        room::{
7            encrypted,
8            message::{MessageType as RumaMessageType, Relation},
9            redaction::SyncRoomRedactionEvent,
10        },
11        AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent,
12        MessageLikeEventContent as RumaMessageLikeEventContent, RedactContent,
13        RedactedStateEventContent, StaticStateEventContent, SyncMessageLikeEvent, SyncStateEvent,
14    },
15    EventId,
16};
17
18use crate::{
19    room_member::MembershipState,
20    ruma::{MessageType, RtcNotificationType},
21    utils::Timestamp,
22    ClientError,
23};
24
25#[derive(uniffi::Object)]
26pub struct TimelineEvent(pub(crate) Box<AnySyncTimelineEvent>);
27
28#[matrix_sdk_ffi_macros::export]
29impl TimelineEvent {
30    pub fn event_id(&self) -> String {
31        self.0.event_id().to_string()
32    }
33
34    pub fn sender_id(&self) -> String {
35        self.0.sender().to_string()
36    }
37
38    pub fn timestamp(&self) -> Timestamp {
39        self.0.origin_server_ts().into()
40    }
41
42    pub fn event_type(&self) -> Result<TimelineEventType, ClientError> {
43        let event_type = match &*self.0 {
44            AnySyncTimelineEvent::MessageLike(event) => {
45                TimelineEventType::MessageLike { content: event.clone().try_into()? }
46            }
47            AnySyncTimelineEvent::State(event) => {
48                TimelineEventType::State { content: event.clone().try_into()? }
49            }
50        };
51        Ok(event_type)
52    }
53
54    /// Returns the thread root event id for the event, if it's part of a
55    /// thread.
56    pub fn thread_root_event_id(&self) -> Option<String> {
57        match &*self.0 {
58            AnySyncTimelineEvent::MessageLike(event) => {
59                match event.original_content().and_then(|content| content.relation()) {
60                    Some(encrypted::Relation::Thread(thread)) => Some(thread.event_id.to_string()),
61                    _ => None,
62                }
63            }
64            AnySyncTimelineEvent::State(_) => None,
65        }
66    }
67}
68
69impl From<AnyTimelineEvent> for TimelineEvent {
70    fn from(event: AnyTimelineEvent) -> Self {
71        Self(Box::new(event.into()))
72    }
73}
74
75#[derive(uniffi::Enum)]
76// A note about this `allow(clippy::large_enum_variant)`.
77// In order to reduce the size of `TimelineEventType`, we would need to
78// put some parts in a `Box`, or an `Arc`. Sadly, it doesn't play well with
79// UniFFI. We would need to change the `uniffi::Record` of the subtypes into
80// `uniffi::Object`, which is a radical change. It would simplify the memory
81// usage, but it would slow down the performance around the FFI border. Thus,
82// let's consider this is a false-positive lint in this particular case.
83#[allow(clippy::large_enum_variant)]
84pub enum TimelineEventType {
85    MessageLike { content: MessageLikeEventContent },
86    State { content: StateEventContent },
87}
88
89#[derive(uniffi::Enum)]
90pub enum StateEventContent {
91    PolicyRuleRoom,
92    PolicyRuleServer,
93    PolicyRuleUser,
94    RoomAliases,
95    RoomAvatar,
96    RoomCanonicalAlias,
97    RoomCreate,
98    RoomEncryption,
99    RoomGuestAccess,
100    RoomHistoryVisibility,
101    RoomJoinRules,
102    RoomMemberContent { user_id: String, membership_state: MembershipState },
103    RoomName,
104    RoomPinnedEvents,
105    RoomPowerLevels,
106    RoomServerAcl,
107    RoomThirdPartyInvite,
108    RoomTombstone,
109    RoomTopic { topic: String },
110    SpaceChild,
111    SpaceParent,
112}
113
114impl TryFrom<AnySyncStateEvent> for StateEventContent {
115    type Error = anyhow::Error;
116
117    fn try_from(value: AnySyncStateEvent) -> anyhow::Result<Self> {
118        let event = match value {
119            AnySyncStateEvent::PolicyRuleRoom(_) => StateEventContent::PolicyRuleRoom,
120            AnySyncStateEvent::PolicyRuleServer(_) => StateEventContent::PolicyRuleServer,
121            AnySyncStateEvent::PolicyRuleUser(_) => StateEventContent::PolicyRuleUser,
122            AnySyncStateEvent::RoomAliases(_) => StateEventContent::RoomAliases,
123            AnySyncStateEvent::RoomAvatar(_) => StateEventContent::RoomAvatar,
124            AnySyncStateEvent::RoomCanonicalAlias(_) => StateEventContent::RoomCanonicalAlias,
125            AnySyncStateEvent::RoomCreate(_) => StateEventContent::RoomCreate,
126            AnySyncStateEvent::RoomEncryption(_) => StateEventContent::RoomEncryption,
127            AnySyncStateEvent::RoomGuestAccess(_) => StateEventContent::RoomGuestAccess,
128            AnySyncStateEvent::RoomHistoryVisibility(_) => StateEventContent::RoomHistoryVisibility,
129            AnySyncStateEvent::RoomJoinRules(_) => StateEventContent::RoomJoinRules,
130            AnySyncStateEvent::RoomMember(content) => {
131                let state_key = content.state_key().to_string();
132                let original_content = get_state_event_original_content(content)?;
133                StateEventContent::RoomMemberContent {
134                    user_id: state_key,
135                    membership_state: original_content.membership.try_into()?,
136                }
137            }
138            AnySyncStateEvent::RoomName(_) => StateEventContent::RoomName,
139            AnySyncStateEvent::RoomPinnedEvents(_) => StateEventContent::RoomPinnedEvents,
140            AnySyncStateEvent::RoomPowerLevels(_) => StateEventContent::RoomPowerLevels,
141            AnySyncStateEvent::RoomServerAcl(_) => StateEventContent::RoomServerAcl,
142            AnySyncStateEvent::RoomThirdPartyInvite(_) => StateEventContent::RoomThirdPartyInvite,
143            AnySyncStateEvent::RoomTombstone(_) => StateEventContent::RoomTombstone,
144            AnySyncStateEvent::RoomTopic(content) => {
145                let content = get_state_event_original_content(content)?;
146
147                StateEventContent::RoomTopic { topic: content.topic }
148            }
149            AnySyncStateEvent::SpaceChild(_) => StateEventContent::SpaceChild,
150            AnySyncStateEvent::SpaceParent(_) => StateEventContent::SpaceParent,
151            _ => bail!("Unsupported state event: {:?}", value.event_type()),
152        };
153        Ok(event)
154    }
155}
156
157#[derive(uniffi::Enum)]
158// A note about this `allow(clippy::large_enum_variant)`.
159// In order to reduce the size of `MessageLineEventContent`, we would need to
160// put some parts in a `Box`, or an `Arc`. Sadly, it doesn't play well with
161// UniFFI. We would need to change the `uniffi::Record` of the subtypes into
162// `uniffi::Object`, which is a radical change. It would simplify the memory
163// usage, but it would slow down the performance around the FFI border. Thus,
164// let's consider this is a false-positive lint in this particular case.
165#[allow(clippy::large_enum_variant)]
166pub enum MessageLikeEventContent {
167    CallAnswer,
168    CallInvite,
169    RtcNotification {
170        notification_type: RtcNotificationType,
171        /// The timestamp at which this notification is considered invalid.
172        expiration_ts: Timestamp,
173    },
174    CallHangup,
175    CallCandidates,
176    KeyVerificationReady,
177    KeyVerificationStart,
178    KeyVerificationCancel,
179    KeyVerificationAccept,
180    KeyVerificationKey,
181    KeyVerificationMac,
182    KeyVerificationDone,
183    Poll {
184        question: String,
185    },
186    ReactionContent {
187        related_event_id: String,
188    },
189    RoomEncrypted,
190    RoomMessage {
191        message_type: MessageType,
192        in_reply_to_event_id: Option<String>,
193    },
194    RoomRedaction {
195        redacted_event_id: Option<String>,
196        reason: Option<String>,
197    },
198    Sticker,
199}
200
201impl TryFrom<AnySyncMessageLikeEvent> for MessageLikeEventContent {
202    type Error = anyhow::Error;
203
204    fn try_from(value: AnySyncMessageLikeEvent) -> anyhow::Result<Self> {
205        let content = match value {
206            AnySyncMessageLikeEvent::CallAnswer(_) => MessageLikeEventContent::CallAnswer,
207            AnySyncMessageLikeEvent::CallInvite(_) => MessageLikeEventContent::CallInvite,
208            AnySyncMessageLikeEvent::RtcNotification(event) => {
209                let origin_server_ts = event.origin_server_ts();
210                let original_content = get_message_like_event_original_content(event)?;
211                let expiration_ts = original_content.expiration_ts(origin_server_ts, None).into();
212                MessageLikeEventContent::RtcNotification {
213                    notification_type: original_content.notification_type.into(),
214                    expiration_ts,
215                }
216            }
217            AnySyncMessageLikeEvent::CallHangup(_) => MessageLikeEventContent::CallHangup,
218            AnySyncMessageLikeEvent::CallCandidates(_) => MessageLikeEventContent::CallCandidates,
219            AnySyncMessageLikeEvent::KeyVerificationReady(_) => {
220                MessageLikeEventContent::KeyVerificationReady
221            }
222            AnySyncMessageLikeEvent::KeyVerificationStart(_) => {
223                MessageLikeEventContent::KeyVerificationStart
224            }
225            AnySyncMessageLikeEvent::KeyVerificationCancel(_) => {
226                MessageLikeEventContent::KeyVerificationCancel
227            }
228            AnySyncMessageLikeEvent::KeyVerificationAccept(_) => {
229                MessageLikeEventContent::KeyVerificationAccept
230            }
231            AnySyncMessageLikeEvent::KeyVerificationKey(_) => {
232                MessageLikeEventContent::KeyVerificationKey
233            }
234            AnySyncMessageLikeEvent::KeyVerificationMac(_) => {
235                MessageLikeEventContent::KeyVerificationMac
236            }
237            AnySyncMessageLikeEvent::KeyVerificationDone(_) => {
238                MessageLikeEventContent::KeyVerificationDone
239            }
240            AnySyncMessageLikeEvent::UnstablePollStart(content) => {
241                let original_content = get_message_like_event_original_content(content)?;
242                MessageLikeEventContent::Poll {
243                    question: original_content.poll_start().question.text.clone(),
244                }
245            }
246            AnySyncMessageLikeEvent::Reaction(content) => {
247                let original_content = get_message_like_event_original_content(content)?;
248                MessageLikeEventContent::ReactionContent {
249                    related_event_id: original_content.relates_to.event_id.to_string(),
250                }
251            }
252            AnySyncMessageLikeEvent::RoomEncrypted(_) => MessageLikeEventContent::RoomEncrypted,
253            AnySyncMessageLikeEvent::RoomMessage(content) => {
254                let original_content = get_message_like_event_original_content(content)?;
255                let in_reply_to_event_id =
256                    original_content.relates_to.and_then(|relation| match relation {
257                        Relation::Reply { in_reply_to } => Some(in_reply_to.event_id.to_string()),
258                        _ => None,
259                    });
260                MessageLikeEventContent::RoomMessage {
261                    message_type: original_content.msgtype.try_into()?,
262                    in_reply_to_event_id,
263                }
264            }
265            AnySyncMessageLikeEvent::RoomRedaction(c) => {
266                let (redacted_event_id, reason) = match c {
267                    SyncRoomRedactionEvent::Original(o) => {
268                        let id =
269                            if o.content.redacts.is_some() { o.content.redacts } else { o.redacts };
270                        (id.map(|id| id.to_string()), o.content.reason)
271                    }
272                    SyncRoomRedactionEvent::Redacted(_) => (None, None),
273                };
274                MessageLikeEventContent::RoomRedaction { redacted_event_id, reason }
275            }
276            AnySyncMessageLikeEvent::Sticker(_) => MessageLikeEventContent::Sticker,
277            _ => bail!("Unsupported Event Type: {:?}", value.event_type()),
278        };
279        Ok(content)
280    }
281}
282
283fn get_state_event_original_content<C>(event: SyncStateEvent<C>) -> anyhow::Result<C>
284where
285    C: StaticStateEventContent + RedactContent + Clone,
286    <C as RedactContent>::Redacted: RedactedStateEventContent<StateKey = C::StateKey>,
287{
288    let original_content =
289        event.as_original().context("Failed to get original content")?.content.clone();
290    Ok(original_content)
291}
292
293fn get_message_like_event_original_content<C>(event: SyncMessageLikeEvent<C>) -> anyhow::Result<C>
294where
295    C: RumaMessageLikeEventContent + RedactContent + Clone,
296    <C as ruma::events::RedactContent>::Redacted: ruma::events::RedactedMessageLikeEventContent,
297{
298    let original_content =
299        event.as_original().context("Failed to get original content")?.content.clone();
300    Ok(original_content)
301}
302
303#[derive(Clone, uniffi::Enum)]
304pub enum StateEventType {
305    CallMember,
306    PolicyRuleRoom,
307    PolicyRuleServer,
308    PolicyRuleUser,
309    RoomAliases,
310    RoomAvatar,
311    RoomCanonicalAlias,
312    RoomCreate,
313    RoomEncryption,
314    RoomGuestAccess,
315    RoomHistoryVisibility,
316    RoomJoinRules,
317    RoomMemberEvent,
318    RoomName,
319    RoomPinnedEvents,
320    RoomPowerLevels,
321    RoomServerAcl,
322    RoomThirdPartyInvite,
323    RoomTombstone,
324    RoomTopic,
325    SpaceChild,
326    SpaceParent,
327}
328
329impl From<StateEventType> for ruma::events::StateEventType {
330    fn from(val: StateEventType) -> Self {
331        match val {
332            StateEventType::CallMember => Self::CallMember,
333            StateEventType::PolicyRuleRoom => Self::PolicyRuleRoom,
334            StateEventType::PolicyRuleServer => Self::PolicyRuleServer,
335            StateEventType::PolicyRuleUser => Self::PolicyRuleUser,
336            StateEventType::RoomAliases => Self::RoomAliases,
337            StateEventType::RoomAvatar => Self::RoomAvatar,
338            StateEventType::RoomCanonicalAlias => Self::RoomCanonicalAlias,
339            StateEventType::RoomCreate => Self::RoomCreate,
340            StateEventType::RoomEncryption => Self::RoomEncryption,
341            StateEventType::RoomGuestAccess => Self::RoomGuestAccess,
342            StateEventType::RoomHistoryVisibility => Self::RoomHistoryVisibility,
343            StateEventType::RoomJoinRules => Self::RoomJoinRules,
344            StateEventType::RoomMemberEvent => Self::RoomMember,
345            StateEventType::RoomName => Self::RoomName,
346            StateEventType::RoomPinnedEvents => Self::RoomPinnedEvents,
347            StateEventType::RoomPowerLevels => Self::RoomPowerLevels,
348            StateEventType::RoomServerAcl => Self::RoomServerAcl,
349            StateEventType::RoomThirdPartyInvite => Self::RoomThirdPartyInvite,
350            StateEventType::RoomTombstone => Self::RoomTombstone,
351            StateEventType::RoomTopic => Self::RoomTopic,
352            StateEventType::SpaceChild => Self::SpaceChild,
353            StateEventType::SpaceParent => Self::SpaceParent,
354        }
355    }
356}
357
358#[derive(Clone, uniffi::Enum)]
359pub enum MessageLikeEventType {
360    CallAnswer,
361    CallCandidates,
362    CallHangup,
363    CallInvite,
364    RtcNotification,
365    KeyVerificationAccept,
366    KeyVerificationCancel,
367    KeyVerificationDone,
368    KeyVerificationKey,
369    KeyVerificationMac,
370    KeyVerificationReady,
371    KeyVerificationStart,
372    PollEnd,
373    PollResponse,
374    PollStart,
375    Reaction,
376    RoomEncrypted,
377    RoomMessage,
378    RoomRedaction,
379    Sticker,
380    UnstablePollEnd,
381    UnstablePollResponse,
382    UnstablePollStart,
383    Other(String),
384}
385
386impl From<MessageLikeEventType> for ruma::events::MessageLikeEventType {
387    fn from(val: MessageLikeEventType) -> Self {
388        match val {
389            MessageLikeEventType::CallAnswer => Self::CallAnswer,
390            MessageLikeEventType::CallInvite => Self::CallInvite,
391            MessageLikeEventType::RtcNotification => Self::RtcNotification,
392            MessageLikeEventType::CallHangup => Self::CallHangup,
393            MessageLikeEventType::CallCandidates => Self::CallCandidates,
394            MessageLikeEventType::KeyVerificationReady => Self::KeyVerificationReady,
395            MessageLikeEventType::KeyVerificationStart => Self::KeyVerificationStart,
396            MessageLikeEventType::KeyVerificationCancel => Self::KeyVerificationCancel,
397            MessageLikeEventType::KeyVerificationAccept => Self::KeyVerificationAccept,
398            MessageLikeEventType::KeyVerificationKey => Self::KeyVerificationKey,
399            MessageLikeEventType::KeyVerificationMac => Self::KeyVerificationMac,
400            MessageLikeEventType::KeyVerificationDone => Self::KeyVerificationDone,
401            MessageLikeEventType::Reaction => Self::Reaction,
402            MessageLikeEventType::RoomEncrypted => Self::RoomEncrypted,
403            MessageLikeEventType::RoomMessage => Self::RoomMessage,
404            MessageLikeEventType::RoomRedaction => Self::RoomRedaction,
405            MessageLikeEventType::Sticker => Self::Sticker,
406            MessageLikeEventType::PollEnd => Self::PollEnd,
407            MessageLikeEventType::PollResponse => Self::PollResponse,
408            MessageLikeEventType::PollStart => Self::PollStart,
409            MessageLikeEventType::UnstablePollEnd => Self::UnstablePollEnd,
410            MessageLikeEventType::UnstablePollResponse => Self::UnstablePollResponse,
411            MessageLikeEventType::UnstablePollStart => Self::UnstablePollStart,
412            MessageLikeEventType::Other(msgtype) => Self::from(msgtype),
413        }
414    }
415}
416
417#[derive(Debug, PartialEq, Clone, uniffi::Enum)]
418pub enum RoomMessageEventMessageType {
419    Audio,
420    Emote,
421    File,
422    #[cfg(feature = "unstable-msc4274")]
423    Gallery,
424    Image,
425    Location,
426    Notice,
427    ServerNotice,
428    Text,
429    Video,
430    VerificationRequest,
431    Other,
432}
433
434impl From<RumaMessageType> for RoomMessageEventMessageType {
435    fn from(val: ruma::events::room::message::MessageType) -> Self {
436        match val {
437            RumaMessageType::Audio { .. } => Self::Audio,
438            RumaMessageType::Emote { .. } => Self::Emote,
439            RumaMessageType::File { .. } => Self::File,
440            #[cfg(feature = "unstable-msc4274")]
441            RumaMessageType::Gallery { .. } => Self::Gallery,
442            RumaMessageType::Image { .. } => Self::Image,
443            RumaMessageType::Location { .. } => Self::Location,
444            RumaMessageType::Notice { .. } => Self::Notice,
445            RumaMessageType::ServerNotice { .. } => Self::ServerNotice,
446            RumaMessageType::Text { .. } => Self::Text,
447            RumaMessageType::Video { .. } => Self::Video,
448            RumaMessageType::VerificationRequest { .. } => Self::VerificationRequest,
449            _ => Self::Other,
450        }
451    }
452}
453
454/// Contains the 2 possible identifiers of an event, either it has a remote
455/// event id or a local transaction id, never both or none.
456#[derive(Clone, uniffi::Enum)]
457pub enum EventOrTransactionId {
458    EventId { event_id: String },
459    TransactionId { transaction_id: String },
460}
461
462impl From<TimelineEventItemId> for EventOrTransactionId {
463    fn from(value: TimelineEventItemId) -> Self {
464        match value {
465            TimelineEventItemId::EventId(event_id) => {
466                EventOrTransactionId::EventId { event_id: event_id.to_string() }
467            }
468            TimelineEventItemId::TransactionId(transaction_id) => {
469                EventOrTransactionId::TransactionId { transaction_id: transaction_id.to_string() }
470            }
471        }
472    }
473}
474
475impl TryFrom<EventOrTransactionId> for TimelineEventItemId {
476    type Error = IdParseError;
477    fn try_from(value: EventOrTransactionId) -> Result<Self, Self::Error> {
478        match value {
479            EventOrTransactionId::EventId { event_id } => {
480                Ok(TimelineEventItemId::EventId(EventId::parse(event_id)?))
481            }
482            EventOrTransactionId::TransactionId { transaction_id } => {
483                Ok(TimelineEventItemId::TransactionId(transaction_id.into()))
484            }
485        }
486    }
487}