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 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#[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#[allow(clippy::large_enum_variant)]
166pub enum MessageLikeEventContent {
167 CallAnswer,
168 CallInvite,
169 RtcNotification {
170 notification_type: RtcNotificationType,
171 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#[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}