1use std::{
16 collections::{BTreeSet, HashMap},
17 sync::Arc,
18 time::Duration,
19};
20
21use extension_trait::extension_trait;
22use matrix_sdk::attachment::{BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseVideoInfo};
23use ruma::{
24 assign,
25 events::{
26 direct::DirectEventContent,
27 fully_read::FullyReadEventContent,
28 identity_server::IdentityServerEventContent,
29 ignored_user_list::{IgnoredUser as RumaIgnoredUser, IgnoredUserListEventContent},
30 location::AssetType as RumaAssetType,
31 marked_unread::{MarkedUnreadEventContent, UnstableMarkedUnreadEventContent},
32 media_preview_config::{
33 InviteAvatars as RumaInviteAvatars, MediaPreviewConfigEventContent,
34 MediaPreviews as RumaMediaPreviews,
35 },
36 poll::start::PollKind as RumaPollKind,
37 push_rules::PushRulesEventContent,
38 room::{
39 message::{
40 AudioInfo as RumaAudioInfo,
41 AudioMessageEventContent as RumaAudioMessageEventContent,
42 EmoteMessageEventContent as RumaEmoteMessageEventContent, FileInfo as RumaFileInfo,
43 FileMessageEventContent as RumaFileMessageEventContent,
44 FormattedBody as RumaFormattedBody,
45 ImageMessageEventContent as RumaImageMessageEventContent,
46 LocationMessageEventContent as RumaLocationMessageEventContent,
47 MessageType as RumaMessageType,
48 NoticeMessageEventContent as RumaNoticeMessageEventContent,
49 RoomMessageEventContentWithoutRelation,
50 TextMessageEventContent as RumaTextMessageEventContent, UnstableAmplitude,
51 UnstableAudioDetailsContentBlock as RumaUnstableAudioDetailsContentBlock,
52 UnstableVoiceContentBlock as RumaUnstableVoiceContentBlock,
53 VideoInfo as RumaVideoInfo,
54 VideoMessageEventContent as RumaVideoMessageEventContent,
55 },
56 ImageInfo as RumaImageInfo, MediaSource as RumaMediaSource,
57 ThumbnailInfo as RumaThumbnailInfo,
58 },
59 rtc::notification::NotificationType as RumaNotificationType,
60 secret_storage::{
61 default_key::SecretStorageDefaultKeyEventContent,
62 key::{
63 PassPhrase as RumaPassPhrase,
64 SecretStorageEncryptionAlgorithm as RumaSecretStorageEncryptionAlgorithm,
65 SecretStorageKeyEventContent,
66 SecretStorageV1AesHmacSha2Properties as RumaSecretStorageV1AesHmacSha2Properties,
67 },
68 },
69 tag::{
70 TagEventContent, TagInfo as RumaTagInfo, TagName as RumaTagName,
71 UserTagName as RumaUserTagName,
72 },
73 GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
74 GlobalAccountDataEventType as RumaGlobalAccountDataEventType,
75 RoomAccountDataEvent as RumaRoomAccountDataEvent,
76 RoomAccountDataEventType as RumaRoomAccountDataEventType,
77 },
78 matrix_uri::MatrixId as RumaMatrixId,
79 push::{
80 ConditionalPushRule as RumaConditionalPushRule, PatternedPushRule as RumaPatternedPushRule,
81 Ruleset as RumaRuleset, SimplePushRule as RumaSimplePushRule,
82 },
83 serde::JsonObject,
84 KeyDerivationAlgorithm as RumaKeyDerivationAlgorithm, MatrixToUri, MatrixUri as RumaMatrixUri,
85 OwnedRoomId, OwnedUserId, UInt, UserId,
86};
87use tracing::info;
88
89use crate::{
90 error::{ClientError, MediaInfoError},
91 helpers::unwrap_or_clone_arc,
92 notification_settings::{Action, PushCondition},
93 timeline::MessageContent,
94 utils::u64_to_uint,
95};
96
97#[derive(uniffi::Enum)]
98pub enum AuthData {
99 Password { password_details: AuthDataPasswordDetails },
101}
102
103#[derive(uniffi::Record)]
104pub struct AuthDataPasswordDetails {
105 identifier: String,
107
108 password: String,
110}
111
112impl From<AuthData> for ruma::api::client::uiaa::AuthData {
113 fn from(value: AuthData) -> ruma::api::client::uiaa::AuthData {
114 match value {
115 AuthData::Password { password_details } => {
116 let user_id = ruma::UserId::parse(password_details.identifier).unwrap();
117
118 ruma::api::client::uiaa::AuthData::Password(ruma::api::client::uiaa::Password::new(
119 user_id.into(),
120 password_details.password,
121 ))
122 }
123 }
124 }
125}
126
127#[matrix_sdk_ffi_macros::export]
130pub fn parse_matrix_entity_from(uri: String) -> Option<MatrixEntity> {
131 if let Ok(matrix_uri) = RumaMatrixUri::parse(&uri) {
132 return Some(MatrixEntity {
133 id: matrix_uri.id().into(),
134 via: matrix_uri.via().iter().map(|via| via.to_string()).collect(),
135 });
136 }
137
138 if let Ok(matrix_to_uri) = MatrixToUri::parse(&uri) {
139 return Some(MatrixEntity {
140 id: matrix_to_uri.id().into(),
141 via: matrix_to_uri.via().iter().map(|via| via.to_string()).collect(),
142 });
143 }
144
145 None
146}
147
148#[derive(uniffi::Record)]
151pub struct MatrixEntity {
152 id: MatrixId,
153 via: Vec<String>,
154}
155
156#[derive(Clone, uniffi::Enum)]
158pub enum MatrixId {
159 Room { id: String },
160 RoomAlias { alias: String },
161 User { id: String },
162 EventOnRoomId { room_id: String, event_id: String },
163 EventOnRoomAlias { alias: String, event_id: String },
164}
165
166impl From<&RumaMatrixId> for MatrixId {
167 fn from(value: &RumaMatrixId) -> Self {
168 match value {
169 RumaMatrixId::User(id) => MatrixId::User { id: id.to_string() },
170 RumaMatrixId::Room(id) => MatrixId::Room { id: id.to_string() },
171 RumaMatrixId::RoomAlias(id) => MatrixId::RoomAlias { alias: id.to_string() },
172
173 RumaMatrixId::Event(room_id_or_alias, event_id) => {
174 if room_id_or_alias.is_room_id() {
175 MatrixId::EventOnRoomId {
176 room_id: room_id_or_alias.to_string(),
177 event_id: event_id.to_string(),
178 }
179 } else if room_id_or_alias.is_room_alias_id() {
180 MatrixId::EventOnRoomAlias {
181 alias: room_id_or_alias.to_string(),
182 event_id: event_id.to_string(),
183 }
184 } else {
185 panic!("Unexpected MatrixId type: {room_id_or_alias:?}")
186 }
187 }
188 _ => panic!("Unexpected MatrixId type: {value:?}"),
189 }
190 }
191}
192
193#[matrix_sdk_ffi_macros::export]
194pub fn message_event_content_new(
195 msgtype: MessageType,
196) -> Result<Arc<RoomMessageEventContentWithoutRelation>, ClientError> {
197 Ok(Arc::new(RoomMessageEventContentWithoutRelation::new(msgtype.try_into()?)))
198}
199
200#[matrix_sdk_ffi_macros::export]
201pub fn message_event_content_from_markdown(
202 md: String,
203) -> Arc<RoomMessageEventContentWithoutRelation> {
204 Arc::new(RoomMessageEventContentWithoutRelation::new(RumaMessageType::text_markdown(md)))
205}
206
207#[matrix_sdk_ffi_macros::export]
208pub fn message_event_content_from_markdown_as_emote(
209 md: String,
210) -> Arc<RoomMessageEventContentWithoutRelation> {
211 Arc::new(RoomMessageEventContentWithoutRelation::new(RumaMessageType::emote_markdown(md)))
212}
213
214#[matrix_sdk_ffi_macros::export]
215pub fn message_event_content_from_html(
216 body: String,
217 html_body: String,
218) -> Arc<RoomMessageEventContentWithoutRelation> {
219 Arc::new(RoomMessageEventContentWithoutRelation::new(RumaMessageType::text_html(
220 body, html_body,
221 )))
222}
223
224#[matrix_sdk_ffi_macros::export]
225pub fn message_event_content_from_html_as_emote(
226 body: String,
227 html_body: String,
228) -> Arc<RoomMessageEventContentWithoutRelation> {
229 Arc::new(RoomMessageEventContentWithoutRelation::new(RumaMessageType::emote_html(
230 body, html_body,
231 )))
232}
233
234#[derive(Clone, uniffi::Object)]
235pub struct MediaSource {
236 pub(crate) media_source: RumaMediaSource,
237}
238
239#[matrix_sdk_ffi_macros::export]
240impl MediaSource {
241 #[uniffi::constructor]
242 pub fn from_url(url: String) -> Result<Arc<MediaSource>, ClientError> {
243 let media_source = RumaMediaSource::Plain(url.into());
244 media_source.verify()?;
245
246 Ok(Arc::new(MediaSource { media_source }))
247 }
248
249 pub fn url(&self) -> String {
250 self.media_source.url()
251 }
252
253 #[uniffi::constructor]
255 pub fn from_json(json: String) -> Result<Arc<Self>, ClientError> {
256 let media_source: RumaMediaSource = serde_json::from_str(&json)?;
257 media_source.verify()?;
258
259 Ok(Arc::new(MediaSource { media_source }))
260 }
261
262 pub fn to_json(&self) -> String {
264 serde_json::to_string(&self.media_source)
265 .expect("Media source should always be serializable ")
266 }
267}
268
269impl TryFrom<RumaMediaSource> for MediaSource {
270 type Error = ClientError;
271
272 fn try_from(value: RumaMediaSource) -> Result<Self, Self::Error> {
273 value.verify()?;
274 Ok(Self { media_source: value })
275 }
276}
277
278impl TryFrom<&RumaMediaSource> for MediaSource {
279 type Error = ClientError;
280
281 fn try_from(value: &RumaMediaSource) -> Result<Self, Self::Error> {
282 value.verify()?;
283 Ok(Self { media_source: value.clone() })
284 }
285}
286
287impl From<MediaSource> for RumaMediaSource {
288 fn from(value: MediaSource) -> Self {
289 value.media_source
290 }
291}
292
293#[extension_trait]
294pub(crate) impl MediaSourceExt for RumaMediaSource {
295 fn verify(&self) -> Result<(), ClientError> {
296 match self {
297 RumaMediaSource::Plain(url) => {
298 url.validate().map_err(ClientError::from_err)?;
299 }
300 RumaMediaSource::Encrypted(file) => {
301 file.url.validate().map_err(ClientError::from_err)?;
302 }
303 }
304
305 Ok(())
306 }
307
308 fn url(&self) -> String {
309 match self {
310 RumaMediaSource::Plain(url) => url.to_string(),
311 RumaMediaSource::Encrypted(file) => file.url.to_string(),
312 }
313 }
314}
315
316#[extension_trait]
317pub impl RoomMessageEventContentWithoutRelationExt for RoomMessageEventContentWithoutRelation {
318 fn with_mentions(self: Arc<Self>, mentions: Mentions) -> Arc<Self> {
319 let mut content = unwrap_or_clone_arc(self);
320 content.mentions = Some(mentions.into());
321 Arc::new(content)
322 }
323}
324
325#[derive(Clone)]
326pub struct Mentions {
327 pub user_ids: Vec<String>,
328 pub room: bool,
329}
330
331impl From<Mentions> for ruma::events::Mentions {
332 fn from(value: Mentions) -> Self {
333 let mut user_ids = BTreeSet::<OwnedUserId>::new();
334 for user_id in value.user_ids {
335 if let Ok(user_id) = UserId::parse(user_id) {
336 user_ids.insert(user_id);
337 }
338 }
339 let mut result = Self::default();
340 result.user_ids = user_ids;
341 result.room = value.room;
342 result
343 }
344}
345
346#[derive(Clone, uniffi::Enum)]
347pub enum MessageType {
348 Emote {
349 content: EmoteMessageContent,
350 },
351 Image {
352 content: ImageMessageContent,
353 },
354 Audio {
355 content: AudioMessageContent,
356 },
357 Video {
358 content: VideoMessageContent,
359 },
360 File {
361 content: FileMessageContent,
362 },
363 #[cfg(feature = "unstable-msc4274")]
364 Gallery {
365 content: GalleryMessageContent,
366 },
367 Notice {
368 content: NoticeMessageContent,
369 },
370 Text {
371 content: TextMessageContent,
372 },
373 Location {
374 content: LocationContent,
375 },
376 Other {
377 msgtype: String,
378 body: String,
379 },
380}
381
382fn get_body_and_filename(filename: String, caption: Option<String>) -> (String, Option<String>) {
392 if let Some(caption) = caption {
393 (caption, Some(filename))
394 } else {
395 (filename, None)
396 }
397}
398
399impl TryFrom<MessageType> for RumaMessageType {
400 type Error = ClientError;
401
402 fn try_from(value: MessageType) -> Result<Self, Self::Error> {
403 Ok(match value {
404 MessageType::Emote { content } => {
405 Self::Emote(assign!(RumaEmoteMessageEventContent::plain(content.body), {
406 formatted: content.formatted.map(Into::into),
407 }))
408 }
409 MessageType::Image { content } => Self::Image(content.into()),
410 MessageType::Audio { content } => Self::Audio(content.into()),
411 MessageType::Video { content } => Self::Video(content.into()),
412 MessageType::File { content } => Self::File(content.into()),
413 #[cfg(feature = "unstable-msc4274")]
414 MessageType::Gallery { content } => Self::Gallery(content.try_into()?),
415 MessageType::Notice { content } => {
416 Self::Notice(assign!(RumaNoticeMessageEventContent::plain(content.body), {
417 formatted: content.formatted.map(Into::into),
418 }))
419 }
420 MessageType::Text { content } => {
421 Self::Text(assign!(RumaTextMessageEventContent::plain(content.body), {
422 formatted: content.formatted.map(Into::into),
423 }))
424 }
425 MessageType::Location { content } => {
426 Self::Location(RumaLocationMessageEventContent::new(content.body, content.geo_uri))
427 }
428 MessageType::Other { msgtype, body } => {
429 Self::new(&msgtype, body, JsonObject::default())?
430 }
431 })
432 }
433}
434
435impl TryFrom<RumaMessageType> for MessageType {
436 type Error = ClientError;
437
438 fn try_from(value: RumaMessageType) -> Result<Self, Self::Error> {
439 Ok(match value {
440 RumaMessageType::Emote(c) => MessageType::Emote {
441 content: EmoteMessageContent {
442 body: c.body.clone(),
443 formatted: c.formatted.as_ref().map(Into::into),
444 },
445 },
446 RumaMessageType::Image(c) => MessageType::Image { content: c.try_into()? },
447 RumaMessageType::Audio(c) => MessageType::Audio { content: c.try_into()? },
448 RumaMessageType::Video(c) => MessageType::Video { content: c.try_into()? },
449 RumaMessageType::File(c) => MessageType::File { content: c.try_into()? },
450 #[cfg(feature = "unstable-msc4274")]
451 RumaMessageType::Gallery(c) => MessageType::Gallery { content: c.try_into()? },
452 RumaMessageType::Notice(c) => MessageType::Notice {
453 content: NoticeMessageContent {
454 body: c.body.clone(),
455 formatted: c.formatted.as_ref().map(Into::into),
456 },
457 },
458 RumaMessageType::Text(c) => MessageType::Text {
459 content: TextMessageContent {
460 body: c.body.clone(),
461 formatted: c.formatted.as_ref().map(Into::into),
462 },
463 },
464 RumaMessageType::Location(c) => {
465 let (description, zoom_level) =
466 c.location.map(|loc| (loc.description, loc.zoom_level)).unwrap_or((None, None));
467 MessageType::Location {
468 content: LocationContent {
469 body: c.body,
470 geo_uri: c.geo_uri,
471 description,
472 zoom_level: zoom_level.and_then(|z| z.get().try_into().ok()),
473 asset: c.asset.and_then(|a| match a.type_ {
474 RumaAssetType::Self_ => Some(AssetType::Sender),
475 RumaAssetType::Pin => Some(AssetType::Pin),
476 _ => None,
477 }),
478 },
479 }
480 }
481 _ => MessageType::Other {
482 msgtype: value.msgtype().to_owned(),
483 body: value.body().to_owned(),
484 },
485 })
486 }
487}
488
489#[derive(Clone, uniffi::Enum)]
490pub enum RtcNotificationType {
491 Ring,
492 Notification,
493}
494
495impl From<RumaNotificationType> for RtcNotificationType {
496 fn from(val: RumaNotificationType) -> Self {
497 match val {
498 RumaNotificationType::Ring => Self::Ring,
499 _ => Self::Notification,
500 }
501 }
502}
503
504impl From<RtcNotificationType> for RumaNotificationType {
505 fn from(value: RtcNotificationType) -> Self {
506 match value {
507 RtcNotificationType::Ring => RumaNotificationType::Ring,
508 RtcNotificationType::Notification => RumaNotificationType::Notification,
509 }
510 }
511}
512
513#[derive(Clone, uniffi::Record)]
514pub struct EmoteMessageContent {
515 pub body: String,
516 pub formatted: Option<FormattedBody>,
517}
518
519#[derive(Clone, uniffi::Record)]
520pub struct ImageMessageContent {
521 pub filename: String,
523 pub caption: Option<String>,
524 pub formatted_caption: Option<FormattedBody>,
525 pub source: Arc<MediaSource>,
526 pub info: Option<ImageInfo>,
527}
528
529impl From<ImageMessageContent> for RumaImageMessageEventContent {
530 fn from(value: ImageMessageContent) -> Self {
531 let (body, filename) = get_body_and_filename(value.filename, value.caption);
532 let mut event_content = Self::new(body, (*value.source).clone().into())
533 .info(value.info.map(Into::into).map(Box::new));
534 event_content.formatted = value.formatted_caption.map(Into::into);
535 event_content.filename = filename;
536 event_content
537 }
538}
539
540impl TryFrom<RumaImageMessageEventContent> for ImageMessageContent {
541 type Error = ClientError;
542
543 fn try_from(value: RumaImageMessageEventContent) -> Result<Self, Self::Error> {
544 Ok(Self {
545 filename: value.filename().to_owned(),
546 caption: value.caption().map(ToString::to_string),
547 formatted_caption: value.formatted_caption().map(Into::into),
548 source: Arc::new(value.source.try_into()?),
549 info: value.info.as_deref().map(TryInto::try_into).transpose()?,
550 })
551 }
552}
553
554#[derive(Clone, uniffi::Record)]
555pub struct AudioMessageContent {
556 pub filename: String,
558 pub caption: Option<String>,
559 pub formatted_caption: Option<FormattedBody>,
560 pub source: Arc<MediaSource>,
561 pub info: Option<AudioInfo>,
562 pub audio: Option<UnstableAudioDetailsContent>,
563 pub voice: Option<UnstableVoiceContent>,
564}
565
566impl From<AudioMessageContent> for RumaAudioMessageEventContent {
567 fn from(value: AudioMessageContent) -> Self {
568 let (body, filename) = get_body_and_filename(value.filename, value.caption);
569 let mut event_content = Self::new(body, (*value.source).clone().into())
570 .info(value.info.map(Into::into).map(Box::new));
571 event_content.formatted = value.formatted_caption.map(Into::into);
572 event_content.filename = filename;
573 event_content.audio = value.audio.map(Into::into);
574 event_content.voice = value.voice.map(Into::into);
575 event_content
576 }
577}
578
579impl TryFrom<RumaAudioMessageEventContent> for AudioMessageContent {
580 type Error = ClientError;
581
582 fn try_from(value: RumaAudioMessageEventContent) -> Result<Self, Self::Error> {
583 Ok(Self {
584 filename: value.filename().to_owned(),
585 caption: value.caption().map(ToString::to_string),
586 formatted_caption: value.formatted_caption().map(Into::into),
587 source: Arc::new(value.source.try_into()?),
588 info: value.info.as_deref().map(Into::into),
589 audio: value.audio.map(Into::into),
590 voice: value.voice.map(Into::into),
591 })
592 }
593}
594
595#[derive(Clone, uniffi::Record)]
596pub struct VideoMessageContent {
597 pub filename: String,
599 pub caption: Option<String>,
600 pub formatted_caption: Option<FormattedBody>,
601 pub source: Arc<MediaSource>,
602 pub info: Option<VideoInfo>,
603}
604
605impl From<VideoMessageContent> for RumaVideoMessageEventContent {
606 fn from(value: VideoMessageContent) -> Self {
607 let (body, filename) = get_body_and_filename(value.filename, value.caption);
608 let mut event_content = Self::new(body, (*value.source).clone().into())
609 .info(value.info.map(Into::into).map(Box::new));
610 event_content.formatted = value.formatted_caption.map(Into::into);
611 event_content.filename = filename;
612 event_content
613 }
614}
615
616impl TryFrom<RumaVideoMessageEventContent> for VideoMessageContent {
617 type Error = ClientError;
618
619 fn try_from(value: RumaVideoMessageEventContent) -> Result<Self, Self::Error> {
620 Ok(Self {
621 filename: value.filename().to_owned(),
622 caption: value.caption().map(ToString::to_string),
623 formatted_caption: value.formatted_caption().map(Into::into),
624 source: Arc::new(value.source.try_into()?),
625 info: value.info.as_deref().map(TryInto::try_into).transpose()?,
626 })
627 }
628}
629
630#[derive(Clone, uniffi::Record)]
631pub struct FileMessageContent {
632 pub filename: String,
634 pub caption: Option<String>,
635 pub formatted_caption: Option<FormattedBody>,
636 pub source: Arc<MediaSource>,
637 pub info: Option<FileInfo>,
638}
639
640impl From<FileMessageContent> for RumaFileMessageEventContent {
641 fn from(value: FileMessageContent) -> Self {
642 let (body, filename) = get_body_and_filename(value.filename, value.caption);
643 let mut event_content = Self::new(body, (*value.source).clone().into())
644 .info(value.info.map(Into::into).map(Box::new));
645 event_content.formatted = value.formatted_caption.map(Into::into);
646 event_content.filename = filename;
647 event_content
648 }
649}
650
651impl TryFrom<RumaFileMessageEventContent> for FileMessageContent {
652 type Error = ClientError;
653
654 fn try_from(value: RumaFileMessageEventContent) -> Result<Self, Self::Error> {
655 Ok(Self {
656 filename: value.filename().to_owned(),
657 caption: value.caption().map(ToString::to_string),
658 formatted_caption: value.formatted_caption().map(Into::into),
659 source: Arc::new(value.source.try_into()?),
660 info: value.info.as_deref().map(TryInto::try_into).transpose()?,
661 })
662 }
663}
664
665#[derive(Clone, uniffi::Record)]
666pub struct ImageInfo {
667 pub height: Option<u64>,
668 pub width: Option<u64>,
669 pub mimetype: Option<String>,
670 pub size: Option<u64>,
671 pub thumbnail_info: Option<ThumbnailInfo>,
672 pub thumbnail_source: Option<Arc<MediaSource>>,
673 pub blurhash: Option<String>,
674 pub is_animated: Option<bool>,
675}
676
677impl From<ImageInfo> for RumaImageInfo {
678 fn from(value: ImageInfo) -> Self {
679 assign!(RumaImageInfo::new(), {
680 height: value.height.map(u64_to_uint),
681 width: value.width.map(u64_to_uint),
682 mimetype: value.mimetype,
683 size: value.size.map(u64_to_uint),
684 thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new),
685 thumbnail_source: value.thumbnail_source.map(|source| (*source).clone().into()),
686 blurhash: value.blurhash,
687 is_animated: value.is_animated,
688 })
689 }
690}
691
692impl TryFrom<&ImageInfo> for BaseImageInfo {
693 type Error = MediaInfoError;
694
695 fn try_from(value: &ImageInfo) -> Result<Self, MediaInfoError> {
696 let height = UInt::try_from(value.height.ok_or(MediaInfoError::MissingField)?)
697 .map_err(|_| MediaInfoError::InvalidField)?;
698 let width = UInt::try_from(value.width.ok_or(MediaInfoError::MissingField)?)
699 .map_err(|_| MediaInfoError::InvalidField)?;
700 let size = UInt::try_from(value.size.ok_or(MediaInfoError::MissingField)?)
701 .map_err(|_| MediaInfoError::InvalidField)?;
702 let blurhash = value.blurhash.clone().ok_or(MediaInfoError::MissingField)?;
703
704 Ok(BaseImageInfo {
705 height: Some(height),
706 width: Some(width),
707 size: Some(size),
708 blurhash: Some(blurhash),
709 is_animated: value.is_animated,
710 })
711 }
712}
713
714#[derive(Clone, uniffi::Record)]
715pub struct AudioInfo {
716 pub duration: Option<Duration>,
717 pub size: Option<u64>,
718 pub mimetype: Option<String>,
719}
720
721impl From<AudioInfo> for RumaAudioInfo {
722 fn from(value: AudioInfo) -> Self {
723 assign!(RumaAudioInfo::new(), {
724 duration: value.duration,
725 size: value.size.map(u64_to_uint),
726 mimetype: value.mimetype,
727 })
728 }
729}
730
731impl TryFrom<&AudioInfo> for BaseAudioInfo {
732 type Error = MediaInfoError;
733
734 fn try_from(value: &AudioInfo) -> Result<Self, MediaInfoError> {
735 let duration = value.duration.ok_or(MediaInfoError::MissingField)?;
736 let size = UInt::try_from(value.size.ok_or(MediaInfoError::MissingField)?)
737 .map_err(|_| MediaInfoError::InvalidField)?;
738
739 Ok(BaseAudioInfo { duration: Some(duration), size: Some(size), waveform: None })
740 }
741}
742
743#[derive(Clone, uniffi::Record)]
744pub struct UnstableAudioDetailsContent {
745 pub duration: Duration,
746 pub waveform: Vec<u16>,
747}
748
749impl From<RumaUnstableAudioDetailsContentBlock> for UnstableAudioDetailsContent {
750 fn from(details: RumaUnstableAudioDetailsContentBlock) -> Self {
751 Self {
752 duration: details.duration,
753 waveform: details
754 .waveform
755 .iter()
756 .map(|x| u16::try_from(x.get()).unwrap_or(0))
757 .collect(),
758 }
759 }
760}
761
762impl From<UnstableAudioDetailsContent> for RumaUnstableAudioDetailsContentBlock {
763 fn from(details: UnstableAudioDetailsContent) -> Self {
764 Self::new(
765 details.duration,
766 details.waveform.iter().map(|x| UnstableAmplitude::new(x.to_owned())).collect(),
767 )
768 }
769}
770
771#[derive(Clone, uniffi::Record)]
772pub struct UnstableVoiceContent {}
773
774impl From<RumaUnstableVoiceContentBlock> for UnstableVoiceContent {
775 fn from(_details: RumaUnstableVoiceContentBlock) -> Self {
776 Self {}
777 }
778}
779
780impl From<UnstableVoiceContent> for RumaUnstableVoiceContentBlock {
781 fn from(_details: UnstableVoiceContent) -> Self {
782 Self::new()
783 }
784}
785
786#[derive(Clone, uniffi::Record)]
787pub struct VideoInfo {
788 pub duration: Option<Duration>,
789 pub height: Option<u64>,
790 pub width: Option<u64>,
791 pub mimetype: Option<String>,
792 pub size: Option<u64>,
793 pub thumbnail_info: Option<ThumbnailInfo>,
794 pub thumbnail_source: Option<Arc<MediaSource>>,
795 pub blurhash: Option<String>,
796}
797
798impl From<VideoInfo> for RumaVideoInfo {
799 fn from(value: VideoInfo) -> Self {
800 assign!(RumaVideoInfo::new(), {
801 duration: value.duration,
802 height: value.height.map(u64_to_uint),
803 width: value.width.map(u64_to_uint),
804 mimetype: value.mimetype,
805 size: value.size.map(u64_to_uint),
806 thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new),
807 thumbnail_source: value.thumbnail_source.map(|source| (*source).clone().into()),
808 blurhash: value.blurhash,
809 })
810 }
811}
812
813impl TryFrom<&VideoInfo> for BaseVideoInfo {
814 type Error = MediaInfoError;
815
816 fn try_from(value: &VideoInfo) -> Result<Self, MediaInfoError> {
817 let duration = value.duration.ok_or(MediaInfoError::MissingField)?;
818 let height = UInt::try_from(value.height.ok_or(MediaInfoError::MissingField)?)
819 .map_err(|_| MediaInfoError::InvalidField)?;
820 let width = UInt::try_from(value.width.ok_or(MediaInfoError::MissingField)?)
821 .map_err(|_| MediaInfoError::InvalidField)?;
822 let size = UInt::try_from(value.size.ok_or(MediaInfoError::MissingField)?)
823 .map_err(|_| MediaInfoError::InvalidField)?;
824 let blurhash = value.blurhash.clone().ok_or(MediaInfoError::MissingField)?;
825
826 Ok(BaseVideoInfo {
827 duration: Some(duration),
828 height: Some(height),
829 width: Some(width),
830 size: Some(size),
831 blurhash: Some(blurhash),
832 })
833 }
834}
835
836#[derive(Clone, uniffi::Record)]
837pub struct FileInfo {
838 pub mimetype: Option<String>,
839 pub size: Option<u64>,
840 pub thumbnail_info: Option<ThumbnailInfo>,
841 pub thumbnail_source: Option<Arc<MediaSource>>,
842}
843
844impl From<FileInfo> for RumaFileInfo {
845 fn from(value: FileInfo) -> Self {
846 assign!(RumaFileInfo::new(), {
847 mimetype: value.mimetype,
848 size: value.size.map(u64_to_uint),
849 thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new),
850 thumbnail_source: value.thumbnail_source.map(|source| (*source).clone().into()),
851 })
852 }
853}
854
855impl TryFrom<&FileInfo> for BaseFileInfo {
856 type Error = MediaInfoError;
857
858 fn try_from(value: &FileInfo) -> Result<Self, MediaInfoError> {
859 let size = UInt::try_from(value.size.ok_or(MediaInfoError::MissingField)?)
860 .map_err(|_| MediaInfoError::InvalidField)?;
861
862 Ok(BaseFileInfo { size: Some(size) })
863 }
864}
865
866#[derive(Clone, uniffi::Record)]
867pub struct ThumbnailInfo {
868 pub height: Option<u64>,
869 pub width: Option<u64>,
870 pub mimetype: Option<String>,
871 pub size: Option<u64>,
872}
873
874impl From<ThumbnailInfo> for RumaThumbnailInfo {
875 fn from(value: ThumbnailInfo) -> Self {
876 assign!(RumaThumbnailInfo::new(), {
877 height: value.height.map(u64_to_uint),
878 width: value.width.map(u64_to_uint),
879 mimetype: value.mimetype,
880 size: value.size.map(u64_to_uint),
881 })
882 }
883}
884
885#[derive(Clone, uniffi::Record)]
886pub struct NoticeMessageContent {
887 pub body: String,
888 pub formatted: Option<FormattedBody>,
889}
890
891#[derive(Clone, uniffi::Record)]
892pub struct TextMessageContent {
893 pub body: String,
894 pub formatted: Option<FormattedBody>,
895}
896
897#[derive(Clone, uniffi::Record)]
898pub struct LocationContent {
899 pub body: String,
900 pub geo_uri: String,
901 pub description: Option<String>,
902 pub zoom_level: Option<u8>,
903 pub asset: Option<AssetType>,
904}
905
906#[derive(Clone, uniffi::Enum)]
907pub enum AssetType {
908 Sender,
909 Pin,
910}
911
912impl From<AssetType> for RumaAssetType {
913 fn from(value: AssetType) -> Self {
914 match value {
915 AssetType::Sender => Self::Self_,
916 AssetType::Pin => Self::Pin,
917 }
918 }
919}
920
921#[derive(Clone, uniffi::Record)]
922pub struct FormattedBody {
923 pub format: MessageFormat,
924 pub body: String,
925}
926
927impl From<FormattedBody> for RumaFormattedBody {
928 fn from(f: FormattedBody) -> Self {
929 Self {
930 format: match f.format {
931 MessageFormat::Html => matrix_sdk::ruma::events::room::message::MessageFormat::Html,
932 MessageFormat::Unknown { format } => format.into(),
933 },
934 body: f.body,
935 }
936 }
937}
938
939impl From<&RumaFormattedBody> for FormattedBody {
940 fn from(f: &RumaFormattedBody) -> Self {
941 Self {
942 format: match &f.format {
943 matrix_sdk::ruma::events::room::message::MessageFormat::Html => MessageFormat::Html,
944 _ => MessageFormat::Unknown { format: f.format.to_string() },
945 },
946 body: f.body.clone(),
947 }
948 }
949}
950
951#[derive(Clone, uniffi::Enum)]
952pub enum MessageFormat {
953 Html,
954 Unknown { format: String },
955}
956
957impl TryFrom<&matrix_sdk::ruma::events::room::ImageInfo> for ImageInfo {
958 type Error = ClientError;
959
960 fn try_from(info: &matrix_sdk::ruma::events::room::ImageInfo) -> Result<Self, Self::Error> {
961 let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo {
962 height: info.height.map(Into::into),
963 width: info.width.map(Into::into),
964 mimetype: info.mimetype.clone(),
965 size: info.size.map(Into::into),
966 });
967
968 Ok(Self {
969 height: info.height.map(Into::into),
970 width: info.width.map(Into::into),
971 mimetype: info.mimetype.clone(),
972 size: info.size.map(Into::into),
973 thumbnail_info,
974 thumbnail_source: info
975 .thumbnail_source
976 .as_ref()
977 .map(TryInto::try_into)
978 .transpose()?
979 .map(Arc::new),
980 blurhash: info.blurhash.clone(),
981 is_animated: info.is_animated,
982 })
983 }
984}
985
986impl From<&RumaAudioInfo> for AudioInfo {
987 fn from(info: &RumaAudioInfo) -> Self {
988 Self {
989 duration: info.duration,
990 size: info.size.map(Into::into),
991 mimetype: info.mimetype.clone(),
992 }
993 }
994}
995
996impl TryFrom<&RumaVideoInfo> for VideoInfo {
997 type Error = ClientError;
998
999 fn try_from(info: &RumaVideoInfo) -> Result<Self, Self::Error> {
1000 let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo {
1001 height: info.height.map(Into::into),
1002 width: info.width.map(Into::into),
1003 mimetype: info.mimetype.clone(),
1004 size: info.size.map(Into::into),
1005 });
1006
1007 Ok(Self {
1008 duration: info.duration,
1009 height: info.height.map(Into::into),
1010 width: info.width.map(Into::into),
1011 mimetype: info.mimetype.clone(),
1012 size: info.size.map(Into::into),
1013 thumbnail_info,
1014 thumbnail_source: info
1015 .thumbnail_source
1016 .as_ref()
1017 .map(TryInto::try_into)
1018 .transpose()?
1019 .map(Arc::new),
1020 blurhash: info.blurhash.clone(),
1021 })
1022 }
1023}
1024
1025impl TryFrom<&RumaFileInfo> for FileInfo {
1026 type Error = ClientError;
1027
1028 fn try_from(info: &RumaFileInfo) -> Result<Self, Self::Error> {
1029 let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo {
1030 height: info.height.map(Into::into),
1031 width: info.width.map(Into::into),
1032 mimetype: info.mimetype.clone(),
1033 size: info.size.map(Into::into),
1034 });
1035
1036 Ok(Self {
1037 mimetype: info.mimetype.clone(),
1038 size: info.size.map(Into::into),
1039 thumbnail_info,
1040 thumbnail_source: info
1041 .thumbnail_source
1042 .as_ref()
1043 .map(TryInto::try_into)
1044 .transpose()?
1045 .map(Arc::new),
1046 })
1047 }
1048}
1049
1050#[derive(Clone, uniffi::Enum)]
1051pub enum PollKind {
1052 Disclosed,
1053 Undisclosed,
1054}
1055
1056impl From<PollKind> for RumaPollKind {
1057 fn from(value: PollKind) -> Self {
1058 match value {
1059 PollKind::Disclosed => Self::Disclosed,
1060 PollKind::Undisclosed => Self::Undisclosed,
1061 }
1062 }
1063}
1064
1065impl From<RumaPollKind> for PollKind {
1066 fn from(value: RumaPollKind) -> Self {
1067 match value {
1068 RumaPollKind::Disclosed => Self::Disclosed,
1069 RumaPollKind::Undisclosed => Self::Undisclosed,
1070 _ => {
1071 info!("Unknown poll kind, defaulting to undisclosed");
1072 Self::Undisclosed
1073 }
1074 }
1075 }
1076}
1077
1078#[matrix_sdk_ffi_macros::export]
1081pub fn content_without_relation_from_message(
1082 message: MessageContent,
1083) -> Result<Arc<RoomMessageEventContentWithoutRelation>, ClientError> {
1084 let msg_type = message.msg_type.try_into()?;
1085 Ok(Arc::new(RoomMessageEventContentWithoutRelation::new(msg_type)))
1086}
1087
1088#[derive(Clone, uniffi::Enum)]
1090pub enum AccountDataEventType {
1091 Direct,
1093 IdentityServer,
1095 IgnoredUserList,
1097 PushRules,
1099 SecretStorageDefaultKey,
1101 SecretStorageKey { key_id: String },
1103}
1104
1105impl TryFrom<RumaGlobalAccountDataEventType> for AccountDataEventType {
1106 type Error = String;
1107
1108 fn try_from(value: RumaGlobalAccountDataEventType) -> Result<Self, Self::Error> {
1109 match value {
1110 RumaGlobalAccountDataEventType::Direct => Ok(Self::Direct),
1111 RumaGlobalAccountDataEventType::IdentityServer => Ok(Self::IdentityServer),
1112 RumaGlobalAccountDataEventType::IgnoredUserList => Ok(Self::IgnoredUserList),
1113 RumaGlobalAccountDataEventType::PushRules => Ok(Self::PushRules),
1114 RumaGlobalAccountDataEventType::SecretStorageDefaultKey => {
1115 Ok(Self::SecretStorageDefaultKey)
1116 }
1117 RumaGlobalAccountDataEventType::SecretStorageKey(key_id) => {
1118 Ok(Self::SecretStorageKey { key_id })
1119 }
1120 _ => Err("Unsupported account data event type".to_owned()),
1121 }
1122 }
1123}
1124
1125#[derive(Clone, uniffi::Enum)]
1127pub enum AccountDataEvent {
1128 Direct {
1130 map: HashMap<String, Vec<String>>,
1133 },
1134 IdentityServer {
1136 base_url: Option<String>,
1138 },
1139 IgnoredUserList {
1141 ignored_users: HashMap<String, IgnoredUser>,
1144 },
1145 PushRules {
1147 global: Ruleset,
1149 },
1150 SecretStorageDefaultKey {
1152 key_id: String,
1154 },
1155 SecretStorageKey {
1157 key_id: String,
1159
1160 name: Option<String>,
1162
1163 algorithm: SecretStorageEncryptionAlgorithm,
1167
1168 passphrase: Option<PassPhrase>,
1170 },
1171}
1172
1173#[derive(Clone, uniffi::Enum, Default)]
1175pub enum MediaPreviews {
1176 #[default]
1178 On,
1179 Private,
1181 Off,
1183}
1184
1185impl From<RumaMediaPreviews> for MediaPreviews {
1186 fn from(value: RumaMediaPreviews) -> Self {
1187 match value {
1188 RumaMediaPreviews::On => Self::On,
1189 RumaMediaPreviews::Private => Self::Private,
1190 RumaMediaPreviews::Off => Self::Off,
1191 _ => Default::default(),
1192 }
1193 }
1194}
1195
1196impl From<MediaPreviews> for RumaMediaPreviews {
1197 fn from(value: MediaPreviews) -> Self {
1198 match value {
1199 MediaPreviews::On => Self::On,
1200 MediaPreviews::Private => Self::Private,
1201 MediaPreviews::Off => Self::Off,
1202 }
1203 }
1204}
1205
1206#[derive(Clone, uniffi::Enum, Default)]
1208pub enum InviteAvatars {
1209 #[default]
1211 On,
1212 Off,
1214}
1215
1216impl From<RumaInviteAvatars> for InviteAvatars {
1217 fn from(value: RumaInviteAvatars) -> Self {
1218 match value {
1219 RumaInviteAvatars::On => Self::On,
1220 RumaInviteAvatars::Off => Self::Off,
1221 _ => Default::default(),
1222 }
1223 }
1224}
1225
1226impl From<InviteAvatars> for RumaInviteAvatars {
1227 fn from(value: InviteAvatars) -> Self {
1228 match value {
1229 InviteAvatars::On => Self::On,
1230 InviteAvatars::Off => Self::Off,
1231 }
1232 }
1233}
1234
1235#[derive(Clone, uniffi::Record)]
1239pub struct IgnoredUser {}
1240
1241impl From<RumaIgnoredUser> for IgnoredUser {
1242 fn from(_value: RumaIgnoredUser) -> Self {
1243 IgnoredUser {}
1244 }
1245}
1246
1247#[derive(Clone, uniffi::Record)]
1249pub struct Ruleset {
1250 pub content: Vec<PatternedPushRule>,
1253
1254 pub override_: Vec<ConditionalPushRule>,
1259
1260 pub room: Vec<SimplePushRule>,
1262
1263 pub sender: Vec<SimplePushRule>,
1266
1267 pub underride: Vec<ConditionalPushRule>,
1270}
1271
1272impl TryFrom<RumaRuleset> for Ruleset {
1273 type Error = String;
1274
1275 fn try_from(value: RumaRuleset) -> Result<Self, Self::Error> {
1276 Ok(Self {
1277 content: value
1278 .content
1279 .into_iter()
1280 .map(TryInto::try_into)
1281 .collect::<Result<Vec<_>, _>>()?,
1282 override_: value
1283 .override_
1284 .into_iter()
1285 .map(TryInto::try_into)
1286 .collect::<Result<Vec<_>, _>>()?,
1287 room: value.room.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, _>>()?,
1288 sender: value
1289 .sender
1290 .into_iter()
1291 .map(TryInto::try_into)
1292 .collect::<Result<Vec<_>, _>>()?,
1293 underride: value
1294 .underride
1295 .into_iter()
1296 .map(TryInto::try_into)
1297 .collect::<Result<Vec<_>, _>>()?,
1298 })
1299 }
1300}
1301
1302#[derive(Clone, uniffi::Record)]
1304pub struct PatternedPushRule {
1305 pub actions: Vec<Action>,
1308
1309 pub default: bool,
1311
1312 pub enabled: bool,
1314
1315 pub rule_id: String,
1317
1318 pub pattern: String,
1320}
1321
1322impl TryFrom<RumaPatternedPushRule> for PatternedPushRule {
1323 type Error = String;
1324
1325 fn try_from(value: RumaPatternedPushRule) -> Result<Self, Self::Error> {
1326 Ok(Self {
1327 actions: value
1328 .actions
1329 .into_iter()
1330 .map(TryInto::try_into)
1331 .collect::<Result<Vec<_>, _>>()?,
1332 default: value.default,
1333 enabled: value.enabled,
1334 rule_id: value.rule_id,
1335 pattern: value.pattern,
1336 })
1337 }
1338}
1339
1340#[derive(Clone, uniffi::Record)]
1342pub struct ConditionalPushRule {
1343 pub actions: Vec<Action>,
1346
1347 pub default: bool,
1349
1350 pub enabled: bool,
1352
1353 pub rule_id: String,
1355
1356 pub conditions: Vec<PushCondition>,
1361}
1362
1363impl TryFrom<RumaConditionalPushRule> for ConditionalPushRule {
1364 type Error = String;
1365
1366 fn try_from(value: RumaConditionalPushRule) -> Result<Self, Self::Error> {
1367 Ok(Self {
1368 actions: value
1369 .actions
1370 .into_iter()
1371 .map(TryInto::try_into)
1372 .collect::<Result<Vec<_>, _>>()?,
1373 default: value.default,
1374 enabled: value.enabled,
1375 rule_id: value.rule_id,
1376 conditions: value
1377 .conditions
1378 .into_iter()
1379 .map(TryInto::try_into)
1380 .collect::<Result<Vec<_>, _>>()?,
1381 })
1382 }
1383}
1384
1385#[derive(Clone, uniffi::Record)]
1389pub struct SimplePushRule {
1390 pub actions: Vec<Action>,
1393
1394 pub default: bool,
1396
1397 pub enabled: bool,
1399
1400 pub rule_id: String,
1404}
1405
1406impl TryFrom<RumaSimplePushRule<OwnedRoomId>> for SimplePushRule {
1407 type Error = String;
1408
1409 fn try_from(value: RumaSimplePushRule<OwnedRoomId>) -> Result<Self, Self::Error> {
1410 Ok(Self {
1411 actions: value
1412 .actions
1413 .into_iter()
1414 .map(TryInto::try_into)
1415 .collect::<Result<Vec<_>, _>>()?,
1416 default: value.default,
1417 enabled: value.enabled,
1418 rule_id: value.rule_id.into(),
1419 })
1420 }
1421}
1422
1423impl TryFrom<RumaSimplePushRule<OwnedUserId>> for SimplePushRule {
1424 type Error = String;
1425
1426 fn try_from(value: RumaSimplePushRule<OwnedUserId>) -> Result<Self, Self::Error> {
1427 Ok(Self {
1428 actions: value
1429 .actions
1430 .into_iter()
1431 .map(TryInto::try_into)
1432 .collect::<Result<Vec<_>, _>>()?,
1433 default: value.default,
1434 enabled: value.enabled,
1435 rule_id: value.rule_id.into(),
1436 })
1437 }
1438}
1439
1440#[derive(Clone, uniffi::Enum)]
1442pub enum SecretStorageEncryptionAlgorithm {
1443 V1AesHmacSha2 { properties: SecretStorageV1AesHmacSha2Properties },
1448}
1449
1450impl TryFrom<RumaSecretStorageEncryptionAlgorithm> for SecretStorageEncryptionAlgorithm {
1451 type Error = String;
1452
1453 fn try_from(value: RumaSecretStorageEncryptionAlgorithm) -> Result<Self, Self::Error> {
1454 match value {
1455 RumaSecretStorageEncryptionAlgorithm::V1AesHmacSha2(properties) => {
1456 Ok(Self::V1AesHmacSha2 { properties: properties.into() })
1457 }
1458 _ => Err("Unsupported encryption algorithm".to_owned()),
1459 }
1460 }
1461}
1462
1463#[derive(Clone, uniffi::Record)]
1465pub struct SecretStorageV1AesHmacSha2Properties {
1466 pub iv: Option<String>,
1468
1469 pub mac: Option<String>,
1471}
1472
1473impl From<RumaSecretStorageV1AesHmacSha2Properties> for SecretStorageV1AesHmacSha2Properties {
1474 fn from(value: RumaSecretStorageV1AesHmacSha2Properties) -> Self {
1475 Self {
1476 iv: value.iv.map(|base64| base64.encode()),
1477 mac: value.mac.map(|base64| base64.encode()),
1478 }
1479 }
1480}
1481
1482#[derive(Clone, uniffi::Record, Default)]
1487pub struct MediaPreviewConfig {
1488 pub media_previews: Option<MediaPreviews>,
1490
1491 pub invite_avatars: Option<InviteAvatars>,
1493}
1494
1495impl From<MediaPreviewConfigEventContent> for MediaPreviewConfig {
1496 fn from(value: MediaPreviewConfigEventContent) -> Self {
1497 Self {
1498 media_previews: value.media_previews.map(Into::into),
1499 invite_avatars: value.invite_avatars.map(Into::into),
1500 }
1501 }
1502}
1503
1504#[derive(Clone, uniffi::Record)]
1506pub struct PassPhrase {
1507 pub algorithm: KeyDerivationAlgorithm,
1511
1512 pub salt: String,
1514
1515 pub iterations: u64,
1517
1518 pub bits: u64,
1522}
1523
1524impl TryFrom<RumaPassPhrase> for PassPhrase {
1525 type Error = String;
1526
1527 fn try_from(value: RumaPassPhrase) -> Result<Self, Self::Error> {
1528 Ok(PassPhrase {
1529 algorithm: value.algorithm.try_into()?,
1530 salt: value.salt,
1531 iterations: value.iterations.into(),
1532 bits: value.bits.into(),
1533 })
1534 }
1535}
1536
1537#[derive(Clone, uniffi::Enum)]
1539pub enum KeyDerivationAlgorithm {
1540 Pbkfd2,
1542}
1543
1544impl TryFrom<RumaKeyDerivationAlgorithm> for KeyDerivationAlgorithm {
1545 type Error = String;
1546
1547 fn try_from(value: RumaKeyDerivationAlgorithm) -> Result<Self, Self::Error> {
1548 match value {
1549 RumaKeyDerivationAlgorithm::Pbkfd2 => Ok(Self::Pbkfd2),
1550 _ => Err("Unsupported key derivation algorithm".to_owned()),
1551 }
1552 }
1553}
1554
1555impl From<RumaGlobalAccountDataEvent<DirectEventContent>> for AccountDataEvent {
1556 fn from(value: RumaGlobalAccountDataEvent<DirectEventContent>) -> Self {
1557 Self::Direct {
1558 map: value
1559 .content
1560 .0
1561 .into_iter()
1562 .map(|(user_id, room_ids)| {
1563 (user_id.to_string(), room_ids.iter().map(ToString::to_string).collect())
1564 })
1565 .collect(),
1566 }
1567 }
1568}
1569
1570impl From<RumaGlobalAccountDataEvent<IdentityServerEventContent>> for AccountDataEvent {
1571 fn from(value: RumaGlobalAccountDataEvent<IdentityServerEventContent>) -> Self {
1572 Self::IdentityServer { base_url: value.content.base_url.into_option() }
1573 }
1574}
1575
1576impl From<RumaGlobalAccountDataEvent<IgnoredUserListEventContent>> for AccountDataEvent {
1577 fn from(value: RumaGlobalAccountDataEvent<IgnoredUserListEventContent>) -> Self {
1578 Self::IgnoredUserList {
1579 ignored_users: value
1580 .content
1581 .ignored_users
1582 .into_iter()
1583 .map(|(user_id, ignored_user)| {
1584 (user_id.to_string(), IgnoredUser::from(ignored_user))
1585 })
1586 .collect(),
1587 }
1588 }
1589}
1590
1591impl TryFrom<RumaGlobalAccountDataEvent<PushRulesEventContent>> for AccountDataEvent {
1592 type Error = String;
1593
1594 fn try_from(
1595 value: RumaGlobalAccountDataEvent<PushRulesEventContent>,
1596 ) -> Result<Self, Self::Error> {
1597 Ok(Self::PushRules { global: value.content.global.try_into()? })
1598 }
1599}
1600
1601impl From<RumaGlobalAccountDataEvent<SecretStorageDefaultKeyEventContent>> for AccountDataEvent {
1602 fn from(value: RumaGlobalAccountDataEvent<SecretStorageDefaultKeyEventContent>) -> Self {
1603 Self::SecretStorageDefaultKey { key_id: value.content.key_id }
1604 }
1605}
1606
1607impl TryFrom<RumaGlobalAccountDataEvent<SecretStorageKeyEventContent>> for AccountDataEvent {
1608 type Error = String;
1609
1610 fn try_from(
1611 value: RumaGlobalAccountDataEvent<SecretStorageKeyEventContent>,
1612 ) -> Result<Self, Self::Error> {
1613 Ok(Self::SecretStorageKey {
1614 key_id: value.content.key_id,
1615 name: value.content.name,
1616 algorithm: value.content.algorithm.try_into()?,
1617 passphrase: value.content.passphrase.map(TryInto::try_into).transpose()?,
1618 })
1619 }
1620}
1621
1622#[derive(Clone, uniffi::Enum)]
1624pub enum RoomAccountDataEventType {
1625 FullyRead,
1627 MarkedUnread,
1629 Tag,
1631 UnstableMarkedUnread,
1633}
1634
1635impl TryFrom<RumaRoomAccountDataEventType> for RoomAccountDataEventType {
1636 type Error = String;
1637
1638 fn try_from(value: RumaRoomAccountDataEventType) -> Result<Self, Self::Error> {
1639 match value {
1640 RumaRoomAccountDataEventType::FullyRead => Ok(Self::FullyRead),
1641 RumaRoomAccountDataEventType::MarkedUnread => Ok(Self::MarkedUnread),
1642 RumaRoomAccountDataEventType::Tag => Ok(Self::Tag),
1643 RumaRoomAccountDataEventType::UnstableMarkedUnread => Ok(Self::UnstableMarkedUnread),
1644 _ => Err("Unsupported account data event type".to_owned()),
1645 }
1646 }
1647}
1648
1649#[derive(Clone, uniffi::Enum)]
1651pub enum RoomAccountDataEvent {
1652 FullyReadEvent {
1654 event_id: String,
1656 },
1657 MarkedUnread {
1659 unread: bool,
1661 },
1662 Tag { tags: HashMap<TagName, TagInfo> },
1664 UnstableMarkedUnread {
1666 unread: bool,
1668 },
1669}
1670
1671#[derive(Clone, PartialEq, Eq, Hash, uniffi::Enum)]
1673pub enum TagName {
1674 Favorite,
1676
1677 LowPriority,
1680
1681 ServerNotice,
1683
1684 User { name: UserTagName },
1686}
1687
1688impl TryFrom<RumaTagName> for TagName {
1689 type Error = String;
1690
1691 fn try_from(value: RumaTagName) -> Result<Self, Self::Error> {
1692 match value {
1693 RumaTagName::Favorite => Ok(Self::Favorite),
1694 RumaTagName::LowPriority => Ok(Self::LowPriority),
1695 RumaTagName::ServerNotice => Ok(Self::ServerNotice),
1696 RumaTagName::User(name) => Ok(Self::User { name: name.into() }),
1697 _ => Err("Unsupported tag name".to_owned()),
1698 }
1699 }
1700}
1701
1702#[derive(Clone, PartialEq, Eq, Hash, uniffi::Record)]
1704pub struct UserTagName {
1705 name: String,
1706}
1707
1708impl From<RumaUserTagName> for UserTagName {
1709 fn from(value: RumaUserTagName) -> Self {
1710 Self { name: value.as_ref().to_owned() }
1711 }
1712}
1713
1714#[derive(Clone, uniffi::Record)]
1716pub struct TagInfo {
1717 pub order: Option<f64>,
1719}
1720
1721impl From<RumaTagInfo> for TagInfo {
1722 fn from(value: RumaTagInfo) -> Self {
1723 Self { order: value.order }
1724 }
1725}
1726
1727impl From<RumaRoomAccountDataEvent<FullyReadEventContent>> for RoomAccountDataEvent {
1728 fn from(value: RumaRoomAccountDataEvent<FullyReadEventContent>) -> Self {
1729 Self::FullyReadEvent { event_id: value.content.event_id.into() }
1730 }
1731}
1732
1733impl From<RumaRoomAccountDataEvent<MarkedUnreadEventContent>> for RoomAccountDataEvent {
1734 fn from(value: RumaRoomAccountDataEvent<MarkedUnreadEventContent>) -> Self {
1735 Self::MarkedUnread { unread: value.content.unread }
1736 }
1737}
1738
1739impl TryFrom<RumaRoomAccountDataEvent<TagEventContent>> for RoomAccountDataEvent {
1740 type Error = String;
1741
1742 fn try_from(value: RumaRoomAccountDataEvent<TagEventContent>) -> Result<Self, Self::Error> {
1743 Ok(Self::Tag {
1744 tags: value
1745 .content
1746 .tags
1747 .into_iter()
1748 .map(|(name, info)| name.try_into().map(|name| (name, info.into())))
1749 .collect::<Result<HashMap<TagName, _>, _>>()?,
1750 })
1751 }
1752}
1753
1754impl From<RumaRoomAccountDataEvent<UnstableMarkedUnreadEventContent>> for RoomAccountDataEvent {
1755 fn from(value: RumaRoomAccountDataEvent<UnstableMarkedUnreadEventContent>) -> Self {
1756 Self::UnstableMarkedUnread { unread: value.content.unread }
1757 }
1758}
1759
1760#[cfg(feature = "unstable-msc4274")]
1761pub use galleries::*;
1762
1763#[cfg(feature = "unstable-msc4274")]
1764mod galleries {
1765 use ruma::{
1766 events::room::message::{
1767 GalleryItemType as RumaGalleryItemType,
1768 GalleryMessageEventContent as RumaGalleryMessageEventContent,
1769 },
1770 serde::JsonObject,
1771 };
1772
1773 use crate::{
1774 error::ClientError,
1775 ruma::{
1776 AudioMessageContent, FileMessageContent, FormattedBody, ImageMessageContent,
1777 VideoMessageContent,
1778 },
1779 };
1780
1781 #[derive(Clone, uniffi::Record)]
1782 pub struct GalleryMessageContent {
1783 pub body: String,
1784 pub formatted: Option<FormattedBody>,
1785 pub itemtypes: Vec<GalleryItemType>,
1786 }
1787
1788 impl TryFrom<GalleryMessageContent> for RumaGalleryMessageEventContent {
1789 type Error = ClientError;
1790
1791 fn try_from(value: GalleryMessageContent) -> Result<Self, Self::Error> {
1792 Ok(Self::new(
1793 value.body,
1794 value.formatted.map(Into::into),
1795 value.itemtypes.into_iter().map(TryInto::try_into).collect::<Result<_, _>>()?,
1796 ))
1797 }
1798 }
1799
1800 impl TryFrom<RumaGalleryMessageEventContent> for GalleryMessageContent {
1801 type Error = ClientError;
1802
1803 fn try_from(value: RumaGalleryMessageEventContent) -> Result<Self, Self::Error> {
1804 Ok(Self {
1805 body: value.body,
1806 formatted: value.formatted.as_ref().map(Into::into),
1807 itemtypes: value
1808 .itemtypes
1809 .into_iter()
1810 .map(TryInto::try_into)
1811 .collect::<Result<_, _>>()?,
1812 })
1813 }
1814 }
1815
1816 #[derive(Clone, uniffi::Enum)]
1817 pub enum GalleryItemType {
1818 Image { content: ImageMessageContent },
1819 Audio { content: AudioMessageContent },
1820 Video { content: VideoMessageContent },
1821 File { content: FileMessageContent },
1822 Other { itemtype: String, body: String },
1823 }
1824
1825 impl TryFrom<GalleryItemType> for RumaGalleryItemType {
1826 type Error = ClientError;
1827
1828 fn try_from(value: GalleryItemType) -> Result<Self, Self::Error> {
1829 Ok(match value {
1830 GalleryItemType::Image { content } => Self::Image(content.into()),
1831 GalleryItemType::Audio { content } => Self::Audio(content.into()),
1832 GalleryItemType::Video { content } => Self::Video(content.into()),
1833 GalleryItemType::File { content } => Self::File(content.into()),
1834 GalleryItemType::Other { itemtype, body } => {
1835 Self::new(&itemtype, body, JsonObject::default())?
1836 }
1837 })
1838 }
1839 }
1840
1841 impl TryFrom<RumaGalleryItemType> for GalleryItemType {
1842 type Error = ClientError;
1843
1844 fn try_from(value: RumaGalleryItemType) -> Result<Self, Self::Error> {
1845 Ok(match value {
1846 RumaGalleryItemType::Image(c) => GalleryItemType::Image { content: c.try_into()? },
1847 RumaGalleryItemType::Audio(c) => GalleryItemType::Audio { content: c.try_into()? },
1848 RumaGalleryItemType::Video(c) => GalleryItemType::Video { content: c.try_into()? },
1849 RumaGalleryItemType::File(c) => GalleryItemType::File { content: c.try_into()? },
1850 _ => GalleryItemType::Other {
1851 itemtype: value.itemtype().to_owned(),
1852 body: value.body().to_owned(),
1853 },
1854 })
1855 }
1856 }
1857}