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