matrix_sdk_ffi/
event.rs

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