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#[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#[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#[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}