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