1use std::{collections::BTreeSet, sync::Arc, time::Duration};
16
17use extension_trait::extension_trait;
18use matrix_sdk::attachment::{BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseVideoInfo};
19use ruma::{
20 assign,
21 events::{
22 call::notify::NotifyType as RumaNotifyType,
23 location::AssetType as RumaAssetType,
24 poll::start::PollKind as RumaPollKind,
25 room::{
26 message::{
27 AudioInfo as RumaAudioInfo,
28 AudioMessageEventContent as RumaAudioMessageEventContent,
29 EmoteMessageEventContent as RumaEmoteMessageEventContent, FileInfo as RumaFileInfo,
30 FileMessageEventContent as RumaFileMessageEventContent,
31 FormattedBody as RumaFormattedBody,
32 ImageMessageEventContent as RumaImageMessageEventContent,
33 LocationMessageEventContent as RumaLocationMessageEventContent,
34 MessageType as RumaMessageType,
35 NoticeMessageEventContent as RumaNoticeMessageEventContent,
36 RoomMessageEventContentWithoutRelation,
37 TextMessageEventContent as RumaTextMessageEventContent,
38 UnstableAudioDetailsContentBlock as RumaUnstableAudioDetailsContentBlock,
39 UnstableVoiceContentBlock as RumaUnstableVoiceContentBlock,
40 VideoInfo as RumaVideoInfo,
41 VideoMessageEventContent as RumaVideoMessageEventContent,
42 },
43 ImageInfo as RumaImageInfo, MediaSource as RumaMediaSource,
44 ThumbnailInfo as RumaThumbnailInfo,
45 },
46 },
47 matrix_uri::MatrixId as RumaMatrixId,
48 serde::JsonObject,
49 MatrixToUri, MatrixUri as RumaMatrixUri, OwnedUserId, UInt, UserId,
50};
51use tracing::info;
52
53use crate::{
54 error::{ClientError, MediaInfoError},
55 helpers::unwrap_or_clone_arc,
56 timeline::MessageContent,
57 utils::u64_to_uint,
58};
59
60#[derive(uniffi::Enum)]
61pub enum AuthData {
62 Password { password_details: AuthDataPasswordDetails },
64}
65
66#[derive(uniffi::Record)]
67pub struct AuthDataPasswordDetails {
68 identifier: String,
70
71 password: String,
73}
74
75impl From<AuthData> for ruma::api::client::uiaa::AuthData {
76 fn from(value: AuthData) -> ruma::api::client::uiaa::AuthData {
77 match value {
78 AuthData::Password { password_details } => {
79 let user_id = ruma::UserId::parse(password_details.identifier).unwrap();
80
81 ruma::api::client::uiaa::AuthData::Password(ruma::api::client::uiaa::Password::new(
82 user_id.into(),
83 password_details.password,
84 ))
85 }
86 }
87 }
88}
89
90#[matrix_sdk_ffi_macros::export]
93pub fn parse_matrix_entity_from(uri: String) -> Option<MatrixEntity> {
94 if let Ok(matrix_uri) = RumaMatrixUri::parse(&uri) {
95 return Some(MatrixEntity {
96 id: matrix_uri.id().into(),
97 via: matrix_uri.via().iter().map(|via| via.to_string()).collect(),
98 });
99 }
100
101 if let Ok(matrix_to_uri) = MatrixToUri::parse(&uri) {
102 return Some(MatrixEntity {
103 id: matrix_to_uri.id().into(),
104 via: matrix_to_uri.via().iter().map(|via| via.to_string()).collect(),
105 });
106 }
107
108 None
109}
110
111#[derive(uniffi::Record)]
114pub struct MatrixEntity {
115 id: MatrixId,
116 via: Vec<String>,
117}
118
119#[derive(Clone, uniffi::Enum)]
121pub enum MatrixId {
122 Room { id: String },
123 RoomAlias { alias: String },
124 User { id: String },
125 EventOnRoomId { room_id: String, event_id: String },
126 EventOnRoomAlias { alias: String, event_id: String },
127}
128
129impl From<&RumaMatrixId> for MatrixId {
130 fn from(value: &RumaMatrixId) -> Self {
131 match value {
132 RumaMatrixId::User(id) => MatrixId::User { id: id.to_string() },
133 RumaMatrixId::Room(id) => MatrixId::Room { id: id.to_string() },
134 RumaMatrixId::RoomAlias(id) => MatrixId::RoomAlias { alias: id.to_string() },
135
136 RumaMatrixId::Event(room_id_or_alias, event_id) => {
137 if room_id_or_alias.is_room_id() {
138 MatrixId::EventOnRoomId {
139 room_id: room_id_or_alias.to_string(),
140 event_id: event_id.to_string(),
141 }
142 } else if room_id_or_alias.is_room_alias_id() {
143 MatrixId::EventOnRoomAlias {
144 alias: room_id_or_alias.to_string(),
145 event_id: event_id.to_string(),
146 }
147 } else {
148 panic!("Unexpected MatrixId type: {:?}", room_id_or_alias)
149 }
150 }
151 _ => panic!("Unexpected MatrixId type: {:?}", value),
152 }
153 }
154}
155
156#[matrix_sdk_ffi_macros::export]
157pub fn message_event_content_new(
158 msgtype: MessageType,
159) -> Result<Arc<RoomMessageEventContentWithoutRelation>, ClientError> {
160 Ok(Arc::new(RoomMessageEventContentWithoutRelation::new(msgtype.try_into()?)))
161}
162
163#[matrix_sdk_ffi_macros::export]
164pub fn message_event_content_from_markdown(
165 md: String,
166) -> Arc<RoomMessageEventContentWithoutRelation> {
167 Arc::new(RoomMessageEventContentWithoutRelation::new(RumaMessageType::text_markdown(md)))
168}
169
170#[matrix_sdk_ffi_macros::export]
171pub fn message_event_content_from_markdown_as_emote(
172 md: String,
173) -> Arc<RoomMessageEventContentWithoutRelation> {
174 Arc::new(RoomMessageEventContentWithoutRelation::new(RumaMessageType::emote_markdown(md)))
175}
176
177#[matrix_sdk_ffi_macros::export]
178pub fn message_event_content_from_html(
179 body: String,
180 html_body: String,
181) -> Arc<RoomMessageEventContentWithoutRelation> {
182 Arc::new(RoomMessageEventContentWithoutRelation::new(RumaMessageType::text_html(
183 body, html_body,
184 )))
185}
186
187#[matrix_sdk_ffi_macros::export]
188pub fn message_event_content_from_html_as_emote(
189 body: String,
190 html_body: String,
191) -> Arc<RoomMessageEventContentWithoutRelation> {
192 Arc::new(RoomMessageEventContentWithoutRelation::new(RumaMessageType::emote_html(
193 body, html_body,
194 )))
195}
196
197#[derive(Clone, uniffi::Object)]
198pub struct MediaSource {
199 pub(crate) media_source: RumaMediaSource,
200}
201
202#[matrix_sdk_ffi_macros::export]
203impl MediaSource {
204 #[uniffi::constructor]
205 pub fn from_url(url: String) -> Result<Arc<MediaSource>, ClientError> {
206 let media_source = RumaMediaSource::Plain(url.into());
207 media_source.verify()?;
208
209 Ok(Arc::new(MediaSource { media_source }))
210 }
211
212 pub fn url(&self) -> String {
213 self.media_source.url()
214 }
215
216 #[uniffi::constructor]
218 pub fn from_json(json: String) -> Result<Arc<Self>, ClientError> {
219 let media_source: RumaMediaSource = serde_json::from_str(&json)?;
220 media_source.verify()?;
221
222 Ok(Arc::new(MediaSource { media_source }))
223 }
224
225 pub fn to_json(&self) -> String {
227 serde_json::to_string(&self.media_source)
228 .expect("Media source should always be serializable ")
229 }
230}
231
232impl TryFrom<RumaMediaSource> for MediaSource {
233 type Error = ClientError;
234
235 fn try_from(value: RumaMediaSource) -> Result<Self, Self::Error> {
236 value.verify()?;
237 Ok(Self { media_source: value })
238 }
239}
240
241impl TryFrom<&RumaMediaSource> for MediaSource {
242 type Error = ClientError;
243
244 fn try_from(value: &RumaMediaSource) -> Result<Self, Self::Error> {
245 value.verify()?;
246 Ok(Self { media_source: value.clone() })
247 }
248}
249
250impl From<MediaSource> for RumaMediaSource {
251 fn from(value: MediaSource) -> Self {
252 value.media_source
253 }
254}
255
256#[extension_trait]
257pub(crate) impl MediaSourceExt for RumaMediaSource {
258 fn verify(&self) -> Result<(), ClientError> {
259 match self {
260 RumaMediaSource::Plain(url) => {
261 url.validate().map_err(|e| ClientError::Generic { msg: e.to_string() })?;
262 }
263 RumaMediaSource::Encrypted(file) => {
264 file.url.validate().map_err(|e| ClientError::Generic { msg: e.to_string() })?;
265 }
266 }
267
268 Ok(())
269 }
270
271 fn url(&self) -> String {
272 match self {
273 RumaMediaSource::Plain(url) => url.to_string(),
274 RumaMediaSource::Encrypted(file) => file.url.to_string(),
275 }
276 }
277}
278
279#[extension_trait]
280pub impl RoomMessageEventContentWithoutRelationExt for RoomMessageEventContentWithoutRelation {
281 fn with_mentions(self: Arc<Self>, mentions: Mentions) -> Arc<Self> {
282 let mut content = unwrap_or_clone_arc(self);
283 content.mentions = Some(mentions.into());
284 Arc::new(content)
285 }
286}
287
288#[derive(Clone)]
289pub struct Mentions {
290 pub user_ids: Vec<String>,
291 pub room: bool,
292}
293
294impl From<Mentions> for ruma::events::Mentions {
295 fn from(value: Mentions) -> Self {
296 let mut user_ids = BTreeSet::<OwnedUserId>::new();
297 for user_id in value.user_ids {
298 if let Ok(user_id) = UserId::parse(user_id) {
299 user_ids.insert(user_id);
300 }
301 }
302 let mut result = Self::default();
303 result.user_ids = user_ids;
304 result.room = value.room;
305 result
306 }
307}
308
309#[derive(Clone, uniffi::Enum)]
310pub enum MessageType {
311 Emote { content: EmoteMessageContent },
312 Image { content: ImageMessageContent },
313 Audio { content: AudioMessageContent },
314 Video { content: VideoMessageContent },
315 File { content: FileMessageContent },
316 Notice { content: NoticeMessageContent },
317 Text { content: TextMessageContent },
318 Location { content: LocationContent },
319 Other { msgtype: String, body: String },
320}
321
322fn get_body_and_filename(filename: String, caption: Option<String>) -> (String, Option<String>) {
332 if let Some(caption) = caption {
333 (caption, Some(filename))
334 } else {
335 (filename, None)
336 }
337}
338
339impl TryFrom<MessageType> for RumaMessageType {
340 type Error = ClientError;
341
342 fn try_from(value: MessageType) -> Result<Self, Self::Error> {
343 Ok(match value {
344 MessageType::Emote { content } => {
345 Self::Emote(assign!(RumaEmoteMessageEventContent::plain(content.body), {
346 formatted: content.formatted.map(Into::into),
347 }))
348 }
349 MessageType::Image { content } => {
350 let (body, filename) = get_body_and_filename(content.filename, content.caption);
351 let mut event_content =
352 RumaImageMessageEventContent::new(body, (*content.source).clone().into())
353 .info(content.info.map(Into::into).map(Box::new));
354 event_content.formatted = content.formatted_caption.map(Into::into);
355 event_content.filename = filename;
356 Self::Image(event_content)
357 }
358 MessageType::Audio { content } => {
359 let (body, filename) = get_body_and_filename(content.filename, content.caption);
360 let mut event_content =
361 RumaAudioMessageEventContent::new(body, (*content.source).clone().into())
362 .info(content.info.map(Into::into).map(Box::new));
363 event_content.formatted = content.formatted_caption.map(Into::into);
364 event_content.filename = filename;
365 Self::Audio(event_content)
366 }
367 MessageType::Video { content } => {
368 let (body, filename) = get_body_and_filename(content.filename, content.caption);
369 let mut event_content =
370 RumaVideoMessageEventContent::new(body, (*content.source).clone().into())
371 .info(content.info.map(Into::into).map(Box::new));
372 event_content.formatted = content.formatted_caption.map(Into::into);
373 event_content.filename = filename;
374 Self::Video(event_content)
375 }
376 MessageType::File { content } => {
377 let (body, filename) = get_body_and_filename(content.filename, content.caption);
378 let mut event_content =
379 RumaFileMessageEventContent::new(body, (*content.source).clone().into())
380 .info(content.info.map(Into::into).map(Box::new));
381 event_content.formatted = content.formatted_caption.map(Into::into);
382 event_content.filename = filename;
383 Self::File(event_content)
384 }
385 MessageType::Notice { content } => {
386 Self::Notice(assign!(RumaNoticeMessageEventContent::plain(content.body), {
387 formatted: content.formatted.map(Into::into),
388 }))
389 }
390 MessageType::Text { content } => {
391 Self::Text(assign!(RumaTextMessageEventContent::plain(content.body), {
392 formatted: content.formatted.map(Into::into),
393 }))
394 }
395 MessageType::Location { content } => {
396 Self::Location(RumaLocationMessageEventContent::new(content.body, content.geo_uri))
397 }
398 MessageType::Other { msgtype, body } => {
399 Self::new(&msgtype, body, JsonObject::default())?
400 }
401 })
402 }
403}
404
405impl TryFrom<RumaMessageType> for MessageType {
406 type Error = ClientError;
407
408 fn try_from(value: RumaMessageType) -> Result<Self, Self::Error> {
409 Ok(match value {
410 RumaMessageType::Emote(c) => MessageType::Emote {
411 content: EmoteMessageContent {
412 body: c.body.clone(),
413 formatted: c.formatted.as_ref().map(Into::into),
414 },
415 },
416 RumaMessageType::Image(c) => MessageType::Image {
417 content: ImageMessageContent {
418 filename: c.filename().to_owned(),
419 caption: c.caption().map(ToString::to_string),
420 formatted_caption: c.formatted_caption().map(Into::into),
421 source: Arc::new(c.source.try_into()?),
422 info: c.info.as_deref().map(TryInto::try_into).transpose()?,
423 },
424 },
425
426 RumaMessageType::Audio(c) => MessageType::Audio {
427 content: AudioMessageContent {
428 filename: c.filename().to_owned(),
429 caption: c.caption().map(ToString::to_string),
430 formatted_caption: c.formatted_caption().map(Into::into),
431 source: Arc::new(c.source.try_into()?),
432 info: c.info.as_deref().map(Into::into),
433 audio: c.audio.map(Into::into),
434 voice: c.voice.map(Into::into),
435 },
436 },
437 RumaMessageType::Video(c) => MessageType::Video {
438 content: VideoMessageContent {
439 filename: c.filename().to_owned(),
440 caption: c.caption().map(ToString::to_string),
441 formatted_caption: c.formatted_caption().map(Into::into),
442 source: Arc::new(c.source.try_into()?),
443 info: c.info.as_deref().map(TryInto::try_into).transpose()?,
444 },
445 },
446 RumaMessageType::File(c) => MessageType::File {
447 content: FileMessageContent {
448 filename: c.filename().to_owned(),
449 caption: c.caption().map(ToString::to_string),
450 formatted_caption: c.formatted_caption().map(Into::into),
451 source: Arc::new(c.source.try_into()?),
452 info: c.info.as_deref().map(TryInto::try_into).transpose()?,
453 },
454 },
455 RumaMessageType::Notice(c) => MessageType::Notice {
456 content: NoticeMessageContent {
457 body: c.body.clone(),
458 formatted: c.formatted.as_ref().map(Into::into),
459 },
460 },
461 RumaMessageType::Text(c) => MessageType::Text {
462 content: TextMessageContent {
463 body: c.body.clone(),
464 formatted: c.formatted.as_ref().map(Into::into),
465 },
466 },
467 RumaMessageType::Location(c) => {
468 let (description, zoom_level) =
469 c.location.map(|loc| (loc.description, loc.zoom_level)).unwrap_or((None, None));
470 MessageType::Location {
471 content: LocationContent {
472 body: c.body,
473 geo_uri: c.geo_uri,
474 description,
475 zoom_level: zoom_level.and_then(|z| z.get().try_into().ok()),
476 asset: c.asset.and_then(|a| match a.type_ {
477 RumaAssetType::Self_ => Some(AssetType::Sender),
478 RumaAssetType::Pin => Some(AssetType::Pin),
479 _ => None,
480 }),
481 },
482 }
483 }
484 _ => MessageType::Other {
485 msgtype: value.msgtype().to_owned(),
486 body: value.body().to_owned(),
487 },
488 })
489 }
490}
491
492#[derive(Clone, uniffi::Enum)]
493pub enum NotifyType {
494 Ring,
495 Notify,
496}
497
498impl From<RumaNotifyType> for NotifyType {
499 fn from(val: RumaNotifyType) -> Self {
500 match val {
501 RumaNotifyType::Ring => Self::Ring,
502 _ => Self::Notify,
503 }
504 }
505}
506
507impl From<NotifyType> for RumaNotifyType {
508 fn from(value: NotifyType) -> Self {
509 match value {
510 NotifyType::Ring => RumaNotifyType::Ring,
511 NotifyType::Notify => RumaNotifyType::Notify,
512 }
513 }
514}
515
516#[derive(Clone, uniffi::Record)]
517pub struct EmoteMessageContent {
518 pub body: String,
519 pub formatted: Option<FormattedBody>,
520}
521
522#[derive(Clone, uniffi::Record)]
523pub struct ImageMessageContent {
524 pub filename: String,
526 pub caption: Option<String>,
527 pub formatted_caption: Option<FormattedBody>,
528 pub source: Arc<MediaSource>,
529 pub info: Option<ImageInfo>,
530}
531
532#[derive(Clone, uniffi::Record)]
533pub struct AudioMessageContent {
534 pub filename: String,
536 pub caption: Option<String>,
537 pub formatted_caption: Option<FormattedBody>,
538 pub source: Arc<MediaSource>,
539 pub info: Option<AudioInfo>,
540 pub audio: Option<UnstableAudioDetailsContent>,
541 pub voice: Option<UnstableVoiceContent>,
542}
543
544#[derive(Clone, uniffi::Record)]
545pub struct VideoMessageContent {
546 pub filename: String,
548 pub caption: Option<String>,
549 pub formatted_caption: Option<FormattedBody>,
550 pub source: Arc<MediaSource>,
551 pub info: Option<VideoInfo>,
552}
553
554#[derive(Clone, uniffi::Record)]
555pub struct FileMessageContent {
556 pub filename: String,
558 pub caption: Option<String>,
559 pub formatted_caption: Option<FormattedBody>,
560 pub source: Arc<MediaSource>,
561 pub info: Option<FileInfo>,
562}
563
564#[derive(Clone, uniffi::Record)]
565pub struct ImageInfo {
566 pub height: Option<u64>,
567 pub width: Option<u64>,
568 pub mimetype: Option<String>,
569 pub size: Option<u64>,
570 pub thumbnail_info: Option<ThumbnailInfo>,
571 pub thumbnail_source: Option<Arc<MediaSource>>,
572 pub blurhash: Option<String>,
573 pub is_animated: Option<bool>,
574}
575
576impl From<ImageInfo> for RumaImageInfo {
577 fn from(value: ImageInfo) -> Self {
578 assign!(RumaImageInfo::new(), {
579 height: value.height.map(u64_to_uint),
580 width: value.width.map(u64_to_uint),
581 mimetype: value.mimetype,
582 size: value.size.map(u64_to_uint),
583 thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new),
584 thumbnail_source: value.thumbnail_source.map(|source| (*source).clone().into()),
585 blurhash: value.blurhash,
586 is_animated: value.is_animated,
587 })
588 }
589}
590
591impl TryFrom<&ImageInfo> for BaseImageInfo {
592 type Error = MediaInfoError;
593
594 fn try_from(value: &ImageInfo) -> Result<Self, MediaInfoError> {
595 let height = UInt::try_from(value.height.ok_or(MediaInfoError::MissingField)?)
596 .map_err(|_| MediaInfoError::InvalidField)?;
597 let width = UInt::try_from(value.width.ok_or(MediaInfoError::MissingField)?)
598 .map_err(|_| MediaInfoError::InvalidField)?;
599 let size = UInt::try_from(value.size.ok_or(MediaInfoError::MissingField)?)
600 .map_err(|_| MediaInfoError::InvalidField)?;
601 let blurhash = value.blurhash.clone().ok_or(MediaInfoError::MissingField)?;
602
603 Ok(BaseImageInfo {
604 height: Some(height),
605 width: Some(width),
606 size: Some(size),
607 blurhash: Some(blurhash),
608 is_animated: value.is_animated,
609 })
610 }
611}
612
613#[derive(Clone, uniffi::Record)]
614pub struct AudioInfo {
615 pub duration: Option<Duration>,
616 pub size: Option<u64>,
617 pub mimetype: Option<String>,
618}
619
620impl From<AudioInfo> for RumaAudioInfo {
621 fn from(value: AudioInfo) -> Self {
622 assign!(RumaAudioInfo::new(), {
623 duration: value.duration,
624 size: value.size.map(u64_to_uint),
625 mimetype: value.mimetype,
626 })
627 }
628}
629
630impl TryFrom<&AudioInfo> for BaseAudioInfo {
631 type Error = MediaInfoError;
632
633 fn try_from(value: &AudioInfo) -> Result<Self, MediaInfoError> {
634 let duration = value.duration.ok_or(MediaInfoError::MissingField)?;
635 let size = UInt::try_from(value.size.ok_or(MediaInfoError::MissingField)?)
636 .map_err(|_| MediaInfoError::InvalidField)?;
637
638 Ok(BaseAudioInfo { duration: Some(duration), size: Some(size) })
639 }
640}
641
642#[derive(Clone, uniffi::Record)]
643pub struct UnstableAudioDetailsContent {
644 pub duration: Duration,
645 pub waveform: Vec<u16>,
646}
647
648impl From<RumaUnstableAudioDetailsContentBlock> for UnstableAudioDetailsContent {
649 fn from(details: RumaUnstableAudioDetailsContentBlock) -> Self {
650 Self {
651 duration: details.duration,
652 waveform: details
653 .waveform
654 .iter()
655 .map(|x| u16::try_from(x.get()).unwrap_or(0))
656 .collect(),
657 }
658 }
659}
660
661#[derive(Clone, uniffi::Record)]
662pub struct UnstableVoiceContent {}
663
664impl From<RumaUnstableVoiceContentBlock> for UnstableVoiceContent {
665 fn from(_details: RumaUnstableVoiceContentBlock) -> Self {
666 Self {}
667 }
668}
669
670#[derive(Clone, uniffi::Record)]
671pub struct VideoInfo {
672 pub duration: Option<Duration>,
673 pub height: Option<u64>,
674 pub width: Option<u64>,
675 pub mimetype: Option<String>,
676 pub size: Option<u64>,
677 pub thumbnail_info: Option<ThumbnailInfo>,
678 pub thumbnail_source: Option<Arc<MediaSource>>,
679 pub blurhash: Option<String>,
680}
681
682impl From<VideoInfo> for RumaVideoInfo {
683 fn from(value: VideoInfo) -> Self {
684 assign!(RumaVideoInfo::new(), {
685 duration: value.duration,
686 height: value.height.map(u64_to_uint),
687 width: value.width.map(u64_to_uint),
688 mimetype: value.mimetype,
689 size: value.size.map(u64_to_uint),
690 thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new),
691 thumbnail_source: value.thumbnail_source.map(|source| (*source).clone().into()),
692 blurhash: value.blurhash,
693 })
694 }
695}
696
697impl TryFrom<&VideoInfo> for BaseVideoInfo {
698 type Error = MediaInfoError;
699
700 fn try_from(value: &VideoInfo) -> Result<Self, MediaInfoError> {
701 let duration = value.duration.ok_or(MediaInfoError::MissingField)?;
702 let height = UInt::try_from(value.height.ok_or(MediaInfoError::MissingField)?)
703 .map_err(|_| MediaInfoError::InvalidField)?;
704 let width = UInt::try_from(value.width.ok_or(MediaInfoError::MissingField)?)
705 .map_err(|_| MediaInfoError::InvalidField)?;
706 let size = UInt::try_from(value.size.ok_or(MediaInfoError::MissingField)?)
707 .map_err(|_| MediaInfoError::InvalidField)?;
708 let blurhash = value.blurhash.clone().ok_or(MediaInfoError::MissingField)?;
709
710 Ok(BaseVideoInfo {
711 duration: Some(duration),
712 height: Some(height),
713 width: Some(width),
714 size: Some(size),
715 blurhash: Some(blurhash),
716 })
717 }
718}
719
720#[derive(Clone, uniffi::Record)]
721pub struct FileInfo {
722 pub mimetype: Option<String>,
723 pub size: Option<u64>,
724 pub thumbnail_info: Option<ThumbnailInfo>,
725 pub thumbnail_source: Option<Arc<MediaSource>>,
726}
727
728impl From<FileInfo> for RumaFileInfo {
729 fn from(value: FileInfo) -> Self {
730 assign!(RumaFileInfo::new(), {
731 mimetype: value.mimetype,
732 size: value.size.map(u64_to_uint),
733 thumbnail_info: value.thumbnail_info.map(Into::into).map(Box::new),
734 thumbnail_source: value.thumbnail_source.map(|source| (*source).clone().into()),
735 })
736 }
737}
738
739impl TryFrom<&FileInfo> for BaseFileInfo {
740 type Error = MediaInfoError;
741
742 fn try_from(value: &FileInfo) -> Result<Self, MediaInfoError> {
743 let size = UInt::try_from(value.size.ok_or(MediaInfoError::MissingField)?)
744 .map_err(|_| MediaInfoError::InvalidField)?;
745
746 Ok(BaseFileInfo { size: Some(size) })
747 }
748}
749
750#[derive(Clone, uniffi::Record)]
751pub struct ThumbnailInfo {
752 pub height: Option<u64>,
753 pub width: Option<u64>,
754 pub mimetype: Option<String>,
755 pub size: Option<u64>,
756}
757
758impl From<ThumbnailInfo> for RumaThumbnailInfo {
759 fn from(value: ThumbnailInfo) -> Self {
760 assign!(RumaThumbnailInfo::new(), {
761 height: value.height.map(u64_to_uint),
762 width: value.width.map(u64_to_uint),
763 mimetype: value.mimetype,
764 size: value.size.map(u64_to_uint),
765 })
766 }
767}
768
769#[derive(Clone, uniffi::Record)]
770pub struct NoticeMessageContent {
771 pub body: String,
772 pub formatted: Option<FormattedBody>,
773}
774
775#[derive(Clone, uniffi::Record)]
776pub struct TextMessageContent {
777 pub body: String,
778 pub formatted: Option<FormattedBody>,
779}
780
781#[derive(Clone, uniffi::Record)]
782pub struct LocationContent {
783 pub body: String,
784 pub geo_uri: String,
785 pub description: Option<String>,
786 pub zoom_level: Option<u8>,
787 pub asset: Option<AssetType>,
788}
789
790#[derive(Clone, uniffi::Enum)]
791pub enum AssetType {
792 Sender,
793 Pin,
794}
795
796impl From<AssetType> for RumaAssetType {
797 fn from(value: AssetType) -> Self {
798 match value {
799 AssetType::Sender => Self::Self_,
800 AssetType::Pin => Self::Pin,
801 }
802 }
803}
804
805#[derive(Clone, uniffi::Record)]
806pub struct FormattedBody {
807 pub format: MessageFormat,
808 pub body: String,
809}
810
811impl From<FormattedBody> for RumaFormattedBody {
812 fn from(f: FormattedBody) -> Self {
813 Self {
814 format: match f.format {
815 MessageFormat::Html => matrix_sdk::ruma::events::room::message::MessageFormat::Html,
816 MessageFormat::Unknown { format } => format.into(),
817 },
818 body: f.body,
819 }
820 }
821}
822
823impl From<&RumaFormattedBody> for FormattedBody {
824 fn from(f: &RumaFormattedBody) -> Self {
825 Self {
826 format: match &f.format {
827 matrix_sdk::ruma::events::room::message::MessageFormat::Html => MessageFormat::Html,
828 _ => MessageFormat::Unknown { format: f.format.to_string() },
829 },
830 body: f.body.clone(),
831 }
832 }
833}
834
835#[derive(Clone, uniffi::Enum)]
836pub enum MessageFormat {
837 Html,
838 Unknown { format: String },
839}
840
841impl TryFrom<&matrix_sdk::ruma::events::room::ImageInfo> for ImageInfo {
842 type Error = ClientError;
843
844 fn try_from(info: &matrix_sdk::ruma::events::room::ImageInfo) -> Result<Self, Self::Error> {
845 let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo {
846 height: info.height.map(Into::into),
847 width: info.width.map(Into::into),
848 mimetype: info.mimetype.clone(),
849 size: info.size.map(Into::into),
850 });
851
852 Ok(Self {
853 height: info.height.map(Into::into),
854 width: info.width.map(Into::into),
855 mimetype: info.mimetype.clone(),
856 size: info.size.map(Into::into),
857 thumbnail_info,
858 thumbnail_source: info
859 .thumbnail_source
860 .as_ref()
861 .map(TryInto::try_into)
862 .transpose()?
863 .map(Arc::new),
864 blurhash: info.blurhash.clone(),
865 is_animated: info.is_animated,
866 })
867 }
868}
869
870impl From<&RumaAudioInfo> for AudioInfo {
871 fn from(info: &RumaAudioInfo) -> Self {
872 Self {
873 duration: info.duration,
874 size: info.size.map(Into::into),
875 mimetype: info.mimetype.clone(),
876 }
877 }
878}
879
880impl TryFrom<&RumaVideoInfo> for VideoInfo {
881 type Error = ClientError;
882
883 fn try_from(info: &RumaVideoInfo) -> Result<Self, Self::Error> {
884 let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo {
885 height: info.height.map(Into::into),
886 width: info.width.map(Into::into),
887 mimetype: info.mimetype.clone(),
888 size: info.size.map(Into::into),
889 });
890
891 Ok(Self {
892 duration: info.duration,
893 height: info.height.map(Into::into),
894 width: info.width.map(Into::into),
895 mimetype: info.mimetype.clone(),
896 size: info.size.map(Into::into),
897 thumbnail_info,
898 thumbnail_source: info
899 .thumbnail_source
900 .as_ref()
901 .map(TryInto::try_into)
902 .transpose()?
903 .map(Arc::new),
904 blurhash: info.blurhash.clone(),
905 })
906 }
907}
908
909impl TryFrom<&RumaFileInfo> for FileInfo {
910 type Error = ClientError;
911
912 fn try_from(info: &RumaFileInfo) -> Result<Self, Self::Error> {
913 let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo {
914 height: info.height.map(Into::into),
915 width: info.width.map(Into::into),
916 mimetype: info.mimetype.clone(),
917 size: info.size.map(Into::into),
918 });
919
920 Ok(Self {
921 mimetype: info.mimetype.clone(),
922 size: info.size.map(Into::into),
923 thumbnail_info,
924 thumbnail_source: info
925 .thumbnail_source
926 .as_ref()
927 .map(TryInto::try_into)
928 .transpose()?
929 .map(Arc::new),
930 })
931 }
932}
933
934#[derive(Clone, uniffi::Enum)]
935pub enum PollKind {
936 Disclosed,
937 Undisclosed,
938}
939
940impl From<PollKind> for RumaPollKind {
941 fn from(value: PollKind) -> Self {
942 match value {
943 PollKind::Disclosed => Self::Disclosed,
944 PollKind::Undisclosed => Self::Undisclosed,
945 }
946 }
947}
948
949impl From<RumaPollKind> for PollKind {
950 fn from(value: RumaPollKind) -> Self {
951 match value {
952 RumaPollKind::Disclosed => Self::Disclosed,
953 RumaPollKind::Undisclosed => Self::Undisclosed,
954 _ => {
955 info!("Unknown poll kind, defaulting to undisclosed");
956 Self::Undisclosed
957 }
958 }
959 }
960}
961
962#[matrix_sdk_ffi_macros::export]
965pub fn content_without_relation_from_message(
966 message: MessageContent,
967) -> Result<Arc<RoomMessageEventContentWithoutRelation>, ClientError> {
968 let msg_type = message.msg_type.try_into()?;
969 Ok(Arc::new(RoomMessageEventContentWithoutRelation::new(msg_type)))
970}